Spaces:
Running
Running
Commit ·
a2f70bd
1
Parent(s): 37886e5
Corrigir previsão de casos
Browse files- predict.py +61 -40
predict.py
CHANGED
|
@@ -9,7 +9,6 @@ import tensorflow as tf
|
|
| 9 |
import matplotlib.pyplot as plt
|
| 10 |
import base64
|
| 11 |
from io import BytesIO
|
| 12 |
-
import math
|
| 13 |
|
| 14 |
warnings.filterwarnings('ignore')
|
| 15 |
plt.style.use('seaborn-v0_8-darkgrid')
|
|
@@ -32,15 +31,13 @@ class DenguePredictor:
|
|
| 32 |
self.load_assets()
|
| 33 |
|
| 34 |
def load_assets(self):
|
| 35 |
-
AI_ASSETS_DIR = self.project_root / "models"
|
| 36 |
INFERENCE_PATH = self.project_root / "data" / "inference_data.parquet"
|
| 37 |
SCALER_DIR = AI_ASSETS_DIR / "scalers"
|
| 38 |
MODEL_PATH = AI_ASSETS_DIR / "checkpoints" / "model_checkpoint_best.keras"
|
| 39 |
|
| 40 |
-
|
| 41 |
self.scaler_dir = SCALER_DIR
|
| 42 |
|
| 43 |
-
# Carregar dados atualizados
|
| 44 |
df_master = pd.read_parquet(INFERENCE_PATH)
|
| 45 |
df_master['codigo_ibge'] = df_master['codigo_ibge'].astype(int)
|
| 46 |
df_master['data_semana_iso'] = pd.to_datetime(
|
|
@@ -51,7 +48,6 @@ class DenguePredictor:
|
|
| 51 |
self.df_master = df_master
|
| 52 |
self.municipios = df_master[['codigo_ibge', 'municipio']].drop_duplicates().sort_values('codigo_ibge')
|
| 53 |
|
| 54 |
-
# Carregar modelo LSTM
|
| 55 |
self.model = tf.keras.models.load_model(MODEL_PATH)
|
| 56 |
|
| 57 |
def plot_to_base64(self):
|
|
@@ -70,7 +66,6 @@ class DenguePredictor:
|
|
| 70 |
return scaler.inverse_transform(dummy)[:, 0]
|
| 71 |
|
| 72 |
def get_scalers(self, ibge_code: int):
|
| 73 |
-
"""Carrega scalers do município apenas uma vez."""
|
| 74 |
if ibge_code not in self.scaler_cache:
|
| 75 |
dynamic_scaler = joblib.load(self.scaler_dir / "dynamics" / f"{ibge_code}_dynamic.pkl")
|
| 76 |
static_scaler = joblib.load(self.scaler_dir / "statics" / f"{ibge_code}_static.pkl")
|
|
@@ -78,70 +73,97 @@ class DenguePredictor:
|
|
| 78 |
return self.scaler_cache[ibge_code]["dynamic"], self.scaler_cache[ibge_code]["static"]
|
| 79 |
|
| 80 |
def predict(self, ibge_code: int, weeks_to_predict: int):
|
| 81 |
-
# Carregar scalers
|
| 82 |
scaler_dyn, scaler_static = self.get_scalers(ibge_code)
|
| 83 |
-
|
| 84 |
-
# Obter dados do município
|
| 85 |
df_mun = self.df_master[self.df_master['codigo_ibge'] == ibge_code].copy().reset_index(drop=True)
|
| 86 |
if df_mun.empty:
|
| 87 |
raise ValueError(f"Não há dados para o município {ibge_code}")
|
| 88 |
-
municipio_name = self.municipios[self.municipios['codigo_ibge'] == ibge_code].iloc[0]['municipio']
|
| 89 |
|
|
|
|
| 90 |
dynamic_features = list(self.feature_names_pt.keys())
|
| 91 |
-
# Pegar últimas 'sequence_length' semanas para iniciar a previsão
|
| 92 |
-
last_sequence = df_mun[dynamic_features].iloc[-self.sequence_length:].copy()
|
| 93 |
-
|
| 94 |
-
# Substituir NaNs da sequência inicial por zeros apenas para o scaler (não para salvar)
|
| 95 |
-
# Isso é só para evitar erro do scaler; depois o modelo vai gerar previsão real
|
| 96 |
-
last_sequence_filled = last_sequence.fillna(0)
|
| 97 |
-
dynamic_sequence_scaled = scaler_dyn.transform(last_sequence_filled)
|
| 98 |
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
static_input = scaler_static.transform(static_data)
|
| 101 |
-
|
| 102 |
-
# Lookup de clima por (ano, semana)
|
| 103 |
climate_lookup = {(row['ano'], row['semana']): row for _, row in df_mun.iterrows()}
|
| 104 |
-
|
| 105 |
predictions = []
|
| 106 |
-
|
|
|
|
| 107 |
|
| 108 |
for i in range(weeks_to_predict):
|
| 109 |
-
dynamic_input = np.array([
|
| 110 |
pred_scaled = self.model.predict([dynamic_input, static_input], verbose=0)[0][0]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
future_date = last_date + timedelta(weeks=i + 1)
|
| 115 |
predictions.append({
|
| 116 |
"date": future_date.strftime('%Y-%m-%d'),
|
| 117 |
"predicted_cases": max(0, round(pred_real))
|
| 118 |
})
|
| 119 |
-
|
|
|
|
| 120 |
future_year, future_week = future_date.year, future_date.isocalendar()[1]
|
| 121 |
if (future_year, future_week) in climate_lookup:
|
| 122 |
future_climate = climate_lookup[(future_year, future_week)]
|
| 123 |
else:
|
| 124 |
-
|
| 125 |
-
|
| 126 |
|
| 127 |
new_row = np.zeros(len(dynamic_features), dtype=np.float32)
|
| 128 |
-
new_row[0] = pred_scaled
|
| 129 |
for j, feat in enumerate(dynamic_features[1:], start=1):
|
| 130 |
new_row[j] = future_climate[feat]
|
| 131 |
-
|
| 132 |
-
# 6) Escala a linha futura e atualiza sequência
|
| 133 |
new_row_scaled = scaler_dyn.transform(new_row.reshape(1, -1))[0]
|
| 134 |
-
|
| 135 |
|
|
|
|
| 136 |
# Histórico das últimas 52 semanas
|
| 137 |
-
historic_data = [
|
| 138 |
-
{"date": row['data_semana_iso'].strftime('%Y-%m-%d'),
|
| 139 |
-
"cases": int(row["numero_casos"]) if not pd.isna(row["numero_casos"]) else 0}
|
| 140 |
-
for _, row in df_mun.tail(52).iterrows()
|
| 141 |
-
]
|
| 142 |
|
| 143 |
|
| 144 |
-
#
|
| 145 |
df_analysis = df_mun[dynamic_features].rename(columns=self.feature_names_pt)
|
| 146 |
max_lag = 12
|
| 147 |
cases_col_name = 'Nº de Casos de Dengue'
|
|
@@ -157,7 +179,6 @@ class DenguePredictor:
|
|
| 157 |
corrs.append(corr)
|
| 158 |
lag_correlations[col] = corrs
|
| 159 |
|
| 160 |
-
|
| 161 |
plt.figure(figsize=(10, 6), facecolor='#18181b')
|
| 162 |
ax = plt.gca()
|
| 163 |
ax.set_facecolor('#18181b')
|
|
|
|
| 9 |
import matplotlib.pyplot as plt
|
| 10 |
import base64
|
| 11 |
from io import BytesIO
|
|
|
|
| 12 |
|
| 13 |
warnings.filterwarnings('ignore')
|
| 14 |
plt.style.use('seaborn-v0_8-darkgrid')
|
|
|
|
| 31 |
self.load_assets()
|
| 32 |
|
| 33 |
def load_assets(self):
|
| 34 |
+
AI_ASSETS_DIR = self.project_root / "models"
|
| 35 |
INFERENCE_PATH = self.project_root / "data" / "inference_data.parquet"
|
| 36 |
SCALER_DIR = AI_ASSETS_DIR / "scalers"
|
| 37 |
MODEL_PATH = AI_ASSETS_DIR / "checkpoints" / "model_checkpoint_best.keras"
|
| 38 |
|
|
|
|
| 39 |
self.scaler_dir = SCALER_DIR
|
| 40 |
|
|
|
|
| 41 |
df_master = pd.read_parquet(INFERENCE_PATH)
|
| 42 |
df_master['codigo_ibge'] = df_master['codigo_ibge'].astype(int)
|
| 43 |
df_master['data_semana_iso'] = pd.to_datetime(
|
|
|
|
| 48 |
self.df_master = df_master
|
| 49 |
self.municipios = df_master[['codigo_ibge', 'municipio']].drop_duplicates().sort_values('codigo_ibge')
|
| 50 |
|
|
|
|
| 51 |
self.model = tf.keras.models.load_model(MODEL_PATH)
|
| 52 |
|
| 53 |
def plot_to_base64(self):
|
|
|
|
| 66 |
return scaler.inverse_transform(dummy)[:, 0]
|
| 67 |
|
| 68 |
def get_scalers(self, ibge_code: int):
|
|
|
|
| 69 |
if ibge_code not in self.scaler_cache:
|
| 70 |
dynamic_scaler = joblib.load(self.scaler_dir / "dynamics" / f"{ibge_code}_dynamic.pkl")
|
| 71 |
static_scaler = joblib.load(self.scaler_dir / "statics" / f"{ibge_code}_static.pkl")
|
|
|
|
| 73 |
return self.scaler_cache[ibge_code]["dynamic"], self.scaler_cache[ibge_code]["static"]
|
| 74 |
|
| 75 |
def predict(self, ibge_code: int, weeks_to_predict: int):
|
|
|
|
| 76 |
scaler_dyn, scaler_static = self.get_scalers(ibge_code)
|
|
|
|
|
|
|
| 77 |
df_mun = self.df_master[self.df_master['codigo_ibge'] == ibge_code].copy().reset_index(drop=True)
|
| 78 |
if df_mun.empty:
|
| 79 |
raise ValueError(f"Não há dados para o município {ibge_code}")
|
|
|
|
| 80 |
|
| 81 |
+
municipio_name = self.municipios[self.municipios['codigo_ibge'] == ibge_code].iloc[0]['municipio']
|
| 82 |
dynamic_features = list(self.feature_names_pt.keys())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
+
# --- LÓGICA DE PRÉ-PREDIÇÃO PARA O PRIMEIRO NaN ---
|
| 85 |
+
# Verifica se a última semana no dataframe tem casos NaN (nosso cenário da semana 32)
|
| 86 |
+
if pd.isna(df_mun['numero_casos'].iloc[-1]):
|
| 87 |
+
print("INFO: Última semana com NaN detectada. Realizando predição inicial para preenchê-la.")
|
| 88 |
+
|
| 89 |
+
# 1. Pega as 12 semanas ANTERIORES à semana com NaN. Esta sequência está completa.
|
| 90 |
+
# Ex: se a linha 100 é a semana 32 (com NaN), pegamos da 88 a 99 (12 semanas)
|
| 91 |
+
initial_sequence_df = df_mun.iloc[-self.sequence_length-1:-1]
|
| 92 |
+
|
| 93 |
+
# Garante que os dados de CLIMA desta sequência inicial estão preenchidos (apenas por segurança)
|
| 94 |
+
for col in dynamic_features[1:]: # Ignora 'numero_casos' aqui pois já sabemos que está preenchido
|
| 95 |
+
if initial_sequence_df[col].isna().any():
|
| 96 |
+
col_mean = initial_sequence_df[col].mean(skipna=True)
|
| 97 |
+
initial_sequence_df.loc[:, col] = initial_sequence_df[col].fillna(col_mean)
|
| 98 |
+
|
| 99 |
+
# 2. Prepara os dados de entrada para o modelo
|
| 100 |
+
dynamic_input_scaled = scaler_dyn.transform(initial_sequence_df[dynamic_features])
|
| 101 |
+
dynamic_input = np.array([dynamic_input_scaled], dtype=np.float32)
|
| 102 |
+
|
| 103 |
+
# Os dados estáticos são da própria semana que queremos prever (a com NaN)
|
| 104 |
+
static_input = scaler_static.transform(df_mun.iloc[-1][["latitude", "longitude"]].values.reshape(1, -1))
|
| 105 |
+
|
| 106 |
+
# 3. Faz a predição da semana que continha o NaN
|
| 107 |
+
pred_scaled_initial = self.model.predict([dynamic_input, static_input], verbose=0)[0][0]
|
| 108 |
+
pred_real_initial = self.inverse_transform_cases(scaler_dyn, np.array([pred_scaled_initial]))[0]
|
| 109 |
+
|
| 110 |
+
predicted_cases_initial = max(0, round(pred_real_initial))
|
| 111 |
+
|
| 112 |
+
# 4. ATUALIZA o dataframe em memória com o valor previsto, substituindo o NaN
|
| 113 |
+
df_mun.iloc[-1, df_mun.columns.get_loc('numero_casos')] = predicted_cases_initial
|
| 114 |
+
print(f"INFO: Valor previsto para {df_mun.iloc[-1]['data_semana_iso'].date()}: {predicted_cases_initial} casos")
|
| 115 |
+
|
| 116 |
+
# --- FIM DA LÓGICA DE PRÉ-PREDIÇÃO ---
|
| 117 |
+
|
| 118 |
+
# Agora, o loop de predição principal começa a partir de um dataframe completo
|
| 119 |
+
|
| 120 |
+
# Pega a sequência final ATUALIZADA para iniciar o loop de predições futuras
|
| 121 |
+
last_sequence_scaled = scaler_dyn.transform(df_mun[dynamic_features].iloc[-self.sequence_length:])
|
| 122 |
+
|
| 123 |
+
static_data = df_mun[["latitude", "longitude"]].iloc[-1].values.reshape(1, -1)
|
| 124 |
static_input = scaler_static.transform(static_data)
|
| 125 |
+
|
|
|
|
| 126 |
climate_lookup = {(row['ano'], row['semana']): row for _, row in df_mun.iterrows()}
|
|
|
|
| 127 |
predictions = []
|
| 128 |
+
# A última data REAL no dataframe original
|
| 129 |
+
last_real_date = df_mun.iloc[-1]['data_semana_iso']
|
| 130 |
|
| 131 |
for i in range(weeks_to_predict):
|
| 132 |
+
dynamic_input = np.array([last_sequence_scaled], dtype=np.float32)
|
| 133 |
pred_scaled = self.model.predict([dynamic_input, static_input], verbose=0)[0][0]
|
| 134 |
+
|
| 135 |
+
pred_real = 0
|
| 136 |
+
if pd.notna(pred_scaled) and np.isfinite(pred_scaled):
|
| 137 |
+
pred_real = self.inverse_transform_cases(scaler_dyn, np.array([pred_scaled]))[0]
|
| 138 |
|
| 139 |
+
future_date = last_real_date + timedelta(weeks=i + 1)
|
|
|
|
|
|
|
| 140 |
predictions.append({
|
| 141 |
"date": future_date.strftime('%Y-%m-%d'),
|
| 142 |
"predicted_cases": max(0, round(pred_real))
|
| 143 |
})
|
| 144 |
+
|
| 145 |
+
# Monta a próxima linha para a sequência rolante
|
| 146 |
future_year, future_week = future_date.year, future_date.isocalendar()[1]
|
| 147 |
if (future_year, future_week) in climate_lookup:
|
| 148 |
future_climate = climate_lookup[(future_year, future_week)]
|
| 149 |
else:
|
| 150 |
+
# Se não houver clima futuro, usa a média das últimas 4 semanas
|
| 151 |
+
future_climate = df_mun[dynamic_features[1:]].tail(4).mean(axis=0, skipna=True)
|
| 152 |
|
| 153 |
new_row = np.zeros(len(dynamic_features), dtype=np.float32)
|
| 154 |
+
new_row[0] = pred_scaled
|
| 155 |
for j, feat in enumerate(dynamic_features[1:], start=1):
|
| 156 |
new_row[j] = future_climate[feat]
|
| 157 |
+
|
|
|
|
| 158 |
new_row_scaled = scaler_dyn.transform(new_row.reshape(1, -1))[0]
|
| 159 |
+
last_sequence_scaled = np.vstack([last_sequence_scaled[1:], new_row_scaled])
|
| 160 |
|
| 161 |
+
# ... O resto da sua função (geração de gráficos, etc.) continua igual ...
|
| 162 |
# Histórico das últimas 52 semanas
|
| 163 |
+
historic_data = [ {"date": row['data_semana_iso'].strftime('%Y-%m-%d'), "cases": int(row["numero_casos"]) if not pd.isna(row["numero_casos"]) else None} for _, row in df_mun.tail(52).iterrows() ]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
|
| 165 |
|
| 166 |
+
# Lag analysis
|
| 167 |
df_analysis = df_mun[dynamic_features].rename(columns=self.feature_names_pt)
|
| 168 |
max_lag = 12
|
| 169 |
cases_col_name = 'Nº de Casos de Dengue'
|
|
|
|
| 179 |
corrs.append(corr)
|
| 180 |
lag_correlations[col] = corrs
|
| 181 |
|
|
|
|
| 182 |
plt.figure(figsize=(10, 6), facecolor='#18181b')
|
| 183 |
ax = plt.gca()
|
| 184 |
ax.set_facecolor('#18181b')
|