Powered by AppSignal & Oban Pro

Getting Started with Fred

livebooks/1_getting_started.livemd

Getting Started with Fred

Mix.install([
  {:fred, "~> 0.4.0"},
  {:vega_lite, "~> 0.1"},
  {:kino_vega_lite, "~> 0.1"}
])

Introduction

FRED® (Federal Reserve Economic Data) provides access to over 800,000 economic time series from 100+ sources including the Bureau of Labor Statistics, the Bureau of Economic Analysis, and the Federal Reserve Board. This library was written to allow readers of Elixir For Finance to collect, analyze and visualize economic data from Fred, but it is a complete Fred API client and can be used outside the context of the book.

To learn how you can analyze and visualize the financial markets using Livebook, Explorer, Scholar and Nx, be sure to pick up a copy of our book:

”Elixir

Setup

To being, start by installing the notebook dependencies. This notebook uses the fred library for API access and VegaLite for charting.

You’ll need a FRED API key before making any API calls. You can get your free API key from the FRED API website. After you have a FRED API key, add it to your Livebook secrets undet the key FRED_API_KEY so the the downstream code can access it.

With your API key in place, you can set the application configuration for the Fred library and attach the default logger.

alias VegaLite, as: Vl

# API key pulled from Livebook secrets
Application.put_env(:fred, :api_key, System.fetch_env!("LB_FRED_API_KEY"))

# Attach the default logger to keep an eye on requests
Fred.Telemetry.Logger.attach(level: :info)

With that you are ready to rock and roll! Let’s now take a look at some simple endpoints to see what data we can extract from FRED.

Exploring a Series

We’ll begin by looking up metadata for the Unemployment Rate series (which has the key UNRATE). The meta data will include things like the frequency of the series, the human readable name, and the units of the series.

# We use the `Fred.Series.get/1` function to get metadata about the `UNRATE` series and pluck
# the head of the response list.
{:ok, %{"seriess" => [data | _]}} =
  Fred.Series.get("UNRATE")

# Print out some of the metadata from the series
IO.puts("""
Title:       #{data["title"]}
Frequency:   #{data["frequency"]}
Units:       #{data["units"]}
Seasonal:    #{data["seasonal_adjustment"]}
Last Update: #{data["last_updated"]}
""")

:ok

Fetching Observations

Now let’s pull the actual unemployment rate data from 2000-01-01 onward. We’ll work with the raw response data from the FRED API and massage it into a format that works with VegaLite.

# Get the observation data for the `UNRATE` series after (and including) 2000-01-01
observation_start = ~D[2000-01-01]

{:ok, %{"observations" => observations}} =
  Fred.Series.observations("UNRATE",
    observation_start: observation_start,
    frequency: :m
  )

# Parse into a list of maps, extract the necessary data and cast the float values. FRED returns
# "." for dates without any data so we'll ignore those.
formatted_observations =
  observations
  |> Enum.reduce([], fn
    %{"value" => "."}, acc ->
      acc

    %{"date" => date, "value" => value}, acc ->
      [%{date: Date.from_iso8601!(date), value: String.to_float(value)} | acc]
  end)
  |> Enum.sort_by(
    fn %{date: date} ->
      date
    end,
    Date
  )

# Get the min and max Dates for the series
{%{date: min_date}, %{date: max_date}} = Enum.min_max_by(formatted_observations, &(&1.date), Date)

# Print some information about the series
IO.puts("Fetched #{length(formatted_observations)} observations")
IO.puts("Date range: #{min_date} to #{max_date}")

# Preview the first few rows
Enum.take(formatted_observations, 5)

With the UNRATE data formatted nicely inside of the observations variable, let’s now see how we can plot this data using the VegaLite library inside of Livebook.

Plotting with VegaLite

With our unemployment data now formatted as a list of maps with the date and value, we can now plot the simple time series data using a VegaLite line chart:

Vl.new(width: 750, height: 400, title: "U.S. Unemployment Rate (#{min_date} - #{max_date})")
|> Vl.data_from_values(formatted_observations)
|> Vl.mark(:line, tooltip: true, color: "#2563eb")
|> Vl.encode_field(:x, "date",
  type: :temporal,
  title: "Date",
  axis: [format: "%Y"]
)
|> Vl.encode_field(:y, "value",
  type: :quantitative,
  title: "Unemployment Rate (%)",
  scale: [zero: false]
)

Adding Recession Shading

FRED provides the National Bureau of Economic Research (NBER for short) recession indicator as series "USREC". The data value is a 1 during a recession and 0 otherwise. We’ll convert this time series data set into a list of tuples to mark the beginning and end of recession periods which we’ll then use to overlay on top of the unemployment data in VegaLite.

