From 32d61ebdb252be562bddd075eda3729fe81c3f0f Mon Sep 17 00:00:00 2001 From: John Stachurski Date: Tue, 16 Jun 2026 06:27:17 +1000 Subject: [PATCH 1/3] Update business cycle data to latest available (#364) FRED series had hardcoded end dates of 2022-12-31, capping the unemployment, NBER recession, consumer sentiment/CPI and industrial output series. Switch these to datetime.now() so the lecture always pulls the most recent data, matching the auto-updating World Bank series. Also pin the most recent year as an explicit x-axis tick in plot_series and plot_comparison, so the extended GDP series read as intentional rather than trailing off past the last labelled tick. Update prose that referenced fixed end years (2022) to "the present". Co-Authored-By: Claude Opus 4.8 (1M context) --- lectures/business_cycle.md | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/lectures/business_cycle.md b/lectures/business_cycle.md index 81a2325f..6f18d106 100644 --- a/lectures/business_cycle.md +++ b/lectures/business_cycle.md @@ -175,6 +175,13 @@ def plot_series(data, country, ylabel, ax.axhline(y=baseline, color='black', linestyle='--') + + # Pin the most recent year as an x-axis tick + final_year = data.columns.max() + ax.set_xlim(right=final_year) + xticks = [t for t in ax.get_xticks() if t <= final_year - 3] + ax.set_xticks(xticks + [final_year]) + ax.set_ylabel(ylabel) ax.legend() return ax @@ -316,7 +323,7 @@ economy recessions in the 1970s and 1990s. Another important measure of business cycles is the unemployment rate. -We study unemployment using rate data from FRED spanning from [1929-1942](https://fred.stlouisfed.org/series/M0892AUSM156SNBR) to [1948-2022](https://fred.stlouisfed.org/series/UNRATE), combined unemployment rate data over 1942-1948 estimated by the [Census Bureau](https://www.census.gov/library/publications/1975/compendia/hist_stats_colonial-1970.html). +We study unemployment using rate data from FRED spanning from [1929-1942](https://fred.stlouisfed.org/series/M0892AUSM156SNBR) to [1948 onwards](https://fred.stlouisfed.org/series/UNRATE), combined unemployment rate data over 1942-1948 estimated by the [Census Bureau](https://www.census.gov/library/publications/1975/compendia/hist_stats_colonial-1970.html). ```{code-cell} ipython3 :tags: [hide-input] @@ -330,13 +337,13 @@ unrate_history.rename(columns={'M0892AUSM156SNBR': 'UNRATE'}, inplace=True) start_date = datetime.datetime(1948, 1, 1) -end_date = datetime.datetime(2022, 12, 31) +end_date = datetime.datetime.now() unrate = web.DataReader('UNRATE', 'fred', start_date, end_date) ``` -Let's plot the unemployment rate in the US from 1929 to 2022 with recessions +Let's plot the unemployment rate in the US from 1929 to the present with recessions defined by the NBER. ```{code-cell} ipython3 @@ -359,7 +366,7 @@ unrate_census.set_index('DATE', inplace=True) # Obtain the NBER-defined recession periods start_date = datetime.datetime(1929, 1, 1) -end_date = datetime.datetime(2022, 12, 31) +end_date = datetime.datetime.now() nber = web.DataReader('USREC', 'fred', start_date, end_date) @@ -479,6 +486,13 @@ def plot_comparison(data, countries, 'GFC\n(2008)', **t_params) ax.text(2020, ylim + ylim*txt_pos, 'Covid-19\n(2020)', **t_params) + + # Pin the most recent year as an x-axis tick + final_year = data.columns.max() + ax.set_xlim(right=final_year) + xticks = [t for t in ax.get_xticks() if t <= final_year - 3] + ax.set_xticks(xticks + [final_year]) + if baseline != None: ax.hlines(y=baseline, xmin=ax.get_xlim()[0], xmax=ax.get_xlim()[1], color='black', @@ -616,7 +630,7 @@ of Michigan. Here we plot the University of Michigan Consumer Sentiment Index and year-on-year [core consumer price index](https://fred.stlouisfed.org/series/CPILFESL) -(CPI) change from 1978-2022 in the US. +(CPI) change from 1978 to the present in the US. ```{code-cell} ipython3 --- @@ -628,11 +642,11 @@ tags: [hide-input] --- start_date = datetime.datetime(1978, 1, 1) -end_date = datetime.datetime(2022, 12, 31) +end_date = datetime.datetime.now() # Limit the plot to a specific range start_date_graph = datetime.datetime(1977, 1, 1) -end_date_graph = datetime.datetime(2023, 12, 31) +end_date_graph = end_date + datetime.timedelta(days=365) nber = web.DataReader('USREC', 'fred', start_date, end_date) consumer_confidence = web.DataReader('UMCSENT', 'fred', @@ -698,7 +712,7 @@ However, it is not a leading indicator, as the peak of contraction in production is delayed relative to consumer confidence and inflation. We plot the real industrial output change from the previous year -from 1919 to 2022 in the US to show this trend. +from 1919 to the present in the US to show this trend. ```{code-cell} ipython3 --- @@ -710,7 +724,7 @@ tags: [hide-input] --- start_date = datetime.datetime(1919, 1, 1) -end_date = datetime.datetime(2022, 12, 31) +end_date = datetime.datetime.now() nber = web.DataReader('USREC', 'fred', start_date, end_date) @@ -746,7 +760,7 @@ activity and gloomy expectations for the future. One example is domestic credit to the private sector by banks in the UK. The following graph shows the domestic credit to the private sector as a -percentage of GDP by banks from 1970 to 2022 in the UK. +percentage of GDP by banks from 1970 to the present in the UK. ```{code-cell} ipython3 --- From a7c4913b1ad38a9fb0d257021f742f5230f69f15 Mon Sep 17 00:00:00 2001 From: John Stachurski Date: Tue, 16 Jun 2026 06:44:29 +1000 Subject: [PATCH 2/3] Centralize data cutoff and make narrative evergreen Address narrative-drift risk from dynamic data dates: - Define the data cutoff once (end_date) with a documented one-line switch to pin it for reproducible builds, instead of repeating datetime.now() across four FRED cells. - Rename the 1942 historical cutoff to hist_end_date so it no longer clobbers the shared end_date in the unemployment cell. - Reword the post-pandemic labour-market sentence to be anchored to the 2020 shock rather than to the moving right edge of the chart. Co-Authored-By: Claude Opus 4.8 (1M context) --- lectures/business_cycle.md | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/lectures/business_cycle.md b/lectures/business_cycle.md index 6f18d106..58daaf44 100644 --- a/lectures/business_cycle.md +++ b/lectures/business_cycle.md @@ -53,6 +53,15 @@ cycler = plt.cycler(linestyle=['-', '-.', '--', ':'], plt.rc('axes', prop_cycle=cycler) ``` +We pull all time series through to the most recent available data. + +```{code-cell} ipython3 +# The cutoff date for the data we fetch below. +# To freeze the data for a reproducible build, replace this with a +# fixed date such as datetime.datetime(2024, 12, 31). +end_date = datetime.datetime.now() +``` + ## Data acquisition @@ -329,15 +338,14 @@ We study unemployment using rate data from FRED spanning from [1929-1942](https: :tags: [hide-input] start_date = datetime.datetime(1929, 1, 1) -end_date = datetime.datetime(1942, 6, 1) +hist_end_date = datetime.datetime(1942, 6, 1) unrate_history = web.DataReader('M0892AUSM156SNBR', - 'fred', start_date,end_date) + 'fred', start_date, hist_end_date) unrate_history.rename(columns={'M0892AUSM156SNBR': 'UNRATE'}, inplace=True) start_date = datetime.datetime(1948, 1, 1) -end_date = datetime.datetime.now() unrate = web.DataReader('UNRATE', 'fred', start_date, end_date) @@ -366,7 +374,6 @@ unrate_census.set_index('DATE', inplace=True) # Obtain the NBER-defined recession periods start_date = datetime.datetime(1929, 1, 1) -end_date = datetime.datetime.now() nber = web.DataReader('USREC', 'fred', start_date, end_date) @@ -405,10 +412,10 @@ The plot shows that * cycles are, in general, asymmetric: sharp rises in unemployment are followed by slow recoveries. -It also shows us how unique labor market conditions were in the US during the -post-pandemic recovery. +It also shows how unusual the US labor market was in the recovery from the 2020 +pandemic shock. -The labor market recovered at an unprecedented rate after the shock in 2020-2021. +Unemployment spiked sharply in 2020 and then fell back at an unprecedented rate. (synchronization)= @@ -642,7 +649,6 @@ tags: [hide-input] --- start_date = datetime.datetime(1978, 1, 1) -end_date = datetime.datetime.now() # Limit the plot to a specific range start_date_graph = datetime.datetime(1977, 1, 1) @@ -724,7 +730,6 @@ tags: [hide-input] --- start_date = datetime.datetime(1919, 1, 1) -end_date = datetime.datetime.now() nber = web.DataReader('USREC', 'fred', start_date, end_date) From aafdd93a981ef4f83f3c1190f9d8ddd050b1f045 Mon Sep 17 00:00:00 2001 From: John Stachurski Date: Tue, 16 Jun 2026 07:06:43 +1000 Subject: [PATCH 3/3] Drop the pinned final-year x-axis tick The added final-year tick crowded the existing 2020 ("Covid-19") tick and looked worse than the gap it was meant to address. Revert to matplotlib's default x-axis ticks in plot_series and plot_comparison. Co-Authored-By: Claude Opus 4.8 (1M context) --- lectures/business_cycle.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lectures/business_cycle.md b/lectures/business_cycle.md index 58daaf44..c1fa6ce0 100644 --- a/lectures/business_cycle.md +++ b/lectures/business_cycle.md @@ -184,13 +184,6 @@ def plot_series(data, country, ylabel, ax.axhline(y=baseline, color='black', linestyle='--') - - # Pin the most recent year as an x-axis tick - final_year = data.columns.max() - ax.set_xlim(right=final_year) - xticks = [t for t in ax.get_xticks() if t <= final_year - 3] - ax.set_xticks(xticks + [final_year]) - ax.set_ylabel(ylabel) ax.legend() return ax @@ -493,13 +486,6 @@ def plot_comparison(data, countries, 'GFC\n(2008)', **t_params) ax.text(2020, ylim + ylim*txt_pos, 'Covid-19\n(2020)', **t_params) - - # Pin the most recent year as an x-axis tick - final_year = data.columns.max() - ax.set_xlim(right=final_year) - xticks = [t for t in ax.get_xticks() if t <= final_year - 3] - ax.set_xticks(xticks + [final_year]) - if baseline != None: ax.hlines(y=baseline, xmin=ax.get_xlim()[0], xmax=ax.get_xlim()[1], color='black',