| Indicateur | Pièce principale | Chambre |
|---|---|---|
| T maximale | 31.7 °C — 28 juin 2026, 19:12 | 31.0 °C — 28 juin 2026, 17:36 |
| T minimale | 18.8 °C — 11 juin 2026, 06:17 | 19.3 °C — 11 juin 2026, 06:17 |
| Jours T max ≥ 26 °C | 14 jours | 12 jours |
| Jours T max ≥ 28 °C | 11 jours | 9 jours |
| T extérieure max (GVE) | 36.7 °C — 27 juin 2026, 18:00 | — |
| T extérieure min (GVE) | 9.4 °C | — |
| Nuits tropicales ext. (T min ≥ 20 °C) | 4 nuits | — |
| Canicule officielle | 18 au 29 juin 2026 | — |
| CO₂ maximal mesuré | 1054 ppm | 2172 ppm |
Juin 2026
Visualisation des données
Données
Les mesures de la température intérieure sont prise avec un appareil Aranet4, pour la pièce principale et la chambre. L’appareil est posé environ à 1.20 m de hauteur, sur une étagère, plutôt à l’opposé de la fenêtre, et sans exposition directe au soleil. Intervalle de mesure : 5 minutes. Période : 3 juin 2026–28 juin 2026.
Il s’agit d’un appartement de 45 m2 au 8e étage, exposition Sud-Sud-Est, avec de larges fenêtres sur la façade.
Comparaison température intérieure et extérieure
Afficher le code
RED_DARK = "#c62828" # pièce principale
BROWN = "#e65100" # chambre
BLUE = "#1565c0" # extérieur
CANICULE = "#ffe0b2"
NUIT_TROP = "#ede7f6"
df_fig = df[(df["time"] >= fig_start) & (df["time"] <= fig_end)]
dfc_fig = df_c[(df_c["time"] >= fig_start) & (df_c["time"] <= fig_end)]
ext_fig = ext[(ext["time"] >= fig_start) & (ext["time"] <= fig_end)]
fig, ax = plt.subplots(figsize=(14, 5))
fig.patch.set_facecolor("white")
# ── 1. Période de canicule officielle ────────────────────────────────────────
canicule_start = pd.Timestamp("2026-06-18 00:00")
canicule_end = max(df_fig["time"].max(), ext_fig["time"].max())
ax.axvspan(canicule_start, canicule_end,
color=CANICULE, alpha=0.55, zorder=0,
label="Période de canicule (plan cantonal GE, dès le 18 juin)")
# ── 2. Nuits tropicales ──────────────────────────────────────────────────────
ext_tmp = ext_fig.copy()
ext_tmp["night_date"] = ext_tmp["time"].dt.date
mask_ev = ext_tmp["time"].dt.hour >= 20
ext_tmp.loc[mask_ev, "night_date"] = (
ext_tmp.loc[mask_ev, "time"] + pd.Timedelta(days=1)
).dt.date
mask_n = (ext_tmp["time"].dt.hour >= 20) | (ext_tmp["time"].dt.hour < 6)
nuits_dates = (
ext_tmp[mask_n]
.groupby("night_date")["temp_ext"]
.min()
.pipe(lambda s: s[s >= 20])
.index
)
first_nuit = True
for nuit in nuits_dates:
nuit_ts = pd.Timestamp(nuit)
span_start = nuit_ts - pd.Timedelta(hours=4)
span_end = nuit_ts + pd.Timedelta(hours=6)
label = "Nuit tropicale (T ext min ≥ 20 °C)" if first_nuit else ""
ax.axvspan(span_start, span_end,
color=NUIT_TROP, alpha=0.85, zorder=1, label=label)
first_nuit = False
# ── 3. Courbes de température ────────────────────────────────────────────────
ax.plot(dfc_fig["time"], dfc_fig["temperature"],
color=BROWN, linewidth=0.8, alpha=0.75, zorder=3,
label="T intérieure chambre (Aranet4, 5 min)")
ax.plot(df_fig["time"], df_fig["temperature"],
color=RED_DARK, linewidth=0.8, alpha=0.9, zorder=4,
label="T intérieure pièce principale (Aranet4, 5 min)")
ax.plot(ext_fig["time"], ext_fig["temp_ext"],
color=BLUE, linewidth=1.3, zorder=5,
marker="o", markersize=1.8, alpha=0.85,
label="T extérieure (MétéoSuisse GVE, horaire)")
# ── 4. Seuil nuit tropicale ──────────────────────────────────────────────────
ax.axhline(20, color="#7b1fa2", lw=0.8, ls=":", alpha=0.7,
label="Seuil nuit tropicale (20 °C)")
# ── 5. Habillage ─────────────────────────────────────────────────────────────
ax.yaxis.set_ticks_position("both")
ax.tick_params(axis="y", right=True, labelright=True, labelsize=9)
ax.set_ylabel("Température (°C)", fontsize=10)
ax.set_ylim(10, 38)
ax.legend(loc="upper left", fontsize=8, framealpha=0.9)
ax.set_title(
"T intérieure (8e étage, exp. SSE) vs T extérieure (GVE – Cointrin)\n"
"Source : Aranet4 / MétéoSuisse OGD — 3–28 juin 2026",
fontsize=10
)
ax.grid(axis="y", ls=":", alpha=0.4)
ax.xaxis.set_major_locator(mdates.DayLocator(interval=2))
ax.xaxis.set_minor_locator(mdates.DayLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%-d %b"))
plt.setp(ax.xaxis.get_majorticklabels(), rotation=0, ha="center", fontsize=9)
fig.tight_layout()
plt.show()
Pratique d’aération et limites physiques
La stratégie appliquée est optimale dans les contraintes du logement : les fenêtres sont ouvertes le soir dès que la température extérieure rejoint la température intérieure (croisement des courbes), et maintenues ouvertes pendant la nuit.
Le matin, dès que le rayonnement solaire direct atteint les vitrages (vers 6h–7h en juin pour une exposition Sud-Sud-Est), fenêtres et stores sont fermés. Cette fermeture est visible sur le graphique : la courbe intérieure repart à la hausse alors que la courbe extérieure est encore basse ou en descente.
Les stores ne constituent pas une protection suffisante. De couleur sombre et en métal, ils absorbent et accumulent le rayonnement solaire. Cette chaleur est ensuite diffusée vers l’intérieur par conduction à travers les cadres de fenêtres et les vitrages, dont l’isolation thermique est insuffisante. La fermeture des stores ralentit la montée en température mais ne l’empêche pas.
Afficher le code
import numpy as np
from matplotlib.ticker import FuncFormatter
JOURS_FR = {0: "Lun", 1: "Mar", 2: "Mer", 3: "Jeu",
4: "Ven", 5: "Sam", 6: "Dim"}
def fmt_date_fr_court(ts):
return f"{JOURS_FR[ts.weekday()]} {ts.day} {MOIS_FR[ts.month]}"
zoom_start = pd.Timestamp("2026-06-25 00:00")
zoom_end = pd.Timestamp("2026-06-28 23:59")
dz = df[(df["time"] >= zoom_start) & (df["time"] <= zoom_end)].copy().reset_index(drop=True)
dz_c = df_c[(df_c["time"] >= zoom_start) & (df_c["time"] <= zoom_end)].copy()
ez = ext[(ext["time"] >= zoom_start) & (ext["time"] <= zoom_end)].copy()
# Interpolation T extérieure sur la grille 5 min de l'Aranet
temp_ext_interp = (
ez.set_index("time")["temp_ext"]
.reindex(ez.set_index("time")["temp_ext"].index.union(dz["time"]))
.interpolate("index")
.reindex(dz["time"])
.values
)
diff = dz["temperature"].values - temp_ext_interp
sign_changes = np.where(np.diff(np.sign(diff)))[0]
# Détection des fermetures : minimum local de T intérieure entre 5h et 10h
# (on cherche le moment où la courbe intérieure cesse de descendre et repart)
dz["hour"] = dz["time"].dt.hour
dz["diff_temp"] = dz["temperature"].diff() # dérivée discrète
fig, ax = plt.subplots(figsize=(14, 5))
fig.patch.set_facecolor("white")
# Fond canicule
ax.axvspan(zoom_start, zoom_end, color="#ffe0b2", alpha=0.35, zorder=0)
# Zones nocturnes (20h–6h)
for day_offset in range(4):
night_s = zoom_start + pd.Timedelta(days=day_offset, hours=20)
night_e = night_s + pd.Timedelta(hours=10)
ax.axvspan(night_s, night_e, color="#eceff1", alpha=0.6, zorder=0)
# Courbes
ax.plot(dz_c["time"], dz_c["temperature"],
color=BROWN, linewidth=0.8, alpha=0.75, zorder=3,
linestyle="--", label="T intérieure chambre (Aranet4)")
ax.plot(dz["time"], dz["temperature"],
color=RED_DARK, linewidth=1.3, zorder=4,
label="T intérieure pièce principale (Aranet4)")
ax.plot(ez["time"], ez["temp_ext"],
color=BLUE, linewidth=1.5, zorder=5,
marker="o", markersize=2.5, label="T extérieure (MétéoSuisse GVE)")
# ── Annotations ouvertures (croisement ext descend sous int, soirée) ─────────
annotated_open = 0
for idx in sign_changes:
if idx + 1 >= len(dz):
continue
s_before = np.sign(diff[idx])
s_after = np.sign(diff[idx + 1])
hour = dz["time"].iloc[idx].hour
if s_before < 0 and s_after > 0 and 17 <= hour <= 23 and annotated_open < 4:
ax.annotate(
"ouverture",
xy=(dz["time"].iloc[idx], dz["temperature"].iloc[idx]),
xytext=(0, 25), textcoords="offset points",
fontsize=7.5, color=BLUE, ha="center",
arrowprops=dict(arrowstyle="->", color=BLUE, lw=0.8),
)
annotated_open += 1
# ── Annotations fermetures : minimum local de T int entre 5h et 10h ──────────
# Par jour, on cherche l'indice du minimum de T intérieure dans la fenêtre 5h–10h
annotated_solar = 0
for day_offset in range(4):
day = zoom_start.date() + pd.Timedelta(days=day_offset)
mask = (
(dz["time"].dt.date == day) &
(dz["hour"] >= 5) &
(dz["hour"] <= 10)
)
subset = dz[mask]
if subset.empty:
continue
# minimum de T intérieure dans la fenêtre → point de divergence
idx_min = subset["temperature"].idxmin()
t_close = dz.loc[idx_min, "time"]
y_close = dz.loc[idx_min, "temperature"]
if annotated_solar < 4:
ax.annotate(
"fermeture",
xy=(t_close, y_close),
xytext=(0, -32), textcoords="offset points",
fontsize=7.5, color=RED_DARK, ha="center",
arrowprops=dict(arrowstyle="->", color=RED_DARK, lw=0.8),
)
annotated_solar += 1
# ── Habillage ─────────────────────────────────────────────────────────────────
ax.yaxis.set_ticks_position("both")
ax.tick_params(axis="y", right=True, labelright=True, labelsize=9)
ax.set_ylabel("Température (°C)", fontsize=10)
ax.set_ylim(19, 37)
ax.legend(loc="upper left", fontsize=8, framealpha=0.9)
ax.set_title(
"Zoom canicule 25–28 juin 2026 — croisements T intérieure / T extérieure\n"
"Zones grises : tranches nocturnes (20h–6h)",
fontsize=10
)
ax.grid(axis="y", ls=":", alpha=0.4)
# Axe X : jours en français, heures intermédiaires avec label
ax.xaxis.set_major_locator(mdates.DayLocator(interval=1))
ax.xaxis.set_major_formatter(FuncFormatter(
lambda x, _: fmt_date_fr_court(mdates.num2date(x))
))
ax.xaxis.set_minor_locator(mdates.HourLocator(byhour=[6, 12, 18]))
ax.xaxis.set_minor_formatter(mdates.DateFormatter("%Hh"))
plt.setp(ax.xaxis.get_minorticklabels(), fontsize=7, color="#888888")
plt.setp(ax.xaxis.get_majorticklabels(), rotation=0, ha="center", fontsize=9)
fig.tight_layout()
plt.show()