02 — Compare locations fairly

Learning objectives: compile one year at three Swedish locations, compare monthly climate and degree hours, and separate station distance from the requested solar point.

Network: SMHI and, if recovery needs it, Open-Meteo. Expected runtime: 5–15 minutes initially. All locations use 2023 and UTC+1 so the comparison changes space, not time conventions.

[ ]:
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)
YEAR = 2023
LOCATIONS = {
    "Gothenburg": (57.7089, 11.9746, None),
    "Stockholm": (59.3293, 18.0686, None),
    # Falsterbo A is used explicitly because the closer Sturup 2023 archive
    # fails strict RH validation; the requested Malmö solar point is unchanged.
    "Malmö": (55.6050, 13.0038, 52240),
}
[ ]:
results = {}
frames = {}
for city, (latitude, longitude, station_id) in LOCATIONS.items():
    path = OUTPUT_DIR / f"{city.lower()}_{YEAR}.epw"
    results[city] = compile_epw(
        EPWConfig(
            YEAR,
            str(path),
            station_id=station_id,
            city=city,
            latitude=latitude,
            longitude=longitude,
        )
    )
    frames[city] = read_epw(path)

pd.DataFrame(
    {
        city: {
            "station_km": result.coordinate_distance_km,
            "solar": result.report.solar_source,
        }
        for city, result in results.items()
    }
).T

Comparable metrics

Raw annual sums are appropriate because all three files contain the same number of hours. Heating degree hours use an 18°C base and cooling degree hours a 22°C base; these are teaching defaults, not universal building setpoints.

[ ]:
summary_rows = []
for city, frame in frames.items():
    temperature = frame.dry_bulb
    summary_rows.append(
        {
            "city": city,
            "mean_temperature_C": temperature.mean(),
            "mean_RH_percent": frame.relative_humidity.mean(),
            "mean_wind_m_s": frame.wind_speed.mean(),
            "GHI_kWh_m2": frame.ghi.sum() / 1000,
            "HDD18_C_h": (18 - temperature).clip(lower=0).sum(),
            "CDD22_C_h": (temperature - 22).clip(lower=0).sum(),
        }
    )
summary = pd.DataFrame(summary_rows).set_index("city")
summary.round(1)
[ ]:
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
for city, frame in frames.items():
    monthly = frame.groupby("month").agg({"dry_bulb": "mean", "ghi": "sum"})
    axes[0].plot(monthly.index, monthly.dry_bulb, marker="o", label=city)
    axes[1].plot(monthly.index, monthly.ghi / 1000, marker="o", label=city)
axes[0].set(ylabel="Monthly mean temperature (°C)", xlabel="Month")
axes[1].set(ylabel="Monthly GHI (kWh/m²)", xlabel="Month")
axes[0].legend()
fig.tight_layout()

Key takeaways and exercise

Differences reflect both regional climate and each selected station’s exposure. Always report station distance and solar provenance with the chart.

Try it: add Umeå. Before running, predict how annual GHI and HDD18 will change. Why should all sites retain the same standard-time convention?