Mathew K Analytics

Lesson 34 · Mastering Pandas

Mastering Dates and Times in Pandas for Effective Data Analysis

In modern data analysis, handling dates and times is essential. Pandas offers powerful DateTime tools for filtering, resampling, plotting, and more. Let us…

⬇ Download notebookOpen in Colab ↗

What you'll learn

Data

No separate download needed — the notebook creates or downloads everything it uses.

📓 Full notebook

Download .ipynb

Pandas DatetimeIndex and Timestamps: An Intermediate Beginner's Guide#

In modern data analysis, handling dates and times is essential. Pandas offers powerful DateTime tools for filtering, resampling, plotting, and more. Let us explore how to master time data in practical, friendly steps.

Real-World Time Series Example: U.S. Airline Passengers#

We will use a built-in flights dataset. First, let us see the raw data.

import warnings; warnings.filterwarnings("ignore")

# Data setup (Airline Passenger Flights Dataset)
import seaborn as sns
import numpy as np
np.random.seed(42)
df = sns.load_dataset('flights')
print(df.shape)
print(df.head(3))
(144, 3)
   year month  passengers
0  1949   Jan         112
1  1949   Feb         118
2  1949   Mar         132

Looking at the Data Structure#

Current columns are year, month, and passengers. True time series need a real date column. Let us create one.

# Combine year and month columns into a single date string
df['date_str'] = df['year'].astype(str) + '-' + df['month'] + '-01'
print(df[['year','month','date_str']].head())
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[2], line 2
      1 # Combine year and month columns into a single date string
----> 2 df['date_str'] = df['year'].astype(str) + '-' + df['month'] + '-01'
      3 print(df[['year','month','date_str']].head())

File c:\Users\makmw\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\ops\common.py:76, in _unpack_zerodim_and_defer.<locals>.new_method(self, other)
     72             return NotImplemented
     74 other = item_from_zerodim(other)
---> 76 return method(self, other)

File c:\Users\makmw\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\arraylike.py:186, in OpsMixin.__add__(self, other)
     98 @unpack_zerodim_and_defer("__add__")
     99 def __add__(self, other):
    100     """
    101     Get Addition of DataFrame and other, column-wise.
    102 
   (...)
    184     moose     3.0     NaN
    185     """
--> 186     return self._arith_method(other, operator.add)

File c:\Users\makmw\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\series.py:6146, in Series._arith_method(self, other, op)
   6144 def _arith_method(self, other, op):
   6145     self, other = self._align_for_op(other)
-> 6146     return base.IndexOpsMixin._arith_method(self, other, op)

File c:\Users\makmw\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\base.py:1391, in IndexOpsMixin._arith_method(self, other, op)
   1388     rvalues = np.arange(rvalues.start, rvalues.stop, rvalues.step)
   1390 with np.errstate(all="ignore"):
-> 1391     result = ops.arithmetic_op(lvalues, rvalues, op)
   1393 return self._construct_result(result, name=res_name)

