Mathew K Analytics

Lesson 12 · Data analytics zero to hero

Pandas Time Series Analysis Tutorial | Data Analytics #12

Video twelve of the 30-part series, and the last stop in the pandas block: dates as an index, resampling, rolling windows, and percent change. We're using a…

What you'll learn

Datasets used in this lesson

Save these next to the notebook. In Google Colab, upload them with the 📁 icon on the left first.

📓 Full notebook

Download .ipynb

Data Analytics Zero to Hero, Video 12: Time Series with Pandas#

  • Video twelve of the 30-part series, and the last stop in the pandas block: dates as an index, resampling, rolling windows, and percent change.
  • We're using a real dataset: about a year of actual AAPL daily stock price and volume history.
  • Let's jump straight in.

Before You Start#

  • Open a new Jupyter Notebook in VS Code and select your Python interpreter as the kernel.
  • Place aapl_ohlcv.csv in the same folder as this notebook.

Part 1: Dates as the Index#

import pandas as pd

df = pd.read_csv('aapl_ohlcv.csv', parse_dates=['Date'])
df = df.set_index('Date').sort_index()
print(df.head(3))
print(df.index)
           Ticker       Close        High         Low        Open    Volume
Date                                                                       
2025-07-15   AAPL  208.283691  211.052705  208.094440  208.393257  42296300
2025-07-16   AAPL  209.329544  211.560683  207.815546  209.468990  47490500
2025-07-17   AAPL  209.190094  210.963059  208.761785  209.737924  48068100
DatetimeIndex(['2025-07-15', '2025-07-16', '2025-07-17', '2025-07-18',
               '2025-07-21', '2025-07-22', '2025-07-23', '2025-07-24',
               '2025-07-25', '2025-07-28',
               ...
               '2026-06-30', '2026-07-01', '2026-07-02', '2026-07-06',
               '2026-07-07', '2026-07-08', '2026-07-09', '2026-07-10',
               '2026-07-13', '2026-07-14'],
              dtype='datetime64[ns]', name='Date', length=251, freq=None)
print(df.loc['2025-08'].shape)
print(df.loc['2025-08':'2025-09'].head(3))
(21, 6)
           Ticker       Close        High         Low        Open     Volume
Date                                                                        
2025-08-01   AAPL  201.580292  212.736031  200.703764  210.036733  104434500
2025-08-04   AAPL  202.546463  207.058561  200.883049  203.701868   75109300
2025-08-05   AAPL  202.118149  204.528584  201.361157  202.596248   44155100

Part 2: Resampling#

weekly_close = df['Close'].resample('W').mean()
print(weekly_close.head(5))
Date
2025-07-20    209.287209
2025-07-27    212.889420
2025-08-03    208.038672
2025-08-10    212.935239
2025-08-17    230.254581
Freq: W-SUN, Name: Close, dtype: float64
monthly_summary = df['Close'].resample('ME').agg(['first', 'last', 'min', 'max'])
print(monthly_summary.head(5))
                 first        last         min         max
Date                                                      
2025-07-31  208.283691  206.749786  206.749786  213.552780
2025-08-31  201.580292  231.485107  201.580292  232.671753
2025-09-30  229.071945  253.911667  226.150192  256.145325
2025-10-31  254.729340  269.607269  244.578064  270.634338
2025-11-30  268.290955  278.332886  265.756256  278.332886
monthly_volume = df['Volume'].resample('ME').sum()
print(monthly_volume.head(3))
Date
2025-07-31     633372300
2025-08-31    1218079100
2025-09-30    1265536400
Freq: ME, Name: Volume, dtype: int64

Part 3: Rolling Windows#

df['MA20'] = df['Close'].rolling(window=20).mean()
print(df[['Close', 'MA20']].tail(5))
                 Close        MA20
Date                              
2026-07-08  313.390015  295.632500
2026-07-09  316.220001  296.916000
2026-07-10  315.320007  298.103001
2026-07-13  317.309998  299.187001
2026-07-14         NaN         NaN
df['MA5'] = df['Close'].rolling(window=5).mean()
df['Above20MA'] = df['MA5'] > df['MA20']
print(df[['MA5', 'MA20', 'Above20MA']].tail(5))
                   MA5        MA20  Above20MA
Date                                         
2026-07-08  307.944006  295.632500       True
2026-07-09  312.312006  296.916000       True
2026-07-10  313.650006  298.103001       True
2026-07-13  314.580005  299.187001       True
2026-07-14         NaN         NaN      False

Part 4: pct_change, shift, and diff#

df['DailyReturn'] = df['Close'].pct_change(fill_method=None)
print(df['DailyReturn'].describe())
count    249.000000
mean       0.001807
std        0.015221
min       -0.061178
25%       -0.005489
50%        0.000899
75%        0.008788
max        0.050907
Name: DailyReturn, dtype: float64
df['PriceYesterday'] = df['Close'].shift(1)
df['PriceChange'] = df['Close'].diff()
print(df[['Close', 'PriceYesterday', 'PriceChange']].tail(3))
                 Close  PriceYesterday  PriceChange
Date                                               
2026-07-10  315.320007      316.220001    -0.899994
2026-07-13  317.309998      315.320007     1.989990
2026-07-14         NaN      317.309998          NaN
cumulative_return = (1 + df['DailyReturn']).cumprod() - 1
print(cumulative_return.tail(3))
last_valid = cumulative_return.dropna().iloc[-1]
print(f"Total return over the period: {last_valid:.2%}")
Date
2026-07-10    0.513897
2026-07-13    0.523451
2026-07-14         NaN
Name: DailyReturn, dtype: float64
Total return over the period: 52.35%

Wrap-Up: What You Learned#

  • Turning a date column into a real DatetimeIndex with parse_dates and set_index, and slicing it with partial date strings.
  • resample, for bucketing a time series into weekly or monthly summaries.
  • rolling, for moving averages and other smoothed statistics.
  • pct_change, diff, and shift, for comparing each row to an earlier point in time.
  • Computing a compounded cumulative return from daily percent changes.
  • All of it on a real year of AAPL trading data. This wraps up the pandas block. Video thirteen starts a visualization block with Matplotlib, turning tables like these into real charts. Subscribe so it lands automatically see you there.

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.