Ctrl K

FRED - Federal Reserve Economic Data: API Introduction

FRED (Federal Reserve Economic Data) is maintained by the Federal Reserve Bank of St. Louis. It provides over 800,000 U.S. and international economic time series covering rates, inflation, employment, GDP, trade, and commodities - all accessible via a free, documented REST API.

This notebook covers the FRED API structure, demonstrates the most commonly used series for macroeconomic research, and shows commodity price data available through the same interface.

API access: Register for a free key at fred.stlouisfed.org/docs/api/api_key.html.
This notebook reads FRED_API_KEY from a .env file in the working directory.

Resource Link
FRED website fred.stlouisfed.org
API documentation fred.stlouisfed.org/docs/api/fred
Series browser fred.stlouisfed.org/categories
Python client pip install fredapi
In [5]:
import os
import requests
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import matplotlib.dates as mdates
import warnings
warnings.filterwarnings('ignore')

from dotenv import load_dotenv
from fredapi import Fred # pip install fredapi
from IPython.display import display, HTML

load_dotenv()
FRED_API_KEY = os.getenv('FRED_API_KEY', '').strip()
if not FRED_API_KEY:
    raise EnvironmentError('FRED_API_KEY not found in .env - see fred.stlouisfed.org/docs/api/api_key.html')

fred = Fred(api_key=FRED_API_KEY)
BASE_URL = 'https://api.stlouisfed.org/fred'
OBS_START = '2015-01-01'

FL_BLUE   = '#2563eb'
FL_SLATE  = '#64748b'
FL_AMBER  = '#f59e0b'
FL_GREEN  = '#16a34a'
FL_RED    = '#ef4444'
FL_BG     = '#ffffff'
FL_GRID   = '#e2e8f0'
FL_TEXT   = '#0f172a'
FL_TEXT2  = '#334155'
FL_BORDER = '#e2e8f0'

matplotlib.rcParams.update({
    'figure.facecolor': FL_BG,   'axes.facecolor':  FL_BG,
    'axes.edgecolor':   FL_BORDER,'axes.labelcolor': FL_TEXT2,
    'axes.spines.top':  False,    'axes.spines.right': False,
    'axes.grid':        True,     'grid.color':       FL_GRID,
    'grid.linewidth':   0.7,      'xtick.color':      FL_TEXT2,
    'ytick.color':      FL_TEXT2, 'xtick.labelsize':  10,
    'ytick.labelsize':  10,       'axes.labelsize':   11,
    'axes.titlesize':   12,       'axes.titlecolor':  FL_TEXT,
    'axes.titlepad':    12,       'legend.frameon':   False,
    'legend.fontsize':  10,       'figure.dpi':       300,
    'savefig.bbox':     'tight',  'font.family':      'sans-serif',
    'font.sans-serif':  ['Inter', 'Helvetica Neue', 'Arial', 'DejaVu Sans'],
})

def fetch(series_id, start=OBS_START):
    info   = fred.get_series_info(series_id)
    values = fred.get_series(series_id, observation_start=start)
    df = values.dropna().reset_index()
    df.columns = ['date', 'value']
    df['date'] = pd.to_datetime(df['date'])
    return df, info

def series_meta_table(info):
    rows = pd.DataFrame([{
        'Series ID':          info.name,
        'Title':              info.title,
        'Frequency':          info.frequency,
        'Units':              info.units,
        'Seasonal adj.':      info.seasonal_adjustment,
        'Observation start':  str(info.observation_start)[:10],
        'Observation end':    str(info.observation_end)[:10],
        'Last updated':       str(info.last_updated)[:10],
    }]).T.rename(columns={0: 'Value'})
    rows.index.name = 'Field'
    return rows.reset_index()

#print(f'FRED API key loaded ({len(FRED_API_KEY)} chars)')

_test = fred.get_series('FEDFUNDS', observation_start='2024-01-01')
print(f'Connection OK  FEDFUNDS latest: {_test.dropna().iloc[-1]:.2f}%')
Connection OK  FEDFUNDS latest: 3.64%

FRED API structure

FRED organises all series into a category tree. The eight root categories contain thousands of sub-categories, each holding individual series. Series are identified by a short string ID (e.g. FEDFUNDS, CPIAUCSL, DGS10).

