ECOHEALTH / feature_handler.py
angeetoile's picture
Update feature_handler.py
d09b7ea verified
Raw
History Blame Contribute Delete
4.39 kB
import pandas as pd
import numpy as np
from config import CITIES_COORDS
def apply_feature_engineering(df_input):
df = df_input.copy()
# 1. Nettoyage des types (Évite l'erreur TypeError str vs int)
df['time'] = pd.to_datetime(df['time'])
numeric_cols = [
'temperature_2m_max', 'temperature_2m_min', 'temperature_2m_mean',
'precipitation_sum', 'wind_speed_10m_max', 'wind_gusts_10m_max',
'shortwave_radiation_sum', 'et0_fao_evapotranspiration', 'sunshine_duration', 'daylight_duration'
]
for col in numeric_cols:
if col in df.columns:
df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0)
# 2. Tri impératif pour les calculs de séries temporelles
df = df.sort_values(['city', 'time']).reset_index(drop=True)
# 3. Calendrier
df['mois'] = df['time'].dt.month
df['annee'] = df['time'].dt.year
df['quarter'] = df['time'].dt.quarter
df['day_of_year'] = df['time'].dt.dayofyear
df['month_sin'] = np.sin(2 * np.pi * df['mois'] / 12)
df['month_cos'] = np.cos(2 * np.pi * df['mois'] / 12)
df['day_sin'] = np.sin(2 * np.pi * df['day_of_year'] / 365)
df['day_cos'] = np.cos(2 * np.pi * df['day_of_year'] / 365)
# 4. Physique et Indices
df['amplitude_thermique'] = df['temperature_2m_max'] - df['temperature_2m_min']
df['ecart_ressenti'] = df['apparent_temperature_mean'] - df['temperature_2m_mean']
df['bilan_hydrique'] = df['precipitation_sum'] - df['et0_fao_evapotranspiration']
df['sunshine_ratio'] = df['sunshine_duration'] / (df['daylight_duration'] + 1e-6)
df['gust_ratio'] = df['wind_gusts_10m_max'] / (df['wind_speed_10m_max'] + 1e-6)
df['heat_stress'] = df['temperature_2m_mean'] * df['et0_fao_evapotranspiration']
df['temp_per_radiation'] = df['temperature_2m_mean'] / (df['shortwave_radiation_sum'] + 1e-6)
df['is_dry_season'] = df['mois'].isin([11, 12, 1, 2, 3]).astype(int)
df['is_weekend'] = df['time'].dt.dayofweek.isin([5, 6]).astype(int)
df['is_no_rain'] = (df['precipitation_sum'] < 0.1).astype(int)
df['is_no_wind'] = (df['wind_speed_10m_max'] < 5).astype(int)
df['stagnation_index'] = ((df['wind_speed_10m_max'] < 15) & (df['precipitation_sum'] == 0)).astype(int)
df['is_hot_day'] = (df['temperature_2m_max'] >= 35).astype(int)
df['is_heavy_rain'] = (df['precipitation_sum'] >= 20).astype(int)
# 5. Groupement par ville pour les Lags et Rolling
# On utilise groupby().transform pour garder la taille originale du DF
grouped = df.groupby('city')
for lag in [1, 3, 7]:
df[f'temp_lag{lag}'] = grouped['temperature_2m_mean'].shift(lag)
df[f'wind_lag{lag}'] = grouped['wind_speed_10m_max'].shift(lag)
df[f'precip_lag{lag}'] = grouped['precipitation_sum'].shift(lag)
df[f'wind_dir_lag{lag}'] = grouped['wind_direction_10m_dominant'].shift(lag)
df[f'sunshine_lag{lag}'] = grouped['sunshine_duration'].shift(lag)
df['temp_roll7'] = grouped['temperature_2m_mean'].transform(lambda x: x.rolling(7, min_periods=1).mean())
df['precip_roll7'] = grouped['precipitation_sum'].transform(lambda x: x.rolling(7, min_periods=1).mean())
df['wind_roll7'] = grouped['wind_speed_10m_max'].transform(lambda x: x.rolling(7, min_periods=1).mean())
df['temp_roll30'] = grouped['temperature_2m_mean'].transform(lambda x: x.rolling(30, min_periods=1).mean())
df['temp_anomaly'] = df['temperature_2m_mean'] - df['temp_roll30']
df['precip_cumul7'] = grouped['precipitation_sum'].transform(lambda x: x.rolling(7, min_periods=1).sum())
df['precip_cumul3'] = grouped['precipitation_sum'].transform(lambda x: x.rolling(3, min_periods=1).sum())
def get_region_name(city):
return CITIES_COORDS.get(city, {}).get('region', 'Littoral')
df['region'] = df['city'].apply(get_region_name)
def categorize_weather(code):
if code == 0: return 'Ciel dégagé'
elif code in [1, 2]: return 'Nuageux'
elif code == 3: return 'Couvert'
else: return 'Pluie/Bruine'
df['weather_categorie'] = df['weather_code'].apply(categorize_weather)
df['weather_encoded'] = df['weather_code'].astype(float)
cols_to_fix = [c for c in df.columns if 'lag' in c or 'roll' in c]
for col in cols_to_fix:
df[col] = df.groupby('city')[col].ffill().bfill()
return df.fillna(0)