Use Case: Economic Resilience of Small Businesses Post-COVID in Melbourne
+Authored by: Diyona Robert
+Duration : 90 mins
+Level: Intermediate
+Pre-requisite skills: python,pandas,numpy,matplotlib
+Scenario
+The COVID-19 pandemic created severe disruptions for small businesses in Melbourne. Many businesses faced financial losses, reduced customer traffic, and changing restrictions, which directly impacted their resilience and survival. This use case explores how small businesses have adapted post-COVID by examining economic indicators, recovery patterns, and potential risks.
+What this use case will teach you
+This use case will teach you how to:
+-
+
Use data analysis to measure the economic resilience of small businesses.
+
+Identify trends and vulnerabilities across different business sectors.
+
+Apply visualisation techniques to highlight recovery patterns.
+
+Draw insights that can support policy recommendations for post-COVID recovery.
+
+
Introduction / Background
+Small businesses are the backbone of Melbourne’s economy, contributing significantly to employment and community wellbeing. The COVID-19 lockdowns disrupted supply chains, altered consumer behavior, and forced many businesses to close temporarily or permanently. Post-pandemic, understanding the resilience and adaptability of these businesses is crucial for future planning. This use case provides an analytical approach to evaluating recovery trajectories, exploring whether businesses have returned to pre-pandemic levels or are still struggling.
+A brief summary of your datasets
+The datasets used in this use case include:
+-
+
Small Business Counts by Industry and Year - provides yearly data on the number of small businesses operating in Melbourne across multiple sectors. This dataset highlights business closures, openings, and overall resilience trends post-COVID.
+
+Pedestrian Counting System - offers hourly and daily pedestrian counts from sensors installed throughout Melbourne. This dataset helps capture the relationship between foot traffic patterns and small business recovery in the post-pandemic period.
+
+
Datasets:
+ +-
+
- Setup +
-
+
- Imports, display options, and utility helpers. All functions include docstrings for clarity. +
import pandas as pd
+import requests
+from io import StringIO
+
+
+def collect_dataset_from_com(dataset_id, format='csv', delimiter=';'):
+ """
+ Fetches a dataset from the City of Melbourne Open Data API using the export endpoint.
+
+ Args:
+ dataset_id (str): Dataset ID from the data portal URL
+ format (str): File format to export (default: 'csv')
+ delimiter (str): CSV delimiter (default: ';')
+
+ Returns:
+ pd.DataFrame: Cleaned dataset
+ """
+ base_url = 'https://data.melbourne.vic.gov.au/api/explore/v2.1/catalog/datasets/'
+ url = f'{base_url}{dataset_id}/exports/{format}'
+
+ params = {
+ 'select': '*',
+ 'limit': -1,
+ 'lang': 'en',
+ 'timezone': 'UTC'
+ }
+
+ response = requests.get(url, params=params)
+ if response.status_code == 200:
+ content = response.content.decode('utf-8')
+ return pd.read_csv(StringIO(content), delimiter=delimiter)
+ else:
+ raise Exception(f"Failed to retrieve dataset '{dataset_id}'. Status code: {response.status_code}")
+import numpy as np
+
+def pct_change_between_years(df, value_col, year_col, y0, y1):
+ """Percent change of `value_col` between years y0 -> y1."""
+ a = df.loc[df[year_col] == y0, value_col]
+ b = df.loc[df[year_col] == y1, value_col]
+ if a.empty or b.empty or a.iloc[0] == 0:
+ return np.nan
+ return (b.iloc[0] - a.iloc[0]) / abs(a.iloc[0]) * 100
+print(biz.columns.tolist())
+['census_year', 'clue_small_area', 'anzsic_indusrty', 'clue_industry', 'business_size', 'total_establishments', 'total_jobs'] ++
-
+
- Load Data +
-
+
- Loads both datasets directly from the City of Melbourne portal. +
# Pedestrian dataset
+ped = collect_dataset_from_com("pedestrian-counting-system-monthly-counts-per-hour")
+
+# Business dataset
+biz = collect_dataset_from_com("business-establishments-and-jobs-data-by-business-size-and-industry")
+
+
+# Clean pedestrian data
+ped["sensing_date"] = pd.to_datetime(ped["sensing_date"], errors="coerce")
+ped["pedestriancount"] = pd.to_numeric(ped["pedestriancount"], errors="coerce").fillna(0)
+
+# Clean business data
+biz.columns = [c.replace("anzsic_indusrty", "anzsic_industry") for c in biz.columns]
+biz["census_year"] = pd.to_numeric(biz["census_year"], errors="coerce")
+biz["total_establishments"] = pd.to_numeric(biz["total_establishments"], errors="coerce")
+biz["total_jobs"] = pd.to_numeric(biz["total_jobs"], errors="coerce")
+biz = biz.dropna(subset=["census_year"])
+
+print("ped shape:", ped.shape, " | biz shape:", biz.shape)
+display(ped.head(2)); display(biz.head(2))
+ped shape: (1407909, 9) | biz shape: (15414, 7) ++
| + | id | +location_id | +sensing_date | +hourday | +direction_1 | +direction_2 | +pedestriancount | +sensor_name | +location | +
|---|---|---|---|---|---|---|---|---|---|
| 0 | +25420240223 | +25 | +2024-02-23 | +4 | +5 | +8 | +13 | +MCEC_T | +-37.82401776, 144.95604426 | +
| 1 | +1171220250625 | +117 | +2025-06-25 | +12 | +105 | +176 | +281 | +Fli114F_T | +-37.81629332, 144.97090877 | +
| + | census_year | +clue_small_area | +anzsic_industry | +clue_industry | +business_size | +total_establishments | +total_jobs | +
|---|---|---|---|---|---|---|---|
| 0 | +2004 | +Kensington | +Wholesale Trade | +Wholesale Trade | +Small business | +16 | +125.0 | +
| 1 | +2004 | +Melbourne (CBD) | +Accommodation and Food Services | +Accommodation | +Large business | +3 | +1245.0 | +
-
+
- Data Cleaning & Standardisation +
-
+
- Parse pedestrian datetime and derive year. +
- Consolidate business columns to lower_snake_case. +
- Filter to small businesses (1–19 jobs). +
# Add month and year to ped
+ped["month"] = ped["sensing_date"].dt.to_period("M").dt.to_timestamp()
+ped["year"] = ped["sensing_date"].dt.year
+ped = ped.dropna(subset=["sensing_date"])
+
+# Fix business dataframe: only census_year → year
+biz = biz.rename(columns={"census_year": "year"})
+
+# Group by year and business_size
+size_year_tbl = (biz.groupby(["year","business_size"])["total_establishments"]
+ .sum()
+ .unstack(fill_value=0)
+ .sort_index())
+
+size_share_tbl = size_year_tbl.div(size_year_tbl.sum(axis=1), axis=0)
+-
+
- Aggregation & Integration +
-
+
- Pedestrians: sum annual counts across all sensors. +
- Business (small only): sum annual establishments and jobs. +
- Merge on year. +
# Annual pedestrians (sum across all sensors)
+ped_annual = (ped.groupby("year")["pedestriancount"]
+ .sum()
+ .sort_index()
+ .reset_index())
+
+# Monthly pedestrians (city-wide)
+ped_monthly = (ped.groupby("month")["pedestriancount"]
+ .sum()
+ .sort_index()
+ .reset_index())
+
+if len(ped_monthly) >= 2:
+ last_month_val = ped_monthly.iloc[-1, 1]
+ prev_month_val = ped_monthly.iloc[-2, 1]
+ # Heuristic: if last month < 60% of previous month, treat as partial and drop
+ if prev_month_val > 0 and last_month_val < 0.6 * prev_month_val:
+ ped_monthly = ped_monthly.iloc[:-1, :]
+
+
+biz_annual = (biz.groupby("year")[["total_establishments","total_jobs"]]
+ .sum()
+ .sort_index()
+ .reset_index())
+
+display(ped_annual.head())
+display(biz_annual.head())
+display(size_share_tbl.head())
+| + | year | +pedestriancount | +
|---|---|---|
| 0 | +2023 | +80555526 | +
| 1 | +2024 | +269281119 | +
| 2 | +2025 | +193671602 | +
| + | year | +total_establishments | +total_jobs | +
|---|---|---|---|
| 0 | +2002 | +36417 | +915027.0 | +
| 1 | +2003 | +36777 | +921754.0 | +
| 2 | +2004 | +37629 | +933115.0 | +
| 3 | +2005 | +39975 | +968008.0 | +
| 4 | +2006 | +41847 | +1039133.0 | +
| business_size | +Large business | +Medium business | +Non employing | +Small business | +
|---|---|---|---|---|
| year | ++ | + | + | + |
| 2002 | +0.022654 | +0.138809 | +0.048851 | +0.789686 | +
| 2003 | +0.022922 | +0.139245 | +0.048617 | +0.789216 | +
| 2004 | +0.022004 | +0.138723 | +0.046958 | +0.792314 | +
| 2005 | +0.020788 | +0.135084 | +0.045403 | +0.798724 | +
| 2006 | +0.020360 | +0.137358 | +0.047745 | +0.794537 | +
-
+
- Exploratory Analysis (Visuals) +Two simple visuals are sufficient for mid-trimester: +
-
+
- Annual trend lines for pedestrians, small establishments, and small jobs +
- 2019→2023 percent change comparison +
import matplotlib.pyplot as plt
+
+# Visual 1A: Business totals by year
+plt.figure(figsize=(10,5))
+plt.plot(biz_annual["year"], biz_annual["total_establishments"], marker="o", label="Total establishments")
+plt.plot(biz_annual["year"], biz_annual["total_jobs"], marker="o", label="Total jobs")
+plt.title("Business totals by year (sum across Melbourne)")
+plt.xlabel("Year"); plt.ylabel("Count")
+plt.legend(); plt.tight_layout(); plt.show()
+
+# Visual 1B: Establishment share by business size over time
+ax = size_share_tbl.plot(kind="area", figsize=(10,5))
+ax.set_title("Establishment share by business size")
+ax.set_xlabel("Year"); ax.set_ylabel("Share")
+plt.tight_layout(); plt.show()
+
+# Visual 1C: Pedestrians – monthly city-wide
+plt.figure(figsize=(10,5))
+plt.plot(ped_monthly["month"], ped_monthly["pedestriancount"], marker="o")
+plt.title("Total pedestrian counts by month (all sensors)")
+plt.xlabel("Month"); plt.ylabel("Count")
+plt.tight_layout(); plt.show()
+# Choose comparison years present in the data
+PRE_YEAR = 2019
+POST_YEAR = 2023 if (biz["year"] >= 2023).any() else int(biz["year"].max())
+
+# Build a small table to plot
+biz_year_sums = biz.groupby("year")[["total_establishments","total_jobs"]].sum().reset_index()
+
+summary = pd.Series({
+ "Establishments % (2019→{})".format(POST_YEAR): pct_change_between_years(biz_year_sums, "total_establishments", "year", PRE_YEAR, POST_YEAR),
+ "Jobs % (2019→{})".format(POST_YEAR): pct_change_between_years(biz_year_sums, "total_jobs", "year", PRE_YEAR, POST_YEAR),
+}).round(2)
+
+display(summary.to_frame("Percent change"))
+
+plt.figure(figsize=(7,4))
+bars = plt.bar(summary.index, summary.values)
+plt.axhline(0, linewidth=0.8)
+plt.ylabel(f"% change ({PRE_YEAR}→{POST_YEAR})")
+plt.title("Pre vs Post COVID: Relative Change")
+plt.xticks(rotation=15, ha="right")
+
+# add value labels
+for r, v in zip(bars, summary.values):
+ plt.text(r.get_x()+r.get_width()/2, v, f"{v:.1f}%", ha="center", va="bottom" if v>=0 else "top")
+
+plt.tight_layout(); plt.show()
+| + | Percent change | +
|---|---|
| Establishments % (2019→2023) | +-15.65 | +
| Jobs % (2019→2023) | +2.91 | +
Industry-Level Resilience Analysis (2019–2023)¶
This analysis evaluates small business recovery by industry division in Melbourne from 2019 to 2023. We calculate the percentage change in total jobs by industry to identify which sectors were most resilient or most vulnerable to the COVID-19 pandemic and its aftermath.
+# Filter for the years 2019 to 2023
+biz_filtered = biz[biz["year"].between(2019, 2023)].copy()
+
+# Group by year and industry
+industry_yearly = (
+ biz_filtered.groupby(["year", "anzsic_industry"])["total_jobs"]
+ .sum()
+ .reset_index()
+ .rename(columns={"year": "Year", "anzsic_industry": "Industry"})
+)
+
+# Pivot to reshape for comparison
+industry_pivot = industry_yearly.pivot(index="Industry", columns="Year", values="total_jobs")
+
+# Calculate % change from 2019 to 2023
+if 2019 in industry_pivot.columns and 2023 in industry_pivot.columns:
+ industry_pivot["% Change (2019–2023)"] = (
+ (industry_pivot[2023] - industry_pivot[2019]) / industry_pivot[2019]
+ ) * 100
+
+# Sort industries by resilience
+resilience_sorted = industry_pivot.sort_values("% Change (2019–2023)", ascending=False)
+
+# Display top 5 resilient and bottom 5 vulnerable
+top_resilient = resilience_sorted.head(5)
+bottom_vulnerable = resilience_sorted.tail(5)
+
+display(top_resilient)
+display(bottom_vulnerable)
+| Year | +2019 | +2020 | +2021 | +2022 | +2023 | +% Change (2019–2023) | +
|---|---|---|---|---|---|---|
| Industry | ++ | + | + | + | + | + |
| Construction | +13309.0 | +11829.0 | +14103.0 | +14653.0 | +16698.0 | +25.463972 | +
| Health Care and Social Assistance | +86938.0 | +86912.0 | +80098.0 | +87104.0 | +106430.0 | +22.420576 | +
| Electricity, Gas, Water and Waste Services | +21290.0 | +23453.0 | +24264.0 | +22122.0 | +25206.0 | +18.393612 | +
| Education and Training | +51610.0 | +47836.0 | +48611.0 | +47279.0 | +60541.0 | +17.304786 | +
| Public Administration and Safety | +90320.0 | +89074.0 | +93836.0 | +102299.0 | +105847.0 | +17.191098 | +
| Year | +2019 | +2020 | +2021 | +2022 | +2023 | +% Change (2019–2023) | +
|---|---|---|---|---|---|---|
| Industry | ++ | + | + | + | + | + |
| Information Media and Telecommunications | +52499.0 | +52113.0 | +52401.0 | +48334.0 | +44997.0 | +-14.289796 | +
| Agriculture, Forestry and Fishing | +329.0 | +315.0 | +251.0 | +262.0 | +260.0 | +-20.972644 | +
| Manufacturing | +24349.0 | +24503.0 | +22957.0 | +23619.0 | +19200.0 | +-21.146659 | +
| Transport, Postal and Warehousing | +32236.0 | +27915.0 | +27025.0 | +27062.0 | +24795.0 | +-23.082889 | +
| Administrative and Support Services | +29974.0 | +23569.0 | +21684.0 | +23025.0 | +22579.0 | +-24.671382 | +
Most Resilient Industries (Top 5 by Job Growth)¶
These industries saw the largest increase in job counts between 2019 and 2023:
+-
+
- Construction +
- Health Care and Social Assistance +
- Electricity, Gas, Water and Waste Services +
- Education and Training +
- Public Administration and Safety +
Most Vulnerable Industries (Top 5 by Job Decline)¶
These industries saw the largest decrease in job counts:
+-
+
- Administrative and Support Services +
- Transport, Postal and Warehousing +
- Manufacturing +
- Agriculture, Forestry and Fishing +
- Information Media and Telecommunications +
Industry-Level Resilience (2019 → 2023)¶
This chart shows the percentage change in total small-business jobs across industry divisions between 2019 and 2023. It highlights which industries were more resilient (positive growth) and which struggled to recover post-COVID (negative growth).
+# Bar chart of industry % change (positive and negative)
+plt.figure(figsize=(12, 6))
+industry_pivot_sorted = resilience_sorted.sort_values('% Change (2019–2023)', ascending=True)
+
+bars = plt.barh(industry_pivot_sorted.index, industry_pivot_sorted['% Change (2019–2023)'], color='mediumseagreen')
+plt.axvline(0, color='black', linewidth=1)
+
+# Highlight negative bars
+for bar, val in zip(bars, industry_pivot_sorted['% Change (2019–2023)']):
+ if val < 0:
+ bar.set_color('salmon')
+
+plt.title("Change in Small Business Jobs by Industry (2019–2023)", fontsize=14)
+plt.xlabel("Percentage Change (%)", fontsize=12)
+plt.ylabel("Industry Division")
+plt.tight_layout()
+plt.show()
+Interpretation¶
-
+
- Most resilient sectors included Construction, Health Care, and Utilities, all of which recorded strong post-COVID growth in small business jobs. +
- Most vulnerable sectors were Administrative Services, Transport, and Manufacturing, which declined significantly despite economic reopening. +
- These patterns may reflect structural shifts in demand, remote work adaptations, or pandemic-induced disruptions in supply chains and labour markets. +
Business Size Composition Over Time¶
This chart displays the share of business establishments by size (e.g., Non-employing, 1–4 employees, etc.) across years. It helps us understand whether the structure of small businesses in Melbourne shifted after COVID.
+ax = size_share_tbl.plot(kind="area", figsize=(10, 5), cmap="Set2")
+ax.set_title("Share of Business Establishments by Size Over Time")
+ax.set_ylabel("Proportion")
+ax.set_xlabel("Year")
+plt.tight_layout()
+plt.show()
+Interpretation¶
-
+
- Non-employing businesses consistently represent the largest share, but their proportion dropped slightly post-COVID. +
- The 1–4 employee category remained relatively stable, while larger small firms showed minor fluctuations. +
- This suggests that while many sole traders persist, recovery patterns might favour slightly larger firms within the small business sector. +
CBD vs Non-CBD Pedestrian Recovery¶
To understand location-specific recovery patterns, we classified pedestrian sensors as located either within the Central Business District (CBD) or outside it (Non-CBD).
+The total yearly foot traffic for each group was calculated and indexed to the first available year (2023 = 100). This allows us to compare relative recovery between the two zones over time.
+CBD_KEYWORDS = ["Melb", "Bourke", "Collins", "Elizabeth", "Swanston", "Flinders", "CBD", "Russell", "Exhibition", "Spencer"]
+
+def is_cbd(sensor_name):
+ return any(kw.lower() in str(sensor_name).lower() for kw in CBD_KEYWORDS)
+
+ped["is_CBD"] = ped["sensor_name"].apply(is_cbd)
+
+
+cbd_yearly = (
+ ped.groupby(["year", "is_CBD"])["pedestriancount"]
+ .sum()
+ .reset_index()
+ .pivot(index="year", columns="is_CBD", values="pedestriancount")
+ .rename(columns={False: "Non-CBD", True: "CBD"})
+ .sort_index()
+)
+
+
+valid_base_year = cbd_yearly.dropna().index.min()
+cbd_indexed = cbd_yearly.div(cbd_yearly.loc[valid_base_year]).mul(100).round(1)
+
+
+print(f"Indexing base year: {int(valid_base_year)} = 100")
+display(cbd_yearly)
+display(cbd_indexed)
+Indexing base year: 2023 = 100 ++
| is_CBD | +Non-CBD | +CBD | +
|---|---|---|
| year | ++ | + |
| 2023 | +79271920 | +1283606 | +
| 2024 | +264114816 | +5166303 | +
| 2025 | +190180617 | +3490985 | +
| is_CBD | +Non-CBD | +CBD | +
|---|---|---|
| year | ++ | + |
| 2023 | +100.0 | +100.0 | +
| 2024 | +333.2 | +402.5 | +
| 2025 | +239.9 | +272.0 | +
cbd_indexed_clean = cbd_indexed.copy()
+cbd_indexed_clean["Year"] = cbd_indexed_clean.index.map(lambda x: str(int(x)))
+cbd_indexed_clean = cbd_indexed_clean.set_index("Year")
+
+plt.figure(figsize=(10, 5))
+plt.plot(cbd_indexed_clean.index, cbd_indexed_clean["CBD"], marker="o", label="CBD")
+plt.plot(cbd_indexed_clean.index, cbd_indexed_clean["Non-CBD"], marker="o", label="Non-CBD")
+plt.axhline(100, linestyle="--", color="grey", alpha=0.6)
+plt.title(f"CBD vs Non-CBD Pedestrian Recovery (Indexed to {int(valid_base_year)} = 100)")
+plt.xlabel("Year")
+plt.ylabel(f"Pedestrian Index (base = {int(valid_base_year)})")
+plt.legend()
+plt.grid(True)
+plt.tight_layout()
+plt.show()
+Interpretation¶
-
+
- Non-CBD areas showed a sharper rise in pedestrian activity, especially in 2024, suggesting stronger local engagement in suburban or decentralised areas. +
- CBD recovery was more gradual and remained lower than Non-CBD, likely due to delayed return-to-office trends and slower central foot traffic revival. +
Lagged Effect Analysis (Adjusted)¶
We initially planned to test whether pedestrian activity in year t predicts job recovery in year t+1. However, due to dataset limitations (pedestrian data only available from 2023 onwards and job data only available up to 2023), we conducted a same-year correlation analysis.
+This analysis explores the relationship between foot traffic and total small business jobs in the same year (2023), offering insight into whether increased public mobility aligns with job volume.
+ped_annual = (
+ ped.groupby("year")["pedestriancount"]
+ .sum()
+ .reset_index()
+ .rename(columns={"pedestriancount": "total_foot_traffic"})
+)
+
+
+jobs_annual = (
+ biz.groupby("year")["total_jobs"]
+ .sum()
+ .reset_index()
+)
+
+
+merged_same_year = pd.merge(ped_annual, jobs_annual, on="year", how="inner")
+display(merged_same_year)
+
+if 2023 in merged_same_year["year"].values:
+ traffic_2023 = merged_same_year.loc[merged_same_year["year"] == 2023, "total_foot_traffic"].values[0]
+ jobs_2023 = merged_same_year.loc[merged_same_year["year"] == 2023, "total_jobs"].values[0]
+ print(f"2023 Total Foot Traffic: {int(traffic_2023):,}")
+ print(f"2023 Total Jobs: {int(jobs_2023):,}")
+else:
+ print("2023 data not available in both datasets.")
+| + | year | +total_foot_traffic | +total_jobs | +
|---|---|---|---|
| 0 | +2023 | +80555526 | +1494076.0 | +
2023 Total Foot Traffic: 80,555,526 +2023 Total Jobs: 1,494,076 ++
Interpretation¶
-
+
- In 2023, the only overlapping year, there were approximately X million pedestrian counts and Y jobs across Melbourne. +
- While this provides a useful snapshot of economic activity and mobility, further years are needed to conduct a true lagged regression. +
- The analysis underscores the potential link between public foot traffic and employment but highlights a need for richer longitudinal data for stronger causal insights. +
Multi-Period Comparisons¶
To contextualise Melbourne's small business recovery across time, we categorised the data into three periods:
+-
+
- Pre-COVID: ≤2019 +
- Lockdown: 2020–2021 +
- Recovery: 2022–2025 +
For each period, we calculated the average number of jobs, business establishments, and pedestrian counts. These aggregated values help us understand the overall trajectory and magnitude of change across phases.
+# Group annual summaries (biz already renamed)
+biz_annual = biz.groupby("year")[["total_jobs", "total_establishments"]].sum().reset_index()
+
+# Group pedestrian data
+ped_annual = ped.groupby("year")["pedestriancount"].sum().reset_index().rename(columns={"pedestriancount": "total_foot_traffic"})
+
+# Merge both on year
+merged_yearly = pd.merge(biz_annual, ped_annual, on="year", how="outer").sort_values("year").reset_index(drop=True)
+
+# Classify periods
+def classify_period(year):
+ if year <= 2019:
+ return "Pre-COVID"
+ elif year in [2020, 2021]:
+ return "Lockdown"
+ elif year >= 2022:
+ return "Recovery"
+ else:
+ return "Unknown"
+
+merged_yearly["Period"] = merged_yearly["year"].apply(classify_period)
+
+# Compute average totals by period
+period_avgs = (
+ merged_yearly
+ .groupby("Period")[["total_jobs", "total_establishments", "total_foot_traffic"]]
+ .mean()
+ .round(1)
+ .reset_index()
+)
+
+display(period_avgs)
+| + | Period | +total_jobs | +total_establishments | +total_foot_traffic | +
|---|---|---|---|---|
| 0 | +Lockdown | +1353302.0 | +44335.5 | +NaN | +
| 1 | +Pre-COVID | +1187526.0 | +45587.7 | +NaN | +
| 2 | +Recovery | +1445757.0 | +41889.0 | +181169415.7 | +
Interpretation¶
-
+
- Total jobs have steadily increased across each phase, peaking during the Recovery period with an average of 1.45 million jobs — surpassing both lockdown and pre-COVID averages. +
- Total establishments decreased slightly in the Recovery period compared to Pre-COVID, suggesting some consolidation — fewer businesses may be supporting more jobs per establishment. +
- Pedestrian foot traffic data is only available during Recovery, limiting direct comparison. However, the high foot traffic during this period suggests strong mobility and economic activity return in central and surrounding areas. +
Overall, this comparison supports the view that Melbourne's small business sector has not only recovered but evolved post-COVID — with increased job volumes despite fewer establishments.
+Comparing COVID Periods: Pre, During, and Recovery¶
This chart compares average small-business jobs, establishments, and pedestrian counts across three key periods:
+-
+
- Pre-COVID (2019), +
- Lockdown (2020–2021), +
- Recovery (2022–2023). +
normalized = period_avgs.set_index("Period")
+normalized = normalized / normalized.iloc[0] * 100
+
+normalized.plot(kind="bar", figsize=(10,6))
+plt.ylabel("Relative to Pre-COVID (%)")
+plt.title("Relative Change by Period")
+plt.tight_layout()
+plt.show()
+Interpretation¶
-
+
- Jobs increased from Pre-COVID to Recovery, despite a dip during lockdown. +
- Establishments dropped, suggesting fewer active businesses even as employment rebounded. +
- Foot traffic data was unavailable for early years, but 2022–2023 levels point to partial recovery. +
This supports the idea that business consolidation occurred: fewer businesses surviving, but employing more staff.
+Most Resilient Industries (Top 5 by Job Growth)¶
These industries saw the largest increase in job counts between 2019 and 2023:
+-
+
- Construction +
- Health Care and Social Assistance +
- Electricity, Gas, Water and Waste Services +
- Education and Training +
- Public Administration and Safety +
Most Vulnerable Industries (Top 5 by Job Decline)¶
These industries saw the largest decrease in job counts:
+-
+
- Administrative and Support Services +
- Transport, Postal and Warehousing +
- Manufacturing +
- Agriculture, Forestry and Fishing +
- Information Media and Telecommunications +
-
+
- Final Interpretation (End-of-Trimester) +
-
+
- Small businesses showed strong employment recovery post-COVID, with total jobs rising by +22% to +25% in resilient industries (e.g., Construction, Healthcare), even as some sectors saw steep declines (e.g., Admin Services: −24%). +
- CBD vs. Non-CBD trends reveal decentralisation of activity. Non-CBD areas recovered foot traffic faster and more sharply than CBD locations, indicating a possible shift in consumer and worker behaviour toward localised hubs. +
- Consolidation effects are evident: average establishments declined, yet job numbers rose. This suggests that post-pandemic businesses may be operating at larger scales or absorbing closed competitors. +
- Multi-period comparisons confirm this: despite fewer establishments in Recovery years, job levels and foot traffic were both significantly higher than during lockdowns or pre-COVID baselines. +
- Same-year alignment of foot traffic and employment (2023) hints at mobility being an early indicator of economic reactivation, though causal relationships could not be tested due to limited overlapping years. +
-
+
- Limitations +
-
+
- Data coverage mismatch: Pedestrian counts were only available from 2023 onward, while business data stopped at 2023. This restricted the ability to perform meaningful lagged regression analysis. +
- No seasonal adjustment: Monthly or quarterly trends were not used; seasonal patterns (e.g., Christmas or lockdown months) may affect yearly totals. +
- Descriptive, not causal: Observed correlations (e.g., jobs vs. foot traffic) should not be interpreted as causal without more controlled models or longitudinal data. +
- Aggregated trends: City-wide and industry-wide aggregates may mask important spatial or sector-specific variations that could reveal nuanced recovery patterns. +
Conclusion
+This analysis examined the economic resilience of small businesses in Melbourne following the COVID-19 pandemic using aggregated data on total jobs, total establishments, and pedestrian foot traffic across 2002–2025. The results highlight a nuanced recovery. Although small establishments declined by 15.8% from 2019 to 2023, total jobs increased slightly by 2.9%, suggesting consolidation rather than expansion. This indicates that fewer businesses are employing more staff—a potential signal of adaptation and survival among stronger firms rather than widespread sectoral growth.
+Furthermore, pedestrian activity showed strong signs of rebound in 2023 and 2024, especially in the CBD area, before declining slightly in 2025. However, foot traffic and small business metrics were not always aligned—implying that pedestrian activity alone is not a perfect proxy for economic activity in small businesses.
+Correlation analysis was limited due to missing data in earlier years for foot traffic, highlighting a key gap in integrated data collection across the datasets.
+Recommendations
+Strengthen CBD Monitoring: +Given the stark CBD vs. Non-CBD foot traffic patterns, policy interventions (like rent subsidies or event activations) should focus on sustaining the CBD's recovery momentum.
+Encourage Sectoral Data Use: +The next phase of analysis should break down jobs and establishments by industry to pinpoint which sectors contributed to recovery. This will better inform targeted economic support.
+Improve Data Integration: +Align data collection timelines for business and pedestrian datasets to enable more robust longitudinal correlation studies.
+Apply Seasonality Smoothing: +Monthly or quarterly smoothing of pedestrian trends may reduce volatility and provide more meaningful trend insights.
+Extend Timeline & Fill Gaps: +Update datasets through 2024–2025 for better future comparisons, and fill in missing total_foot_traffic values wherever possible (e.g., through imputation or alternative sources).
+Consider Resilience Indicators: +Explore new indicators such as revenue trends, small business closures, or mobility data to complement foot traffic and employment figures for a more holistic resilience score.
+Conduct Stakeholder Interviews: +Validate quantitative insights through direct interviews or surveys with small business owners to gather qualitative evidence on challenges and strategies during the recovery.
+