The three core API endpoints used in this notebook:

  • fred/series/observations: fetch the actual data values for a series ID
  • fred/series: fetch metadata (title, frequency, units, date range)
  • fred/category/children: browse the category hierarchy

The fredapi Python client wraps these into fred.get_series() and fred.get_series_info().

In [6]:
ROOT_CATEGORIES = {
    'Money, Banking, & Finance': 32991,
    'Population, Employment, & Labor Markets': 10,
    'National Accounts': 32992,
    'Production & Business Activity': 1,
    'Prices': 32455,
    'International Data': 32263,
    'U.S. Regional Data': 3008,
    'Academic Data': 33060,
}

r = requests.get(
    f'{BASE_URL}/category/children',
    params={'category_id': 32455, 'api_key': FRED_API_KEY, 'file_type': 'json'},
    timeout=15
)
price_subcats = r.json().get('categories', [])

cats_df = pd.DataFrame([
    {'Category': name, 'Root ID': cid}
    for name, cid in ROOT_CATEGORIES.items()
])

price_subcats_df = pd.DataFrame([
    {
        'Category ID': cat.get('id'),
        'Category name': cat.get('name')
    }
    for cat in price_subcats
])

print('FRED root categories')
display(cats_df)

print('Sub-categories of Prices (ID 32455)')
display(price_subcats_df)
FRED root categories
Category Root ID
0 Money, Banking, & Finance 32991
1 Population, Employment, & Labor Markets 10
2 National Accounts 32992
3 Production & Business Activity 1
4 Prices 32455
5 International Data 32263
6 U.S. Regional Data 3008
7 Academic Data 33060
Sub-categories of Prices (ID 32455)
Category ID Category name
0 32217 Commodities
1 9 Consumer Price Indexes (CPI and PCE)
2 33913 Cryptocurrencies
3 4 Employment Cost Index
4 33717 Health Care Indexes
5 32261 House Price Indexes
6 31 Producer Price Indexes (PPI)
7 32220 Trade Indexes

Interest rates

The Federal Funds Rate and the 10-Year Treasury yield are the two most-watched interest rate series in macro research. The Fed Funds Rate is the overnight policy rate set by the FOMC. The 10-Year Treasury yield reflects long-term growth and inflation expectations.

Series ID Description Frequency
FEDFUNDS Federal Funds Effective Rate Monthly
DGS10 10-Year Treasury Constant Maturity Daily
In [7]:
fed_df, fed_info = fetch('FEDFUNDS')
t10_df, t10_info = fetch('DGS10')

plt.figure(figsize=(8, 4.5))
plt.fill_between(fed_df['date'], fed_df['value'], alpha=0.12, color=FL_BLUE)
plt.plot(fed_df['date'], fed_df['value'], color=FL_BLUE, linewidth=1.6)
plt.ylabel('Rate (%)')
plt.title(f'Federal Funds Effective Rate (FEDFUNDS) from {OBS_START[:4]} to present')
plt.gca().yaxis.set_major_formatter(
    mticker.FuncFormatter(lambda y, _: f'{y:.2f}%')
)
plt.tick_params(length=0)
plt.tight_layout()
plt.show()

plt.figure(figsize=(8, 4.5))
plt.fill_between(t10_df['date'], t10_df['value'], alpha=0.12, color=FL_SLATE)
plt.plot(t10_df['date'], t10_df['value'], color=FL_SLATE, linewidth=1.2)
plt.ylabel('Yield (%)')
plt.title('10-Year Treasury Constant Maturity (DGS10), daily')
plt.gca().yaxis.set_major_formatter(
    mticker.FuncFormatter(lambda y, _: f'{y:.2f}%')
)
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
plt.tick_params(length=0)
plt.tight_layout()
plt.show()

fed_meta_df = series_meta_table(fed_info)
display(fed_meta_df)
No description has been provided for this image
No description has been provided for this image
Field Value
0 Series ID None
1 Title Federal Funds Effective Rate
2 Frequency Monthly
3 Units Percent
4 Seasonal adj. Not Seasonally Adjusted
5 Observation start 1954-07-01
6 Observation end 2026-04-01
7 Last updated 2026-05-01

Inflation and prices

