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?