Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -236,46 +236,101 @@ def predict_crime_level(crime_felony, crime_misd, crime_viol, sr311_total, dob_p
|
|
236 |
else:
|
237 |
return "Predicted Crime Level: High (basic fallback)", {"Low": 0.1, "Medium": 0.3, "High": 0.6}
|
238 |
|
239 |
-
def forecast_time_series(geoid):
|
240 |
"""Forecasts crime for a specific GEOID."""
|
|
|
|
|
241 |
if panel_df is None or 'DUMMY' in panel_df['GEOID'].tolist():
|
242 |
fig, ax = plt.subplots()
|
243 |
ax.text(0.5, 0.5, "Data not loaded", ha='center', va='center')
|
244 |
return fig, "Data not loaded."
|
245 |
|
246 |
if geoid not in panel_df['GEOID'].unique():
|
247 |
-
|
|
|
|
|
|
|
248 |
|
249 |
tract_data = panel_df[panel_df['GEOID'] == geoid].set_index('month')['crime_total'].asfreq('MS')
|
250 |
|
251 |
if len(tract_data) < 24: # Need enough data to forecast
|
252 |
-
|
|
|
|
|
|
|
|
|
253 |
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
|
|
261 |
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
279 |
|
280 |
# --- Gradio App Layout ---
|
281 |
with gr.Blocks() as demo:
|
@@ -372,20 +427,42 @@ with gr.Blocks() as demo:
|
|
372 |
|
373 |
with gr.TabItem("Time Series Forecasting"):
|
374 |
gr.Markdown("## Forecast Future Crime Counts")
|
375 |
-
gr.Markdown("
|
376 |
with gr.Row():
|
377 |
with gr.Column():
|
378 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
379 |
forecast_button = gr.Button("Generate Forecast")
|
380 |
with gr.Column():
|
381 |
-
|
382 |
|
383 |
forecast_plot = gr.Plot()
|
384 |
|
385 |
forecast_button.click(
|
386 |
fn=forecast_time_series,
|
387 |
-
inputs=[
|
388 |
-
outputs=[forecast_plot,
|
389 |
)
|
390 |
|
391 |
if __name__ == "__main__":
|
|
|
236 |
else:
|
237 |
return "Predicted Crime Level: High (basic fallback)", {"Low": 0.1, "Medium": 0.3, "High": 0.6}
|
238 |
|
239 |
+
def forecast_time_series(geoid, selected_metric):
|
240 |
"""Forecasts crime for a specific GEOID."""
|
241 |
+
print(f"DEBUG: forecast_time_series called with GEOID={geoid}, metric={selected_metric}")
|
242 |
+
|
243 |
if panel_df is None or 'DUMMY' in panel_df['GEOID'].tolist():
|
244 |
fig, ax = plt.subplots()
|
245 |
ax.text(0.5, 0.5, "Data not loaded", ha='center', va='center')
|
246 |
return fig, "Data not loaded."
|
247 |
|
248 |
if geoid not in panel_df['GEOID'].unique():
|
249 |
+
empty_fig, ax = plt.subplots(figsize=(12, 6))
|
250 |
+
ax.text(0.5, 0.5, f"GEOID {geoid} not found in the dataset.", ha='center', va='center')
|
251 |
+
ax.set_title("GEOID Not Found")
|
252 |
+
return empty_fig, f"GEOID {geoid} not found in the dataset."
|
253 |
|
254 |
tract_data = panel_df[panel_df['GEOID'] == geoid].set_index('month')['crime_total'].asfreq('MS')
|
255 |
|
256 |
if len(tract_data) < 24: # Need enough data to forecast
|
257 |
+
empty_fig, ax = plt.subplots(figsize=(12, 6))
|
258 |
+
ax.text(0.5, 0.5, f"Not enough historical data for GEOID {geoid}\n(need at least 24 months)",
|
259 |
+
ha='center', va='center')
|
260 |
+
ax.set_title("Insufficient Data")
|
261 |
+
return empty_fig, f"Not enough historical data for GEOID {geoid} to create a forecast."
|
262 |
|
263 |
+
try:
|
264 |
+
# Simple SARIMAX model for demonstration
|
265 |
+
model_ts = SARIMAX(tract_data, order=(1, 1, 1), seasonal_order=(1, 1, 1, 12))
|
266 |
+
results = model_ts.fit(disp=False)
|
267 |
+
|
268 |
+
forecast = results.get_forecast(steps=12)
|
269 |
+
forecast_mean = forecast.predicted_mean
|
270 |
+
forecast_ci = forecast.conf_int()
|
271 |
|
272 |
+
fig, ax = plt.subplots(figsize=(12, 6))
|
273 |
+
tract_data.plot(ax=ax, label='Historical', color='blue')
|
274 |
+
forecast_mean.plot(ax=ax, label='Forecast', color='red')
|
275 |
+
ax.fill_between(forecast_ci.index,
|
276 |
+
forecast_ci.iloc[:, 0],
|
277 |
+
forecast_ci.iloc[:, 1], color='red', alpha=.25, label='Confidence Interval')
|
278 |
+
ax.set_title(f'Crime Forecast for Census Tract {geoid}')
|
279 |
+
ax.set_xlabel('Date')
|
280 |
+
ax.set_ylabel('Crime Total')
|
281 |
+
ax.legend()
|
282 |
+
ax.grid(True)
|
283 |
+
plt.tight_layout()
|
284 |
+
|
285 |
+
# Calculate different metrics based on selection
|
286 |
+
# For demonstration, we'll use in-sample fit statistics
|
287 |
+
metrics_text = f"Forecast Results for GEOID: {geoid}\n"
|
288 |
+
metrics_text += f"Selected Metric: {selected_metric}\n"
|
289 |
+
metrics_text += "="*50 + "\n\n"
|
290 |
+
|
291 |
+
if selected_metric == "Mean Absolute Error (MAE)":
|
292 |
+
# Calculate MAE on fitted values vs actual
|
293 |
+
fitted_values = results.fittedvalues
|
294 |
+
mae = np.mean(np.abs(tract_data - fitted_values))
|
295 |
+
metrics_text += f"In-Sample MAE: {mae:.2f}\n"
|
296 |
+
metrics_text += "Lower MAE indicates better model fit.\n"
|
297 |
+
|
298 |
+
elif selected_metric == "Root Mean Square Error (RMSE)":
|
299 |
+
fitted_values = results.fittedvalues
|
300 |
+
rmse = np.sqrt(np.mean((tract_data - fitted_values)**2))
|
301 |
+
metrics_text += f"In-Sample RMSE: {rmse:.2f}\n"
|
302 |
+
metrics_text += "Lower RMSE indicates better model fit.\n"
|
303 |
+
|
304 |
+
elif selected_metric == "Mean Absolute Percentage Error (MAPE)":
|
305 |
+
fitted_values = results.fittedvalues
|
306 |
+
mape = np.mean(np.abs((tract_data - fitted_values) / tract_data)) * 100
|
307 |
+
metrics_text += f"In-Sample MAPE: {mape:.2f}%\n"
|
308 |
+
metrics_text += "Lower MAPE indicates better model fit.\n"
|
309 |
+
|
310 |
+
elif selected_metric == "Akaike Information Criterion (AIC)":
|
311 |
+
aic = results.aic
|
312 |
+
metrics_text += f"AIC: {aic:.2f}\n"
|
313 |
+
metrics_text += "Lower AIC indicates better model quality.\n"
|
314 |
+
|
315 |
+
elif selected_metric == "Bayesian Information Criterion (BIC)":
|
316 |
+
bic = results.bic
|
317 |
+
metrics_text += f"BIC: {bic:.2f}\n"
|
318 |
+
metrics_text += "Lower BIC indicates better model quality.\n"
|
319 |
+
|
320 |
+
metrics_text += f"\nForecast Summary:\n"
|
321 |
+
metrics_text += f"• Historical data points: {len(tract_data)}\n"
|
322 |
+
metrics_text += f"• Forecast horizon: 12 months\n"
|
323 |
+
metrics_text += f"• Average historical crime: {tract_data.mean():.2f}\n"
|
324 |
+
metrics_text += f"• Average forecast: {forecast_mean.mean():.2f}\n"
|
325 |
+
|
326 |
+
return fig, metrics_text
|
327 |
+
|
328 |
+
except Exception as e:
|
329 |
+
print(f"DEBUG: Error in forecasting: {e}")
|
330 |
+
error_fig, ax = plt.subplots(figsize=(12, 6))
|
331 |
+
ax.text(0.5, 0.5, f"Error in forecasting:\n{str(e)}", ha='center', va='center')
|
332 |
+
ax.set_title("Forecasting Error")
|
333 |
+
return error_fig, f"Error in forecasting for GEOID {geoid}: {str(e)}"
|
334 |
|
335 |
# --- Gradio App Layout ---
|
336 |
with gr.Blocks() as demo:
|
|
|
427 |
|
428 |
with gr.TabItem("Time Series Forecasting"):
|
429 |
gr.Markdown("## Forecast Future Crime Counts")
|
430 |
+
gr.Markdown("Select a Census Tract GEOID to forecast the total crime count for the next 12 months.")
|
431 |
with gr.Row():
|
432 |
with gr.Column():
|
433 |
+
# Create list of available GEOIDs for dropdown
|
434 |
+
available_geoids = sorted(panel_df['GEOID'].unique().tolist()) if 'DUMMY' not in panel_df['GEOID'].tolist() else ['36005000100', '36005000200']
|
435 |
+
|
436 |
+
geoid_dropdown = gr.Dropdown(
|
437 |
+
label="Select GEOID",
|
438 |
+
choices=available_geoids,
|
439 |
+
value=available_geoids[0] if available_geoids else None,
|
440 |
+
allow_custom_value=True,
|
441 |
+
filterable=True,
|
442 |
+
info="Type to search or select from list"
|
443 |
+
)
|
444 |
+
|
445 |
+
forecast_metrics_dropdown = gr.Dropdown(
|
446 |
+
label="Forecast Evaluation Metric",
|
447 |
+
choices=["Mean Absolute Error (MAE)",
|
448 |
+
"Root Mean Square Error (RMSE)",
|
449 |
+
"Mean Absolute Percentage Error (MAPE)",
|
450 |
+
"Akaike Information Criterion (AIC)",
|
451 |
+
"Bayesian Information Criterion (BIC)"],
|
452 |
+
value="Mean Absolute Error (MAE)",
|
453 |
+
info="Select metric to display in forecast evaluation"
|
454 |
+
)
|
455 |
+
|
456 |
forecast_button = gr.Button("Generate Forecast")
|
457 |
with gr.Column():
|
458 |
+
forecast_metrics_output = gr.Textbox(label="Forecast Metrics", interactive=False, lines=5)
|
459 |
|
460 |
forecast_plot = gr.Plot()
|
461 |
|
462 |
forecast_button.click(
|
463 |
fn=forecast_time_series,
|
464 |
+
inputs=[geoid_dropdown, forecast_metrics_dropdown],
|
465 |
+
outputs=[forecast_plot, forecast_metrics_output]
|
466 |
)
|
467 |
|
468 |
if __name__ == "__main__":
|