"""Structures et helpers communs pour les analyses météorologiques.""" from __future__ import annotations from dataclasses import dataclass import numpy as np import pandas as pd __all__ = ['MONTH_ORDER', 'DiurnalCycleStats', 'BinnedStatistics'] MONTH_ORDER = list(range(1, 13)) @dataclass class DiurnalCycleStats: """Conteneur pour les statistiques agrégées par heure (moyenne, médiane et quantiles optionnels).""" mean: pd.DataFrame median: pd.DataFrame quantile_low: pd.DataFrame | None quantile_high: pd.DataFrame | None quantile_low_level: float | None = None quantile_high_level: float | None = None @dataclass class BinnedStatistics: """Structure englobant les résultats calculés sur des intervalles (bins) réguliers ou personnalisés.""" centers: np.ndarray intervals: pd.IntervalIndex counts: pd.Series mean: pd.DataFrame median: pd.DataFrame quantile_low: pd.DataFrame | None quantile_high: pd.DataFrame | None quantile_low_level: float | None = None quantile_high_level: float | None = None def _ensure_datetime_index(df: pd.DataFrame) -> pd.DatetimeIndex: """Valide la présence d'un index temporel et le retourne pour uniformiser les traitements.""" if not isinstance(df.index, pd.DatetimeIndex): raise TypeError("Cette fonction nécessite un DataFrame indexé par le temps.") return df.index def _infer_time_step(index: pd.DatetimeIndex) -> pd.Timedelta: """Estime la résolution temporelle représentative (médiane) d'un index daté.""" diffs = index.to_series().diff().dropna() if diffs.empty: return pd.Timedelta(minutes=1) return diffs.median()