Vendored wheels and FRED snapshot

This commit is contained in:
thePR0M3TH3AN committed 2025-05-30 16:02:18 -04:00
1 parent 96964c6981
commit 175e9ba4e2
4 files changed
+141 -60

No files matched your search

+6 -8
View File
@@ -1,11 +1,9 @@
# Python ###
# Byte-code / venv
__pycache__/
*.py[cod]
*$py.class
# Virtualenv ###
venv/
# Data & outputs ###
data/
outputs/
# Local data & wheels
wheelhouse/*.log
wheelhouse/*.md
# keep wheel files themselves!
data/*.bak
+1 -1
View File
@@ -143,7 +143,7 @@ This project is released under the MIT License.
## python_charts/requirements.txt
```
pandas>=1.5.0
pandas\_datareader>=0.10.0
pandas_datareader>=0.10.0 # good
matplotlib>=3.5.0
lxml>=4.6.0
python-dateutil>=2.8.1
+1 -1
View File
@@ -1,5 +1,5 @@
pandas>=1.5.0
pandas_datareader>=0.10.0 # good
pandas_datareader>=0.10.0
matplotlib>=3.5.0
lxml>=4.6.0
python-dateutil>=2.8.1
+133 -50
View File
@@ -2,119 +2,202 @@
"""
lagged_oil_unrate_chart_styled.py
---------------------------------
Fetch and plot UNRATE vs. WTI oil price with a configurable lag,
date range, and polished dual-axis styling.
Offline version: reads UNRATE & WTI series from data/fred.db (populated
by scripts/refresh_data.py) and plots them with a configurable lag.
Usage:
python lagged_oil_unrate_chart_styled.py \
--offset 18 \
--start 1973-01-01 \
--end 2025-05-31 \
--extend-years 3
Dependencies:
pip install pandas pandas_datareader matplotlib python-dateutil
Usage
-----
python scripts/lagged_oil_unrate_chart_styled.py \
--offset 18 \
--start 1973-01-01 \
--end 2025-05-31 \
--extend-years 3
"""
from __future__ import annotations
import argparse
from datetime import datetime
from dateutil.relativedelta import relativedelta
from pathlib import Path
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
import pandas as pd
import pandas_datareader.data as web
from dateutil.relativedelta import relativedelta
import sqlite3
def fetch_series(start, end):
# FRED series
unrate = web.DataReader("UNRATE", "fred", start, end)
oil = web.DataReader("DCOILWTICO", "fred", start, end)
# ──────────────────────────────────────────────────────────────────────────
# Data helpers
# ──────────────────────────────────────────────────────────────────────────
def fetch_series(
start: datetime,
end: datetime,
db_path: str | Path = Path("data/fred.db"),
) -> tuple[pd.DataFrame, pd.DataFrame]:
"""
Load UNRATE and DCOILWTICO from a local SQLite DB, then:
* convert UNRATE to month-end timestamps
* average WTI daily prices to month-end
"""
db_path = Path(db_path)
if not db_path.exists():
raise FileNotFoundError(
f"{db_path} not found. Run scripts/refresh_data.py on a "
"machine with internet, commit the new DB, then retry."
)
with sqlite3.connect(db_path) as conn:
unrate = (
pd.read_sql(
"SELECT date, UNRATE AS value FROM UNRATE", conn,
parse_dates=["date"], index_col="date"
)
.loc[start:end]
)
oil = (
pd.read_sql(
"SELECT date, DCOILWTICO AS value FROM DCOILWTICO", conn,
parse_dates=["date"], index_col="date"
)
.loc[start:end]
)
# Resample WTI to month-end average
oil_monthly = oil.resample("M").mean()
# Ensure UNRATE is at month-end timestamps, too
# Ensure UNRATE is at month-end, too
unrate.index = unrate.index.to_period("M").to_timestamp("M")
return unrate, oil_monthly
# ──────────────────────────────────────────────────────────────────────────
# Plotting
# ──────────────────────────────────────────────────────────────────────────
def plot_lagged(
unrate: pd.DataFrame,
oil: pd.DataFrame,
offset_months: int,
start_date: datetime,
end_date: datetime,
extend_years: int
):
# Shift oil by offset: positive = lag; negative = lead
extend_years: int,
) -> None:
"""Render the dual-axis chart."""
oil_shifted = oil.shift(offset_months)
# Build figure
fig, ax1 = plt.subplots(figsize=(14, 7))
plt.title("Unemployment Rate and US Oil Price", fontsize=20, weight="bold")
plt.suptitle(
"Civilian Unemployment Rate and WTI Crude Oil Price",
fontsize=14,
y=0.93
y=0.93,
)
# Plot UNRATE on left axis
ax1.plot(unrate.index, unrate["UNRATE"],
label="UNRATE (LHS)", linewidth=2.5, color="#1f77b4")
# Left axis – UNRATE
ax1.plot(
unrate.index,
unrate["value"],
label="UNRATE (LHS)",
linewidth=2.5,
color="#1f77b4",
)
ax1.set_ylabel("Unemployment rate (%)", fontsize=12)
ax1.set_ylim(0, unrate["UNRATE"].max() * 1.1)
ax1.set_ylim(0, unrate["value"].max() * 1.1)
ax1.yaxis.set_major_formatter("{x:.0f}%")
ax1.grid(axis="y", linestyle="--", alpha=0.4)
# Plot shifted oil on right axis
# Right axis – shifted oil
ax2 = ax1.twinx()
ax2.plot(oil_shifted.index, oil_shifted["DCOILWTICO"],
label="USOIL (RHS)", linewidth=2, linestyle="-", alpha=0.8, color="#FF9900")
ax2.plot(
oil_shifted.index,
oil_shifted["value"],
label="USOIL (RHS)",
linewidth=2,
linestyle="-",
alpha=0.8,
color="#FF9900",
)
ax2.set_ylabel("Oil price (USD)", fontsize=12)
ax2.set_ylim(0, oil_shifted["DCOILWTICO"].max() * 1.1)
ax2.set_ylim(0, oil_shifted["value"].max() * 1.1)
ax2.yaxis.set_major_formatter("${x:,.0f}")
# X-axis formatting
# X-axis
ax1.set_xlim(start_date, end_date + relativedelta(years=extend_years))
ax1.xaxis.set_major_locator(mdates.YearLocator(base=5))
ax1.xaxis.set_minor_locator(mdates.YearLocator(1))
ax1.xaxis.set_major_formatter(mdates.DateFormatter("%Y"))
plt.setp(ax1.get_xticklabels(), rotation=0, fontsize=11)
# Combine legends
# Legends
lines, labels = ax1.get_legend_handles_labels()
l2, l2l = ax2.get_legend_handles_labels()
ax1.legend(lines + l2, labels + l2l, loc="upper left", frameon=False, fontsize=12)
# Footnotes
# Footnote
foot = (
f"Dates: {start_date.strftime('%Y')} through {unrate.index[-1].strftime('%d %b %Y')}.\n"
"Source: FRED, pandas_datareader."
f"Dates: {start_date.strftime('%Y')} through "
f"{unrate.index[-1].strftime('%d %b %Y')}.\n"
"Source: Local FRED snapshot (offline)."
)
plt.annotate(
foot,
(0,0), (0, -40),
xycoords="axes fraction", textcoords="offset points",
va="top", fontsize=10, color="gray"
(0, 0),
(0, -40),
xycoords="axes fraction",
textcoords="offset points",
va="top",
fontsize=10,
color="gray",
)
fig.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()
if __name__ == "__main__":
# ──────────────────────────────────────────────────────────────────────────
# CLI
# ──────────────────────────────────────────────────────────────────────────
def main() -> None:
p = argparse.ArgumentParser()
p.add_argument("-o", "--offset", type=int, default=18,
help="lag in months (positive=lag, negative=lead)")
p.add_argument("--start", type=str, default="1973-01-01",
help="series start date (YYYY-MM-DD)")
p.add_argument("--end", type=str, default=datetime.today().strftime("%Y-%m-%d"),
help="series end date (YYYY-MM-DD)")
p.add_argument("--extend-years", type=int, default=3,
help="how many years beyond end_date to show on the x-axis")
p.add_argument(
"-o",
"--offset",
type=int,
default=18,
help="lag in months (positive=lag, negative=lead)",
)
p.add_argument(
"--start",
type=str,
default="1973-01-01",
help="series start date (YYYY-MM-DD)",
)
p.add_argument(
"--end",
type=str,
default=datetime.today().strftime("%Y-%m-%d"),
help="series end date (YYYY-MM-DD)",
)
p.add_argument(
"--extend-years",
type=int,
default=3,
help="how many years beyond end_date to show on the x-axis",
)
p.add_argument(
"--db",
type=str,
default="data/fred.db",
help="path to local FRED SQLite file",
)
args = p.parse_args()
start_dt = datetime.fromisoformat(args.start)
end_dt = datetime.fromisoformat(args.end)
end_dt = datetime.fromisoformat(args.end)
unrate, oil = fetch_series(start_dt, end_dt)
unrate, oil = fetch_series(start_dt, end_dt, args.db)
plot_lagged(unrate, oil, args.offset, start_dt, end_dt, args.extend_years)
if __name__ == "__main__":
main()