National Debt

Python
Federal Debt
Three charts on the state of the national debt.
Published

September 20, 2024

The Wall Street Journal recently ran an excellent article on the U.S. federal debt, you can check it here though it is behind their paywall.

In this blog post we show how to replicate the main charts using Python and freely available data from FRED. But beware, seeing these charts will leave you somewhat depressed about the current state of our public finances.

First step, import libraries.

# for data wrangling
import pandas as pd
# for pulling FRED data
import pandas_datareader as pdr
# for visualization
import altair as alt

Figure 1: Public debt as share of GDP

The first chart plots the public debt as a percentage of GDP. This series comes straight from FRED, it has the not-really-meaningful mnemonic FYPUGDA188S.

gross_debt = (
    # pull data from FRED
    pdr.get_data_fred('FYPUGDA188S',
        start = '1939-01-01')
    # extract index as column
    .reset_index()
    # debt in decimals
    .assign(debt = lambda df: df['FYPUGDA188S']/100)
)

Let’s create some labels that we will use in the graph to mark three important points in time: World War II, the Financial Crisis, and the Covid-19 shock.

label_df = ( gross_debt
    .query("DATE.isin(@pd.to_datetime(['1945-01-01','2009-01-01','2020-01-01']))")
    .assign(event = ['WWII', 'Financial Crisis', 'Covid'])
)

Let’s put everything in a chart using altair. We create three layers, one for the bar graph, another for the labels, and a last one for the source attribution.

# bar chart
plot = (
alt.Chart(gross_debt,
    title='Publicly held debt as a share of GDP')
    .mark_bar(size=10, 
            color='tomato',
            opacity=0.9)
    .encode(
        alt.X('DATE:T').title(None),
        alt.Y('debt:Q').title(None)
            .axis(format='%')
    )
)

# labels
labels = (
    alt.Chart(label_df)
    .mark_text(dy=-20, fontSize=14)
    .encode(
        alt.X('DATE:T'),
        alt.Y('debt:Q'),
        alt.Text('event:N')
    )
)

# source attribution
footer = (
    alt.Chart()
    .mark_text(text="Source: FRED", 
        # coordinates for footer
        y='height', x=0, 
        # send it even lower
        dy=40, 
        align='left',
        color='gray')
)

# all together
alt.layer(plot, labels, footer)

The chart makes the challenge clear, we have public debt levels not seen since the Second World War.

Figure 2: Annual budget as share of GDP

The previous chart represents the stock of debt, this next one is the flow of debt. This series comes from FRED with the mnemonic FYFSGDA188S.

fed_budget = (
    # pull data from FRED
    pdr.get_data_fred('FYFSGDA188S',
        start = '1929-01-01')
    # extract index as column
    .reset_index()
    .assign(budget = lambda df: df['FYFSGDA188S']/100,
            # indicator for deficits
            deficit = lambda df: df['budget'] < 0)
)

We now make the chart. We create two labels, one for surpluses and another one for deficits. The actual placement of these labels depends on the figure size (in pixels), we are using a 740 by 555 aspect, so that explains our choices for x and y in the labels.

# main layer
budget_plot = (
    alt.Chart(fed_budget,
        title = 'Surplus/deficit as a share of GDP')
    .mark_bar(size=10)
    .encode(
        alt.X('DATE:T').title(None),
        alt.Y('budget:Q').title(None)
            .axis(format='%', 
                values=[-0.3,-0.2,-0.1,0,0.1]),
        alt.Color('deficit:N').legend(None)
            .scale(domain=[True, False],
                range=['red','black'])
        )
    )

# create label for surpluses
label_surplus = (
    alt.Chart()
    .mark_text(text='Surpluses',
        fontSize=16,
        x = 740/2,
        y = 80 )
)

# create label for deficits
label_deficit = (
    alt.Chart()
    .mark_text(text='Deficits',
        color='red',
        fontSize=16,
        x = 740/2,
        y = 300 )
)

# all together
alt.layer(budget_plot, label_surplus, label_deficit, footer)

For most years since the Great Depression, the federal government has run a deficit (i.e., spend more than it collects in revenue) which explains the overall growth of debt. And deficits got larger since the financial crisis.

Figure 3: Total spending on Defense and Interest Payments

Our last chart compares the time series of nominal dollars spent on National Defense versus the spending on servicing the debt itself, namely the interest payments. Both items come from FRED, the mnemonic for defense expenditures is FDEFX and the one for interest payments is A091RC1Q027SBEA.

Note that we make our dataframe long (also sometimes called tidy or stacked), this type of long data is more convenient for performing operations and creating charts.

def_exp = (
    # pull data from FRED
    pdr.get_data_fred(['FDEFX', 'A091RC1Q027SBEA'],
        start='1980-01-01')
    # go from billions to trillions
    .assign(Defense = lambda df: df['FDEFX']/1000,
            Interest = lambda df: df['A091RC1Q027SBEA']/1000)
    # pull out index as column
    .reset_index()
    # make data long
    .melt(id_vars='DATE',
        value_vars=['Defense','Interest'])
)

Now we make a the chart. Notice that we create our own label for the y-axis so we can make clear the data is in trillions of dollars. In addition, we name a color encoding which is what actually separates the series and assigns them different colors.

# main layer
def_plot = (
alt.Chart(def_exp,
    title='Government spending on interest versus defense')
    .mark_line()
    .encode(
        alt.X('DATE:T').title(None),
        alt.Y('value:Q').title(None)
            .axis(labelExpr='"$" + datum.value + "T"'),
        alt.Color('variable:N').legend(None)
            .scale(domain=['Defense','Interest'],
                range=['olive','darkred'])
    )
)

# labels for series
labels = (
    alt.Chart(def_exp.query('DATE=="2020-01-01"'))
    .mark_text(dy=-30, dx=-10,
        fontSize=16)
    .encode(
        alt.X('DATE:T'),
        alt.Y('value:Q'),
        alt.Color('variable:N'),
        alt.Text('variable:N')
    )
)

# all together
alt.layer(def_plot, labels, footer)

Did we mention that these charts are a bit depressing? We are now paying more to service the debt than in national defense.