Skip to content

Trends

Many business datasets have a time dimension built into them. Sales, profit, employee numbers, customer satisfaction, and digital maturity may all change gradually over several years. Trend visualisation allows an analyst to distinguish genuine, persistent movement from mere temporary fluctuation, a distinction that matters enormously when deciding whether a single unusual year reflects a real underlying shift or just short-term noise.

A line chart is usually the most natural way to visualise time-series data. When the horizontal axis represents time, a viewer can quickly judge whether a variable is rising, falling, cycling, or remaining broadly stable. In management and economics, this is very often the first step in understanding performance, well before any formal statistical model is built.

When multiple companies are plotted on the same chart, an analyst can compare their growth paths directly against one another. Some firms may grow steadily, others may fluctuate considerably, and others may remain relatively flat throughout. These kinds of visual differences often motivate further, more targeted analysis, for example, asking why one firm's trajectory looks so different from the rest.

# Company-level sales trends

pivot_sales = data.pivot_table(
    index="year",
    columns="company",
    values="sales_million"
)

plt.figure(figsize=(11, 6))
for company in pivot_sales.columns:
    plt.plot(pivot_sales.index, pivot_sales[company], label=company)

plt.title("Sales Trends by Company")
plt.xlabel("Year")
plt.ylabel("Sales (£m)")
plt.legend(fontsize=8, loc="upper left", bbox_to_anchor=(1, 1))
plt.grid(True)
plt.tight_layout()
plt.show()

Expected output / interpretation

The figure should show 10 company sales lines, one for each business in the dataset.

Breaking down the approach:

  • pivot_table() reshapes the data from its original "long" format (one row per company-year) into a "wide" format, with years running down the rows and a separate column for each company's sales. This reshaping step is what makes it straightforward to plot every company's trend on the same chart, since each company now has its own clearly separated column to loop over.
  • The for company in pivot_sales.columns: loop draws one line per company, adding a label=company each time so the legend can correctly identify which line belongs to which firm. This is a common and genuinely useful pattern whenever you want to compare several categories' trends on a single chart, rather than manually writing out ten near-identical plt.plot() calls by hand.
  • plt.legend(fontsize=8, loc="upper left", bbox_to_anchor=(1, 1)) deliberately places the legend outside the main plotting area, to the right of the chart. With 10 separate companies to label, a legend placed inside the chart area would likely overlap the very lines it's meant to explain; positioning it externally like this keeps the chart itself uncluttered.

Reading a multi-line chart like this well: with 10 lines on a single chart, it's easy to feel a little overwhelmed at first glance. A useful strategy is to look for the overall envelope of movement first (are most companies moving in the same general direction, or are trajectories quite mixed?), before picking out one or two individual lines that stand out, either for growing unusually quickly, declining, or behaving very differently from the rest of the group.

Python Code: Average Growth Over Time

# Sales growth and profit margin over time

data = data.sort_values(["company", "year"])
data["sales_growth"] = data.groupby("company")["sales_million"].pct_change()

growth_summary = data.groupby("year")["sales_growth"].mean()

plt.figure(figsize=(9, 5))
plt.plot(growth_summary.index, growth_summary.values, marker="o")
plt.title("Average Sales Growth Across Companies")
plt.xlabel("Year")
plt.ylabel("Average sales growth")
plt.axhline(0, linestyle="--")
plt.grid(True)
plt.show()

Expected output / interpretation

The chart should show average annual sales growth across companies. Values above zero indicate average expansion.

Why each step matters here:

  • data.sort_values(["company", "year"]) is essential before calculating any growth rate. .pct_change() calculates each row's change relative to the row immediately before it, so if the data weren't correctly sorted by company and then by year within each company, you'd risk calculating a "growth rate" between two entirely unrelated companies, a subtle but serious error that wouldn't necessarily be obvious just from looking at the resulting numbers.
  • data.groupby("company")["sales_million"].pct_change() calculates each company's year-on-year growth rate separately, resetting at the start of each new company, rather than accidentally calculating a change from one company's last year to a different company's first year.
  • data.groupby("year")["sales_growth"].mean() then collapses across companies, producing a single average growth figure for each year, this is what actually gets plotted.
  • plt.axhline(0, linestyle="--") draws a dashed horizontal reference line at zero. This is a small addition that makes an enormous difference to interpretation: without it, a reader has to mentally estimate where "zero growth" falls on the y-axis; with it, they can immediately see which years sit above the line (average expansion) versus below it (average contraction).

Connecting this back to Session 3: this is exactly the same pct_change() method used previously to calculate stock returns from prices, applied here to sales figures instead of prices. Recognising that "growth rate" and "return" are really the same underlying calculation, a percentage change from one period to the next, is a useful piece of intuition that transfers directly between financial and general business contexts.