CPI (Consumer Price Index) measures the average change in prices paid by urban consumers. The yearly percentage change is the standard inflation measure quoted by the Fed.

Series ID Description Frequency
CPIAUCSL CPI - All Urban Consumers, All Items Monthly
FPCPITOTLZGUSA Inflation, consumer prices (% annual) Annual
In [8]:
cpi_df, cpi_info = fetch('CPIAUCSL')
inf_df, inf_info = fetch('FPCPITOTLZGUSA', start='2000-01-01')

cpi_df = cpi_df.set_index('date').sort_index()
cpi_df['yoy'] = cpi_df['value'].pct_change(12) * 100
cpi_df = cpi_df.dropna().reset_index()

pos = cpi_df['yoy'] >= 0

plt.figure(figsize=(8, 4.5))
plt.bar(
    cpi_df.loc[pos, 'date'],
    cpi_df.loc[pos, 'yoy'],
    color=FL_RED,
    alpha=0.75,
    width=20,
    label='Positive YoY'
)
plt.bar(
    cpi_df.loc[~pos, 'date'],
    cpi_df.loc[~pos, 'yoy'],
    color=FL_BLUE,
    alpha=0.75,
    width=20,
    label='Negative YoY'
)
plt.axhline(0, color=FL_BORDER, linewidth=0.8)
plt.axhline(2, color=FL_AMBER, linewidth=1.2, linestyle='--', label='2% target')
plt.ylabel('YoY change (%)')
plt.title('CPI year over year inflation (CPIAUCSL), monthly')
plt.gca().yaxis.set_major_formatter(
    mticker.FuncFormatter(lambda y, _: f'{y:.1f}%')
)
plt.legend(fontsize=9)
plt.tick_params(length=0)
plt.tight_layout()
plt.show()

plt.figure(figsize=(8, 4.5))
plt.plot(
    inf_df['date'],
    inf_df['value'],
    color=FL_AMBER,
    linewidth=1.8,
    marker='o',
    markersize=4
)
plt.axhline(2, color=FL_BLUE, linewidth=1.2, linestyle='--', label='2% target')
plt.ylabel('Inflation (%)')
plt.title('Annual consumer price inflation (FPCPITOTLZGUSA)')
plt.gca().yaxis.set_major_formatter(
    mticker.FuncFormatter(lambda y, _: f'{y:.1f}%')
)
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
plt.legend(fontsize=9)
plt.tick_params(length=0)
plt.tight_layout()
plt.show()
No description has been provided for this image
No description has been provided for this image

Labour market

Unemployment and nonfarm payrolls are the two primary labour market indicators reported monthly. Together they determine the direction of Fed policy alongside inflation.

Series ID Description Frequency
UNRATE Unemployment Rate (seasonally adjusted) Monthly
PAYEMS All Employees, Total Nonfarm (thousands) Monthly
In [9]:
unrate_df, unrate_info = fetch('UNRATE')
payems_df, payems_info = fetch('PAYEMS')

payems_df = payems_df.set_index('date').sort_index()
payems_df['mom'] = payems_df['value'].diff()
payems_df = payems_df.dropna().reset_index()

plt.figure(figsize=(8, 4.5))
plt.fill_between(unrate_df['date'], unrate_df['value'], alpha=0.12, color=FL_RED)
plt.plot(unrate_df['date'], unrate_df['value'], color=FL_RED, linewidth=1.6)
plt.ylabel('Unemployment rate (%)')
plt.title('U.S. unemployment rate (UNRATE), seasonally adjusted')
plt.gca().yaxis.set_major_formatter(
    mticker.FuncFormatter(lambda y, _: f'{y:.1f}%')
)
plt.tick_params(length=0)
plt.tight_layout()
plt.show()

pos_p = payems_df['mom'] >= 0

