Files
Climate-Mood-Analysis/scripts/apply_gridmet_humidity_metric_to_climate_data.py
T
2026-06-11 14:50:33 -04:00

120 lines
4.3 KiB
Python

#!/usr/bin/env python3
"""
Merge selected gridMET humidity and heat metrics into climate-data.csv.
Currently adds:
- avgSummerSpecificHumidityGKg
- humidHeatDays
- humidHeatSourceFips
- humidHeatFipsAdjustment
"""
from __future__ import annotations
import argparse
import csv
from pathlib import Path
DEFAULT_CLIMATE_DATA = Path("data/climate-data.csv")
DEFAULT_GRIDMET_HUMIDITY = Path("data/gridmet/county_gridmet_humidity.csv")
METRIC_FIELDS = [
"avgSummerSpecificHumidityGKg",
"humidHeatDays",
"humidHeatSourceFips",
"humidHeatFipsAdjustment",
]
SOURCE_TAGS_BY_FIELD = {
"avgSummerSpecificHumidityGKg": "gridmet-specific-humidity-1991-2020",
"humidHeatDays": "heat-index-noaa-tmax-gridmet-rmin-1991-2020",
}
def load_gridmet_values(path: Path) -> dict[str, dict[str, str]]:
values: dict[str, dict[str, str]] = {}
with path.open("r", encoding="utf-8", newline="") as handle:
reader = csv.DictReader(handle)
fieldnames = reader.fieldnames or []
missing_fields = [field for field in METRIC_FIELDS if field not in fieldnames]
if missing_fields:
raise ValueError(f"{path} is missing fields: {', '.join(missing_fields)}")
for row in reader:
county_fips = (row.get("countyFips") or "").strip().zfill(5)
if county_fips:
values[county_fips] = {
field: (row.get(field) or "").strip() for field in METRIC_FIELDS
}
return values
def append_source_tag(source: str, tag: str) -> str:
clean_source = source.strip()
if tag in clean_source:
return clean_source
return f"{clean_source} + {tag}".strip(" +")
def ensure_field_after(fieldnames: list[str], field: str, after_field: str | None = None) -> None:
if field in fieldnames:
return
if after_field and after_field in fieldnames:
fieldnames.insert(fieldnames.index(after_field) + 1, field)
return
insert_at = fieldnames.index("source") if "source" in fieldnames else len(fieldnames)
fieldnames.insert(insert_at, field)
def merge_metrics(climate_data: Path, humidity_csv: Path) -> tuple[int, dict[str, int]]:
gridmet_by_fips = load_gridmet_values(humidity_csv)
with climate_data.open("r", encoding="utf-8", newline="") as handle:
reader = csv.DictReader(handle)
fieldnames = list(reader.fieldnames or [])
rows = list(reader)
if "countyFips" not in fieldnames:
raise ValueError(f"{climate_data} is missing countyFips")
ensure_field_after(fieldnames, "avgSummerSpecificHumidityGKg")
ensure_field_after(fieldnames, "humidHeatDays", "avgSummerSpecificHumidityGKg")
ensure_field_after(fieldnames, "humidHeatSourceFips", "humidHeatDays")
ensure_field_after(fieldnames, "humidHeatFipsAdjustment", "humidHeatSourceFips")
missing_counts = {field: 0 for field in METRIC_FIELDS}
for row in rows:
county_fips = (row.get("countyFips") or "").strip().zfill(5)
gridmet_values = gridmet_by_fips.get(county_fips, {})
for field in METRIC_FIELDS:
value = gridmet_values.get(field, "")
row[field] = value
if not value:
missing_counts[field] += 1
for field, tag in SOURCE_TAGS_BY_FIELD.items():
if row.get(field):
row["source"] = append_source_tag(row.get("source", ""), tag)
with climate_data.open("w", encoding="utf-8", newline="") as handle:
writer = csv.DictWriter(handle, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(rows)
return len(rows), missing_counts
def main() -> int:
parser = argparse.ArgumentParser(description="Merge gridMET humidity and Heat Index metrics into climate-data.csv.")
parser.add_argument("--climate-data", type=Path, default=DEFAULT_CLIMATE_DATA)
parser.add_argument("--humidity-csv", type=Path, default=DEFAULT_GRIDMET_HUMIDITY)
args = parser.parse_args()
row_count, missing_counts = merge_metrics(args.climate_data, args.humidity_csv)
print(f"Updated {args.climate_data} with {', '.join(METRIC_FIELDS)}")
print(f"Rows: {row_count}")
for field, missing_count in missing_counts.items():
print(f"Missing {field}: {missing_count}")
return 0
if __name__ == "__main__":
raise SystemExit(main())