{
  "source_file": "0930",
  "source_platform": "Medium",
  "author": "Marco Peixeiro",
  "title_listed": "The Complete Guide to Time Series Analysis and Forecasting",
  "keywords": "",
  "domain": "数据处理",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://towardsdatascience.com/the-complete-guide-to-time-series-analysis-and-forecasting-70d476bfe775/",
  "publish_date": "2020-10-01",
  "corpus_stage": "",
  "notes": "",
  "id": "f94cf65be99e",
  "collected_at": "2026-10-02T08:57:31",
  "title": "Time Series Forecasting: A Practical Guide to Exploratory Data Analysis | Towards Data Science",
  "body_markdown": "![Revisit consent button](../images/body-df29beacd7496220cc94.svg)\nWe value your privacy\nWe use cookies to enhance your browsing experience, serve personalised ads or content, and analyse our traffic. By clicking \"Accept All\", you consent to our use of cookies.\nCustomise Reject All Accept All\nCustomise Consent Preferences ![](../images/body-2159bb67f3de8ad870eb.svg)\nWe use cookies to help you navigate efficiently and perform certain functions. You will find detailed information about all cookies under each consent category below.\nThe cookies that are categorised as \"Necessary\" are stored on your browser as they are essential for enabling the basic functionalities of the site. ... Show more\nNecessaryAlways Active\nNecessary cookies are required to enable the basic features of this site, such as providing secure log-in or adjusting your consent preferences. These cookies do not store any personally identifiable data.\n  * Cookie\nBCTempID\n  * Duration\n10 minutes\n  * Description\nNo description available.\n\n\n  * Cookie\n__cf_bm\n  * Duration\n1 hour\n  * Description\nThis cookie, set by Cloudflare, is used to support Cloudflare Bot Management. \n\n\n  * Cookie\nAWSALBCORS\n  * Duration\n7 days\n  * Description\nAmazon Web Services set this cookie for load balancing.\n\n\n  * Cookie\n_cfuvid\n  * Duration\nsession\n  * Description\nCloudflare sets this cookie to track users across sessions to optimize user experience by maintaining session consistency and providing personalized services\n\n\n  * Cookie\nli_gc\n  * Duration\n6 months\n  * Description\nLinkedin set this cookie for storing visitor's consent regarding using cookies for non-essential purposes.\n\n\n  * Cookie\n__hssrc\n  * Duration\nsession\n  * Description\nThis cookie is set by Hubspot whenever it changes the session cookie. The __hssrc cookie set to 1 indicates that the user has restarted the browser, and if the cookie does not exist, it is assumed to be a new session.\n\n\n  * Cookie\n__hssc\n  * Duration\n1 hour\n  * Description\nHubSpot sets this cookie to keep track of sessions and to determine if HubSpot should increment the session number and timestamps in the __hstc cookie.\n\n\n  * Cookie\nwpEmojiSettingsSupports\n  * Duration\nsession\n  * Description\nWordPress sets this cookie when a user interacts with emojis on a WordPress site. It helps determine if the user's browser can display emojis properly.\n\n\n  * Cookie\nBCSessionID\n  * Duration\n1 year 1 month 4 days\n  * Description\nBlueconic sets this cookie as a unique identifier for the BlueConic profile.\n\n\n  * Cookie\n_octo\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nlogged_in\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\n__Secure-YEC\n  * Duration\npast\n  * Description\nYouTube sets this cookie to stores the user's video player preferences using embedded YouTube video\n\n\n  * Cookie\n__eoi\n  * Duration\n6 months\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nAWSALBTGCORS\n  * Duration\n7 days\n  * Description\nNo description available.\n\n\n  * Cookie\nlogin-status-p\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nAWSALBTG\n  * Duration\n7 days\n  * Description\nNo description available.\n\n\n  * Cookie\ncsrf_token\n  * Duration\nsession\n  * Description\nNo description available.\n\n\n  * Cookie\ntoken_v2\n  * Duration\n1 day\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nD\n  * Duration\n1 year\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nPHPSESSID\n  * Duration\nsession\n  * Description\nThis cookie is native to PHP applications. The cookie stores and identifies a user's unique session ID to manage user sessions on the website. The cookie is a session cookie and will be deleted when all the browser windows are closed.\n\n\n  * Cookie\nVISITOR_PRIVACY_METADATA\n  * Duration\n6 months\n  * Description\nYouTube sets this cookie to store the user's cookie consent state for the current domain. \n\n\n  * Cookie\ncookietest\n  * Duration\nsession\n  * Description\nThe cookietest cookie is typically used to determine whether the user's browser accepts cookies, essential for website functionality and user experience.\n\n\n  * Cookie\n__Host-airtable-session\n  * Duration\n1 year\n  * Description\nThis cookie is used to enable us to integrate the services of Airtable.\n\n\n  * Cookie\n__Host-airtable-session.sig\n  * Duration\n1 year\n  * Description\nThis cookie is used to enable us to integrate the services of Airtable.\n\n\n  * Cookie\nm\n  * Duration\n1 year 1 month 4 days\n  * Description\nStripe sets this cookie for fraud prevention purposes. It identifies the device used to access the website, allowing the website to be formatted accordingly.\n\n\n  * Cookie\nBIGipServer*\n  * Duration\nsession\n  * Description\nMarketo sets this cookie to collect information about the user's online activity and build a profile about their interests to provide advertisements relevant to the user. \n\n\n  * Cookie\n__cfruid\n  * Duration\nsession\n  * Description\nCloudflare sets this cookie to identify trusted web traffic.\n\n\n  * Cookie\n_GRECAPTCHA\n  * Duration\n6 months\n  * Description\nGoogle Recaptcha service sets this cookie to identify bots to protect the website against malicious spam attacks.\n\n\n  * Cookie\n__Secure-YNID\n  * Duration\n6 months\n  * Description\nGoogle cookie used to protect user security and prevent fraud, especially during the login process.\n\n\n  * Cookie\ncookieyes-consent\n  * Duration\n1 year\n  * Description\nCookieYes sets this cookie to remember users' consent preferences so that their preferences are respected on subsequent visits to this site. It does not collect or store any personal information about the site visitors.\n\n\nFunctional\nFunctional cookies help perform certain functionalities like sharing the content of the website on social media platforms, collecting feedback, and other third-party features.\n  * Cookie\nlidc\n  * Duration\n1 day\n  * Description\nLinkedIn sets the lidc cookie to facilitate data center selection.\n\n\n  * Cookie\nbrw\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nbrwConsent\n  * Duration\n5 minutes\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nWMF-Uniq\n  * Duration\n1 year\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nloom_anon_comment\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nloom_referral_video\n  * Duration\nsession\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nVISITOR_INFO1_LIVE\n  * Duration\n6 months\n  * Description\nA cookie set by YouTube to measure bandwidth that determines whether the user gets the new or old player interface.\n\n\n  * Cookie\nyt-remote-connected-devices\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to store the user's video preferences using embedded YouTube videos.\n\n\n  * Cookie\nytidb::LAST_RESULT_ENTRY_KEY\n  * Duration\nNever Expires\n  * Description\nThe cookie ytidb::LAST_RESULT_ENTRY_KEY is used by YouTube to store the last search result entry that was clicked by the user. This information is used to improve the user experience by providing more relevant search results in the future.\n\n\n  * Cookie\nyt-remote-device-id\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to store the user's video preferences using embedded YouTube videos.\n\n\n  * Cookie\nyt-remote-session-name\n  * Duration\nsession\n  * Description\nThe yt-remote-session-name cookie is used by YouTube to store the user's video player preferences using embedded YouTube video.\n\n\n  * Cookie\nyt-remote-fast-check-period\n  * Duration\nsession\n  * Description\nThe yt-remote-fast-check-period cookie is used by YouTube to store the user's video player preferences for embedded YouTube videos.\n\n\n  * Cookie\nyt-remote-session-app\n  * Duration\nsession\n  * Description\nThe yt-remote-session-app cookie is used by YouTube to store user preferences and information about the interface of the embedded YouTube video player.\n\n\n  * Cookie\nyt-remote-cast-available\n  * Duration\nsession\n  * Description\nThe yt-remote-cast-available cookie is used to store the user's preferences regarding whether casting is available on their YouTube video player.\n\n\n  * Cookie\nyt-remote-cast-installed\n  * Duration\nsession\n  * Description\nThe yt-remote-cast-installed cookie is used to store the user's video player preferences using embedded YouTube video.\n\n\n  * Cookie\ncp_session\n  * Duration\n3 months\n  * Description\nCodepen sets this cookie for Help systems found in the website.\n\n\n  * Cookie\nloid\n  * Duration\n1 year 1 month 4 days\n  * Description\nThis cookie is set by the Reddit. The cookie enables the sharing of content from the website onto the social media platform.\n\n\nAnalytics\nAnalytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics such as the number of visitors, bounce rate, traffic source, etc.\n  * Cookie\n__hstc\n  * Duration\n6 months\n  * Description\nHubspot set this main cookie for tracking visitors. It contains the domain, initial timestamp (first visit), last timestamp (last visit), current timestamp (this visit), and session number (increments for each subsequent session).\n\n\n  * Cookie\nhubspotutk\n  * Duration\n6 months\n  * Description\nHubSpot sets this cookie to keep track of the visitors to the website. This cookie is passed to HubSpot on form submission and used when deduplicating contacts.\n\n\n  * Cookie\n_ga\n  * Duration\n1 year 1 month 4 days\n  * Description\nGoogle Analytics sets this cookie to calculate visitor, session and campaign data and track site usage for the site's analytics report. The cookie stores information anonymously and assigns a randomly generated number to recognise unique visitors.\n\n\n  * Cookie\n_ga_*\n  * Duration\n1 year 1 month 4 days\n  * Description\nGoogle Analytics sets this cookie to store and count page views.\n\n\n  * Cookie\n__Host-psifi.analyticsTrace\n  * Duration\n6 hours\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\n__Host-psifi.analyticsTraceV2\n  * Duration\n6 hours\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\n_gh_sess\n  * Duration\nsession\n  * Description\nGitHub sets this cookie for temporary application and framework state between pages like what step the user is on in a multiple step form.\n\n\n  * Cookie\nYSC\n  * Duration\nsession\n  * Description\nYSC cookie is set by Youtube and is used to track the views of embedded videos on Youtube pages.\n\n\n  * Cookie\najs_anonymous_id\n  * Duration\n1 year\n  * Description\nThis cookie is set by Segment to count the number of people who visit a certain site by tracking if they have visited before.\n\n\n  * Cookie\nvuid\n  * Duration\n1 year 1 month 4 days\n  * Description\nVimeo installs this cookie to collect tracking information by setting a unique ID to embed videos on the website. \n\n\nPerformance\nPerformance cookies are used to understand and analyse the key performance indexes of the website which helps in delivering a better user experience for the visitors.\n  * Cookie\nAWSALB\n  * Duration\n7 days\n  * Description\nAWSALB is an application load balancer cookie set by Amazon Web Services to map the session to the target.\n\n\n  * Cookie\nacq\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nacq.sig\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nptc\n  * Duration\n2 years\n  * Description\nNo description available.\n\n\nAdvertisement\nAdvertisement cookies are used to provide visitors with customised advertisements based on the pages you visited previously and to analyse the effectiveness of the ad campaigns.\n  * Cookie\nmuc_ads\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to collect user behaviour and interaction data to optimize the website.\n\n\n  * Cookie\nguest_id_marketing\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor.\n\n\n  * Cookie\nguest_id_ads\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor.\n\n\n  * Cookie\npersonalization_id\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to integrate and share features for social media and also store information about how the user uses the website, for tracking and targeting.\n\n\n  * Cookie\nguest_id\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor. It registers if a user is signed in to the Twitter platform and collects information about ad preferences.\n\n\n  * Cookie\nbcookie\n  * Duration\n1 year\n  * Description\nLinkedIn sets this cookie from LinkedIn share buttons and ad tags to recognize browser IDs.\n\n\n  * Cookie\n__Secure-ROLLOUT_TOKEN\n  * Duration\n6 months\n  * Description\nYouTube sets this cookie to manage feature rollout and experimentation. It helps Google control which new features or interface changes are shown to users as part of testing and staged rollouts, ensuring consistent experience for a given user during an experiment.\n\n\n  * Cookie\nyt.innertube::nextId\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.\n\n\n  * Cookie\nyt.innertube::requests\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.\n\n\n  * Cookie\nsession_tracker\n  * Duration\nsession\n  * Description\nThis cookie is set by the Reddit. This cookie is used to identify trusted web traffic. It also helps in adverstising on the website.\n\n\n  * Cookie\nedgebucket\n  * Duration\nsession\n  * Description\nReddit sets this cookie to save the information about a log-on Reddit user, for the purpose of advertisement recommendations and updating the content.\n\n\n  * Cookie\ndid\n  * Duration\n1 year\n  * Description\nArbor sets this cookie to show targeted ads to site visitors.This cookie expires after 2 months or 1 year.\n\n\nUncategorised\nOther uncategorised cookies are those that are being analysed and have not been classified into a category as yet.\nNo cookies to display.\nReject All Save My Preferences Accept All\nToggle Search[](https://towardsdatascience.com/)Toggle Mobile Navigation\n[](https://towardsdatascience.com/)\nPublish AI, ML & data-science insights to a global community of data professionals.\nSign in[Submit an Article](https://write.insightmediagroup.io)\n[Latest](https://towardsdatascience.com/latest/)[Editor's Picks](https://towardsdatascience.com/tag/editors-pick/)[Newsletter](https://towardsdatascience.com/tag/the-variable/)[ShipAI](https://towardsdatascience.com/shipai/)\n* * *\n[Write For TDS](https://towardsdatascience.com/submissions/)\n[LinkedIn](https://www.linkedin.com/company/towards-data-science/)[X](https://x.com/TDataScience)Toggle Search\n[Data Science](https://towardsdatascience.com/category/data-science/)\n# Time Series Forecasting: A Practical Guide to Exploratory Data Analysis\nHow to use Exploratory Data Analysis to drive information from time series data and enhance feature engineering using Python\n[Maicol Nicolini](https://towardsdatascience.com/author/maicolnicolini96/)\nMay 9, 202415 min read\n![Photo by Ales Krivec on Unsplash](../images/000.jpg)\nPhoto by [Ales Krivec](https://unsplash.com/@aleskrivec?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral)\n## Introduction\nTime series analysis certainly represents one of the most widespread topics in the field of data science and machine learning: whether predicting financial events, energy consumption, product sales or stock market trends, this field has always been of great interest to businesses.\nObviously, the great increase in data availability, combined with the constant progress in machine learning models, has made this topic even more interesting today. Alongside traditional forecasting methods derived from statistics (e.g. regressive models, ARIMA models, exponential smoothing), techniques relating to machine learning (e.g. tree-based models) and deep learning (e.g. LSTM Networks, CNNs, Transformer-based Models) have emerged for some time now.\nDespite the huge differences between these techniques, there is a preliminary step that must be done, no matter what the model is: _Exploratory Data Analysis._\nLearn this step by step with the interactive [AI and Data Scientist](https://roadmap.sh/ai-data-scientist) roadmap.\nIn statistics, **Exploratory Data Analysis** (EDA) is a discipline consisting in analyzing and visualizing data in order to summarize their main characteristics and gain relevant information from them. This is of considerable importance in the data science field because it allows to lay the foundations to another important step: _feature engineering_. That is, the practice that consists on creating, transforming and extracting features from the dataset so that the model can work to the best of its possibilities.\nThe objective of this article is therefore to define a clear exploratory data analysis template, focused on time series, which can summarize and highlight the most important characteristics of the dataset. To do this, we will use some common Python libraries such as _Pandas_ , _Seaborn_ and S _tatsmodel_.\n## Data\nLet's first define the dataset: for the purposes of this article, we will take Kaggle's [**Hourly Energy Consumption**](https://www.kaggle.com/datasets/robikscube/hourly-energy-consumption) data. This dataset relates to PJM Hourly Energy Consumption data, a regional transmission organization in the United States, that serves electricity to Delaware, Illinois, Indiana, Kentucky, Maryland, Michigan, New Jersey, North Carolina, Ohio, Pennsylvania, Tennessee, Virginia, West Virginia, and the District of Columbia.\nThe hourly power consumption data comes from PJM's website and are in megawatts (MW).\n## Exploratory Data Analysis\nLet's now define which are the most significant analyses to be performed when dealing with time series.\nFor sure, one of the most important thing is to plot the data: graphs can highlight many features, such as patterns, unusual observations, changes over time, and relationships between variables. As already said, the insight that emerge from these plots must then be taken into consideration, as much as possible, into the forecasting model. Moreover, some mathematical tools such as descriptive statistics and time series decomposition, will also be very useful.\nSaid that, the EDA I'm proposing in this article consists on six steps: Descriptive Statistics, Time Plot, Seasonal Plots, Box Plots, Time Series Decomposition, Lag Analysis.\n### 1. Descriptive Statistics\nDescriptive statistic is a summary statistic that quantitatively describes or summarizes features from a collection of structured data.\nSome metrics that are commonly used to describe a dataset are: measures of central tendency (e.g. _mean_ , _median_), measures of dispersion (e.g. _range_ , _standard deviation_), and measure of position (e.g. _percentiles_ , _quartile_). All of them can be summarized by the so called **five number summary** , which include: minimum, first quartile (Q1), median or second quartile (Q2), third quartile (Q3) and maximum of a distribution.\nIn Python, these information can be easily retrieved using the well know `describe` method from Pandas:\npython\n\n```\nimport pandas as pd\n\n# Loading and preprocessing steps\ndf = pd.read_csv('../input/hourly-energy-consumption/PJME_hourly.csv')\ndf = df.set_index('Datetime')\ndf.index = pd.to_datetime(df.index)\n\ndf.describe()\n```\n\nPJME statistic summary. \n### 2. Time plot\nThe obvious graph to start with is the time plot. That is, the observations are plotted against the time they were observed, with consecutive observations joined by lines.\nIn Python , we can use Pandas and Matplotlib:\npython\n\n```\nimport matplotlib.pyplot as plt\n\n# Set pyplot style\nplt.style.use(\"seaborn\")\n\n# Plot\ndf['PJME_MW'].plot(title='PJME - Time Plot', figsize=(10,6))\nplt.ylabel('Consumption [MW]')\nplt.xlabel('Date')\n```\n\n![2.1 PJME Consumption Time Plot.](../images/001.png)\n2.1 PJME Consumption Time Plot.\nThis plot already provides several information:\n  1. As we could expect, the pattern shows yearly seasonality.\n  2. Focusing on a single year, it seems that more pattern emerges. Likely, the consumptions will have a peak in winter and one another in summer, due to the greater electricity consumption.\n  3. The series does not exhibit a clear increasing/decreasing trend over the years, the average consumptions remains stationary.\n  4. There is an anomalous value around 2023, probably it should be imputed when implementing the model.\n\n\n### 3. Seasonal Plots\nA seasonal plot is fundamentally a time plot where data are plotted against the individual \"seasons\" of the series they belong.\nRegarding energy consumption, we usually have hourly data available, so there could be several seasonality: _yearly_ , _weekly_ , _daily_. Before going deep into these plots, let's first set up some variables in our Pandas dataframe:\npython\n\n```\n# Defining required fields\ndf['year'] = [x for x in df.index.year]\ndf['month'] = [x for x in df.index.month]\ndf = df.reset_index()\ndf['week'] = df['Datetime'].apply(lambda x:x.week)\ndf = df.set_index('Datetime')\ndf['hour'] = [x for x in df.index.hour]\ndf['day'] = [x for x in df.index.day_of_week]\ndf['day_str'] = [x.strftime('%a') for x in df.index]\ndf['year_month'] = [str(x.year) + '_' + str(x.month) for x in df.index]\n```\n\n### 3.1 Seasonal plot - Yearly consumption\nA very interesting plot is the one referring to the energy consumption grouped by year over months, this highlights yearly seasonality and can inform us about ascending/descending trends over the years.\nHere is the Python code:\npython\n\n```\nimport numpy as np\n\n# Defining colors palette\nnp.random.seed(42)\ndf_plot = df[['month', 'year', 'PJME_MW']].dropna().groupby(['month', 'year']).mean()[['PJME_MW']].reset_index()\nyears = df_plot['year'].unique()\ncolors = np.random.choice(list(mpl.colors.XKCD_COLORS.keys()), len(years), replace=False)\n\n# Plot\nplt.figure(figsize=(16,12))\nfor i, y in enumerate(years):\n    if i > 0:        \n        plt.plot('month', 'PJME_MW', data=df_plot[df_plot['year'] == y], color=colors[i], label=y)\n        if y == 2018:\n            plt.text(df_plot.loc[df_plot.year==y, :].shape[0]+0.3, df_plot.loc[df_plot.year==y, 'PJME_MW'][-1:].values[0], y, fontsize=12, color=colors[i])\n        else:\n            plt.text(df_plot.loc[df_plot.year==y, :].shape[0]+0.1, df_plot.loc[df_plot.year==y, 'PJME_MW'][-1:].values[0], y, fontsize=12, color=colors[i])\n\n# Setting labels\nplt.gca().set(ylabel= 'PJME_MW', xlabel = 'Month')\nplt.yticks(fontsize=12, alpha=.7)\nplt.title(\"Seasonal Plot - Monthly Consumption\", fontsize=20)\nplt.ylabel('Consumption [MW]')\nplt.xlabel('Month')\nplt.show()\n```\n\n![3.1 PJME Yearly Seasonal Plot](../images/002.png)\n3.1 PJME Yearly Seasonal Plot\nThis plot shows every year has actually a very predefined pattern: the consumption increases significantly during winter and has a peak in summer (due to heating/cooling systems), while has a minima in spring and in autumn when no heating or cooling is usually required.\nFurthermore, this plot tells us that's not a clear increasing/decreasing pattern in the overall consumptions across years.\n### 3.2 Seasonal plot - Weekly consumption\nAnother useful plot is the weekly plot, it depicts the consumptions during the week over months and can also suggest if and how weekly consumptions are changing over a single year.\nLet's see how to figure it out with Python:\npython\n\n```\n# Defining colors palette\nnp.random.seed(42)\ndf_plot = df[['month', 'day_str', 'PJME_MW', 'day']].dropna().groupby(['day_str', 'month', 'day']).mean()[['PJME_MW']].reset_index()\ndf_plot = df_plot.sort_values(by='day', ascending=True)\n\nmonths = df_plot['month'].unique()\ncolors = np.random.choice(list(mpl.colors.XKCD_COLORS.keys()), len(months), replace=False)\n\n# Plot\nplt.figure(figsize=(16,12))\nfor i, y in enumerate(months):\n    if i > 0:        \n        plt.plot('day_str', 'PJME_MW', data=df_plot[df_plot['month'] == y], color=colors[i], label=y)\n        if y == 2018:\n            plt.text(df_plot.loc[df_plot.month==y, :].shape[0]-.9, df_plot.loc[df_plot.month==y, 'PJME_MW'][-1:].values[0], y, fontsize=12, color=colors[i])\n        else:\n            plt.text(df_plot.loc[df_plot.month==y, :].shape[0]-.9, df_plot.loc[df_plot.month==y, 'PJME_MW'][-1:].values[0], y, fontsize=12, color=colors[i])\n\n# Setting Labels\nplt.gca().set(ylabel= 'PJME_MW', xlabel = 'Month')\nplt.yticks(fontsize=12, alpha=.7)\nplt.title(\"Seasonal Plot - Weekly Consumption\", fontsize=20)\nplt.ylabel('Consumption [MW]')\nplt.xlabel('Month')\nplt.show()\n```\n\n![3.2 PJME Weekly Seasonal Plot](../images/003.png)\n3.2 PJME Weekly Seasonal Plot\n### 3.3 Seasonal plot - Daily consumption\nFinally, the last seasonal plot I want to show is the daily consumption plot. As you can guess, it represents how consumption change over the day. In this case, data are first grouped by day of week and then aggregated taking the mean.\nHere's the code:\npython\n\n```\nimport seaborn as sns\n\n# Defining the dataframe\ndf_plot = df[['hour', 'day_str', 'PJME_MW']].dropna().groupby(['hour', 'day_str']).mean()[['PJME_MW']].reset_index()\n\n# Plot using Seaborn\nplt.figure(figsize=(10,8))\nsns.lineplot(data = df_plot, x='hour', y='PJME_MW', hue='day_str', legend=True)\nplt.locator_params(axis='x', nbins=24)\nplt.title(\"Seasonal Plot - Daily Consumption\", fontsize=20)\nplt.ylabel('Consumption [MW]')\nplt.xlabel('Hour')\nplt.legend()\n```\n\n![3.3 PJME Daily Seasonal Plot](../images/004.png)\n3.3 PJME Daily Seasonal Plot\nOften, this plot show a very typical pattern, someone calls it \"M profile\" since consumptions seems to depict an \"M\" during the day. Sometimes this pattern is clear, others not (like in this case).\nHowever, this plots usually shows a relative peak in the middle of the day (from 10 am to 2 pm), then a relative minima (from 2 pm to 6 pm) and another peak (from 6 pm to 8 pm). Finally, it also shows the difference in consumptions from weekends and other days.\n### 3.4 Seasonal plot - Feature Engineering\nLet's now see how to use this information for feature engineering. Let's suppose we are using some ML model that requires good quality features (e.g. ARIMA models or tree-based models).\nThese are the main evidences coming from seasonal plots:\n  1. Yearly consumptions do not change a lot over years: this suggests the possibility to use, when available, yearly seasonality features coming from lag or exogenous variables.\n  2. Weekly consumptions follow the same pattern across months: this suggests to use weekly features coming from lag or exogenous variables.\n  3. Daily consumption differs from normal days and weekends: this suggest to use categorical features able to identify when a day is a normal day and when it is not.\n\n\n### 4. Box Plots\nBoxplot are a useful way to identify how data are distributed. Briefly, boxplots depict percentiles, which represent 1st (Q1), 2nd (Q2/median) and 3rd (Q3) quartile of a distribution and whiskers, which represent the range of the data. Every value beyond the whiskers can be thought as an _outlier_ , more in depth, whiskers are often computed as:\nWhiskers Formula \n### 4.1 Box Plots - Total consumption\nLet's first compute the box plot regarding the total consumption, this can be easily done with _Seaborn_ :\npython\n\n```\nplt.figure(figsize=(8,5))\nsns.boxplot(data=df, x='PJME_MW')\nplt.xlabel('Consumption [MW]')\nplt.title(f'Boxplot - Consumption Distribution');\n```\n\n![4.1 PJME Boxplot](../images/005.png)\n4.1 PJME Boxplot\nEven if this plot seems not to be much informative, it tells us we are dealing with a Gaussian-like distribution, with a tail more accentuated towards the right.\n### 4.2 Box Plots - Day month distribution\nA very interesting plot is the day/month box plot. It is obtained creating a \"day month\" variable and grouping consumptions by it. Here is the code, referring only from year 2017:\npython\n\n```\ndf['year'] = [x for x in df.index.year]\ndf['month'] = [x for x in df.index.month]\ndf['year_month'] = [str(x.year) + '_' + str(x.month) for x in df.index]\n\ndf_plot = df[df['year'] >= 2017].reset_index().sort_values(by='Datetime').set_index('Datetime')\nplt.title(f'Boxplot Year Month Distribution');\nplt.xticks(rotation=90)\nplt.xlabel('Year Month')\nplt.ylabel('MW')\n\nsns.boxplot(x='year_month', y='PJME_MW', data=df_plot)\nplt.ylabel('Consumption [MW]')\nplt.xlabel('Year Month')\n```\n\n![4.2 PJME Year/Month Boxplot](../images/006.png)\n4.2 PJME Year/Month Boxplot\nIt can be seen that consumption are less uncertain in summer/winter months (i.e. when we have peaks) while are more dispersed in spring/autumn (i.e. when temperatures are more variable). Finally, consumption in summer 2018 are higher than 2017, maybe due to a warmer summer. When feature engineering, remember to include (if available) the temperature curve, probably it can be used as an exogenous variable.\n### 4.3 Box Plots - Day distribution\nAnother useful plot is the one referring consumption distribution over the week, this is similar to the weekly consumption seasonal plot.\npython\n\n```\ndf_plot = df[['day_str', 'day', 'PJME_MW']].sort_values(by='day')\nplt.title(f'Boxplot Day Distribution')\nplt.xlabel('Day of week')\nplt.ylabel('MW')\nsns.boxplot(x='day_str', y='PJME_MW', data=df_plot)\nplt.ylabel('Consumption [MW]')\nplt.xlabel('Day of week')\n```\n\n![4.3 PJME Day Boxplot](../images/007.png)\n4.3 PJME Day Boxplot\nAs seen before, consumptions are noticeably lower on weekends. Anyway, there are several outliers pointing out that calendar features like \"day of week\" for sure are useful but could not fully explain the series.\n### 4.4 Box Plots - Hour distribution\nLet's finally see hour distribution box plot. It is similar to the daily consumption seasonal plot since it provides how consumptions are distributed over the day. Following, the code:\npython\n\n```\nplt.title(f'Boxplot Hour Distribution');\nplt.xlabel('Hour')\nplt.ylabel('MW')\nsns.boxplot(x='hour', y='PJME_MW', data=df)\nplt.ylabel('Consumption [MW]')\nplt.xlabel('Hour')\n```\n\n![4.4 PJME Hour Boxplot](../images/008.png)\n4.4 PJME Hour Boxplot\nNote that the \"M\" shape seen before is now much more crushed. Furthermore there are a lot of outliers, this tells us data not only relies on daily seasonality (e.g. the consumption on today's 12 am is similar to the consumption of yesterday 12 am) but also on something else, probably some exogenous climatic feature like temperature or humidity.\n### 5. Time Series Decomposition\nAs already said, time series data can exhibit a variety of patterns. Often, it is helpful to split a time series into several components, each representing an underlying pattern category.\nWe can think of a time series as comprising three components: a _trend_ component, a _seasonal_ component and a _remainder_ component (containing anything else in the time series). For some time series (e.g., energy consumption series), there can be more than one seasonal component, corresponding to different seasonal periods (daily, weekly, monthly, yearly).\nThere are two main type of decomposition: _additive_ and _multiplicative_.\nFor the additive decomposition, we represent a series (𝑦) as the sum of a seasonal component (𝑆), a trend (𝑇) and a remainder (𝑅):\n![](../images/body-3aa6ae460f3ade31a608.png)\nSimilarly, a multiplicative decomposition can be written as:\n![](../images/body-1fd388c5f6d1f84ddabb.png)\nGenerally speaking, additive decomposition best represent series with constant variance while multiplicative decomposition best suits time series with non-stationary variances.\nIn Python, time series decomposition can be easily fulfilled with _Statsmodel_ library:\npython\n\n```\ndf_plot = df[df['year'] == 2017].reset_index()\ndf_plot = df_plot.drop_duplicates(subset=['Datetime']).sort_values(by='Datetime')\ndf_plot = df_plot.set_index('Datetime')\ndf_plot['PJME_MW - Multiplicative Decompose'] = df_plot['PJME_MW']\ndf_plot['PJME_MW - Additive Decompose'] = df_plot['PJME_MW']\n\n# Additive Decomposition\nresult_add = seasonal_decompose(df_plot['PJME_MW - Additive Decompose'], model='additive', period=24*7)\n\n# Multiplicative Decomposition \nresult_mul = seasonal_decompose(df_plot['PJME_MW - Multiplicative Decompose'], model='multiplicative', period=24*7)\n\n# Plot\nresult_add.plot().suptitle('', fontsize=22)\nplt.xticks(rotation=45)\nresult_mul.plot().suptitle('', fontsize=22)\nplt.xticks(rotation=45)\nplt.show()\n```\n\n![5.1 PJME Series Decomposition - Additive Decompose.](../images/009.png)\n5.1 PJME Series Decomposition - Additive Decompose.\n![5.2 PJME Series Decomposition - Multiplicative Decompose.](../images/010.png)\n5.2 PJME Series Decomposition - Multiplicative Decompose.\nThe above plots refers to 2017. In both cases, we see the trend has several local peaks, with higher values in summer. From the seasonal component, we can see the series actually has several periodicities, this plot highlights more the weekly one, but if we focus on a particular month (January) of the same year, daily seasonality emerges too:\npython\n\n```\ndf_plot = df[(df['year'] == 2017)].reset_index()\ndf_plot = df_plot[df_plot['month'] == 1]\ndf_plot['PJME_MW - Multiplicative Decompose'] = df_plot['PJME_MW']\ndf_plot['PJME_MW - Additive Decompose'] = df_plot['PJME_MW']\n\ndf_plot = df_plot.drop_duplicates(subset=['Datetime']).sort_values(by='Datetime')\ndf_plot = df_plot.set_index('Datetime')\n\n# Additive Decomposition\nresult_add = seasonal_decompose(df_plot['PJME_MW - Additive Decompose'], model='additive', period=24*7)\n\n# Multiplicative Decomposition \nresult_mul = seasonal_decompose(df_plot['PJME_MW - Multiplicative Decompose'], model='multiplicative', period=24*7)\n\n# Plot\nresult_add.plot().suptitle('', fontsize=22)\nplt.xticks(rotation=45)\nresult_mul.plot().suptitle('', fontsize=22)\nplt.xticks(rotation=45)\nplt.show()\n```\n\n![5.3 PJME Series Decomposition - Additive Decompose, focus on January 2017.](../images/011.png)\n5.3 PJME Series Decomposition - Additive Decompose, focus on January 2017.\n![5.4 PJME Series Decomposition - Multiplicative Decompose, focus on January 2017.](../images/012.png)\n5.4 PJME Series Decomposition - Multiplicative Decompose, focus on January 2017.\n### 6. Lag Analysis\nIn time series forecasting, a lag is simply a past value of the series. For example, for daily series, the first lag refers to the value the series had the previous day, the second to the value of the day before and so on.\nLag analysis is based on computing correlations between the series and a lagged version of the series itself, this is also called _autocorrelation._ For a k-lagged version of a series, we define the autocorrelation coefficient as:\n![](../images/body-e6028ce6a0926ed00637.png)\nWhere _y_ bar represent the mean value of the series and _k_ the lag.\nThe autocorrelation coefficients make up the _autocorrelation function_ (ACF) for the series, this is simply a plot depicting the auto-correlation coefficient versus the number of lags taken into consideration.\nWhen data has a trend, the autocorrelations for small lags are usually large and positive because observations close in time are also nearby in value. When data show seasonality, autocorrelation values will be larger in correspondence of seasonal lags (and multiples of the seasonal period) than for other lags. Data with both trend and seasonality will show a combination of these effects.\nIn practice, a more useful function is the _partial autocorrelation function_ (PACF). It is similar to the ACF, except that it shows only the direct autocorrelation between two lags. For example, the partial autocorrelation for lag 3 refers to the only correlation lag 1 and 2 do not explain. In other words, the partial correlation refers to the direct effect a certain lag has on the current time value.\nBefore moving to the Python code, it is important to highlight that autocorrelation coefficient emerges more clearly if the series is _stationary,_ so often is better to first differentiate the series to stabilize the signal.\nSaid that, here is the code to plot PACF for different hours of the day:\npython\n\n```\nfrom statsmodels.graphics.tsaplots import plot_pacf\n\nactual = df['PJME_MW']\nhours = range(0, 24, 4)\n\nfor hour in hours:\n    plot_pacf(actual[actual.index.hour == hour].diff().dropna(), lags=30, alpha=0.01)\n    plt.title(f'PACF - h = {hour}')\n    plt.ylabel('Correlation')\n    plt.xlabel('Lags')\n    plt.show()\n```\n\n![6.1 PJME Lag Analysis - Partial Auto Correlation Function \\(h=0\\).](../images/013.png)\n6.1 PJME Lag Analysis - Partial Auto Correlation Function (h=0).\n![6.2 PJME Lag Analysis - Partial Auto Correlation Function \\(h=4\\).](../images/body-828c7d3955b9ec18dcd1.png)\n6.2 PJME Lag Analysis - Partial Auto Correlation Function (h=4).\n![6.3 PJME Lag Analysis - Partial Auto Correlation Function \\(h=8\\).](../images/body-4d18391f0f2a1c980f70.png)\n6.3 PJME Lag Analysis - Partial Auto Correlation Function (h=8).\n![6.4 PJME Lag Analysis - Partial Auto Correlation Function \\(h=12\\).](../images/body-3a9102d0bde83de2eef6.png)\n6.4 PJME Lag Analysis - Partial Auto Correlation Function (h=12).\n![6.5 PJME Lag Analysis - Partial Auto Correlation Function \\(h=16\\).](../images/body-01ed2a02858faa4b549a.png)\n6.5 PJME Lag Analysis - Partial Auto Correlation Function (h=16).\n![6.6 PJME Lag Analysis - Partial Auto Correlation Function \\(h=20\\).](../images/body-e463a01320d3ddd4d903.png)\n6.6 PJME Lag Analysis - Partial Auto Correlation Function (h=20).\nAs you can see, the PACF simply consists on plotting Pearson partial auto-correlation coefficients for different lags. Of course, the non-lagged series shows a perfect auto-correlation with itself, so lag 0 will always be 1. The blue band represent the _confidence interval:_ if a lag exceed that band, then it is statistically significant and we can assert it is has great importance.\n### 6.1 Lag analysis - Feature Engineering\nLag analysis is one of the most impactful study on time series feature engineering. As already said, a lag with high correlation is an important lag for the series, then it should be taken into consideration.\nA widely used feature engineering technique consists on making an **hourly division** of the dataset. That is, splitting data in 24 subset, each one referring to an hour of the day. This has the effect to regularize and smooth the signal, making it more simple to forecast.\nEach subset should then be feature engineered, trained and fine-tuned. The final forecast will be achieved combining the results of these 24 models. Said that, every hourly model will have its peculiarities, most of them will regard important lags.\nBefore moving on, let's define two types of lag we can deal with when doing lag analysis:\n  1. **Auto-regressive lags** : lags close to lag 0, for which we expect high values (recent lags are more likely to predict the present value). They are a representation on how much trend the series shows.\n  2. **Seasonal lags** : lags referring to seasonal periods. When hourly splitting the data, they usually represent weekly seasonality.\n\n\nNote that auto-regressive lag 1 can also be taught as a _daily seasonal lag_ for the series.\nLet's now discuss about the PACF plots printed above.\n### Night Hours\nConsumption on night hours (0, 4) relies more on auto-regressive than on weekly lags, since the most important are all localized on the first five. Seasonal periods such as 7, 14, 21, 28 seems not to be too much important, this advises us to pay particular attention on lag 1 to 5 when feature engineering.\n### Day Hours\nConsumption on day hours (8, 12, 16, 20) exhibit both auto-regressive and seasonal lags. This particularly true for hours 8 and 12 - when consumption is particularly high - while seasonal lags become less important approaching the night. For these subsets we should also include seasonal lag as well as auto-regressive ones.\nFinally, here are some tips when feature engineering lags:\n  * Do not to take into consideration too many lags since this will probably lead to over fitting. Generally, auto-regressive lags goes from 1 to 7, while weekly lags should be 7, 14, 21 and 28. But it's not mandatory to take each of them as features.\n  * Taking into consideration lags that are not auto-regressive or seasonal is usually a bad idea since they could bring to overfitting as well. Rather, try to understand while a certain lag is important.\n  * Transforming lags can often lead to more powerful features. For example, seasonal lags can be aggregated using a weighted mean to create a single feature representing the seasonality of the series.\n\n\n## Free Resources\nFinally, I would like to mention a very useful (and free) book explaining time series, which I have personally used a lot: [Forecasting: Principles and Practice](https://otexts.com/fpp3/).\nEven though it is meant to use R instead of Python, this textbook provides a great introduction to forecasting methods, covering the most important aspects of time series analysis.\n## Conclusion\nThe aim of this article was to present a comprehensive Exploratory Data Analysis template for time series forecasting.\nEDA is a fundamental step in any type of data science study since it allows to understand the nature and the peculiarities of the data and lays the foundation to feature engineering, which in turn can dramatically improve model performance.\nWe have then described some of the most used analysis for time series EDA, these can be both statistical/mathematical and graphical. Obviously, the intention of this work was only to give a practical framework to start with, subsequent investigations need to be carried out based on the type of historical series being examined and the business context.\nThanks for having followed me until the end.\n_Unless otherwise noted, all images are by the author._\n## Register Now to Continue Reading\n### Unlock Towards Data Science with a Free Account\nAll articles on Towards Data Science are available to registered users. Create a free account today to access this article and thousands more.\nRegister for freeSign in to your account\n* * *\n![](../images/body-bb1649100849f0cf5e1a.jpeg)\nWritten By\n## [Maicol Nicolini](https://towardsdatascience.com/author/maicolnicolini96/)\n[See all from Maicol Nicolini](https://towardsdatascience.com/author/maicolnicolini96/)\n[Data Science](https://towardsdatascience.com/tag/data-science/)[Energy Consumption](https://towardsdatascience.com/tag/energy-consumption/)[Exploratory Data Analysis](https://towardsdatascience.com/tag/exploratory-data-analysis/)[Feature Engineering](https://towardsdatascience.com/tag/feature-engineering/)[Time Series Analysis](https://towardsdatascience.com/tag/time-series-analysis/)\nShare This Article\n  * [](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Ftowardsdatascience.com%2Ftime-series-forecasting-a-practical-guide-to-exploratory-data-analysis-a101dc5f85b1%2F&title=Time%20Series%20Forecasting%3A%20A%20Practical%20Guide%20to%20Exploratory%20Data%20Analysis)\n  * [](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Ftowardsdatascience.com%2Ftime-series-forecasting-a-practical-guide-to-exploratory-data-analysis-a101dc5f85b1%2F&title=Time%20Series%20Forecasting%3A%20A%20Practical%20Guide%20to%20Exploratory%20Data%20Analysis)\n  * [](https://x.com/share?url=https%3A%2F%2Ftowardsdatascience.com%2Ftime-series-forecasting-a-practical-guide-to-exploratory-data-analysis-a101dc5f85b1%2F&text=Time%20Series%20Forecasting%3A%20A%20Practical%20Guide%20to%20Exploratory%20Data%20Analysis)\n\n\nTowards Data Science is a community publication. Submit your insights to reach our global audience and earn through the TDS Author Payment Program.\n[Write for TDS](https://towardsdatascience.com/submissions/)\n## Related Articles\n  * [![](../images/014.jpg)](https://towardsdatascience.com/preprocess-audio-data-with-the-signal-envelope-499e6072108/)\n## [Preprocess Audio Data with the Signal Envelope](https://towardsdatascience.com/preprocess-audio-data-with-the-signal-envelope-499e6072108/)\n[Data Science](https://towardsdatascience.com/category/data-science/)\nHow to remove extra noise in your audio data\n[Jeremy Pagirsky](https://towardsdatascience.com/author/jeremy-pagirsky/)\nOctober 27, 20216 min read\n  * [![](../images/015.png)](https://towardsdatascience.com/folium-and-choropleth-map-from-zero-to-pro-6127f9e68564/)\n## [Folium and Choropleth Map: From Zero to Pro](https://towardsdatascience.com/folium-and-choropleth-map-from-zero-to-pro-6127f9e68564/)\n[Data Science](https://towardsdatascience.com/category/data-science/)\nA Step-by-Step Tutorial to Create an Interactive Choropleth Map Using Folium with Layer Control and Customized Tooltips\n[My Data Talk](https://towardsdatascience.com/author/insightsbees/)\nJanuary 4, 20227 min read\n  * [![](../images/016.jpg)](https://towardsdatascience.com/3-basic-steps-of-stock-market-analysis-in-python-917787012143/)\n## [3 Basic Steps of Stock Market Analysis in Python](https://towardsdatascience.com/3-basic-steps-of-stock-market-analysis-in-python-917787012143/)\n[Programming](https://towardsdatascience.com/category/programming/)\nAnalyze Tesla stock in Python, calculate Trading Indicators and plot the OHLC chart. Includes a Jupyter Notebook with code examples.\n[Roman Orac](https://towardsdatascience.com/author/romanorac/)\nOctober 19, 20205 min read\n  * [![](../images/017.jpg)](https://towardsdatascience.com/creating-a-gradient-descent-animation-in-python-3c4dcd20ca51-2/)\n## [Creating a Gradient Descent Animation in Python](https://towardsdatascience.com/creating-a-gradient-descent-animation-in-python-3c4dcd20ca51-2/)\n[Programming](https://towardsdatascience.com/category/programming/)\nHow to plot the trajectory of a point over a complex surface\n[Luis Medina](https://towardsdatascience.com/author/luisdamed/)\nNovember 11, 202310 min read\n  * [![](../images/018.jpeg)](https://towardsdatascience.com/ml-landscape-overview-8a9e48a28641/)\n## [ML landscape overview](https://towardsdatascience.com/ml-landscape-overview-8a9e48a28641/)\n[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)\nA query from an investor friend nudged me into a long weekend deep dive into the ML landscape. As I was gathering notes, I thought it...\n[Pranil Dasika](https://towardsdatascience.com/author/pranildasika/)\nNovember 28, 20206 min read\n  * [![](../images/019.jpg)](https://towardsdatascience.com/5-pca-visualizations-you-must-try-on-your-next-data-science-project-148ec3d31e4d/)\n## [5 PCA Visualizations You Must Try On Your Next Data Science Project](https://towardsdatascience.com/5-pca-visualizations-you-must-try-on-your-next-data-science-project-148ec3d31e4d/)\n[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)\nWhich features carry the most weight? How do original features contribute to principal components? These 5 visualization types have the...\n[Dario Radečić](https://towardsdatascience.com/author/radecicdario/)\nAugust 2, 20248 min read\n  * [![](../images/020.jpg)](https://towardsdatascience.com/what-are-the-most-exciting-challenges-machine-learning-will-tackle-next-45f04974a81f/)\n## [What Are the Most Exciting Challenges Machine Learning Will Tackle Next?](https://towardsdatascience.com/what-are-the-most-exciting-challenges-machine-learning-will-tackle-next-45f04974a81f/)\n[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)\nOur weekly selection of must-read Editors' Picks and original features\n[TDS Editors](https://towardsdatascience.com/author/towardsdatascience/)\nNovember 4, 20214 min read\n  * [![](../images/021.jpeg)](https://towardsdatascience.com/pneumonia-detection-using-deep-learning-and-swagger-18aa5e8af576/)\n## [Pneumonia Detection using deep learning and swagger.](https://towardsdatascience.com/pneumonia-detection-using-deep-learning-and-swagger-18aa5e8af576/)\n[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)\nIn this story, we will walk through the steps of detecting pneumonia using deep learning and deploy the model using swagger.\n[Devarshpatel](https://towardsdatascience.com/author/devarshpatel29/)\nJuly 16, 20206 min read\n  * [![](../images/022.png)](https://towardsdatascience.com/data-driven-multi-touch-attribution-demystified-3eb3486476c6/)\n## [Data-Driven Multi-Touch Attribution Demystified](https://towardsdatascience.com/data-driven-multi-touch-attribution-demystified-3eb3486476c6/)\n[Data Science](https://towardsdatascience.com/category/data-science/)\nGetting Started With Markov Attribution Models\n[Borna Almasi](https://towardsdatascience.com/author/bornawrites/)\nFebruary 23, 202113 min read\n\n\n  * [YouTube](https://www.youtube.com/c/TowardsDataScience)\n  * [X](https://x.com/TDataScience)\n  * [LinkedIn](https://www.linkedin.com/company/towards-data-science/)\n  * [Threads](https://www.threads.net/@towardsdatascience)\n  * [Bluesky](https://bsky.app/profile/towardsdatascience.com)\n\n\n[](https://towardsdatascience.com/)\nYour home for data science and AI. The world's leading publication for data science, data analytics, data engineering, machine learning, and artificial intelligence professionals.\nSubscribe to Our Newsletter[Write For TDS](https://towardsdatascience.com/submissions/)•[About](https://towardsdatascience.com/about-towards-data-science-d691af11cc2f/)•[Advertise](https://contact.towardsdatascience.com/advertise-with-towards-data-science)•[Privacy Policy](https://towardsdatascience.com/privacy-policy/)•[Terms of Use](https://towardsdatascience.com/website-terms-of-use/)\n© Insight Media Group, LLC 2026•[The New Stack](https://thenewstack.io)•[roadmap.sh](https://roadmap.sh)•[Creator Network](https://insightmediagroup.io/creator-network)\n####  Recommended Articles \nClose\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607639/image?etag=1781250125848&width=500&height=500) Javascript Fatigue: HTMX Is All You Need to Build ChatGPT — Part 1 ](https://towardsdatascience.com/javascript-fatigue-you-dont-need-js-to-build-chatgpt/ \"Javascript Fatigue: HTMX Is All You Need to Build ChatGPT — Part 1\")\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607930/image?etag=1782124566054&width=500&height=500) The Subset Sum Problem Solved in Linear Time for Dense Enough Inputs ](https://towardsdatascience.com/subset-sum-problem-solved-in-linear-time-for-dense-enough-inputs/ \"The Subset Sum Problem Solved in Linear Time for Dense Enough Inputs\")\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/3660/image?etag=1780176246070&width=500&height=500) Does Your Company Have a Data Strategy? ](https://towardsdatascience.com/does-your-company-have-a-data-strategy-e8944dbec4ae/ \"Does Your Company Have a Data Strategy?\")\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/4970/image?etag=1780565225463&width=500&height=500) Productivity Tips, Data Career Insights, and Other Recent Must-Reads ](https://towardsdatascience.com/productivity-tips-data-career-insights-and-other-recent-must-reads-5c47eb7a2fe0/ \"Productivity Tips, Data Career Insights, and Other Recent Must-Reads\")\n\n\n",
  "metadata": {
    "title": "Time Series Forecasting: A Practical Guide to Exploratory Data Analysis | Towards Data Science",
    "description": "How to use Exploratory Data Analysis to drive information from time series data and enhance feature engineering using Python",
    "keywords": null,
    "author": "Maicol Nicolini",
    "og:locale": "en_US",
    "og:type": "article",
    "og:title": "Time Series Forecasting: A Practical Guide to Exploratory Data Analysis",
    "og:description": "How to use Exploratory Data Analysis to drive information from time series data and enhance feature engineering using Python",
    "og:url": "https://towardsdatascience.com/time-series-forecasting-a-practical-guide-to-exploratory-data-analysis-a101dc5f85b1/",
    "og:site_name": "Towards Data Science",
    "og:image": "https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/0_4XJDFxzoPez8m4i-scaled.jpg",
    "og:image:width": "2560",
    "og:image:height": "1905",
    "og:image:type": "image/jpeg",
    "twitter:card": "summary_large_image",
    "twitter:site": "@TDataScience",
    "twitter:creator": "@TDataScience",
    "twitter:label1": "Written by",
    "twitter:data1": "Maicol Nicolini",
    "twitter:label2": "Est. reading time",
    "twitter:data2": "15 min read",
    "article:published_time": "2024-05-09T16:58:16.000Z",
    "article:modified_time": "2024-05-09T16:58:16.000Z",
    "article:author": "https://towardsdatascience.com/author/maicolnicolini96/"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 301,
    "error": ""
  },
  "code_blocks": [
    {
      "language": "txt",
      "file": "attachments/f94cf65be99e/code/block_000.txt",
      "chars": 207
    },
    {
      "language": "txt",
      "file": "attachments/f94cf65be99e/code/block_001.txt",
      "chars": 196
    },
    {
      "language": "txt",
      "file": "attachments/f94cf65be99e/code/block_002.txt",
      "chars": 423
    },
    {
      "language": "txt",
      "file": "attachments/f94cf65be99e/code/block_003.txt",
      "chars": 1063
    },
    {
      "language": "txt",
      "file": "attachments/f94cf65be99e/code/block_004.txt",
      "chars": 1128
    },
    {
      "language": "txt",
      "file": "attachments/f94cf65be99e/code/block_005.txt",
      "chars": 456
    },
    {
      "language": "txt",
      "file": "attachments/f94cf65be99e/code/block_006.txt",
      "chars": 141
    },
    {
      "language": "txt",
      "file": "attachments/f94cf65be99e/code/block_007.txt",
      "chars": 476
    },
    {
      "language": "txt",
      "file": "attachments/f94cf65be99e/code/block_008.txt",
      "chars": 257
    },
    {
      "language": "txt",
      "file": "attachments/f94cf65be99e/code/block_009.txt",
      "chars": 171
    },
    {
      "language": "txt",
      "file": "attachments/f94cf65be99e/code/block_010.txt",
      "chars": 731
    },
    {
      "language": "txt",
      "file": "attachments/f94cf65be99e/code/block_011.txt",
      "chars": 775
    },
    {
      "language": "txt",
      "file": "attachments/f94cf65be99e/code/block_012.txt",
      "chars": 310
    }
  ],
  "images": [
    {
      "status": "http_404",
      "url": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607639/image?etag=1781250125848&width=500&height=500",
      "src": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607639/image?etag=1781250125848&width=500&height=500",
      "alt": "",
      "downloaded": false,
      "discovery": "saved_markdown",
      "retried_at": "2026-10-20T17:58:21Z"
    },
    {
      "status": "http_404",
      "url": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607930/image?etag=1782124566054&width=500&height=500",
      "src": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607930/image?etag=1782124566054&width=500&height=500",
      "alt": "",
      "downloaded": false,
      "discovery": "saved_markdown",
      "retried_at": "2026-10-20T17:58:21Z"
    },
    {
      "status": "http_404",
      "url": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/3660/image?etag=1780176246070&width=500&height=500",
      "src": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/3660/image?etag=1780176246070&width=500&height=500",
      "alt": "",
      "downloaded": false,
      "discovery": "saved_markdown",
      "retried_at": "2026-10-20T17:58:21Z"
    },
    {
      "status": "http_404",
      "url": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/4970/image?etag=1780565225463&width=500&height=500",
      "src": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/4970/image?etag=1780565225463&width=500&height=500",
      "alt": "",
      "downloaded": false,
      "discovery": "saved_markdown",
      "retried_at": "2026-10-20T17:58:21Z"
    }
  ],
  "audio": [],
  "videos": [
    {
      "url": "https://www.youtube.com/c/TowardsDataScience",
      "kind": "channel_reference",
      "status": "reference_only"
    }
  ],
  "links": [
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/latest/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/editors-pick/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/the-variable/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/shipai/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/submissions/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/category/data-science/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/author/maicolnicolini96/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/data-science/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/energy-consumption/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/exploratory-data-analysis/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/feature-engineering/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/time-series-analysis/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/preprocess-audio-data-with-the-signal-envelope-499e6072108/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/author/jeremy-pagirsky/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/folium-and-choropleth-map-from-zero-to-pro-6127f9e68564/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/author/insightsbees/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/3-basic-steps-of-stock-market-analysis-in-python-917787012143/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/category/programming/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/author/romanorac/"
    },
    {
      "type": "external",
      "href": "https://write.insightmediagroup.io"
    },
    {
      "type": "external",
      "href": "https://www.linkedin.com/company/towards-data-science/"
    },
    {
      "type": "external",
      "href": "https://x.com/TDataScience"
    },
    {
      "type": "external",
      "href": "https://unsplash.com/@aleskrivec"
    },
    {
      "type": "external",
      "href": "https://unsplash.com"
    },
    {
      "type": "external",
      "href": "https://roadmap.sh/ai-data-scientist"
    },
    {
      "type": "external",
      "href": "https://www.kaggle.com/datasets/robikscube/hourly-energy-consumption"
    },
    {
      "type": "external",
      "href": "https://otexts.com/fpp3/"
    },
    {
      "type": "external",
      "href": "https://www.facebook.com/sharer/sharer.php?title=Time+Series+Forecasting%3A+A+Practical+Guide+to+Exploratory+Data+Analysis&u=https%3A%2F%2Ftowardsdatascience.com%2Ftime-series-forecasting-a-practical-guide-to-exploratory-data-analysis-a101dc5f85b1%2F"
    },
    {
      "type": "external",
      "href": "https://www.linkedin.com/shareArticle?mini=true&title=Time+Series+Forecasting%3A+A+Practical+Guide+to+Exploratory+Data+Analysis&url=https%3A%2F%2Ftowardsdatascience.com%2Ftime-series-forecasting-a-practical-guide-to-exploratory-data-analysis-a101dc5f85b1%2F"
    },
    {
      "type": "external",
      "href": "https://x.com/share?text=Time+Series+Forecasting%3A+A+Practical+Guide+to+Exploratory+Data+Analysis&url=https%3A%2F%2Ftowardsdatascience.com%2Ftime-series-forecasting-a-practical-guide-to-exploratory-data-analysis-a101dc5f85b1%2F"
    },
    {
      "type": "external",
      "href": "https://www.youtube.com/c/TowardsDataScience"
    },
    {
      "type": "external",
      "href": "https://www.threads.net/@towardsdatascience"
    },
    {
      "type": "external",
      "href": "https://bsky.app/profile/towardsdatascience.com"
    },
    {
      "type": "external",
      "href": "https://thenewstack.io"
    },
    {
      "type": "external",
      "href": "https://roadmap.sh"
    },
    {
      "type": "external",
      "href": "https://insightmediagroup.io/creator-network"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-20T17:29:28Z",
  "source_url": "https://towardsdatascience.com/the-complete-guide-to-time-series-analysis-and-forecasting-70d476bfe775/",
  "delivery_status": "unverified",
  "discovery_status": "http_403",
  "missing_reason": "源页面未重新核验:http_403；已按保留正文补采图片",
  "repair_error": "",
  "actual_video_files": [],
  "expected_saved_body_images": 4,
  "image_discovery": "saved_markdown",
  "source_body_markdown": "![Revisit consent button](https://cdn-cookieyes.com/assets/images/revisit.svg)\nWe value your privacy\nWe use cookies to enhance your browsing experience, serve personalised ads or content, and analyse our traffic. By clicking \"Accept All\", you consent to our use of cookies.\nCustomise Reject All Accept All\nCustomise Consent Preferences ![](https://cdn-cookieyes.com/assets/images/close.svg)\nWe use cookies to help you navigate efficiently and perform certain functions. You will find detailed information about all cookies under each consent category below.\nThe cookies that are categorised as \"Necessary\" are stored on your browser as they are essential for enabling the basic functionalities of the site. ... Show more\nNecessaryAlways Active\nNecessary cookies are required to enable the basic features of this site, such as providing secure log-in or adjusting your consent preferences. These cookies do not store any personally identifiable data.\n  * Cookie\nBCTempID\n  * Duration\n10 minutes\n  * Description\nNo description available.\n\n\n  * Cookie\n__cf_bm\n  * Duration\n1 hour\n  * Description\nThis cookie, set by Cloudflare, is used to support Cloudflare Bot Management. \n\n\n  * Cookie\nAWSALBCORS\n  * Duration\n7 days\n  * Description\nAmazon Web Services set this cookie for load balancing.\n\n\n  * Cookie\n_cfuvid\n  * Duration\nsession\n  * Description\nCloudflare sets this cookie to track users across sessions to optimize user experience by maintaining session consistency and providing personalized services\n\n\n  * Cookie\nli_gc\n  * Duration\n6 months\n  * Description\nLinkedin set this cookie for storing visitor's consent regarding using cookies for non-essential purposes.\n\n\n  * Cookie\n__hssrc\n  * Duration\nsession\n  * Description\nThis cookie is set by Hubspot whenever it changes the session cookie. The __hssrc cookie set to 1 indicates that the user has restarted the browser, and if the cookie does not exist, it is assumed to be a new session.\n\n\n  * Cookie\n__hssc\n  * Duration\n1 hour\n  * Description\nHubSpot sets this cookie to keep track of sessions and to determine if HubSpot should increment the session number and timestamps in the __hstc cookie.\n\n\n  * Cookie\nwpEmojiSettingsSupports\n  * Duration\nsession\n  * Description\nWordPress sets this cookie when a user interacts with emojis on a WordPress site. It helps determine if the user's browser can display emojis properly.\n\n\n  * Cookie\nBCSessionID\n  * Duration\n1 year 1 month 4 days\n  * Description\nBlueconic sets this cookie as a unique identifier for the BlueConic profile.\n\n\n  * Cookie\n_octo\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nlogged_in\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\n__Secure-YEC\n  * Duration\npast\n  * Description\nYouTube sets this cookie to stores the user's video player preferences using embedded YouTube video\n\n\n  * Cookie\n__eoi\n  * Duration\n6 months\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nAWSALBTGCORS\n  * Duration\n7 days\n  * Description\nNo description available.\n\n\n  * Cookie\nlogin-status-p\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nAWSALBTG\n  * Duration\n7 days\n  * Description\nNo description available.\n\n\n  * Cookie\ncsrf_token\n  * Duration\nsession\n  * Description\nNo description available.\n\n\n  * Cookie\ntoken_v2\n  * Duration\n1 day\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nD\n  * Duration\n1 year\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nPHPSESSID\n  * Duration\nsession\n  * Description\nThis cookie is native to PHP applications. The cookie stores and identifies a user's unique session ID to manage user sessions on the website. The cookie is a session cookie and will be deleted when all the browser windows are closed.\n\n\n  * Cookie\nVISITOR_PRIVACY_METADATA\n  * Duration\n6 months\n  * Description\nYouTube sets this cookie to store the user's cookie consent state for the current domain. \n\n\n  * Cookie\ncookietest\n  * Duration\nsession\n  * Description\nThe cookietest cookie is typically used to determine whether the user's browser accepts cookies, essential for website functionality and user experience.\n\n\n  * Cookie\n__Host-airtable-session\n  * Duration\n1 year\n  * Description\nThis cookie is used to enable us to integrate the services of Airtable.\n\n\n  * Cookie\n__Host-airtable-session.sig\n  * Duration\n1 year\n  * Description\nThis cookie is used to enable us to integrate the services of Airtable.\n\n\n  * Cookie\nm\n  * Duration\n1 year 1 month 4 days\n  * Description\nStripe sets this cookie for fraud prevention purposes. It identifies the device used to access the website, allowing the website to be formatted accordingly.\n\n\n  * Cookie\nBIGipServer*\n  * Duration\nsession\n  * Description\nMarketo sets this cookie to collect information about the user's online activity and build a profile about their interests to provide advertisements relevant to the user. \n\n\n  * Cookie\n__cfruid\n  * Duration\nsession\n  * Description\nCloudflare sets this cookie to identify trusted web traffic.\n\n\n  * Cookie\n_GRECAPTCHA\n  * Duration\n6 months\n  * Description\nGoogle Recaptcha service sets this cookie to identify bots to protect the website against malicious spam attacks.\n\n\n  * Cookie\n__Secure-YNID\n  * Duration\n6 months\n  * Description\nGoogle cookie used to protect user security and prevent fraud, especially during the login process.\n\n\n  * Cookie\ncookieyes-consent\n  * Duration\n1 year\n  * Description\nCookieYes sets this cookie to remember users' consent preferences so that their preferences are respected on subsequent visits to this site. It does not collect or store any personal information about the site visitors.\n\n\nFunctional\nFunctional cookies help perform certain functionalities like sharing the content of the website on social media platforms, collecting feedback, and other third-party features.\n  * Cookie\nlidc\n  * Duration\n1 day\n  * Description\nLinkedIn sets the lidc cookie to facilitate data center selection.\n\n\n  * Cookie\nbrw\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nbrwConsent\n  * Duration\n5 minutes\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nWMF-Uniq\n  * Duration\n1 year\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nloom_anon_comment\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nloom_referral_video\n  * Duration\nsession\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nVISITOR_INFO1_LIVE\n  * Duration\n6 months\n  * Description\nA cookie set by YouTube to measure bandwidth that determines whether the user gets the new or old player interface.\n\n\n  * Cookie\nyt-remote-connected-devices\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to store the user's video preferences using embedded YouTube videos.\n\n\n  * Cookie\nytidb::LAST_RESULT_ENTRY_KEY\n  * Duration\nNever Expires\n  * Description\nThe cookie ytidb::LAST_RESULT_ENTRY_KEY is used by YouTube to store the last search result entry that was clicked by the user. This information is used to improve the user experience by providing more relevant search results in the future.\n\n\n  * Cookie\nyt-remote-device-id\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to store the user's video preferences using embedded YouTube videos.\n\n\n  * Cookie\nyt-remote-session-name\n  * Duration\nsession\n  * Description\nThe yt-remote-session-name cookie is used by YouTube to store the user's video player preferences using embedded YouTube video.\n\n\n  * Cookie\nyt-remote-fast-check-period\n  * Duration\nsession\n  * Description\nThe yt-remote-fast-check-period cookie is used by YouTube to store the user's video player preferences for embedded YouTube videos.\n\n\n  * Cookie\nyt-remote-session-app\n  * Duration\nsession\n  * Description\nThe yt-remote-session-app cookie is used by YouTube to store user preferences and information about the interface of the embedded YouTube video player.\n\n\n  * Cookie\nyt-remote-cast-available\n  * Duration\nsession\n  * Description\nThe yt-remote-cast-available cookie is used to store the user's preferences regarding whether casting is available on their YouTube video player.\n\n\n  * Cookie\nyt-remote-cast-installed\n  * Duration\nsession\n  * Description\nThe yt-remote-cast-installed cookie is used to store the user's video player preferences using embedded YouTube video.\n\n\n  * Cookie\ncp_session\n  * Duration\n3 months\n  * Description\nCodepen sets this cookie for Help systems found in the website.\n\n\n  * Cookie\nloid\n  * Duration\n1 year 1 month 4 days\n  * Description\nThis cookie is set by the Reddit. The cookie enables the sharing of content from the website onto the social media platform.\n\n\nAnalytics\nAnalytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics such as the number of visitors, bounce rate, traffic source, etc.\n  * Cookie\n__hstc\n  * Duration\n6 months\n  * Description\nHubspot set this main cookie for tracking visitors. It contains the domain, initial timestamp (first visit), last timestamp (last visit), current timestamp (this visit), and session number (increments for each subsequent session).\n\n\n  * Cookie\nhubspotutk\n  * Duration\n6 months\n  * Description\nHubSpot sets this cookie to keep track of the visitors to the website. This cookie is passed to HubSpot on form submission and used when deduplicating contacts.\n\n\n  * Cookie\n_ga\n  * Duration\n1 year 1 month 4 days\n  * Description\nGoogle Analytics sets this cookie to calculate visitor, session and campaign data and track site usage for the site's analytics report. The cookie stores information anonymously and assigns a randomly generated number to recognise unique visitors.\n\n\n  * Cookie\n_ga_*\n  * Duration\n1 year 1 month 4 days\n  * Description\nGoogle Analytics sets this cookie to store and count page views.\n\n\n  * Cookie\n__Host-psifi.analyticsTrace\n  * Duration\n6 hours\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\n__Host-psifi.analyticsTraceV2\n  * Duration\n6 hours\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\n_gh_sess\n  * Duration\nsession\n  * Description\nGitHub sets this cookie for temporary application and framework state between pages like what step the user is on in a multiple step form.\n\n\n  * Cookie\nYSC\n  * Duration\nsession\n  * Description\nYSC cookie is set by Youtube and is used to track the views of embedded videos on Youtube pages.\n\n\n  * Cookie\najs_anonymous_id\n  * Duration\n1 year\n  * Description\nThis cookie is set by Segment to count the number of people who visit a certain site by tracking if they have visited before.\n\n\n  * Cookie\nvuid\n  * Duration\n1 year 1 month 4 days\n  * Description\nVimeo installs this cookie to collect tracking information by setting a unique ID to embed videos on the website. \n\n\nPerformance\nPerformance cookies are used to understand and analyse the key performance indexes of the website which helps in delivering a better user experience for the visitors.\n  * Cookie\nAWSALB\n  * Duration\n7 days\n  * Description\nAWSALB is an application load balancer cookie set by Amazon Web Services to map the session to the target.\n\n\n  * Cookie\nacq\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nacq.sig\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nptc\n  * Duration\n2 years\n  * Description\nNo description available.\n\n\nAdvertisement\nAdvertisement cookies are used to provide visitors with customised advertisements based on the pages you visited previously and to analyse the effectiveness of the ad campaigns.\n  * Cookie\nmuc_ads\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to collect user behaviour and interaction data to optimize the website.\n\n\n  * Cookie\nguest_id_marketing\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor.\n\n\n  * Cookie\nguest_id_ads\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor.\n\n\n  * Cookie\npersonalization_id\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to integrate and share features for social media and also store information about how the user uses the website, for tracking and targeting.\n\n\n  * Cookie\nguest_id\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor. It registers if a user is signed in to the Twitter platform and collects information about ad preferences.\n\n\n  * Cookie\nbcookie\n  * Duration\n1 year\n  * Description\nLinkedIn sets this cookie from LinkedIn share buttons and ad tags to recognize browser IDs.\n\n\n  * Cookie\n__Secure-ROLLOUT_TOKEN\n  * Duration\n6 months\n  * Description\nYouTube sets this cookie to manage feature rollout and experimentation. It helps Google control which new features or interface changes are shown to users as part of testing and staged rollouts, ensuring consistent experience for a given user during an experiment.\n\n\n  * Cookie\nyt.innertube::nextId\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.\n\n\n  * Cookie\nyt.innertube::requests\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.\n\n\n  * Cookie\nsession_tracker\n  * Duration\nsession\n  * Description\nThis cookie is set by the Reddit. This cookie is used to identify trusted web traffic. It also helps in adverstising on the website.\n\n\n  * Cookie\nedgebucket\n  * Duration\nsession\n  * Description\nReddit sets this cookie to save the information about a log-on Reddit user, for the purpose of advertisement recommendations and updating the content.\n\n\n  * Cookie\ndid\n  * Duration\n1 year\n  * Description\nArbor sets this cookie to show targeted ads to site visitors.This cookie expires after 2 months or 1 year.\n\n\nUncategorised\nOther uncategorised cookies are those that are being analysed and have not been classified into a category as yet.\nNo cookies to display.\nReject All Save My Preferences Accept All\nToggle Search[](https://towardsdatascience.com/)Toggle Mobile Navigation\n[](https://towardsdatascience.com/)\nPublish AI, ML & data-science insights to a global community of data professionals.\nSign in[Submit an Article](https://write.insightmediagroup.io)\n[Latest](https://towardsdatascience.com/latest/)[Editor's Picks](https://towardsdatascience.com/tag/editors-pick/)[Newsletter](https://towardsdatascience.com/tag/the-variable/)[ShipAI](https://towardsdatascience.com/shipai/)\n* * *\n[Write For TDS](https://towardsdatascience.com/submissions/)\n[LinkedIn](https://www.linkedin.com/company/towards-data-science/)[X](https://x.com/TDataScience)Toggle Search\n[Data Science](https://towardsdatascience.com/category/data-science/)\n# Time Series Forecasting: A Practical Guide to Exploratory Data Analysis\nHow to use Exploratory Data Analysis to drive information from time series data and enhance feature engineering using Python\n[Maicol Nicolini](https://towardsdatascience.com/author/maicolnicolini96/)\nMay 9, 202415 min read\n![Photo by Ales Krivec on Unsplash](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/0_4XJDFxzoPez8m4i-scaled.jpg)\nPhoto by [Ales Krivec](https://unsplash.com/@aleskrivec?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral)\n## Introduction\nTime series analysis certainly represents one of the most widespread topics in the field of data science and machine learning: whether predicting financial events, energy consumption, product sales or stock market trends, this field has always been of great interest to businesses.\nObviously, the great increase in data availability, combined with the constant progress in machine learning models, has made this topic even more interesting today. Alongside traditional forecasting methods derived from statistics (e.g. regressive models, ARIMA models, exponential smoothing), techniques relating to machine learning (e.g. tree-based models) and deep learning (e.g. LSTM Networks, CNNs, Transformer-based Models) have emerged for some time now.\nDespite the huge differences between these techniques, there is a preliminary step that must be done, no matter what the model is: _Exploratory Data Analysis._\nLearn this step by step with the interactive [AI and Data Scientist](https://roadmap.sh/ai-data-scientist) roadmap.\nIn statistics, **Exploratory Data Analysis** (EDA) is a discipline consisting in analyzing and visualizing data in order to summarize their main characteristics and gain relevant information from them. This is of considerable importance in the data science field because it allows to lay the foundations to another important step: _feature engineering_. That is, the practice that consists on creating, transforming and extracting features from the dataset so that the model can work to the best of its possibilities.\nThe objective of this article is therefore to define a clear exploratory data analysis template, focused on time series, which can summarize and highlight the most important characteristics of the dataset. To do this, we will use some common Python libraries such as _Pandas_ , _Seaborn_ and S _tatsmodel_.\n## Data\nLet's first define the dataset: for the purposes of this article, we will take Kaggle's [**Hourly Energy Consumption**](https://www.kaggle.com/datasets/robikscube/hourly-energy-consumption) data. This dataset relates to PJM Hourly Energy Consumption data, a regional transmission organization in the United States, that serves electricity to Delaware, Illinois, Indiana, Kentucky, Maryland, Michigan, New Jersey, North Carolina, Ohio, Pennsylvania, Tennessee, Virginia, West Virginia, and the District of Columbia.\nThe hourly power consumption data comes from PJM's website and are in megawatts (MW).\n## Exploratory Data Analysis\nLet's now define which are the most significant analyses to be performed when dealing with time series.\nFor sure, one of the most important thing is to plot the data: graphs can highlight many features, such as patterns, unusual observations, changes over time, and relationships between variables. As already said, the insight that emerge from these plots must then be taken into consideration, as much as possible, into the forecasting model. Moreover, some mathematical tools such as descriptive statistics and time series decomposition, will also be very useful.\nSaid that, the EDA I'm proposing in this article consists on six steps: Descriptive Statistics, Time Plot, Seasonal Plots, Box Plots, Time Series Decomposition, Lag Analysis.\n### 1. Descriptive Statistics\nDescriptive statistic is a summary statistic that quantitatively describes or summarizes features from a collection of structured data.\nSome metrics that are commonly used to describe a dataset are: measures of central tendency (e.g. _mean_ , _median_), measures of dispersion (e.g. _range_ , _standard deviation_), and measure of position (e.g. _percentiles_ , _quartile_). All of them can be summarized by the so called **five number summary** , which include: minimum, first quartile (Q1), median or second quartile (Q2), third quartile (Q3) and maximum of a distribution.\nIn Python, these information can be easily retrieved using the well know `describe` method from Pandas:\npython\n\n```\nimport pandas as pd\n\n# Loading and preprocessing steps\ndf = pd.read_csv('../input/hourly-energy-consumption/PJME_hourly.csv')\ndf = df.set_index('Datetime')\ndf.index = pd.to_datetime(df.index)\n\ndf.describe()\n```\n\nPJME statistic summary. \n### 2. Time plot\nThe obvious graph to start with is the time plot. That is, the observations are plotted against the time they were observed, with consecutive observations joined by lines.\nIn Python , we can use Pandas and Matplotlib:\npython\n\n```\nimport matplotlib.pyplot as plt\n\n# Set pyplot style\nplt.style.use(\"seaborn\")\n\n# Plot\ndf['PJME_MW'].plot(title='PJME - Time Plot', figsize=(10,6))\nplt.ylabel('Consumption [MW]')\nplt.xlabel('Date')\n```\n\n![2.1 PJME Consumption Time Plot.](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/1M_vR7hFcqn7DrLWAeOiDEQ.png)\n2.1 PJME Consumption Time Plot.\nThis plot already provides several information:\n  1. As we could expect, the pattern shows yearly seasonality.\n  2. Focusing on a single year, it seems that more pattern emerges. Likely, the consumptions will have a peak in winter and one another in summer, due to the greater electricity consumption.\n  3. The series does not exhibit a clear increasing/decreasing trend over the years, the average consumptions remains stationary.\n  4. There is an anomalous value around 2023, probably it should be imputed when implementing the model.\n\n\n### 3. Seasonal Plots\nA seasonal plot is fundamentally a time plot where data are plotted against the individual \"seasons\" of the series they belong.\nRegarding energy consumption, we usually have hourly data available, so there could be several seasonality: _yearly_ , _weekly_ , _daily_. Before going deep into these plots, let's first set up some variables in our Pandas dataframe:\npython\n\n```\n# Defining required fields\ndf['year'] = [x for x in df.index.year]\ndf['month'] = [x for x in df.index.month]\ndf = df.reset_index()\ndf['week'] = df['Datetime'].apply(lambda x:x.week)\ndf = df.set_index('Datetime')\ndf['hour'] = [x for x in df.index.hour]\ndf['day'] = [x for x in df.index.day_of_week]\ndf['day_str'] = [x.strftime('%a') for x in df.index]\ndf['year_month'] = [str(x.year) + '_' + str(x.month) for x in df.index]\n```\n\n### 3.1 Seasonal plot - Yearly consumption\nA very interesting plot is the one referring to the energy consumption grouped by year over months, this highlights yearly seasonality and can inform us about ascending/descending trends over the years.\nHere is the Python code:\npython\n\n```\nimport numpy as np\n\n# Defining colors palette\nnp.random.seed(42)\ndf_plot = df[['month', 'year', 'PJME_MW']].dropna().groupby(['month', 'year']).mean()[['PJME_MW']].reset_index()\nyears = df_plot['year'].unique()\ncolors = np.random.choice(list(mpl.colors.XKCD_COLORS.keys()), len(years), replace=False)\n\n# Plot\nplt.figure(figsize=(16,12))\nfor i, y in enumerate(years):\n    if i > 0:        \n        plt.plot('month', 'PJME_MW', data=df_plot[df_plot['year'] == y], color=colors[i], label=y)\n        if y == 2018:\n            plt.text(df_plot.loc[df_plot.year==y, :].shape[0]+0.3, df_plot.loc[df_plot.year==y, 'PJME_MW'][-1:].values[0], y, fontsize=12, color=colors[i])\n        else:\n            plt.text(df_plot.loc[df_plot.year==y, :].shape[0]+0.1, df_plot.loc[df_plot.year==y, 'PJME_MW'][-1:].values[0], y, fontsize=12, color=colors[i])\n\n# Setting labels\nplt.gca().set(ylabel= 'PJME_MW', xlabel = 'Month')\nplt.yticks(fontsize=12, alpha=.7)\nplt.title(\"Seasonal Plot - Monthly Consumption\", fontsize=20)\nplt.ylabel('Consumption [MW]')\nplt.xlabel('Month')\nplt.show()\n```\n\n![3.1 PJME Yearly Seasonal Plot](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/1vItcKtPe46aIS2Xnbu4lZg.png)\n3.1 PJME Yearly Seasonal Plot\nThis plot shows every year has actually a very predefined pattern: the consumption increases significantly during winter and has a peak in summer (due to heating/cooling systems), while has a minima in spring and in autumn when no heating or cooling is usually required.\nFurthermore, this plot tells us that's not a clear increasing/decreasing pattern in the overall consumptions across years.\n### 3.2 Seasonal plot - Weekly consumption\nAnother useful plot is the weekly plot, it depicts the consumptions during the week over months and can also suggest if and how weekly consumptions are changing over a single year.\nLet's see how to figure it out with Python:\npython\n\n```\n# Defining colors palette\nnp.random.seed(42)\ndf_plot = df[['month', 'day_str', 'PJME_MW', 'day']].dropna().groupby(['day_str', 'month', 'day']).mean()[['PJME_MW']].reset_index()\ndf_plot = df_plot.sort_values(by='day', ascending=True)\n\nmonths = df_plot['month'].unique()\ncolors = np.random.choice(list(mpl.colors.XKCD_COLORS.keys()), len(months), replace=False)\n\n# Plot\nplt.figure(figsize=(16,12))\nfor i, y in enumerate(months):\n    if i > 0:        \n        plt.plot('day_str', 'PJME_MW', data=df_plot[df_plot['month'] == y], color=colors[i], label=y)\n        if y == 2018:\n            plt.text(df_plot.loc[df_plot.month==y, :].shape[0]-.9, df_plot.loc[df_plot.month==y, 'PJME_MW'][-1:].values[0], y, fontsize=12, color=colors[i])\n        else:\n            plt.text(df_plot.loc[df_plot.month==y, :].shape[0]-.9, df_plot.loc[df_plot.month==y, 'PJME_MW'][-1:].values[0], y, fontsize=12, color=colors[i])\n\n# Setting Labels\nplt.gca().set(ylabel= 'PJME_MW', xlabel = 'Month')\nplt.yticks(fontsize=12, alpha=.7)\nplt.title(\"Seasonal Plot - Weekly Consumption\", fontsize=20)\nplt.ylabel('Consumption [MW]')\nplt.xlabel('Month')\nplt.show()\n```\n\n![3.2 PJME Weekly Seasonal Plot](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/1-ZenhM272ugjj-ErGDQk6A.png)\n3.2 PJME Weekly Seasonal Plot\n### 3.3 Seasonal plot - Daily consumption\nFinally, the last seasonal plot I want to show is the daily consumption plot. As you can guess, it represents how consumption change over the day. In this case, data are first grouped by day of week and then aggregated taking the mean.\nHere's the code:\npython\n\n```\nimport seaborn as sns\n\n# Defining the dataframe\ndf_plot = df[['hour', 'day_str', 'PJME_MW']].dropna().groupby(['hour', 'day_str']).mean()[['PJME_MW']].reset_index()\n\n# Plot using Seaborn\nplt.figure(figsize=(10,8))\nsns.lineplot(data = df_plot, x='hour', y='PJME_MW', hue='day_str', legend=True)\nplt.locator_params(axis='x', nbins=24)\nplt.title(\"Seasonal Plot - Daily Consumption\", fontsize=20)\nplt.ylabel('Consumption [MW]')\nplt.xlabel('Hour')\nplt.legend()\n```\n\n![3.3 PJME Daily Seasonal Plot](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/15UkeyoUqkhzmjHa5z11zdQ.png)\n3.3 PJME Daily Seasonal Plot\nOften, this plot show a very typical pattern, someone calls it \"M profile\" since consumptions seems to depict an \"M\" during the day. Sometimes this pattern is clear, others not (like in this case).\nHowever, this plots usually shows a relative peak in the middle of the day (from 10 am to 2 pm), then a relative minima (from 2 pm to 6 pm) and another peak (from 6 pm to 8 pm). Finally, it also shows the difference in consumptions from weekends and other days.\n### 3.4 Seasonal plot - Feature Engineering\nLet's now see how to use this information for feature engineering. Let's suppose we are using some ML model that requires good quality features (e.g. ARIMA models or tree-based models).\nThese are the main evidences coming from seasonal plots:\n  1. Yearly consumptions do not change a lot over years: this suggests the possibility to use, when available, yearly seasonality features coming from lag or exogenous variables.\n  2. Weekly consumptions follow the same pattern across months: this suggests to use weekly features coming from lag or exogenous variables.\n  3. Daily consumption differs from normal days and weekends: this suggest to use categorical features able to identify when a day is a normal day and when it is not.\n\n\n### 4. Box Plots\nBoxplot are a useful way to identify how data are distributed. Briefly, boxplots depict percentiles, which represent 1st (Q1), 2nd (Q2/median) and 3rd (Q3) quartile of a distribution and whiskers, which represent the range of the data. Every value beyond the whiskers can be thought as an _outlier_ , more in depth, whiskers are often computed as:\nWhiskers Formula \n### 4.1 Box Plots - Total consumption\nLet's first compute the box plot regarding the total consumption, this can be easily done with _Seaborn_ :\npython\n\n```\nplt.figure(figsize=(8,5))\nsns.boxplot(data=df, x='PJME_MW')\nplt.xlabel('Consumption [MW]')\nplt.title(f'Boxplot - Consumption Distribution');\n```\n\n![4.1 PJME Boxplot](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/1jsZ3PKVpFNoIVBNrPF4Ikw.png)\n4.1 PJME Boxplot\nEven if this plot seems not to be much informative, it tells us we are dealing with a Gaussian-like distribution, with a tail more accentuated towards the right.\n### 4.2 Box Plots - Day month distribution\nA very interesting plot is the day/month box plot. It is obtained creating a \"day month\" variable and grouping consumptions by it. Here is the code, referring only from year 2017:\npython\n\n```\ndf['year'] = [x for x in df.index.year]\ndf['month'] = [x for x in df.index.month]\ndf['year_month'] = [str(x.year) + '_' + str(x.month) for x in df.index]\n\ndf_plot = df[df['year'] >= 2017].reset_index().sort_values(by='Datetime').set_index('Datetime')\nplt.title(f'Boxplot Year Month Distribution');\nplt.xticks(rotation=90)\nplt.xlabel('Year Month')\nplt.ylabel('MW')\n\nsns.boxplot(x='year_month', y='PJME_MW', data=df_plot)\nplt.ylabel('Consumption [MW]')\nplt.xlabel('Year Month')\n```\n\n![4.2 PJME Year/Month Boxplot](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/1AjdlfZTA5uqxm9vZyvIZMg.png)\n4.2 PJME Year/Month Boxplot\nIt can be seen that consumption are less uncertain in summer/winter months (i.e. when we have peaks) while are more dispersed in spring/autumn (i.e. when temperatures are more variable). Finally, consumption in summer 2018 are higher than 2017, maybe due to a warmer summer. When feature engineering, remember to include (if available) the temperature curve, probably it can be used as an exogenous variable.\n### 4.3 Box Plots - Day distribution\nAnother useful plot is the one referring consumption distribution over the week, this is similar to the weekly consumption seasonal plot.\npython\n\n```\ndf_plot = df[['day_str', 'day', 'PJME_MW']].sort_values(by='day')\nplt.title(f'Boxplot Day Distribution')\nplt.xlabel('Day of week')\nplt.ylabel('MW')\nsns.boxplot(x='day_str', y='PJME_MW', data=df_plot)\nplt.ylabel('Consumption [MW]')\nplt.xlabel('Day of week')\n```\n\n![4.3 PJME Day Boxplot](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/1Y56hSDyXyumNeyEnUIQ6Vw.png)\n4.3 PJME Day Boxplot\nAs seen before, consumptions are noticeably lower on weekends. Anyway, there are several outliers pointing out that calendar features like \"day of week\" for sure are useful but could not fully explain the series.\n### 4.4 Box Plots - Hour distribution\nLet's finally see hour distribution box plot. It is similar to the daily consumption seasonal plot since it provides how consumptions are distributed over the day. Following, the code:\npython\n\n```\nplt.title(f'Boxplot Hour Distribution');\nplt.xlabel('Hour')\nplt.ylabel('MW')\nsns.boxplot(x='hour', y='PJME_MW', data=df)\nplt.ylabel('Consumption [MW]')\nplt.xlabel('Hour')\n```\n\n![4.4 PJME Hour Boxplot](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/17_4j93v-Uo-z8dOPSWqPA.png)\n4.4 PJME Hour Boxplot\nNote that the \"M\" shape seen before is now much more crushed. Furthermore there are a lot of outliers, this tells us data not only relies on daily seasonality (e.g. the consumption on today's 12 am is similar to the consumption of yesterday 12 am) but also on something else, probably some exogenous climatic feature like temperature or humidity.\n### 5. Time Series Decomposition\nAs already said, time series data can exhibit a variety of patterns. Often, it is helpful to split a time series into several components, each representing an underlying pattern category.\nWe can think of a time series as comprising three components: a _trend_ component, a _seasonal_ component and a _remainder_ component (containing anything else in the time series). For some time series (e.g., energy consumption series), there can be more than one seasonal component, corresponding to different seasonal periods (daily, weekly, monthly, yearly).\nThere are two main type of decomposition: _additive_ and _multiplicative_.\nFor the additive decomposition, we represent a series (𝑦) as the sum of a seasonal component (𝑆), a trend (𝑇) and a remainder (𝑅):\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/0gDj55UE7Bemilxhl.png)\nSimilarly, a multiplicative decomposition can be written as:\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/0rsepm97zulrZKWfX.png)\nGenerally speaking, additive decomposition best represent series with constant variance while multiplicative decomposition best suits time series with non-stationary variances.\nIn Python, time series decomposition can be easily fulfilled with _Statsmodel_ library:\npython\n\n```\ndf_plot = df[df['year'] == 2017].reset_index()\ndf_plot = df_plot.drop_duplicates(subset=['Datetime']).sort_values(by='Datetime')\ndf_plot = df_plot.set_index('Datetime')\ndf_plot['PJME_MW - Multiplicative Decompose'] = df_plot['PJME_MW']\ndf_plot['PJME_MW - Additive Decompose'] = df_plot['PJME_MW']\n\n# Additive Decomposition\nresult_add = seasonal_decompose(df_plot['PJME_MW - Additive Decompose'], model='additive', period=24*7)\n\n# Multiplicative Decomposition \nresult_mul = seasonal_decompose(df_plot['PJME_MW - Multiplicative Decompose'], model='multiplicative', period=24*7)\n\n# Plot\nresult_add.plot().suptitle('', fontsize=22)\nplt.xticks(rotation=45)\nresult_mul.plot().suptitle('', fontsize=22)\nplt.xticks(rotation=45)\nplt.show()\n```\n\n![5.1 PJME Series Decomposition - Additive Decompose.](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/1zxJdVfYW1_e50uMyxpIfyw.png)\n5.1 PJME Series Decomposition - Additive Decompose.\n![5.2 PJME Series Decomposition - Multiplicative Decompose.](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/1Q_MZakE2mHReJBupMTdLGA.png)\n5.2 PJME Series Decomposition - Multiplicative Decompose.\nThe above plots refers to 2017. In both cases, we see the trend has several local peaks, with higher values in summer. From the seasonal component, we can see the series actually has several periodicities, this plot highlights more the weekly one, but if we focus on a particular month (January) of the same year, daily seasonality emerges too:\npython\n\n```\ndf_plot = df[(df['year'] == 2017)].reset_index()\ndf_plot = df_plot[df_plot['month'] == 1]\ndf_plot['PJME_MW - Multiplicative Decompose'] = df_plot['PJME_MW']\ndf_plot['PJME_MW - Additive Decompose'] = df_plot['PJME_MW']\n\ndf_plot = df_plot.drop_duplicates(subset=['Datetime']).sort_values(by='Datetime')\ndf_plot = df_plot.set_index('Datetime')\n\n# Additive Decomposition\nresult_add = seasonal_decompose(df_plot['PJME_MW - Additive Decompose'], model='additive', period=24*7)\n\n# Multiplicative Decomposition \nresult_mul = seasonal_decompose(df_plot['PJME_MW - Multiplicative Decompose'], model='multiplicative', period=24*7)\n\n# Plot\nresult_add.plot().suptitle('', fontsize=22)\nplt.xticks(rotation=45)\nresult_mul.plot().suptitle('', fontsize=22)\nplt.xticks(rotation=45)\nplt.show()\n```\n\n![5.3 PJME Series Decomposition - Additive Decompose, focus on January 2017.](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/1Ma47XHTQF_mrUnxzkIQAAw.png)\n5.3 PJME Series Decomposition - Additive Decompose, focus on January 2017.\n![5.4 PJME Series Decomposition - Multiplicative Decompose, focus on January 2017.](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/1OKs3z7Gxy-8pVJp_JQjnFw.png)\n5.4 PJME Series Decomposition - Multiplicative Decompose, focus on January 2017.\n### 6. Lag Analysis\nIn time series forecasting, a lag is simply a past value of the series. For example, for daily series, the first lag refers to the value the series had the previous day, the second to the value of the day before and so on.\nLag analysis is based on computing correlations between the series and a lagged version of the series itself, this is also called _autocorrelation._ For a k-lagged version of a series, we define the autocorrelation coefficient as:\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/1TB59DjyKh343lYl9X4rxeg.png)\nWhere _y_ bar represent the mean value of the series and _k_ the lag.\nThe autocorrelation coefficients make up the _autocorrelation function_ (ACF) for the series, this is simply a plot depicting the auto-correlation coefficient versus the number of lags taken into consideration.\nWhen data has a trend, the autocorrelations for small lags are usually large and positive because observations close in time are also nearby in value. When data show seasonality, autocorrelation values will be larger in correspondence of seasonal lags (and multiples of the seasonal period) than for other lags. Data with both trend and seasonality will show a combination of these effects.\nIn practice, a more useful function is the _partial autocorrelation function_ (PACF). It is similar to the ACF, except that it shows only the direct autocorrelation between two lags. For example, the partial autocorrelation for lag 3 refers to the only correlation lag 1 and 2 do not explain. In other words, the partial correlation refers to the direct effect a certain lag has on the current time value.\nBefore moving to the Python code, it is important to highlight that autocorrelation coefficient emerges more clearly if the series is _stationary,_ so often is better to first differentiate the series to stabilize the signal.\nSaid that, here is the code to plot PACF for different hours of the day:\npython\n\n```\nfrom statsmodels.graphics.tsaplots import plot_pacf\n\nactual = df['PJME_MW']\nhours = range(0, 24, 4)\n\nfor hour in hours:\n    plot_pacf(actual[actual.index.hour == hour].diff().dropna(), lags=30, alpha=0.01)\n    plt.title(f'PACF - h = {hour}')\n    plt.ylabel('Correlation')\n    plt.xlabel('Lags')\n    plt.show()\n```\n\n![6.1 PJME Lag Analysis - Partial Auto Correlation Function \\(h=0\\).](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/1QDA8IbzpFA3Prje2feEvWA.png)\n6.1 PJME Lag Analysis - Partial Auto Correlation Function (h=0).\n![6.2 PJME Lag Analysis - Partial Auto Correlation Function \\(h=4\\).](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/1K82kFl7e6QqFwh1tgaq_Mw.png)\n6.2 PJME Lag Analysis - Partial Auto Correlation Function (h=4).\n![6.3 PJME Lag Analysis - Partial Auto Correlation Function \\(h=8\\).](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/1v-N6XAjYctJdc2_Ky3LkAA.png)\n6.3 PJME Lag Analysis - Partial Auto Correlation Function (h=8).\n![6.4 PJME Lag Analysis - Partial Auto Correlation Function \\(h=12\\).](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/1pyV1WI3G4rlAk22Mb3insw.png)\n6.4 PJME Lag Analysis - Partial Auto Correlation Function (h=12).\n![6.5 PJME Lag Analysis - Partial Auto Correlation Function \\(h=16\\).](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/18sUpAAY7ljoMZuit0BGsQA.png)\n6.5 PJME Lag Analysis - Partial Auto Correlation Function (h=16).\n![6.6 PJME Lag Analysis - Partial Auto Correlation Function \\(h=20\\).](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/05/1prensTQCmEisWTI0Mow_Yg.png)\n6.6 PJME Lag Analysis - Partial Auto Correlation Function (h=20).\nAs you can see, the PACF simply consists on plotting Pearson partial auto-correlation coefficients for different lags. Of course, the non-lagged series shows a perfect auto-correlation with itself, so lag 0 will always be 1. The blue band represent the _confidence interval:_ if a lag exceed that band, then it is statistically significant and we can assert it is has great importance.\n### 6.1 Lag analysis - Feature Engineering\nLag analysis is one of the most impactful study on time series feature engineering. As already said, a lag with high correlation is an important lag for the series, then it should be taken into consideration.\nA widely used feature engineering technique consists on making an **hourly division** of the dataset. That is, splitting data in 24 subset, each one referring to an hour of the day. This has the effect to regularize and smooth the signal, making it more simple to forecast.\nEach subset should then be feature engineered, trained and fine-tuned. The final forecast will be achieved combining the results of these 24 models. Said that, every hourly model will have its peculiarities, most of them will regard important lags.\nBefore moving on, let's define two types of lag we can deal with when doing lag analysis:\n  1. **Auto-regressive lags** : lags close to lag 0, for which we expect high values (recent lags are more likely to predict the present value). They are a representation on how much trend the series shows.\n  2. **Seasonal lags** : lags referring to seasonal periods. When hourly splitting the data, they usually represent weekly seasonality.\n\n\nNote that auto-regressive lag 1 can also be taught as a _daily seasonal lag_ for the series.\nLet's now discuss about the PACF plots printed above.\n### Night Hours\nConsumption on night hours (0, 4) relies more on auto-regressive than on weekly lags, since the most important are all localized on the first five. Seasonal periods such as 7, 14, 21, 28 seems not to be too much important, this advises us to pay particular attention on lag 1 to 5 when feature engineering.\n### Day Hours\nConsumption on day hours (8, 12, 16, 20) exhibit both auto-regressive and seasonal lags. This particularly true for hours 8 and 12 - when consumption is particularly high - while seasonal lags become less important approaching the night. For these subsets we should also include seasonal lag as well as auto-regressive ones.\nFinally, here are some tips when feature engineering lags:\n  * Do not to take into consideration too many lags since this will probably lead to over fitting. Generally, auto-regressive lags goes from 1 to 7, while weekly lags should be 7, 14, 21 and 28. But it's not mandatory to take each of them as features.\n  * Taking into consideration lags that are not auto-regressive or seasonal is usually a bad idea since they could bring to overfitting as well. Rather, try to understand while a certain lag is important.\n  * Transforming lags can often lead to more powerful features. For example, seasonal lags can be aggregated using a weighted mean to create a single feature representing the seasonality of the series.\n\n\n## Free Resources\nFinally, I would like to mention a very useful (and free) book explaining time series, which I have personally used a lot: [Forecasting: Principles and Practice](https://otexts.com/fpp3/).\nEven though it is meant to use R instead of Python, this textbook provides a great introduction to forecasting methods, covering the most important aspects of time series analysis.\n## Conclusion\nThe aim of this article was to present a comprehensive Exploratory Data Analysis template for time series forecasting.\nEDA is a fundamental step in any type of data science study since it allows to understand the nature and the peculiarities of the data and lays the foundation to feature engineering, which in turn can dramatically improve model performance.\nWe have then described some of the most used analysis for time series EDA, these can be both statistical/mathematical and graphical. Obviously, the intention of this work was only to give a practical framework to start with, subsequent investigations need to be carried out based on the type of historical series being examined and the business context.\nThanks for having followed me until the end.\n_Unless otherwise noted, all images are by the author._\n## Register Now to Continue Reading\n### Unlock Towards Data Science with a Free Account\nAll articles on Towards Data Science are available to registered users. Create a free account today to access this article and thousands more.\nRegister for freeSign in to your account\n* * *\n![](https://assets.insightmediagroup.io/media/1787657554141_o2f84j.jpeg)\nWritten By\n## [Maicol Nicolini](https://towardsdatascience.com/author/maicolnicolini96/)\n[See all from Maicol Nicolini](https://towardsdatascience.com/author/maicolnicolini96/)\n[Data Science](https://towardsdatascience.com/tag/data-science/)[Energy Consumption](https://towardsdatascience.com/tag/energy-consumption/)[Exploratory Data Analysis](https://towardsdatascience.com/tag/exploratory-data-analysis/)[Feature Engineering](https://towardsdatascience.com/tag/feature-engineering/)[Time Series Analysis](https://towardsdatascience.com/tag/time-series-analysis/)\nShare This Article\n  * [](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Ftowardsdatascience.com%2Ftime-series-forecasting-a-practical-guide-to-exploratory-data-analysis-a101dc5f85b1%2F&title=Time%20Series%20Forecasting%3A%20A%20Practical%20Guide%20to%20Exploratory%20Data%20Analysis)\n  * [](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Ftowardsdatascience.com%2Ftime-series-forecasting-a-practical-guide-to-exploratory-data-analysis-a101dc5f85b1%2F&title=Time%20Series%20Forecasting%3A%20A%20Practical%20Guide%20to%20Exploratory%20Data%20Analysis)\n  * [](https://x.com/share?url=https%3A%2F%2Ftowardsdatascience.com%2Ftime-series-forecasting-a-practical-guide-to-exploratory-data-analysis-a101dc5f85b1%2F&text=Time%20Series%20Forecasting%3A%20A%20Practical%20Guide%20to%20Exploratory%20Data%20Analysis)\n\n\nTowards Data Science is a community publication. Submit your insights to reach our global audience and earn through the TDS Author Payment Program.\n[Write for TDS](https://towardsdatascience.com/submissions/)\n## Related Articles\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2021/10/0XJTXLhRY9w0-_uFI-scaled.jpg)](https://towardsdatascience.com/preprocess-audio-data-with-the-signal-envelope-499e6072108/)\n## [Preprocess Audio Data with the Signal Envelope](https://towardsdatascience.com/preprocess-audio-data-with-the-signal-envelope-499e6072108/)\n[Data Science](https://towardsdatascience.com/category/data-science/)\nHow to remove extra noise in your audio data\n[Jeremy Pagirsky](https://towardsdatascience.com/author/jeremy-pagirsky/)\nOctober 27, 20216 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2022/01/1qjpNjPR4htmSITj_NOtxfg.png)](https://towardsdatascience.com/folium-and-choropleth-map-from-zero-to-pro-6127f9e68564/)\n## [Folium and Choropleth Map: From Zero to Pro](https://towardsdatascience.com/folium-and-choropleth-map-from-zero-to-pro-6127f9e68564/)\n[Data Science](https://towardsdatascience.com/category/data-science/)\nA Step-by-Step Tutorial to Create an Interactive Choropleth Map Using Folium with Layer Control and Customized Tooltips\n[My Data Talk](https://towardsdatascience.com/author/insightsbees/)\nJanuary 4, 20227 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2020/10/0Ac13vwYMb-2dKOkL-scaled.jpg)](https://towardsdatascience.com/3-basic-steps-of-stock-market-analysis-in-python-917787012143/)\n## [3 Basic Steps of Stock Market Analysis in Python](https://towardsdatascience.com/3-basic-steps-of-stock-market-analysis-in-python-917787012143/)\n[Programming](https://towardsdatascience.com/category/programming/)\nAnalyze Tesla stock in Python, calculate Trading Indicators and plot the OHLC chart. Includes a Jupyter Notebook with code examples.\n[Roman Orac](https://towardsdatascience.com/author/romanorac/)\nOctober 19, 20205 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2023/11/0Fv351uNXkW5r42Ld-scaled.jpg)](https://towardsdatascience.com/creating-a-gradient-descent-animation-in-python-3c4dcd20ca51-2/)\n## [Creating a Gradient Descent Animation in Python](https://towardsdatascience.com/creating-a-gradient-descent-animation-in-python-3c4dcd20ca51-2/)\n[Programming](https://towardsdatascience.com/category/programming/)\nHow to plot the trajectory of a point over a complex surface\n[Luis Medina](https://towardsdatascience.com/author/luisdamed/)\nNovember 11, 202310 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2020/11/1Ni1g7F3ZVPYOabK0vBs-5Q.jpeg)](https://towardsdatascience.com/ml-landscape-overview-8a9e48a28641/)\n## [ML landscape overview](https://towardsdatascience.com/ml-landscape-overview-8a9e48a28641/)\n[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)\nA query from an investor friend nudged me into a long weekend deep dive into the ML landscape. As I was gathering notes, I thought it...\n[Pranil Dasika](https://towardsdatascience.com/author/pranildasika/)\nNovember 28, 20206 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/08/0ovHhFWEqO1P0Cm4u-scaled.jpg)](https://towardsdatascience.com/5-pca-visualizations-you-must-try-on-your-next-data-science-project-148ec3d31e4d/)\n## [5 PCA Visualizations You Must Try On Your Next Data Science Project](https://towardsdatascience.com/5-pca-visualizations-you-must-try-on-your-next-data-science-project-148ec3d31e4d/)\n[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)\nWhich features carry the most weight? How do original features contribute to principal components? These 5 visualization types have the...\n[Dario Radečić](https://towardsdatascience.com/author/radecicdario/)\nAugust 2, 20248 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2021/11/0hfmiuVtUOQIze1jF-scaled.jpg)](https://towardsdatascience.com/what-are-the-most-exciting-challenges-machine-learning-will-tackle-next-45f04974a81f/)\n## [What Are the Most Exciting Challenges Machine Learning Will Tackle Next?](https://towardsdatascience.com/what-are-the-most-exciting-challenges-machine-learning-will-tackle-next-45f04974a81f/)\n[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)\nOur weekly selection of must-read Editors' Picks and original features\n[TDS Editors](https://towardsdatascience.com/author/towardsdatascience/)\nNovember 4, 20214 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2020/07/1WXCRC0JtmwSVgC2aM8tFcg-scaled.jpeg)](https://towardsdatascience.com/pneumonia-detection-using-deep-learning-and-swagger-18aa5e8af576/)\n## [Pneumonia Detection using deep learning and swagger.](https://towardsdatascience.com/pneumonia-detection-using-deep-learning-and-swagger-18aa5e8af576/)\n[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)\nIn this story, we will walk through the steps of detecting pneumonia using deep learning and deploy the model using swagger.\n[Devarshpatel](https://towardsdatascience.com/author/devarshpatel29/)\nJuly 16, 20206 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2021/02/1Cs8Pg5aLXjpKcs8gy005CQ.png)](https://towardsdatascience.com/data-driven-multi-touch-attribution-demystified-3eb3486476c6/)\n## [Data-Driven Multi-Touch Attribution Demystified](https://towardsdatascience.com/data-driven-multi-touch-attribution-demystified-3eb3486476c6/)\n[Data Science](https://towardsdatascience.com/category/data-science/)\nGetting Started With Markov Attribution Models\n[Borna Almasi](https://towardsdatascience.com/author/bornawrites/)\nFebruary 23, 202113 min read\n\n\n  * [YouTube](https://www.youtube.com/c/TowardsDataScience)\n  * [X](https://x.com/TDataScience)\n  * [LinkedIn](https://www.linkedin.com/company/towards-data-science/)\n  * [Threads](https://www.threads.net/@towardsdatascience)\n  * [Bluesky](https://bsky.app/profile/towardsdatascience.com)\n\n\n[](https://towardsdatascience.com/)\nYour home for data science and AI. The world's leading publication for data science, data analytics, data engineering, machine learning, and artificial intelligence professionals.\nSubscribe to Our Newsletter[Write For TDS](https://towardsdatascience.com/submissions/)•[About](https://towardsdatascience.com/about-towards-data-science-d691af11cc2f/)•[Advertise](https://contact.towardsdatascience.com/advertise-with-towards-data-science)•[Privacy Policy](https://towardsdatascience.com/privacy-policy/)•[Terms of Use](https://towardsdatascience.com/website-terms-of-use/)\n© Insight Media Group, LLC 2026•[The New Stack](https://thenewstack.io)•[roadmap.sh](https://roadmap.sh)•[Creator Network](https://insightmediagroup.io/creator-network)\n####  Recommended Articles \nClose\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607639/image?etag=1781250125848&width=500&height=500) Javascript Fatigue: HTMX Is All You Need to Build ChatGPT — Part 1 ](https://towardsdatascience.com/javascript-fatigue-you-dont-need-js-to-build-chatgpt/ \"Javascript Fatigue: HTMX Is All You Need to Build ChatGPT — Part 1\")\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607930/image?etag=1782124566054&width=500&height=500) The Subset Sum Problem Solved in Linear Time for Dense Enough Inputs ](https://towardsdatascience.com/subset-sum-problem-solved-in-linear-time-for-dense-enough-inputs/ \"The Subset Sum Problem Solved in Linear Time for Dense Enough Inputs\")\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/3660/image?etag=1780176246070&width=500&height=500) Does Your Company Have a Data Strategy? ](https://towardsdatascience.com/does-your-company-have-a-data-strategy-e8944dbec4ae/ \"Does Your Company Have a Data Strategy?\")\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/4970/image?etag=1780565225463&width=500&height=500) Productivity Tips, Data Career Insights, and Other Recent Must-Reads ](https://towardsdatascience.com/productivity-tips-data-career-insights-and-other-recent-must-reads-5c47eb7a2fe0/ \"Productivity Tips, Data Career Insights, and Other Recent Must-Reads\")\n\n\n",
  "body_base_path": "attachments/f94cf65be99e/pages/",
  "body_markdown_path": "attachments/f94cf65be99e/pages/archived-body.md",
  "local_html": "attachments/f94cf65be99e/pages/archived-body.html",
  "body_source": "prior_capture"
}