plt.figure(figsize=(8, 4.5))
plt.bar(
    payems_df.loc[pos_p, 'date'],
    payems_df.loc[pos_p, 'mom'],
    color=FL_GREEN,
    alpha=0.8,
    width=20,
    label='Jobs added'
)
plt.bar(
    payems_df.loc[~pos_p, 'date'],
    payems_df.loc[~pos_p, 'mom'],
    color=FL_RED,
    alpha=0.8,
    width=20,
    label='Jobs lost'
)
plt.axhline(0, color=FL_BORDER, linewidth=0.8)
plt.ylabel('Monthly change (thousands)')
plt.title('Nonfarm payrolls month over month change (PAYEMS)')
plt.gca().yaxis.set_major_formatter(
    mticker.FuncFormatter(lambda y, _: f'{y/1000:.0f}M' if abs(y) >= 1000 else f'{y:.0f}k')
)
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
plt.legend(fontsize=9)
plt.tick_params(length=0)
plt.tight_layout()
plt.show()

recent = unrate_df.tail(8).copy()
recent['date'] = recent['date'].dt.strftime('%Y-%m-%d')
recent.columns = ['Date', 'Unemployment rate (%)']
recent['Unemployment rate (%)'] = recent['Unemployment rate (%)'].map(lambda v: f'{v:.1f}%')

print('UNRATE recent observations')
display(recent)
No description has been provided for this image
No description has been provided for this image
UNRATE recent observations
Date Unemployment rate (%)
127 2025-08-01 4.3%
128 2025-09-01 4.4%
129 2025-11-01 4.5%
130 2025-12-01 4.4%
131 2026-01-01 4.3%
132 2026-02-01 4.4%
133 2026-03-01 4.3%
134 2026-04-01 4.3%

GDP and activity indicators

Real GDP and retail sales measure economic output and consumer demand. These series are fetched and the response structure is shown - the same pattern applies to any series ID in FRED.

Series ID Description Frequency
GDPC1 Real Gross Domestic Product (billions, chained 2017$) Quarterly
A939RX0Q048SBEA Real GDP per Capita Quarterly
RSXFSN Advance Retail Sales: Retail Trade (millions $) Monthly
UMDMNO Manufacturers' New Orders: Durable Goods (millions $) Monthly
In [10]:
gdp_df, gdp_info = fetch('GDPC1')
gdppc_df, gdppc_info = fetch('A939RX0Q048SBEA')
retail_df, retail_info = fetch('RSXFSN')
durable_df, durable_info = fetch('UMDMNO')

plt.figure(figsize=(8, 4.5))
plt.fill_between(gdp_df['date'], gdp_df['value'], alpha=0.10, color=FL_BLUE)
plt.plot(gdp_df['date'], gdp_df['value'], color=FL_BLUE, linewidth=1.4)
plt.title(gdp_info.title[:55] + ('...' if len(gdp_info.title) > 55 else ''))
plt.ylabel(gdp_info.units[:30])
plt.gca().yaxis.set_major_formatter(
    mticker.FuncFormatter(lambda y, _: f'${y/1000:.0f}T')
)
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
plt.tick_params(length=0)
plt.tight_layout()
plt.show()

plt.figure(figsize=(8, 4.5))
plt.fill_between(gdppc_df['date'], gdppc_df['value'], alpha=0.10, color=FL_SLATE)
plt.plot(gdppc_df['date'], gdppc_df['value'], color=FL_SLATE, linewidth=1.4)
plt.title(gdppc_info.title[:55] + ('...' if len(gdppc_info.title) > 55 else ''))
plt.ylabel(gdppc_info.units[:30])
plt.gca().yaxis.set_major_formatter(
    mticker.FuncFormatter(lambda y, _: f'${y:,.0f}')
)
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
plt.tick_params(length=0)
plt.tight_layout()
plt.show()

plt.figure(figsize=(8, 4.5))
plt.fill_between(retail_df['date'], retail_df['value'], alpha=0.10, color=FL_GREEN)
plt.plot(retail_df['date'], retail_df['value'], color=FL_GREEN, linewidth=1.4)
plt.title(retail_info.title[:55] + ('...' if len(retail_info.title) > 55 else ''))
plt.ylabel(retail_info.units[:30])
plt.gca().yaxis.set_major_formatter(
    mticker.FuncFormatter(lambda y, _: f'${y/1000:.0f}B')
)
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
plt.tick_params(length=0)
plt.tight_layout()
plt.show()

