Powered by AppSignal & Oban Pro

Calculating Unemployment Rate

unemployment_rate.livemd

Calculating Unemployment Rate

Mix.install([
  {:fred, "~> 0.0.1"},
  {:vega_lite, "~> 0.1.11"},
  {:kino_vega_lite, "~> 0.1.13"}
])

Introuction

<- Back to index

alias VegaLite, as: Vl

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

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

# Set the start date
observation_start = ~D[1947-01-01]

# Unemployment and recession series IDs
unemployment_series = "UNRATE"
recession_series = "USREC"

:ok
# Fetch metadata on the unemployment series and output it
{:ok, %{"seriess" => [data | _]}} = Fred.Series.get(unemployment_series)

# 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
# Fetch the time series for the unemployment series
{:ok, %{"observations" => observations}} =
  Fred.Series.observations(unemployment_series,
    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 =
  Enum.reduce(observations, [], fn
    %{"value" => "."}, acc ->
      acc

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

      [%{date: date, value: value} | acc]
  end)

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

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

recession_periods =
  recession_data
  |> Enum.flat_map(fn
    %{"value" => "."} ->
      []

    %{"value" => value, "date" => date} ->
      [{Date.from_iso8601!(date), value}]
  end)
  |> Enum.chunk_by(fn {_date, value} -> value end)
  |> Enum.flat_map(fn
    [{_date, "0"} | _] ->
      []

    data ->
      [Enum.map(data, fn {date, _value} -> date end)]
  end)
  |> Enum.map(fn chunk ->
    {start, stop} =
      Enum.min_max_by(chunk, fn date -> date end, Date)

    %{start: start, stop: stop}
  end)

:ok
# Layer recession bands behind the unemployment area chart
[width: 750, height: 400, title: "#{data["title"]} (#{min_date} - #{max_date})"]
|> Vl.new()
|> 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, "stop", 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: data["units"],
    scale: [zero: true]
  )
])