Skillversitas

Plotting Dates

Time series are the most common real-world chart, and dates need special handling: the x-axis is a real number (days since an epoch), but it must display as human dates, with ticks that make sense for the span - years for decades of data, months for a year, days or hours for a week. Matplotlib's matplotlib.dates module is the layer that turns datetime objects into that axis: it converts values to numbers for plotting and attaches locators and formatters for display.

Basic Date Plot

import numpy as np
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
import matplotlib.dates as mdates

start = datetime(2026, 1, 1)
days = np.arange(90)
dates = [start + timedelta(days=int(d)) for d in days]
values = 100 + np.cumsum(np.random.default_rng(13).normal(0, 4, 90))

fig, ax = plt.subplots(figsize=(9, 4))
ax.plot(dates, values)
ax.set_ylabel("active users (k)")
fig.autofmt_xdate()      # rotate date labels so they do not collide
plt.show()

Passing datetime objects (or pandas Timestamps) directly works - matplotlib converts them internally. fig.autofmt_xdate() rotates and right-aligns the labels, the standard finishing touch for any date axis.

Controlling Ticks with Locators and Formatters

import matplotlib.dates as mdates

# a tick per month, labels like "Jan 2026"
ax.xaxis.set_major_locator(mdates.MonthLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter("%b %Y"))

# a week of hourly data: ticks per day, hour labels
ax.xaxis.set_major_locator(mdates.DayLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter("%b %d\n%H:%M"))

# fix the window regardless of the data range
ax.set_xlim(datetime(2026, 1, 1), datetime(2026, 3, 31))

Locators choose tick positions (MonthLocator, WeekdayLocator(byweekday=mdates.MO), DayLocator, HourLocator, plus AutoDateLocator which picks by span); formatters choose tick text via strftime patterns. The Auto versions are the safe defaults; explicit locators are how you get "one tick per Monday" or "quarterly ticks."

One Figure, Multiple Spans

import matplotlib.dates as mdates

# left: full year by month; right: one week by day
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4))

ax1.plot(year_dates, monthly_values)
ax1.xaxis.set_major_locator(mdates.MonthLocator(interval=3))
ax1.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m"))

ax2.plot(week_dates, daily_values)
ax2.xaxis.set_major_locator(mdates.DayLocator())
ax2.xaxis.set_major_formatter(mdates.DateFormatter("%a %d"))
fig.tight_layout()
plt.show()

Zooming into a sub-range with the right tick granularity is the classic "overview plus detail" pair. Each Axes gets its own locator/formatter, so the two panels can show the same data at different magnitudes without fighting.

Weekends and Gaps

Business data plotted on a continuous time axis shows gaps on weekends and holidays - sometimes that is the truth (no activity happened), sometimes it is an artifact (data was only collected on business days). If the latter, either filter the dates to business days and accept the visual gaps, or plot against integer indices and relabel the ticks with dates - the choice should be a deliberate statement about what the x-axis means.

Summary

Plot datetime objects directly, finish with autofmt_xdate, and control the axis explicitly with a DateFormatter plus a matching locator - AutoDate for unknown spans, MonthLocator/DayLocator for chosen ones. Match tick granularity to the visible span, fix the window with set_xlim when the story needs a fixed frame, and decide deliberately whether calendar gaps are data or artifact.