plt.figure(figsize=(8, 4.5))
plt.fill_between(durable_df['date'], durable_df['value'], alpha=0.10, color=FL_AMBER)
plt.plot(durable_df['date'], durable_df['value'], color=FL_AMBER, linewidth=1.4)
plt.title(durable_info.title[:55] + ('...' if len(durable_info.title) > 55 else ''))
plt.ylabel(durable_info.units[:30])
plt.gca().yaxis.set_major_formatter(
    mticker.FuncFormatter(lambda y, _: f'${y/1000:.0f}B')
)
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
plt.tick_params(length=0)
plt.tight_layout()
plt.show()

gdp_meta_df = pd.DataFrame([
    {'Field': 'id', 'Value': gdp_info.name},
    {'Field': 'title', 'Value': gdp_info.title},
    {'Field': 'frequency', 'Value': gdp_info.frequency},
    {'Field': 'units', 'Value': gdp_info.units},
    {'Field': 'seasonal_adjustment', 'Value': gdp_info.seasonal_adjustment},
    {'Field': 'observation_start', 'Value': str(gdp_info.observation_start)[:10]},
    {'Field': 'observation_end', 'Value': str(gdp_info.observation_end)[:10]},
    {'Field': 'last_updated', 'Value': str(gdp_info.last_updated)[:10]},
])

gdp_last4_df = gdp_df.tail(4).copy()
gdp_last4_df['date'] = gdp_last4_df['date'].dt.strftime('%Y-%m-%d')

print('GDPC1 metadata')
display(gdp_meta_df)

print('GDPC1 last 4 observations')
display(gdp_last4_df)
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
GDPC1 metadata
Field Value
0 id NaN
1 title Real Gross Domestic Product
2 frequency Quarterly
3 units Billions of Chained 2017 Dollars
4 seasonal_adjustment Seasonally Adjusted Annual Rate
5 observation_start 1947-01-01
6 observation_end 2026-01-01
7 last_updated 2026-04-30
GDPC1 last 4 observations
date value
41 2025-04-01 23770.976
42 2025-07-01 24026.834
43 2025-10-01 24055.749
44 2026-01-01 24174.527

Energy commodities

WTI and Brent are the two global oil benchmark prices. Both are available as daily series on FRED.

Series ID Description Frequency
DCOILWTICO Crude Oil Prices: WTI - Cushing, Oklahoma ($/barrel) Daily
DCOILBRENTEU Crude Oil Prices: Brent - Europe ($/barrel) Daily
DHHNGSP Henry Hub Natural Gas Spot Price ($/MMBtu) Daily
In [11]:
wti_df, wti_info = fetch('DCOILWTICO')
brent_df, brent_info = fetch('DCOILBRENTEU')
ng_df, ng_info = fetch('DHHNGSP')

plt.figure(figsize=(8, 4.5))
plt.plot(
    wti_df['date'],
    wti_df['value'],
    color=FL_BLUE,
    linewidth=1.2,
    label='WTI (DCOILWTICO)'
)
plt.plot(
    brent_df['date'],
    brent_df['value'],
    color=FL_AMBER,
    linewidth=1.2,
    label='Brent (DCOILBRENTEU)'
)
plt.fill_between(
    wti_df['date'],
    wti_df.set_index('date').reindex(wti_df['date'])['value'],
    brent_df.set_index('date').reindex(wti_df['date'])['value'],
    alpha=0.08,
    color=FL_SLATE,
    label='WTI vs Brent spread'
)
plt.ylabel('Price ($/barrel)')
plt.title('Crude oil prices: WTI vs Brent')
plt.gca().yaxis.set_major_formatter(
    mticker.FuncFormatter(lambda y, _: f'${y:.0f}')
)
plt.legend(fontsize=9)
plt.tick_params(length=0)
plt.tight_layout()
plt.show()

plt.figure(figsize=(8, 4.5))
plt.fill_between(ng_df['date'], ng_df['value'], alpha=0.15, color=FL_GREEN)
plt.plot(ng_df['date'], ng_df['value'], color=FL_GREEN, linewidth=1.2)
plt.ylabel('Price ($/MMBtu)')
plt.title('Henry Hub natural gas spot price (DHHNGSP)')
plt.gca().yaxis.set_major_formatter(
    mticker.FuncFormatter(lambda y, _: f'${y:.2f}')
)
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
plt.tick_params(length=0)
plt.tight_layout()
plt.show()

