Time Series Analysis and Forecasting — Part 1: Fundamentals, Use Cases, and a Case Study from… Programmer Michał Żarnecki published the first part of a three-part series on time series analysis and forecasting, covering fundamentals, use cases, and a Parkinson's disease case study detecting Freezing of Gait episodes from real motion-sensor data. The series walks through theory, a Python and Pandas data-processing workflow, forecasting model families including transformers, and evaluation metrics, with code examples in a public GitHub repository at github.com/mzarnecki/time-series-analysis. Żarnecki emphasizes that forecasting must account for uncertainty, noting a point prediction of 1000 units means different things at a range of 950–1050 versus 600–1400. Hi. My name is Michał Żarnecki, and I’m a programmer specialized in data-heavy systems. Because the datasets I work with are often complex and contain millions of records, analyzing them requires more than classical SQL queries and spreadsheets. To overcome this challenge I incorporate proper data mining and machine learning techniques in to designed data processing pipelines. This article is the first chapter of a three-part series for introduction to time series analysis and forecasting . Across the series, I’ll walk you through the theory, the practical data-processing workflow in Python with Pandas, the main families of forecasting models including transformers , and the evaluation metrics used to distinguish a good model from a bad one. Each part of the series includes code examples from my public repository with Jupyter notebooks. Feel free to run notebooks with Python code locally and experiment. Nothing will give you as much knowledge as “hands on”. I’ll also attach code parts in this article and next ones from this series. Repository with all code examples: https://github.com/mzarnecki/time-series-analysis https://github.com/mzarnecki/time-series-analysis In this first part, we’ll cover the foundations: what a time series actually is, where it shows up in real projects, what a forecasting model does, how to describe a time series in terms of its structure, and how to slice it into training examples using time windows. We’ll finish with a walkthrough of a classification use case — detecting Freezing of Gait episodes in patients with Parkinson’s disease — using real motion-sensor data. Let’s get started. The simplest way to describe a time series is: a sequence of observations ordered in time. It can be hourly temperature measurements, daily order counts, a stock price ticking every minute, or a motion sensor sampled dozens of times per second. The important thing is that order matters — what happened earlier often influences what we see now. This dependence on the past is what makes time series different from a regular dataset. If I shuffle the rows of a customer table, nothing breaks. If I shuffle the rows of a time series, I destroy the signal. Forecasting is the process of estimating future values of a time series based on historical data. In other words: we take what we already know about the past and, using models — from very simple to highly advanced — we try to answer the question: what will probably happen in a moment, tomorrow, or next week? One thing that often gets missed: forecasting is not only about producing a single predicted number. It’s also about uncertainty — how confident we are in that prediction, and what range of values is realistic. A point prediction of “1000 units of demand” means very different things depending on whether the realistic range is 950–1050 or 600–1400. Let’s go through a few practical applications that show why this matters. We want to know how many products will actually sell in the coming days or weeks, so we don’t freeze capital in inventory, but also don’t run out. The forecast directly drives purchasing, logistics, production plans, and sometimes even marketing decisions. And this is exactly where the uncertainty interval becomes critical — planning for “demand around 1000 ± 5%” is a very different exercise than planning for “1000 ± 40%”. Instead of forecasting sales directly, sometimes we forecast the consequences of sales. If I know I’ll sell X products, how much raw material, packaging, labour hours, and transport capacity do I need? In manufacturing and supply chains this is usually modelled as scenarios — a conservative variant, a realistic one, and an aggressive one. This lets a company spot a bottleneck early and quantify the cost of a bad forecast. Consumption patterns typically show strong seasonality and depend on many factors: temperature, day of the week, occupancy, pricing, process changes. Forecasts help plan energy purchases, optimize equipment operation, smooth out peaks, and even detect anomalies. If expected consumption is 1000 kWh but the actual reading is 2000 kWh, either something broke or the process changed. Maybe a machine is running without stopping as scheduled, or it’s drawing more power than its spec. The last example shows precisely why we emphasize uncertainty in forecasting. The atmosphere is a nonlinear system where small differences in initial conditions can grow into large differences in outcome. This is exactly what Edward Lorenz observed in his 1963 research, which later became one of the foundations of chaos theory — popularly known as the butterfly effect the metaphor of a butterfly flapping its wings on one hemisphere influencing a hurricane on the other — note this is a metaphor for sensitivity, not a literal causal chain :D . In practice, this means a weather forecast should rarely sound like “it will rain” as a single certainty. Instead we get probabilistic framings — “60% chance of rain” or “expected precipitation between 5–15 mm”. That makes sense, because we’re not just predicting an outcome — we’re communicating risk. And weather forecasts frequently feed into other forecasts: energy, agriculture, transport, tourism. The thing that actually generates a forecast is a model . You can picture a model as a black box with some logic for processing the data. More formally, a forecasting model is a description of the relationships inside a time series, fitted to historical data with the goal of predicting future values. A good model captures the regularities in the data — particularly trend, seasonality, and cyclicality — and uses them to generate forecasts for upcoming time steps. In practice we either use ready-made models from libraries or we build our own from historical data. The process of extracting patterns from historical data is called training . With these fundamentals in place, I want to introduce three case studies that I’ll come back to throughout the series. They’re also in the repository as interactive Jupyter notebooks, so treat them as hands-on examples you can run, modify, and use as a base for your own projects. Here we work with financial data that takes the form of a time series — successive closing prices for consecutive days. The goal is to predict the future price based on history. This is the classic forecasting problem: we care not only about the prediction itself, but also about understanding trend, seasonality, and volatility. It’s also a good example of how unpredictable time series can become in real-world conditions — which is why we’ll also look at what happens when external events announcements, crises, regulatory changes break the pattern the model learned. In this example we’re not forecasting values — we’re detecting events in a high-frequency signal. The data comes from an accelerometer worn by the patient, and we want to detect the moments when a Freezing of Gait FoG episode occurs. Freezing of Gait is a sudden, short-lived sensation of “feet being glued to the floor” even though the person wants to and is trying to keep walking. It most often shows up when starting to walk, turning, or passing through tight spaces such as doorways. This case shows that time series analysis isn’t just about predicting the future — it’s also about classifying and recognizing patterns in sequential data. Here the input is an audio signal — still a time series, just at a much higher sampling rate. The goal is to recognize what kind of siren we’re hearing. To do this, we transform the signal into a frequency representation and analyze its characteristic patterns. The concrete question is: does the audio contain an emergency siren, and if so, is it a fire truck or an ambulance? Pattern recognition in audio is extremely practical — it’s the same family of techniques used to activate voice assistants via wake phrases like “Ok Google”, “Hey Siri”, “Alexa”. This example shows that time series can have very different physical natures — financial, biological, acoustic — but the methods for analyzing them share common foundations. Before we build any model, we need to be able to describe the series itself: what’s in it, what’s regular, what’s random, and what looks like an anomaly. When working with time series, I find it useful to think about three layers of data . First layer — the time data itself. The thing that actually changes in time and is measured at successive moments: price, event count, acceleration, signal loudness, temperature. Second layer — metadata. These are the descriptors of the series that don’t necessarily change in time. Metadata answers the question “what is this series about?”. Examples: Metadata matters a lot in practice because it lets us combine many series into a single dataset, build models that generalize across different sources, and analyze why a model performs better on some cases than others. Third layer — events. These are signals that “something happened” at a given moment, and that moment is meaningful. Events come in two flavours: The important part: events are often not the raw measurement — they are labels or context we overlay on the time series. First, for supervised machine learning . If we want to detect FoG episodes in Parkinson’s, recognize an alarm siren, or predict equipment failures — we need to know when those events actually happened in the training data. Second, for evaluation . For time series it’s rarely enough to compute a mean error across the whole series. We usually care about how the model behaves around events : does it catch them in time, is it late, does it trigger false alarms? Third, for interpretability . If we see a sudden jump or trend change, an event can be the explanation — a financial report, a sensor swap, a firmware update, a change in environmental conditions. Take May 13, 2021, when Elon Musk publicly announced that Tesla was halting Bitcoin investments. This triggered a significant drop in the price. It’s also a reminder of how difficult forecasting can get in certain domains — a good model on historical prices can be torpedoed by a single announcement that the training data had no way to foresee. Events have their own metadata too: event type, source, confidence, the person or algorithm that labeled them. And in real data, events are often incomplete or noisy, so you’ll need to validate and clean them just like the measurements themselves. To summarize: you have time data, you have metadata describing the context, and you have events telling you “when and what is important”. Only together do these three layers give you enough to build models and draw conclusions. Time series are a particular kind of data. Three characteristics come up constantly. Seasonality means a repeating pattern at fixed intervals. It can be daily more activity in an app during business hours , weekly different behavior on weekends , or annual sales spike in December . Seasonality answers the question: does the data have a cyclical rhythm that’s predictable? Trend is the long-term direction: a systematic increase in user count, a slow decline in sales through a specific channel, a growing resource footprint over time. Trend answers: in the long run, is the series going up, going down, or staying stable? An anomaly is an observation or a fragment of the series that clearly deviates from typical behavior — a one-off spike, a sudden drop, an unusual change in variance, or a complete change of character. Important: an anomaly isn't always an error . In finance, it can mean a significant market event. In sensor data, it might be an FoG episode. In system logs, it could be a failure. The key distinction: trend and seasonality usually help forecasting, while anomalies are typically either what we want to detect or what we don't want the model to learn as "normal". So in the analysis we first identify these three elements, and only then pick a modelling technique. A concrete example: imagine Google Analytics data for a website. On a higher-level plot you can see clear seasonality — more users on business days, a visible drop on weekends, repeating on a weekly cycle. Seasonality can also align with the year: winter clothing sales rise sharply in winter and fall in summer; many retailers see large spikes on Black Friday and in the pre-Christmas window. On another plot, showing traffic across the December–January turn, you'd see a clear downward trend starting mid-December, followed by an upward trend in early January. The same thing happens every year, so based on drops in previous years you can estimate the drop in the upcoming one. Now let's go to a concept that's fundamental for machine learning on time series: time windows . In most ML tasks, we don't feed the entire series to the model at once. Instead, we cut the data into fragments windows and build a training dataset where each example consists of a window of historical values plus a label or target value from the future. A time window has a few parameters: Let's take stock price forecasting with daily data. If I choose: The model outputs either the price in 5 days, or a vector of the next 5 days — depending on how we frame the problem. If I increase the window to 90 days, the model gets more context, but complexity increases, the model risks memorizing noise, and results don't always improve. For motion sensor data it's the same logic on a different time scale. Instead of days we deal with seconds, and instead of price we have acceleration on the X, Y, Z axes. For example: For audio analysis we set things up analogously: Window sizing is a domain decision, not just a technical one . The window has to be long enough to contain information about the phenomenon, but short enough that the model doesn't drown in data and start learning random fluctuations. Before we open the first notebook, let me recap a few definitions we'll need. And one more tool: FFT Fast Fourier Transform lets you move from a time-domain view of the signal to a frequency-domain view. Instead of looking at how the signal changes over time, we look at which frequencies dominate. This is a simplification: we no longer process the series window by window, but work with a closed set of values the frequency components . This is particularly useful in the siren classification case study, because different emergency signals have characteristic frequency bands. Let's now apply these concepts to the first complete notebook: parkinson fog prediction.ipynb . This notebook was built for the Kaggle competition "Parkinson's Freezing of Gait Prediction" organized by The Michael J. Fox Foundation in March 2023. The goal is to predict the probability of three FoG event types — Turn , Walking , and StartHesitation — from lower-back accelerometer readings in patients with Parkinson's disease. The dataset includes the raw sensor recordings plus metadata such as patient age, sex, medication state, years since diagnosis, and clinical assessment scores. We start by loading everything — the time series, subject metadata, task metadata, and event annotations: python import pandas as pdimport globfrom os import pathroot = '../data/parkinson fog/'train = glob.glob path.join root, 'train/ / ' test = glob.glob path.join root, 'test/ / ' subjects = pd.read csv path.join root, 'subjects.csv' tasksBase = pd.read csv path.join root, 'tasks.csv' events = pd.read csv path.join root, 'events.csv' tdcsfog metadata = pd.read csv path.join root, 'tdcsfog metadata.csv' defog metadata = pd.read csv path.join root, 'defog metadata.csv' tdcsfog metadata 'Module' = 'tdcsfog'defog metadata 'Module' = 'defog'full metadata = pd.concat tdcsfog metadata, defog metadata Notice the clean split between the three data layers I described earlier: Sensor recordings have a common quirk: the first and last seconds are often unusable because the sensor was being put on or taken off. Quick visual inspection helps catch this: cols = 'Time', 'AccV', 'AccML', 'AccAP', 'StartHesitation', 'Turn', 'Walking' df = pd.read csv train 15 , index col='Time', usecols=cols def highlight indices, ax : i = 0 while i < len indices : ax.axvspan indices i - 0.5, indices i + 0.5, facecolor='pink', edgecolor='none', alpha=.2 i += 1ax = df 'AccV', 'AccML', 'AccAP' .plot figsize= 15, 5 ax2 = df 'AccV', 'AccML', 'AccAP' .iloc 0:3000 .plot figsize= 15, 5 highlight df.iloc 0:400 .index, ax2 ax3 = df 'AccV', 'AccML', 'AccAP' .iloc -2000:-1 .plot figsize= 15, 5 highlight df.iloc -500:-1 .index, ax3 The three signals AccV, AccML, AccAP correspond to the three accelerometer axes: V ertical, M edio- L ateral, and A ntero- P osterior. The highlighted regions show the beginning and end of the session, which are candidates for removal. Before feature engineering it's worth looking at the distributions of metadata variables — it tells you if the dataset is balanced or dominated by certain subpopulations: subjects 'Age', 'YearsSinceDx', 'Sex', 'Visit', 'UPDRSIII On', 'UPDRSIII Off', 'NFOGQ' .hist figsize= 15, 10 import seaborn as snsplt.figure figsize= 16, 6 heatmap = sns.heatmap subjects.corr , vmin=-1, vmax=1, annot=True, cmap='BrBG' heatmap.set title 'Correlation Heatmap', fontdict={'fontsize': 18}, pad=12 The histograms let you spot class imbalance; the correlation heatmap tells you which metadata columns carry overlapping information. This part is important and often underappreciated. The dataset is a blend of recordings from two different protocols: If you train a model on mixed-unit data without reconciling, the model will silently learn to treat "m/s²" and "g" as the same thing, and its predictions will be garbage. Here's the relevant part of the reader: python def reader file : try: ... load the session ... if dataset == 'defog': c = df 'Valid' .value counts validRatio = c True / c True + c False if validRatio < 25: df.truncate del df 'Valid' unify units: convert g - m/s^2 for the tdcsfog set if dataset == 'tdcsfog': df.AccV = df.AccV 9.80665 df.AccML = df.AccML 9.80665 df.AccAP = df.AccAP 9.80665 ... feature extraction ... return df except: pass Now comes the time-window part we discussed theoretically. We don't feed raw sensor values to the model — we compute statistical features over fixed-size windows and feed those . I use the tsflex library with seglearn feature sets: python from seglearn.feature functions import base features, emg featuresfrom tsflex.features import FeatureCollection, MultipleFeatureDescriptorsfrom tsflex.features.integrations import seglearn feature dict wrapperbasic feats = MultipleFeatureDescriptors functions=seglearn feature dict wrapper base features , series names= 'AccV', 'AccML', 'AccAP' , windows= 5000 , strides= 5000 , emg feats = emg features del emg feats 'simple square integral' duplicate of abs energyemg feats = MultipleFeatureDescriptors functions=seglearn feature dict wrapper emg feats , series names= 'AccV', 'AccML', 'AccAP' , windows= 5000 , strides= 5000 , fc = FeatureCollection basic feats, emg feats The windows= 5000 and strides= 5000 mean we use 5000-sample non-overlapping windows . At 100 Hz that's 50 seconds per window, at 128 Hz about 39 seconds. Each window is summarized by the base features mean, variance, min, max, etc. plus EMG-style features energy, waveform length, etc. on each of the three axes — producing a flat feature vector that a classical ML model can consume. FoG prediction is multi-label — a window can have non-zero probability for StartHesitation , Turn , and Walking simultaneously. I use a LightGBM-based MultiOutputRegressor: python import lightgbm as lgbfrom sklearn.multioutput import MultiOutputRegressorfrom sklearn.model selection import GroupKFoldfrom sklearn.metrics import average precision scorefrom sklearn.base import clonebest params = { 'colsample bytree': 0.528, 'learning rate': 0.227, 'max depth': 8, 'min child weight': 3.12, 'n estimators': 291, 'subsample': 0.996,}def custom average precision y true, y pred : score = average precision score y true, y pred return 'average precision', score, Trueclass LGBMMultiOutputRegressor MultiOutputRegressor : def fit self, X, y, eval set=None, fit params : self.estimators = clone self.estimator for in range y.shape 1 for i, estimator in enumerate self.estimators : if eval set: fit params 'eval set' = eval set 0 , eval set 1 :, i estimator.fit X, y :, i , fit params return self Cross-validation uses GroupKFold grouped by subject — critical for medical data, because we don't want the same patient's samples appearing in both train and test that would leak information and inflate the score : kfold = GroupKFold 5 groups = kfold.split train, groups=train.Subject regs, cvs = , for , tr idx, te idx in enumerate tqdm groups, total=5, desc="Folds" : tr idx = pd.Series tr idx .sample n=200000, random state=42 .values reg = LGBMMultiOutputRegressor lgb.LGBMRegressor best params x train = train.loc tr idx, cols .to numpy y train = train.loc tr idx, pcols .to numpy x test = train.loc te idx, cols .to numpy y test = train.loc te idx, pcols .to numpy reg.fit x train, y train, eval set= x test, y test , eval metric=custom average precision regs.append reg cv = metrics.average precision score y test, reg.predict x test .clip 0.0, 1.0 cvs.append cv print cvs The winning solutions in the Kaggle competition usually combined a gradient-boosted tree model like this one with a deep sequence model. The tree model is great at learning from statistical window features; the sequence model picks up signal-level dynamics the tree model can't see. The final prediction is typically a weighted maximum or average of both. In the end the simplified model in this notebook didn’t score high average precision across the three event classes. It's worth understanding why: This is a nice reminder that in real-world time series problems, the ceiling is often set by what the signal can actually reveal , not by how sophisticated your model is. That's it for Part 1. We've gone from "what is a time series?" all the way through a full classification pipeline on real medical sensor data. Along the way we introduced the three data layers, the key descriptors trend, seasonality, anomalies , and time windows — the foundation of ML on time series. In Part 2 we'll slow down and focus on data preparation with Pandas : handling dates and time zones, up-sampling and down-sampling, filling missing values, smoothing, and checking autocorrelation and stationarity. These are the unglamorous steps that account for most of the quality or lack of it in a final forecast. In Part 3 we'll cover forecasting models and evaluation — ARIMA, SARIMA, ETS, Prophet, time-series transformers, the metrics that really matter MAE, RMSE, MAPE, WAPE, MASE , and probabilistic forecasting with quantile loss. We'll close the series with the stock-price LSTM notebook and the siren-sound classification notebook. All code examples are in the repository: https://github.com/mzarnecki/time-series-analysis https://github.com/mzarnecki/time-series-analysis . Grab the notebooks and play with them — working hands-on is what makes this material stick. See you in Part 2. Time Series Analysis and Forecasting — Part 1: Fundamentals, Use Cases, and a Case Study from… https://pub.towardsai.net/time-series-analysis-and-forecasting-part-1-fundamentals-use-cases-and-a-case-study-from-954496437d8b was originally published in Towards AI https://pub.towardsai.net on Medium, where people are continuing the conversation by highlighting and responding to this story.