Juin 2026

Visualisation des données

Date de publication

29 juin 2026

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.

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

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()
Figure 1: Comparaison des températures intérieure (Aranet4, pièce principale et chambre) et extérieure (MétéoSuisse, station GVE — Genève-Cointrin, données horaires). Période : 3–28 juin 2026. Zone orange : période de canicule officielle (plan cantonal GE, dès le 18 juin). Bandes violettes : nuits tropicales (T ext min ≥ 20 °C, 20h–6h). Source données météo : MétéoSuisse OGD.

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()
Figure 2: Zoom sur la fin de la canicule, 25–28 juin 2026. Croisement vespéral : la température extérieure (en baisse) rejoint la température intérieure → ouverture des fenêtres et stores. Divergence matinale : la température intérieure repart à la hausse sous l’effet du rayonnement solaire sur les vitrages et stores métalliques, alors que la température extérieure est encore basse. Zones grises : tranches nocturnes (20h–6h).