wti_recent = wti_df.tail(6).copy()
wti_recent['date'] = wti_recent['date'].dt.strftime('%Y-%m-%d')
wti_recent.columns = ['Date', 'WTI ($/bbl)']
wti_recent['WTI ($/bbl)'] = wti_recent['WTI ($/bbl)'].map(lambda v: f'${v:.2f}')

print('Recent WTI observations')
display(wti_recent)
No description has been provided for this image
No description has been provided for this image
Recent WTI observations
Date WTI ($/bbl)
2841 2026-05-11 $101.56
2842 2026-05-12 $105.78
2843 2026-05-13 $104.52
2844 2026-05-14 $104.66
2845 2026-05-15 $108.99
2846 2026-05-18 $112.25

Metals and agricultural commodities

FRED carries monthly global commodity price indices from the IMF Primary Commodity Prices database. Series IDs follow the pattern P<COMMODITY>USDM.

Series ID Description Frequency
PCOPPUSDM Global price of Copper ($/mt) Monthly
PALUMUSDM Global price of Aluminum ($/mt) Monthly
PWHEAMTUSDM Global price of Wheat ($/mt) Monthly
PMAIZMTUSDM Global price of Corn ($/mt) Monthly
PCOTTINDUSDM Global price of Cotton (cents/lb) Monthly
PSUGAISAUSDM Global price of Sugar No. 11 (cents/lb) Monthly
PCOFFOTMUSDM Global price of Coffee, Arabica (cents/lb) Monthly
PALLFNFINDEXQ Global Price Index of All Commodities Quarterly
In [12]:
copper_df, copper_info = fetch('PCOPPUSDM', start='2010-01-01')
alum_df, alum_info = fetch('PALUMUSDM', start='2010-01-01')
wheat_df, wheat_info = fetch('PWHEAMTUSDM', start='2010-01-01')
corn_df, corn_info = fetch('PMAIZMTUSDM', start='2010-01-01')
cotton_df, cotton_info = fetch('PCOTTINDUSDM', start='2010-01-01')
sugar_df, sugar_info = fetch('PSUGAISAUSDM', start='2010-01-01')
coffee_df, coffee_info = fetch('PCOFFOTMUSDM', start='2010-01-01')
allcom_df, allcom_info = fetch('PALLFNFINDEXQ', start='2010-01-01')

plt.figure(figsize=(8, 4.5))
plt.plot(
    copper_df['date'],
    copper_df['value'],
    color=FL_AMBER,
    linewidth=1.4,
    label='Copper (PCOPPUSDM)'
)
plt.plot(
    alum_df['date'],
    alum_df['value'],
    color=FL_SLATE,
    linewidth=1.4,
    label='Aluminum (PALUMUSDM)'
)
plt.ylabel('Price ($/mt)')
plt.title('Global metal prices: copper vs aluminum')
plt.gca().yaxis.set_major_formatter(
    mticker.FuncFormatter(lambda y, _: f'${y:,.0f}')
)
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
plt.legend(fontsize=9)
plt.tick_params(length=0)
plt.tight_layout()
plt.show()

plt.figure(figsize=(8, 4.5))
plt.fill_between(allcom_df['date'], allcom_df['value'], alpha=0.12, color=FL_BLUE)
plt.plot(allcom_df['date'], allcom_df['value'], color=FL_BLUE, linewidth=1.4)
plt.ylabel('Index')
plt.title('Global price index for all commodities (PALLFNFINDEXQ)')
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
plt.tick_params(length=0)
plt.tight_layout()
plt.show()

plt.figure(figsize=(8, 4.5))
plt.fill_between(wheat_df['date'], wheat_df['value'], alpha=0.12, color=FL_AMBER)
plt.plot(wheat_df['date'], wheat_df['value'], color=FL_AMBER, linewidth=1.3)
plt.title(wheat_info.title[:40] + ('...' if len(wheat_info.title) > 40 else ''))
plt.ylabel(wheat_info.units[:25])
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
plt.tick_params(length=0)
plt.tight_layout()
plt.show()