File c:\Users\makmw\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\ops\array_ops.py:273, in arithmetic_op(left, right, op)
    260 # NB: We assume that extract_array and ensure_wrapped_if_datetimelike
    261 #  have already been called on `left` and `right`,
    262 #  and `maybe_prepare_scalar_for_op` has already been called on `right`
    263 # We need to special-case datetime64/timedelta64 dtypes (e.g. because numpy
    264 # casts integer dtypes to timedelta64 when operating with timedelta64 - GH#22390)
    266 if (
    267     should_extension_dispatch(left, right)
    268     or isinstance(right, (Timedelta, BaseOffset, Timestamp))
   (...)
    271     # Timedelta/Timestamp and other custom scalars are included in the check
    272     # because numexpr will fail on it, see GH#31457
--> 273     res_values = op(left, right)
    274 else:
    275     # TODO we should handle EAs consistently and move this check before the if/else
    276     # (https://github.com/pandas-dev/pandas/issues/41165)
    277     # error: Argument 2 to "_bool_arith_check" has incompatible type
    278     # "Union[ExtensionArray, ndarray[Any, Any]]"; expected "ndarray[Any, Any]"
    279     _bool_arith_check(op, left, right)  # type: ignore[arg-type]

File c:\Users\makmw\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\arrays\categorical.py:1718, in Categorical.__array_ufunc__(self, ufunc, method, *inputs, **kwargs)
   1714         return result
   1716 # for all other cases, raise for now (similarly as what happens in
   1717 # Series.__array_prepare__)
-> 1718 raise TypeError(
   1719     f"Object with dtype {self.dtype} cannot perform "
   1720     f"the numpy op {ufunc.__name__}"
   1721 )

TypeError: Object with dtype category cannot perform the numpy op add
# Convert date string to pandas datetime (Timestamp) object
df['date'] = pd.to_datetime(df['date_str'], format='%Y-%b-%d')
print(df[['date_str', 'date']].head())
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[3], line 2
      1 # Convert date string to pandas datetime (Timestamp) object
----> 2 df['date'] = pd.to_datetime(df['date_str'], format='%Y-%b-%d')
      3 print(df[['date_str', 'date']].head())

NameError: name 'pd' is not defined
# Set the datetime as the DataFrame index (DatetimeIndex)
df_ts = df.set_index('date')
print(df_ts.head(3))
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_31464\3883130576.py in ?()
      1 # Set the datetime as the DataFrame index (DatetimeIndex)
----> 2 df_ts = df.set_index('date')
      3 print(df_ts.head(3))

c:\Users\makmw\AppData\Local\Programs\Python\Python312\Lib\site-packages\pandas\core\frame.py in ?(self, keys, drop, append, inplace, verify_integrity)
   6125                     if not found:
   6126                         missing.append(col)
   6127 
   6128         if missing:
-> 6129             raise KeyError(f"None of {missing} are in the columns")
   6130 
   6131         if inplace:
   6132             frame = self

KeyError: "None of ['date'] are in the columns"

Why Use a DatetimeIndex?#

  • Fast filtering and slicing by time
  • Makes plotting and resampling easier
  • Friendly with rolling averages and time math
# Plot the time series: passengers over time
import matplotlib.pyplot as plt
df_ts['passengers'].plot(figsize=(10,4), title='Monthly Airline Passengers')
plt.ylabel('Passengers')
plt.xlabel('Date')
plt.tight_layout()
plt.show()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[5], line 3
      1 # Plot the time series: passengers over time
      2 import matplotlib.pyplot as plt
----> 3 df_ts['passengers'].plot(figsize=(10,4), title='Monthly Airline Passengers')
      4 plt.ylabel('Passengers')
      5 plt.xlabel('Date')

NameError: name 'df_ts' is not defined
# Filter for flights in the 1950s only (using the DatetimeIndex)
df_1950s = df_ts.loc['1950-01-01':'1959-12-31']
print(df_1950s.head(3))
print(f"Total records for 1950s: {len(df_1950s)}")
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[6], line 2
      1 # Filter for flights in the 1950s only (using the DatetimeIndex)
----> 2 df_1950s = df_ts.loc['1950-01-01':'1959-12-31']
      3 print(df_1950s.head(3))
      4 print(f"Total records for 1950s: {len(df_1950s)}")

NameError: name 'df_ts' is not defined
# Quick monthly summary: mean number of passengers by year
yearly_avg = df_ts.resample('Y')['passengers'].mean()
print(yearly_avg.head(3))
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[7], line 2
      1 # Quick monthly summary: mean number of passengers by year
----> 2 yearly_avg = df_ts.resample('Y')['passengers'].mean()
      3 print(yearly_avg.head(3))

NameError: name 'df_ts' is not defined
# Rolling average: passengers per month, window=6
df_ts['passengers_rolling6'] = df_ts['passengers'].rolling(window=6).mean()
df_ts[['passengers', 'passengers_rolling6']].head(10)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[8], line 2
      1 # Rolling average: passengers per month, window=6
----> 2 df_ts['passengers_rolling6'] = df_ts['passengers'].rolling(window=6).mean()
      3 df_ts[['passengers', 'passengers_rolling6']].head(10)

NameError: name 'df_ts' is not defined
# Plot comparison: original vs. rolling average
df_ts[['passengers', 'passengers_rolling6']].plot(figsize=(10,4))
plt.title('Monthly Passengers and 6-Month Rolling Average')
plt.ylabel('Number of Passengers')
plt.xlabel('Date')
plt.tight_layout()
plt.show()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[9], line 2
      1 # Plot comparison: original vs. rolling average
----> 2 df_ts[['passengers', 'passengers_rolling6']].plot(figsize=(10,4))
      3 plt.title('Monthly Passengers and 6-Month Rolling Average')
      4 plt.ylabel('Number of Passengers')

NameError: name 'df_ts' is not defined
# Fast slice: Show passengers trend for summer months only
df_summer = df_ts[df_ts.index.month.isin([6,7,8])]
print(df_summer.head(6))
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[10], line 2
      1 # Fast slice: Show passengers trend for summer months only
----> 2 df_summer = df_ts[df_ts.index.month.isin([6,7,8])]
      3 print(df_summer.head(6))

NameError: name 'df_ts' is not defined
# Example: Find the month with the most passengers ever
row_max = df_ts['passengers'].idxmax()
print(f'Maximum passengers was in: {row_max}')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[11], line 2
      1 # Example: Find the month with the most passengers ever
----> 2 row_max = df_ts['passengers'].idxmax()
      3 print(f'Maximum passengers was in: {row_max}')

NameError: name 'df_ts' is not defined
# Convert index back to a column (reset index)
flights_with_date = df_ts.reset_index()
print(flights_with_date[['date','passengers']].head())
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[12], line 2
      1 # Convert index back to a column (reset index)
----> 2 flights_with_date = df_ts.reset_index()
      3 print(flights_with_date[['date','passengers']].head())

NameError: name 'df_ts' is not defined

Practice Prompt: Real Data, Real Dates#

Try changing the rolling window to 12 months. Which year had the slowest average growth? You can explore with just a few lines of code.

More DateTime Tricks#

You can use .dt accessor on date columns to get parts like year, month, or weekday. Let us see examples:

# Use .dt accessor for date parts
years = flights_with_date['date'].dt.year.unique()
print('Years in the dataset:', years)

first_weekdays = flights_with_date['date'].dt.day_name().unique()
print('Days of week present:', first_weekdays)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[13], line 2
      1 # Use .dt accessor for date parts
----> 2 years = flights_with_date['date'].dt.year.unique()
      3 print('Years in the dataset:', years)
      5 first_weekdays = flights_with_date['date'].dt.day_name().unique()

NameError: name 'flights_with_date' is not defined

Common Pitfalls in Datetime Handling#

  • Watch out for string vs. datetime types
  • Always check timezone handling with real-world data
  • Missing or duplicate dates may cause subtle errors

If you get errors, try using pd.to_datetime again, or inspect the column type with type(df['column'].iloc[0]).

# Quick troubleshooting: check data types
print(flights_with_date.dtypes)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[14], line 2
      1 # Quick troubleshooting: check data types
----> 2 print(flights_with_date.dtypes)

NameError: name 'flights_with_date' is not defined

Extensions: Working with Date Ranges and Timestamps#

  • pd.date_range lets you quickly make a time index
  • Pandas Timestamps behave a lot like Python datetimes, but add more power

Let us see a quick example:

# Generate a series of business days in January 2024
rng = pd.date_range(start='2024-01-01', end='2024-01-31', freq='B')
print(rng[:5])

# Example: work with single pandas Timestamp
stamp = pd.Timestamp('2024-01-05 10:30')
print(f'Year: {stamp.year}, Month: {stamp.month}, Hour: {stamp.hour}')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[15], line 2
      1 # Generate a series of business days in January 2024
----> 2 rng = pd.date_range(start='2024-01-01', end='2024-01-31', freq='B')
      3 print(rng[:5])
      5 # Example: work with single pandas Timestamp

NameError: name 'pd' is not defined

Recap: What Have We Learned?#

  • How to create DatetimeIndex and Timestamps
  • How to filter, roll, and plot with time series
  • How to troubleshoot, extract parts, and extend with ranges

Pandas makes handling dates approachable. With a little practice, time series unlocks many new insights.

Next Steps: Keep Exploring!#

Try running experiments with your own CSVs. Practice rolling means, month selections, and custom date parsing.

Like this walkthrough? Subscribe for more hands-on pandas lessons. Let us keep learning together!

Found this useful?

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