To draw a boxplot for time series data in Matplotlib, you need one numeric sample for each time period, such as every observation recorded in January, then February, and so on. Matplotlib draws one box per sample. The chart compares how values are spread within each period. It does not show the order of readings inside a period or the overall direction of change, so if trend is your main question, pair the boxes with a line plot.
What each box shows
The box runs from the first quartile (Q1) to the third quartile (Q3) of the data, with a line at the median. By default, whiskers extend to the most distant observations that lie within 1.5 times the interquartile range (IQR) of the box. Points beyond the whiskers are drawn as individual fliers. The whisker ends are therefore not necessarily the minimum and maximum of the period. These rules come from the Matplotlib boxplot API documentation.
As an Amazon Associate I earn from qualifying purchases.
Every box describes whatever you put into its sample. That makes the grouping step the most important decision in the whole chart.
Step-by-step: one boxplot per month
-
Confirm the data types. Your DataFrame needs a datetime-like
timestampcolumn and a numericvaluecolumn. Convert the timestamps withpd.to_datetimeso that pandas can group them by calendar period. -
Drop unusable rows and set a sorted index. Missing timestamps or values cannot be placed in a period, so remove them before grouping:
import matplotlib.pyplot as plt import pandas as pd work = df.assign(timestamp=pd.to_datetime(df["timestamp"])) work = work.dropna(subset=["timestamp", "value"]) work = work.set_index("timestamp").sort_index() -
Group the raw values by period.
DataFrame.resampleperforms a time-based groupby on a datetime index, and it can be followed by a reduction. For a boxplot, you skip the reduction and keep the values. The"MS"alias groups by month start. Empty months are removed in the same step:periods = [ (period.strftime("%Y-%m"), group.to_numpy()) for period, group in work["value"].resample("MS") if len(group) > 0 ] labels, samples = zip(*periods) -
Draw the boxes.
ax.boxplotaccepts a sequence of one-dimensional arrays and draws one box per array. Pass the labels throughtick_labels, which is the current name for this argument:Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.Rank #2
fig, ax = plt.subplots(figsize=(10, 5)) ax.boxplot(samples, tick_labels=labels, showfliers=True) ax.set_xlabel("Month") ax.set_ylabel("Value") ax.set_title("Distribution of observations by month") ax.tick_params(axis="x", labelrotation=45) fig.tight_layout() plt.show()If you are working with an older Matplotlib release, check the installed version before using
tick_labels. Earlier code often usedlabelsinstead, so an error on that argument usually points to a version mismatch.
The result is one box per month, with each box built from every raw measurement recorded in that month.
The aggregation trap: do not average first
A common shortcut is to write work["value"].resample("MS").mean() and plot the result. That produces one number per month, so the boxes would summarise the spread of monthly averages across months, not the variation of readings within each month. The chart would look like a distribution, but it would answer a different question.
Decide the question first, then choose the grouping to match it:
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstall| Question | What to group | What each box shows |
|---|---|---|
| How variable are readings within each month? | Raw observations per month | Spread of individual readings in that month |
| How do monthly averages vary from month to month? | Monthly means, pooled across a larger set of groups | Spread of those means, which needs enough groups to be meaningful |
| How does one month compare with the same month in other years? | Raw observations grouped by calendar month across years | Pooled readings for that calendar month |
The pandas resample documentation describes the grouping and reduction process, and the pandas resample API reference lists the options for each bin. The closed argument controls which bin edge belongs to a bin, and label controls how each bin is named. Check both when your timestamps fall exactly on period boundaries, because a reading recorded at midnight on the first day of a month may land in the previous bin depending on those settings. The pandas time-series user guide covers the general behaviour.
Handling unequal and missing periods
Real series rarely have the same number of readings in every period. Before you draw, count the observations per period so you can judge whether a box is trustworthy:
- A box built from very few values can look precise when it is not. Consider a minimum sample size and drop or flag periods below it.
- An empty period should be left out, not drawn as a zero-valued box. The code above removes empty months for this reason.
- When sample sizes differ widely, say so in the caption or axis title, because the visual weight of each box does not reflect its count.
- Keep the same bin definition across every comparison. If you compare two sensors or two sites, use identical period edges and, where it helps, the same y-axis limits.
Using a continuous date axis
Text labels work well when the periods are discrete, such as months, weekdays, or seasons. If real elapsed time matters, or your bins are unevenly spaced, place each box at its date instead.
Matplotlib represents dates as floating-point day counts from the default 1970-01-01 UTC epoch, and matplotlib.dates.date2num performs the conversion. Each box then takes a numeric position. Because the widths argument is measured in data units, a width of 1 would be one day, so set it to a value close to the bin size. For monthly boxes, a width around 20 days is a reasonable start:
Free tools Windows power users keep installed
One-click scans. No signup required.
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
periods = [
(period.to_pydatetime(), group.to_numpy())
for period, group in work["value"].resample("MS")
if len(group) > 0
]
positions = [mdates.date2num(start) for start, _ in periods]
samples = [values for _, values in periods]
fig, ax = plt.subplots(figsize=(10, 5))
ax.boxplot(samples, positions=positions, widths=20)
ax.xaxis.set_major_locator(mdates.AutoDateLocator())
ax.xaxis.set_major_formatter(mdates.ConciseDateFormatter(ax.xaxis.get_major_locator()))
ax.set_xlabel("Month")
ax.set_ylabel("Value")
fig.tight_layout()
plt.show()
Do not supply strings as positions and expect them to act as axis labels. The positions are numeric coordinates, and the text labels belong in tick_labels or in the date formatter. The Matplotlib dates API documentation describes the automatic locators and formatters, and it notes that Matplotlib converts datetime.datetime and numpy.datetime64 objects when they are plotted on an axis.
Best Value
Precision also depends on the date range. The same documentation says that microsecond precision holds for dates roughly 70 years on either side of the epoch and degrades farther away. Ordinary daily or monthly charts are unaffected. Plots that need sub-microsecond resolution should use floating-point seconds instead, and any change to the epoch must happen before dates are converted.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.When a boxplot is not enough
A boxplot answers the question of distribution by period. It cannot show whether a series is rising, falling, or cycling within a period, because the order of observations is discarded. If trend is part of your question, draw the raw series as a line and add the boxplot in a second panel, sharing the x-axis. That layout lets a reader see the change over time and the spread in each period together.
For multi-column data, pandas can also draw grouped boxplots through DataFrameGroupBy.boxplot, documented in the pandas grouped boxplot API reference. Use it when pandas already holds the grouping you need. The Matplotlib approach above gives you more control over tick labels, date positions, and styling.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsQuick Recap
Quick checklist before you publish the chart
- Each box is built from raw values, unless you deliberately want a distribution of summaries.
- Empty periods are removed rather than drawn as zero.
- Sample sizes per period are known and, where they are small, flagged.
- Labels use
tick_labelsfor discrete periods, or numeric positions with a date formatter for true time spacing. - The axis titles name the period and the measured quantity, so a reader can tell what each box represents.
”
The Bottom Line
“”
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