# Fetch the recession indicator for the same date range
{:ok, %{"observations" => reccession_data}} =
  Fred.Series.observations("USREC",
    observation_start: observation_start,
    frequency: :m
  )

# Convert monthly 0/1 flags into recession periods: [{start, end}, ...]
recession_periods =
  reccession_data
  |> Enum.reject(fn %{"value" => value} ->
    value == "."
  end)
  |> Enum.chunk_while(
    nil,
    fn
      %{"value" => "1", "date" => date}, nil ->
        {:cont, date}

      %{"value" => "1"}, start_date ->
        {:cont, start_date}

      %{"value" => "0"}, nil ->
        {:cont, nil}

      %{"value" => "0", "date" => date}, start_date ->
        {:cont, %{start: Date.from_iso8601!(start_date), end: Date.from_iso8601!(date)}, nil}
    end,
    fn
      nil ->
        {:cont, nil}

      start_date ->
        {:cont, %{start: start_date, end: Date.utc_today()}, nil}
    end
  )

With the recession date ranges, aggregated, we can now create a new VegaLite chart using the VegaLite.layers/2 function in order to overlay the recession ranges along with the unemployment data.

# Layer recession bands behind the unemployment area chart
Vl.new(width: 750, height: 400, title: "U.S. Unemployment Rate with Recession Shading")
|> Vl.layers([
  # Gray recession bands
  Vl.new()
  |> Vl.data_from_values(recession_periods)
  |> Vl.mark(:rect, color: "#3f3f46", opacity: 0.25)
  |> Vl.encode_field(:x, "start", type: :temporal)
  |> Vl.encode_field(:x2, "end", type: :temporal),

  # Unemployment area
  Vl.new()
  |> Vl.data_from_values(formatted_observations)
  |> Vl.mark(:area,
    tooltip: true,
    color: "#2563eb",
    opacity: 0.5,
    line: [color: "#2563eb"]
  )
  |> Vl.encode_field(:x, "date",
    type: :temporal,
    title: "Date",
    axis: [format: "%Y"]
  )
  |> Vl.encode_field(:y, "value",
    type: :quantitative,
    title: "Unemployment Rate (%)",
    scale: [zero: true]
  )
])

Using FRED’s Built-in Transformations

The FRED API can also compute percent changes and format the response payload accordingly. You can do this via the :units option. Let’s look at month-over-month percent change in the unemployment rate.

{:ok, %{"observations" => pch_observations}} =
  Fred.Series.observations("UNRATE",
    observation_start: observation_start,
    frequency: :m,
    units: :pch
  )

formatted_pch_observations =
  pch_observations
  |> Enum.reduce([], fn
    %{"value" => "."}, acc ->
      acc

    %{"date" => date, "value" => value}, acc ->
      value = String.to_float(value)

      data = %{
        date: Date.from_iso8601!(date),
        value: value,
        direction: if(value >= 0, do: "Increase", else: "Decrease")
      }

      [data | acc]
  end)
  |> Enum.sort_by(
    fn %{date: date} ->
      date
    end,
    Date
  )

With our formatted percent change data, we can once again lean on the VegaLite.layers/2 function in order to overlay the recession periods along with the unemployment rate percent change.

Vl.new(width: 750, height: 400, title: "Unemployment Rate - Month-over-Month % Change")
|> Vl.layers([
  # Gray recession bands
  Vl.new()
  |> Vl.data_from_values(recession_periods)
  |> Vl.mark(:rect, color: "#3f3f46", opacity: 0.25)
  |> Vl.encode_field(:x, "start", type: :temporal)
  |> Vl.encode_field(:x2, "end", type: :temporal),

  # Unemployment area
  Vl.new()
|> Vl.data_from_values(formatted_pch_observations)
|> Vl.mark(:bar, tooltip: true)
|> Vl.encode_field(:x, "date",
  type: :temporal,
  title: "Date",
  axis: [format: "%Y"]
)
|> Vl.encode_field(:y, "value",
  type: :quantitative,
  title: "% Change"
)
|> Vl.encode_field(:color, "direction",
  type: :nominal,
  scale: [domain: ["Increase", "Decrease"], range: ["#ef4444", "#22c55e"]],
  legend: nil
)
])

Searching for Observation Series

In addition to fetching data for known observation series, you can search FRED’s full catalog via full text search. Let’s find the most popular series related to “inflation”.

{:ok, %{"seriess" => search_results}} =
  Fred.Series.search("inflation",
    order_by: :popularity,
    sort_order: :desc,
    limit: 5
  )

search_results
|> Enum.map(fn s ->
  %{
    id: s["id"],
    title: s["title"],
    frequency: s["frequency_short"],
    popularity: s["popularity"]
  }
end)