Line Plot Gallery¶
Line plots show how a value moves across an ordered axis: days since launch, weeks of trading, months of a campaign. They're the chart of choice when the shape of a trend matters more than any single value, and when comparing multiple series that share the same x-axis.
Line plots excel at:
- Sequential trends: trace daily, weekly, or event-relative movement (e.g. days since a competitor opened)
- Multi-series comparison: overlay a line per category to compare growth shapes side by side
- Highlighting standouts: mute the pack to grey and put colour on the one or two series that tell the story
- Sparse data handling: fill missing periods so lines stay continuous instead of breaking apart
import matplotlib.pyplot as plt
import pandas as pd
from openretailscience.plots import line
Basic Line Plot¶
The simplest call: pick an x-axis column and a value column, and the line traces the value across the sequence. Use this shape for ordered-but-not-time-of-day axes like "day of trading", "weeks since launch", or "days since the competitor opened". For real datetime axes, reach for the time_line module instead.
line.plot(
daily_revenue,
x_col="day",
value_col="revenue",
eyebrow="Revenue trend",
title="Daily revenue doubled across the seven-day window",
subtitle="Daily revenue ($000s), week 1",
x_label="Day",
y_label="Revenue ($000s)",
)
plt.show()
Plotting a Pandas Series¶
A Series works directly: the function uses the index as the x-axis and the series name as the line label. Useful when the upstream computation (e.g. a groupby().sum()) already returns a Series and you don't want to wrap it just to plot. When passing a Series, leave value_col, x_col, and group_col all as None.
line.plot(
daily_sales_series,
eyebrow="Daily sales",
title=r"Daily sales rose from \$8K to \$13K over the week",
subtitle="Daily sales ($000s)",
x_label="Day",
y_label="Sales ($000s)",
)
plt.show()
Multiple Lines with group_col¶
Pass group_col to draw one line per category from a long-format DataFrame. The function pivots the data internally and assigns a colour per group. Pick this shape when each row is a single observation of (x, group, value), as you'd get from a SQL GROUP BY query.
line.plot(
category_revenue,
x_col="day",
value_col="revenue",
group_col="category",
eyebrow="Category performance",
title="Electronics revenue pulls away from Apparel and Home each day",
subtitle="Daily revenue ($000s) by product category, days 1 to 6",
x_label="Day",
y_label="Revenue ($000s)",
legend_title="Category",
move_legend_outside=True,
)
plt.show()
Multiple Value Columns¶
If the data is already in wide format (one column per series), pass a list to value_col instead of using group_col. The two are mutually exclusive: passing both raises ValueError. A real risk worth flagging: when the series have very different magnitudes, the small ones get squashed against the x-axis on a shared y-scale, as happens here with revenue versus visitor count.
line.plot(
metrics_data,
x_col="day",
value_col=["Revenue", "Orders", "Visitors"],
eyebrow="Business metrics",
title="Visitor counts dwarf orders and revenue on a shared y-axis",
subtitle="Daily values for revenue ($000s), orders, and visitors, week 1",
x_label="Day",
y_label="Value",
legend_title="Metric",
move_legend_outside=True,
)
plt.show()
Index-Based Plotting¶
Omit x_col and the function uses the DataFrame's index as the x-axis. Convenient when the index already carries the meaning, like a CategoricalIndex of trading weeks or a DatetimeIndex of trading days. The visible data and the underlying DataFrame stay perfectly aligned without an extra column.
line.plot(
weekly_sales,
value_col="sales",
eyebrow="Weekly sales",
title="Weekly sales grew nearly 50% over eight weeks",
subtitle="Weekly sales ($000s)",
x_label="Week",
y_label="Sales ($000s)",
)
plt.show()
Fill Missing Values¶
When group_col triggers an internal pivot, any missing (x, group) combination becomes a NaN and the line breaks. Pass fill_na_value to substitute a fixed value (typically 0) so the lines stay continuous. Use this when the missing periods genuinely mean "zero", and skip it when a gap should remain visible.
line.plot(
regional_sparse,
x_col="month",
value_col="sales",
group_col="region",
fill_na_value=0,
eyebrow="Regional sales",
title="North pulls further ahead of South after a missing month",
subtitle="Monthly sales ($000s) by region, missing observations filled with 0",
x_label="Month",
y_label="Sales ($000s)",
legend_title="Region",
move_legend_outside=True,
)
plt.show()
Highlighting Specific Lines¶
Pass highlight=[...] to keep a few series in saturated colour while muting the rest to grey. Pick this when there are too many lines to read individually but the analysis is about a specific subset (a competitive set, a watchlist, an underperforming group). The muted lines stay as context so the reader can see whether the highlighted subset is moving with or against the pack.
line.plot(
store_performance,
x_col="week",
value_col="performance",
group_col="store_name",
highlight=["Shoreditch", "Hammersmith"],
eyebrow="Store performance",
title="Shoreditch pulls away from the 15-store group while Hammersmith slips below",
subtitle="Weekly performance index by London store, 2 stores highlighted of 15",
x_label="Week",
y_label="Performance Index",
legend_title="Store",
move_legend_outside=True,
)
plt.show()
End-of-Line Legend¶
Pass legend_style="end_of_line" to suppress the boxed legend and place a colored series label at the right end of each line instead. The label sits next to the data it describes, so the reader doesn't bounce between a legend swatch and a line. Works best with three to five series, more than that and the labels start to crowd each other.
line.plot(
category_revenue,
x_col="day",
value_col="revenue",
group_col="category",
legend_style="end_of_line",
eyebrow="Category performance",
title="End-of-line labels sit beside each line instead of in a separate box",
subtitle="Daily revenue ($000s) by product category, days 1 to 6",
x_label="Day",
y_label="Revenue ($000s)",
)
plt.show()