Read cooling/heating degree-day (CDD/HDD) proxy datasets used to weight downscaling.
Degree-day CSVs carry columns [year, country, type, tlim_setpoint, rcp, ssp, value] with
country as ISO3 and type one of CDD/HDD. read_degree_days selects one
(type, tlim_setpoint, rcp, ssp) combination and returns the tidy [iso2, year, value] frame
used as a downscaling proxy — the same shape as :func:iampypsa.io.ssp.read_ssp_data, so callers do
.set_index(["iso2", "year"]) and pass it in the proxies registry.
read_degree_days(path, *, dd_type, tlim_setpoint, rcp, ssp)
Read a degree-day CSV and return the tidy [iso2, year, value] proxy frame.
Filters to type == dd_type (CDD/HDD), the given tlim_setpoint, rcp and
ssp; converts ISO3 → ISO2 (unmapped codes dropped) and sums any duplicates. Raises
ValueError for an unknown dd_type or a selection matching no rows (the message lists the
available values to ease debugging — e.g. a tlim_setpoint outside the data's range).
Source code in src/iampypsa/io/degree_days.py
| def read_degree_days(
path: str | PathLike,
*,
dd_type: str,
tlim_setpoint: float,
rcp: str,
ssp: str,
) -> pd.DataFrame:
"""Read a degree-day CSV and return the tidy ``[iso2, year, value]`` proxy frame.
Filters to ``type == dd_type`` (``CDD``/``HDD``), the given ``tlim_setpoint``, ``rcp`` and
``ssp``; converts ISO3 → ISO2 (unmapped codes dropped) and sums any duplicates. Raises
``ValueError`` for an unknown ``dd_type`` or a selection matching no rows (the message lists the
available values to ease debugging — e.g. a ``tlim_setpoint`` outside the data's range).
"""
dd_type = str(dd_type).upper()
if dd_type not in _VALID_TYPES:
raise ValueError(f"dd_type must be one of {sorted(_VALID_TYPES)}, got {dd_type!r}.")
df = pd.read_csv(path)
df["type"] = df["type"].astype(str).str.upper()
typed = df[df["type"] == dd_type]
if typed.empty:
raise ValueError(
f"No {dd_type} rows in {path} (types present: {sorted(df['type'].unique())})."
)
# rcp/ssp compared as strings (e.g. "4_5", "SSP2"); tlim_setpoint numerically.
selected = typed[
(typed["tlim_setpoint"] == tlim_setpoint)
& (typed["rcp"].astype(str) == str(rcp))
& (typed["ssp"].astype(str) == str(ssp))
]
if selected.empty:
raise ValueError(
f"No {dd_type} rows for tlim_setpoint={tlim_setpoint}, rcp={rcp!r}, ssp={ssp!r} "
f"in {path}. Available: tlim_setpoint={sorted(typed['tlim_setpoint'].unique())}, "
f"rcp={sorted(typed['rcp'].astype(str).unique())}, "
f"ssp={sorted(typed['ssp'].astype(str).unique())}."
)
selected = selected.copy()
# Pin src=ISO3: the country column is uniformly ISO3, so avoid coco's fuzzy source detection.
selected["iso2"] = coco.CountryConverter().convert(
selected["country"].tolist(), to="ISO2", src="ISO3", not_found=None
)
return (
selected.dropna(subset=["iso2"])[["iso2", "year", "value"]]
.groupby(["iso2", "year"], as_index=False)["value"]
.sum()
.sort_values(["iso2", "year"])
.reset_index(drop=True)
)
|