plt.figure(figsize=(8, 4.5))
plt.fill_between(corn_df['date'], corn_df['value'], alpha=0.12, color=FL_GREEN)
plt.plot(corn_df['date'], corn_df['value'], color=FL_GREEN, linewidth=1.3)
plt.title(corn_info.title[:40] + ('...' if len(corn_info.title) > 40 else ''))
plt.ylabel(corn_info.units[:25])
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
plt.tick_params(length=0)
plt.tight_layout()
plt.show()

plt.figure(figsize=(8, 4.5))
plt.fill_between(coffee_df['date'], coffee_df['value'], alpha=0.12, color=FL_SLATE)
plt.plot(coffee_df['date'], coffee_df['value'], color=FL_SLATE, linewidth=1.3)
plt.title(coffee_info.title[:40] + ('...' if len(coffee_info.title) > 40 else ''))
plt.ylabel(coffee_info.units[:25])
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
plt.tick_params(length=0)
plt.tight_layout()
plt.show()

other_agri_df = pd.DataFrame([
    {
        'Series': cotton_info.name,
        'Title': cotton_info.title,
        'Units': cotton_info.units,
        'Frequency': cotton_info.frequency,
        'Last updated': str(cotton_info.last_updated)[:10],
        'Latest value': round(float(cotton_df['value'].iloc[-1]), 4),
        'Latest date': cotton_df['date'].iloc[-1].strftime('%Y-%m-%d'),
        'Observations': len(cotton_df),
    },
    {
        'Series': sugar_info.name,
        'Title': sugar_info.title,
        'Units': sugar_info.units,
        'Frequency': sugar_info.frequency,
        'Last updated': str(sugar_info.last_updated)[:10],
        'Latest value': round(float(sugar_df['value'].iloc[-1]), 4),
        'Latest date': sugar_df['date'].iloc[-1].strftime('%Y-%m-%d'),
        'Observations': len(sugar_df),
    }
])

print('Cotton and sugar series summary')
display(other_agri_df.T)
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
Cotton and sugar series summary
0 1
Series None None
Title Global price of Cotton Global price of Sugar, No. 11, World
Units U.S. Cents per Pound U.S. Cents per Pound
Frequency Monthly Monthly
Last updated 2026-04-15 2026-04-15
Latest value 76.9386 14.7964
Latest date 2026-03-01 2026-03-01
Observations 195 195

Browsing the FRED category hierarchy

Every FRED series belongs to a category. The fred/category/children endpoint returns the sub-categories of any category ID. This is useful for discovering series - you can walk the tree from a root category down to specific series lists.

In [13]:
def fetch_subcategories(category_id):
    r = requests.get(
        f'{BASE_URL}/category/children',
        params={
            'category_id': category_id,
            'api_key': FRED_API_KEY,
            'file_type': 'json'
        },
        timeout=15
    )
    r.raise_for_status()
    return r.json().get('categories', [])

def fetch_series_in_category(category_id, limit=10):
    r = requests.get(
        f'{BASE_URL}/category/series',
        params={
            'category_id': category_id,
            'api_key': FRED_API_KEY,
            'file_type': 'json',
            'limit': limit,
            'order_by': 'popularity',
            'sort_order': 'desc'
        },
        timeout=15
    )
    r.raise_for_status()
    return r.json().get('seriess', [])

mbf_subs = fetch_subcategories(32991)
mbf_subs_df = pd.DataFrame([
    {
        'Category ID': cat.get('id'),
        'Category name': cat.get('name')
    }
    for cat in mbf_subs[:10]
])

print('Sub-categories of Money, Banking, and Finance (32991)')
display(mbf_subs_df)

rate_series = fetch_series_in_category(22, limit=10)
series_df = pd.DataFrame([
    {
        'ID': s['id'],
        'Title': s['title'][:55] + ('...' if len(s['title']) > 55 else ''),
        'Frequency': s['frequency_short'],
        'Units': s['units_short'],
        'Popularity': s['popularity'],
    }
    for s in rate_series
])

print('Top 10 series by popularity in Interest Rates (22)')
display(series_df)
Sub-categories of Money, Banking, and Finance (32991)
Category ID Category name
0 22 Interest Rates
1 15 Exchange Rates
2 24 Monetary Data
3 46 Financial Indicators
4 23 Banking
5 32360 Business Lending
6 32145 Foreign Exchange Intervention
Top 10 series by popularity in Interest Rates (22)