03 — Compare actual years
Learning objectives: compare 2016, leap-year 2020, and 2023 at one point; normalize unequal calendars; and recognize changes in available solar inputs.
Network: SMHI and, if recovery needs it, Open-Meteo. Expected runtime: 5–15 minutes initially. These are actual years, not a trend analysis with only three samples.
[ ]:
from pathlib import Path
import matplotlib.pyplot as plt
import pandas as pd
from smhi2epw import EPWConfig, compile_epw, read_epw
OUTPUT_DIR = Path("examples/output") if Path("examples").exists() else Path("output")
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
YEARS = [2016, 2020, 2023]
STATIONS = {2016: 71420, 2020: 72420, 2023: 71420}
LATITUDE, LONGITUDE = 57.7156, 11.9924
[ ]:
frames = {}
diagnostics = []
for year in YEARS:
path = OUTPUT_DIR / f"gothenburg_{year}.epw"
result = compile_epw(
EPWConfig(
year,
str(path),
station_id=STATIONS[year],
city="Gothenburg",
latitude=LATITUDE,
longitude=LONGITUDE,
utc_offset=1,
)
)
frames[year] = read_epw(path)
diagnostics.append(
{
"year": year,
"hours": len(frames[year]),
"station_id": result.station.station_id if result.station else None,
"weather_classification": result.report.weather_classification,
"required_reconstructed_fraction": result.report.required_reconstructed_fraction,
"warning_codes": [w["code"] for w in result.report.warnings],
"station_distance_km": result.coordinate_distance_km,
"solar_source": result.report.solar_source,
}
)
pd.DataFrame(diagnostics).set_index("year")
Normalize before comparing
2020 contains 8784 hours. Compare mean irradiance or energy per calendar day when the question is typical intensity; retain annual totals when the extra leap day is genuinely part of the load calculation. The current pre-2018 implementation requests only GHI and uses Erbs, although historical STRÅNG DNI is available; direct-horizontal parameter 121 is incomplete for 2017. Göteborg A lacks most 2020 humidity observations, so that year explicitly uses nearby Landvetter (72420); the diagnostics keep this station change visible.
[ ]:
rows = []
for year, frame in frames.items():
days = len(frame) / 24
rows.append(
{
"year": year,
"days": days,
"mean_temperature_C": frame.dry_bulb.mean(),
"minimum_C": frame.dry_bulb.min(),
"maximum_C": frame.dry_bulb.max(),
"GHI_kWh_m2_year": frame.ghi.sum() / 1000,
"GHI_kWh_m2_day": frame.ghi.sum() / 1000 / days,
}
)
annual = pd.DataFrame(rows).set_index("year")
annual.round(2)
[ ]:
fig, ax = plt.subplots(figsize=(9, 4))
for year, frame in frames.items():
monthly = frame.groupby("month").dry_bulb.mean()
ax.plot(monthly.index, monthly, marker="o", label=str(year))
ax.set(xlabel="Month", ylabel="Monthly mean dry-bulb temperature (°C)")
ax.legend(title="AMY")
fig.tight_layout()
Key takeaways and exercise
Three historical years can illustrate variability but cannot establish a climate trend. Differences may also include station selection or solar-source changes, so preserve diagnostics.
Try it: compare annual HDD18 totals and HDD18 per day. Which quantity answers an annual energy question, and which is better for comparing unequal calendar lengths?