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