103 lines
3.1 KiB
Python
103 lines
3.1 KiB
Python
"""Outils de moyennage saisonnier/mensuel et de profils horaires."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Sequence
|
||
|
||
import pandas as pd
|
||
|
||
from meteo.season import SEASON_LABELS
|
||
|
||
from .core import MONTH_ORDER, _ensure_datetime_index, _infer_time_step
|
||
|
||
__all__ = ['compute_monthly_climatology', 'compute_monthly_means', 'compute_seasonal_hourly_profile', 'compute_monthly_daylight_hours']
|
||
|
||
|
||
def compute_monthly_climatology(
|
||
df: pd.DataFrame,
|
||
*,
|
||
columns: Sequence[str],
|
||
) -> pd.DataFrame:
|
||
"""
|
||
Moyenne par mois (1–12) pour les colonnes fournies.
|
||
"""
|
||
_ensure_datetime_index(df)
|
||
missing = [col for col in columns if col not in df.columns]
|
||
if missing:
|
||
raise KeyError(f"Colonnes absentes : {missing}")
|
||
|
||
grouped = df[list(columns)].groupby(df.index.month).mean()
|
||
grouped = grouped.reindex(MONTH_ORDER)
|
||
grouped.index.name = "month"
|
||
return grouped
|
||
|
||
def compute_monthly_means(
|
||
df: pd.DataFrame,
|
||
*,
|
||
columns: Sequence[str],
|
||
) -> pd.DataFrame:
|
||
"""
|
||
Moyennes calendaire par mois (indexé sur la fin de mois).
|
||
"""
|
||
_ensure_datetime_index(df)
|
||
missing = [col for col in columns if col not in df.columns]
|
||
if missing:
|
||
raise KeyError(f"Colonnes absentes : {missing}")
|
||
|
||
monthly = df[list(columns)].resample("1ME").mean()
|
||
return monthly.dropna(how="all")
|
||
|
||
def compute_seasonal_hourly_profile(
|
||
df: pd.DataFrame,
|
||
*,
|
||
value_column: str,
|
||
season_column: str = "season",
|
||
) -> pd.DataFrame:
|
||
"""
|
||
Retourne une matrice (heures x saisons) contenant la moyenne d'une variable.
|
||
"""
|
||
_ensure_datetime_index(df)
|
||
for col in (value_column, season_column):
|
||
if col not in df.columns:
|
||
raise KeyError(f"Colonne absente : {col}")
|
||
|
||
subset = df[[value_column, season_column]].dropna()
|
||
if subset.empty:
|
||
return pd.DataFrame(index=range(24))
|
||
|
||
grouped = subset.groupby([season_column, subset.index.hour])[value_column].mean()
|
||
pivot = grouped.unstack(season_column)
|
||
pivot = pivot.reindex(index=range(24))
|
||
order = [season for season in SEASON_LABELS if season in pivot.columns]
|
||
if order:
|
||
pivot = pivot[order]
|
||
pivot.index.name = "hour"
|
||
return pivot
|
||
|
||
def compute_monthly_daylight_hours(
|
||
df: pd.DataFrame,
|
||
*,
|
||
illuminance_column: str = "illuminance",
|
||
threshold_lux: float = 1000.0,
|
||
) -> pd.Series:
|
||
"""
|
||
Calcule la durée moyenne de luminosité (> threshold_lux) par mois (en heures par jour).
|
||
"""
|
||
_ensure_datetime_index(df)
|
||
if illuminance_column not in df.columns:
|
||
raise KeyError(f"Colonne absente : {illuminance_column}")
|
||
|
||
subset = df[[illuminance_column]].dropna()
|
||
if subset.empty:
|
||
return pd.Series(dtype=float)
|
||
|
||
time_step = _infer_time_step(subset.index)
|
||
hours_per_step = time_step.total_seconds() / 3600.0
|
||
|
||
daylight_flag = (subset[illuminance_column] >= threshold_lux).astype(float)
|
||
daylight_hours = daylight_flag * hours_per_step
|
||
|
||
daily_hours = daylight_hours.resample("1D").sum()
|
||
monthly_avg = daily_hours.resample("1ME").mean()
|
||
return monthly_avg.dropna()
|