Files
python_charts/scripts/common.py
T
thePR0M3TH3AN e5b1d091c5 Enhance documentation and scripts for handling UNRATE null values
- Add policy for managing NaN values in UNRATE series to AGENTS.md.
- Update README.md with new chart script details and usage instructions.
- Modify `lagged_oil_unrate_chart_styled.py` to implement clamping to last valid index for UNRATE data.
- Introduce new script `bitcoin_m2_chart.py` for Bitcoin vs. global M2 overlay chart.
- Ensure all scripts maintain data integrity by avoiding estimates for current month.
2025-05-31 09:59:33 -04:00

80 lines
2.5 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
from datetime import datetime
from pathlib import Path
from typing import Iterable
import matplotlib.pyplot as plt
import pandas as pd
import sqlite3
def save_figure(fig: plt.Figure, output: str | None, script_path: str) -> Path:
"""Save `fig` to `output` or `outputs/` with a timestamped filename."""
out_path = (
Path(output)
if output is not None
else Path("outputs")
/ f"{Path(script_path).stem}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
)
out_path.parent.mkdir(parents=True, exist_ok=True)
fig.savefig(out_path)
print(f"Saved figure to {out_path}")
return out_path
def fetch_series_db(
series: Iterable[str],
start: datetime,
end: datetime,
db_path: str | Path = Path("data/fred.db"),
) -> pd.DataFrame:
"""
Load one or more FRED series from a local SQLite DB.
Returns a DataFrame whose columns are the requested series names.
"""
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 access,"
" commit the DB, then retry."
)
frames: list[pd.DataFrame] = []
with sqlite3.connect(db_path) as conn:
for name in series:
df = pd.read_sql(
f"SELECT date, {name} FROM {name}",
conn,
parse_dates=["date"],
index_col="date",
).loc[start:end]
df.rename(columns={name: name}, inplace=True)
frames.append(df)
if not frames:
raise ValueError("No series loaded")
return pd.concat(frames, axis=1).sort_index()
def validate_dataframe(df: pd.DataFrame, name: str) -> None:
"""Ensure DataFrame has a 'value' column with no nulls and a DatetimeIndex."""
if df.empty:
raise ValueError(f"{name} dataframe is empty")
if "value" not in df.columns:
raise ValueError(f"{name} missing 'value' column")
if df["value"].isna().any():
raise ValueError(f"{name} contains null values")
if not isinstance(df.index, pd.DatetimeIndex):
raise ValueError("Indexes must be a Pandas DatetimeIndex")
def validate_overlap(df1: pd.DataFrame, df2: pd.DataFrame) -> None:
"""Verify that two series share a non-empty overlapping date range."""
start = max(df1.index.min(), df2.index.min())
end = min(df1.index.max(), df2.index.max())
if start >= end:
raise ValueError("Series have no overlapping date range")