Spaces:
Runtime error
Runtime error
| import streamlit as st | |
| import pandas as pd | |
| import numpy as np | |
| import joblib | |
| import os | |
| import plotly.express as px | |
| import plotly.graph_objects as go | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # PAGE CONFIG | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| st.set_page_config( | |
| page_title="GeoHazard ML", | |
| page_icon="๐", | |
| layout="wide", | |
| initial_sidebar_state="collapsed", | |
| ) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # CUSTOM CSS | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| st.markdown(""" | |
| <style> | |
| /* Main background */ | |
| .stApp { background-color: #0e1117; color: #e0e0e0; } | |
| /* Header banner */ | |
| .hero-banner { | |
| background: linear-gradient(135deg, #1a1f35 0%, #0d2137 50%, #1a1f35 100%); | |
| border: 1px solid #2a3a5c; | |
| border-radius: 12px; | |
| padding: 2rem 2.5rem; | |
| margin-bottom: 1.5rem; | |
| text-align: center; | |
| } | |
| .hero-banner h1 { font-size: 2.4rem; font-weight: 800; color: #4fc3f7; margin: 0; } | |
| .hero-banner p { color: #90caf9; font-size: 1.05rem; margin: 0.5rem 0 0; } | |
| /* Risk badge colours */ | |
| .badge { | |
| display: inline-block; | |
| padding: 0.45rem 1.2rem; | |
| border-radius: 20px; | |
| font-size: 1.1rem; | |
| font-weight: 700; | |
| letter-spacing: 0.5px; | |
| } | |
| .badge-very-high { background:#b71c1c; color:#fff; } | |
| .badge-high { background:#e65100; color:#fff; } | |
| .badge-medium { background:#f57f17; color:#fff; } | |
| .badge-low { background:#2e7d32; color:#fff; } | |
| .badge-very-low { background:#1565c0; color:#fff; } | |
| /* Metric cards */ | |
| .metric-card { | |
| background: #1a2035; | |
| border: 1px solid #2a3a5c; | |
| border-radius: 10px; | |
| padding: 1rem 1.2rem; | |
| text-align: center; | |
| } | |
| .metric-card .val { font-size: 1.8rem; font-weight: 700; color: #4fc3f7; } | |
| .metric-card .lbl { font-size: 0.8rem; color: #90a4ae; margin-top: 2px; } | |
| /* Section headers */ | |
| .section-title { | |
| font-size: 1.15rem; | |
| font-weight: 700; | |
| color: #4fc3f7; | |
| border-left: 4px solid #4fc3f7; | |
| padding-left: 0.7rem; | |
| margin: 1.2rem 0 0.8rem; | |
| } | |
| /* Result box */ | |
| .result-box { | |
| background: #1a2035; | |
| border: 1px solid #2a3a5c; | |
| border-radius: 12px; | |
| padding: 1.5rem; | |
| text-align: center; | |
| } | |
| /* Tab styling */ | |
| .stTabs [data-baseweb="tab-list"] { gap: 4px; } | |
| .stTabs [data-baseweb="tab"] { | |
| background: #1a2035; | |
| border-radius: 8px 8px 0 0; | |
| color: #90caf9; | |
| padding: 0.5rem 1.2rem; | |
| } | |
| .stTabs [aria-selected="true"] { | |
| background: #0d2137 !important; | |
| color: #4fc3f7 !important; | |
| border-bottom: 2px solid #4fc3f7; | |
| } | |
| /* Input labels */ | |
| label { color: #b0bec5 !important; font-size: 0.88rem !important; } | |
| /* Divider */ | |
| hr { border-color: #2a3a5c; } | |
| /* Hide Streamlit branding */ | |
| #MainMenu, footer { visibility: hidden; } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # MODEL LOADING | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def load_models(): | |
| base = os.path.dirname(os.path.abspath(__file__)) | |
| model_dir = os.path.join(base, "models") | |
| rf = joblib.load(os.path.join(model_dir, "rf_model.pkl")) | |
| xgb = joblib.load(os.path.join(model_dir, "xgb_model.pkl")) | |
| scaler = joblib.load(os.path.join(model_dir, "scaler.pkl")) | |
| le = joblib.load(os.path.join(model_dir, "label_encoder.pkl")) | |
| feat_cols = joblib.load(os.path.join(model_dir, "feature_columns.pkl")) | |
| return rf, xgb, scaler, le, feat_cols | |
| def load_map_data(): | |
| base = os.path.dirname(os.path.abspath(__file__)) | |
| path = os.path.join(base, "data", "processed", "featured_dataset.csv") | |
| df = pd.read_csv(path) | |
| return df | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # PREDICTION ENGINE | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| RISK_ORDER = ["Very Low", "Low", "Medium", "High", "Very High"] | |
| RISK_COLORS = { | |
| "Very Low": "#1565c0", | |
| "Low": "#2e7d32", | |
| "Medium": "#f57f17", | |
| "High": "#e65100", | |
| "Very High": "#b71c1c", | |
| } | |
| BADGE_CLASS = { | |
| "Very Low": "badge-very-low", | |
| "Low": "badge-low", | |
| "Medium": "badge-medium", | |
| "High": "badge-high", | |
| "Very High": "badge-very-high", | |
| } | |
| def compute_derived(eq_count, eq_mag_mean, eq_depth_mean): | |
| freq_score = np.log1p(eq_count) | |
| energy_proxy = 10 ** (1.5 * eq_mag_mean) if eq_mag_mean > 0 else 1.0 | |
| mag_depth_ratio = eq_mag_mean / (eq_depth_mean + 1) | |
| return freq_score, energy_proxy, mag_depth_ratio | |
| def depth_cat(d): | |
| if d == 0: return 0 | |
| if d < 70: return 1 | |
| if d <= 300: return 2 | |
| return 3 | |
| def predict(model_name, rf, xgb, scaler, le, feat_cols, | |
| lat, lon, eq_count, eq_mag_mean, eq_depth_mean, | |
| has_seismic, continent): | |
| freq_score, energy_proxy, mag_depth_ratio = compute_derived(eq_count, eq_mag_mean, eq_depth_mean) | |
| lat_abs = abs(lat) | |
| lat_seismic_zone = 1 if lat_abs <= 60 else 0 | |
| seismic_intensity = energy_proxy * freq_score | |
| weighted_mag = eq_mag_mean / (eq_depth_mean + 1) | |
| dep_cat = depth_cat(eq_depth_mean) | |
| row = { | |
| "eq_count": eq_count, | |
| "eq_mag_mean": eq_mag_mean, | |
| "eq_depth_mean": eq_depth_mean, | |
| "has_seismic_activity": int(has_seismic), | |
| "eq_frequency_score": freq_score, | |
| "seismic_energy_proxy": energy_proxy, | |
| "mag_depth_ratio": mag_depth_ratio, | |
| "depth_category": dep_cat, | |
| "lat_abs": lat_abs, | |
| "lat_seismic_zone": lat_seismic_zone, | |
| "seismic_intensity": seismic_intensity, | |
| "weighted_mag": weighted_mag, | |
| "neighbor_eq_count": freq_score, # proxy: same as freq_score for live input | |
| "continent_Asia": 1 if continent == "Asia" else 0, | |
| "continent_Europe": 1 if continent == "Europe" else 0, | |
| "continent_North America": 1 if continent == "North America" else 0, | |
| "continent_Oceania": 1 if continent == "Oceania" else 0, | |
| "continent_South America": 1 if continent == "South America" else 0, | |
| } | |
| df = pd.DataFrame([row]) | |
| df = df.reindex(columns=feat_cols, fill_value=0) | |
| X = scaler.transform(df) | |
| if model_name == "Random Forest": | |
| model = rf | |
| pred = model.predict(X)[0] | |
| proba = model.predict_proba(X)[0] | |
| classes = model.classes_ | |
| else: | |
| model = xgb | |
| pred_int = model.predict(X)[0] | |
| pred = le.inverse_transform([int(pred_int)])[0] | |
| proba = model.predict_proba(X)[0] | |
| classes = le.classes_ | |
| prob_dict = {c: float(p) for c, p in zip(classes, proba)} | |
| return pred, prob_dict | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # HERO | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| st.markdown(""" | |
| <div class="hero-banner"> | |
| <h1>๐ GeoHazard ML</h1> | |
| <p>Geological Hazard Risk Classification ยท Powered by XGBoost & Random Forest ยท 166,293 USGS Events ยท 14,613 Grid Cells</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # TABS | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| tab1, tab2, tab3 = st.tabs(["๐บ๏ธ Global Risk Map", "๐ฎ Predict Risk", "๐ Model Performance"]) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # TAB 1 โ GLOBAL RISK MAP | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| with tab1: | |
| st.markdown('<div class="section-title">Global INFORM Risk Distribution</div>', unsafe_allow_html=True) | |
| st.caption("Each point is a 1ยฐร1ยฐ grid cell coloured by INFORM Risk Label. Hover to see details.") | |
| with st.spinner("Loading map data..."): | |
| try: | |
| df_map = load_map_data() | |
| color_map = { | |
| "Very Low": "#1565c0", | |
| "Low": "#2e7d32", | |
| "Medium": "#f57f17", | |
| "High": "#e65100", | |
| "Very High": "#b71c1c", | |
| } | |
| cat_order = ["Very Low", "Low", "Medium", "High", "Very High"] | |
| fig_map = px.scatter_geo( | |
| df_map, | |
| lat="lat_grid", lon="lon_grid", | |
| color="risk_label", | |
| color_discrete_map=color_map, | |
| category_orders={"risk_label": cat_order}, | |
| hover_data={ | |
| "lat_grid": ":.1f", | |
| "lon_grid": ":.1f", | |
| "eq_count": True, | |
| "eq_mag_mean": ":.2f", | |
| "risk_label": True, | |
| }, | |
| labels={ | |
| "lat_grid": "Latitude", | |
| "lon_grid": "Longitude", | |
| "eq_count": "Earthquake Count", | |
| "eq_mag_mean": "Mean Magnitude", | |
| "risk_label": "Risk Level", | |
| }, | |
| title="", | |
| projection="natural earth", | |
| ) | |
| fig_map.update_traces(marker=dict(size=3, opacity=0.75)) | |
| fig_map.update_layout( | |
| paper_bgcolor="#0e1117", | |
| plot_bgcolor="#0e1117", | |
| geo=dict( | |
| bgcolor="#0e1117", | |
| showland=True, landcolor="#1a2035", | |
| showocean=True, oceancolor="#0d1b2a", | |
| showcoastlines=True, coastlinecolor="#2a3a5c", | |
| showframe=False, | |
| ), | |
| legend=dict( | |
| title="Risk Level", | |
| bgcolor="#1a2035", | |
| bordercolor="#2a3a5c", | |
| font=dict(color="#e0e0e0"), | |
| ), | |
| margin=dict(l=0, r=0, t=10, b=0), | |
| height=520, | |
| ) | |
| st.plotly_chart(fig_map, use_container_width=True) | |
| # Quick stats row | |
| c1, c2, c3, c4, c5 = st.columns(5) | |
| counts = df_map["risk_label"].value_counts() | |
| for col, label in zip([c1, c2, c3, c4, c5], cat_order): | |
| n = counts.get(label, 0) | |
| col.markdown(f""" | |
| <div class="metric-card"> | |
| <div class="val" style="color:{color_map[label]}">{n:,}</div> | |
| <div class="lbl">{label}</div> | |
| </div>""", unsafe_allow_html=True) | |
| except Exception as e: | |
| st.error(f"Could not load map data: {e}") | |
| st.info("Make sure `data/processed/featured_dataset.csv` is in your repo.") | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # TAB 2 โ PREDICT RISK | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| with tab2: | |
| # Load models | |
| model_load_error = None | |
| try: | |
| rf_model, xgb_model, scaler, label_enc, feat_cols = load_models() | |
| models_loaded = True | |
| except Exception as e: | |
| models_loaded = False | |
| model_load_error = str(e) | |
| if not models_loaded: | |
| st.error(f"Could not load models: {model_load_error}") | |
| st.info("Ensure all `.pkl` files are in the `models/` folder.") | |
| else: | |
| left, right = st.columns([1, 1], gap="large") | |
| with left: | |
| st.markdown('<div class="section-title">๐ Location</div>', unsafe_allow_html=True) | |
| col_lat, col_lon = st.columns(2) | |
| with col_lat: | |
| lat_in = st.number_input("Latitude", min_value=-90.0, max_value=90.0, value=35.5, step=0.5, format="%.1f") | |
| with col_lon: | |
| lon_in = st.number_input("Longitude", min_value=-180.0, max_value=180.0, value=139.5, step=0.5, format="%.1f") | |
| continent_in = st.selectbox("Continent", ["Africa", "Asia", "Europe", "North America", "Oceania", "South America"], index=1) | |
| st.markdown('<div class="section-title">๐ก Seismic Measurements</div>', unsafe_allow_html=True) | |
| col_a, col_b = st.columns(2) | |
| with col_a: | |
| eq_count_in = st.number_input("Earthquake Count (M4.0+)", min_value=0, max_value=2000, value=45, step=1) | |
| eq_mag_in = st.number_input("Mean Magnitude", min_value=0.0, max_value=10.0, value=4.8, step=0.1, format="%.1f") | |
| with col_b: | |
| eq_depth_in = st.number_input("Mean Depth (km)", min_value=0.0, max_value=750.0, value=32.0, step=1.0, format="%.1f") | |
| has_seismic_in = st.checkbox("Has Seismic Activity", value=eq_count_in > 0) | |
| st.markdown('<div class="section-title">โ๏ธ Auto-Computed Features</div>', unsafe_allow_html=True) | |
| fs, ep, mdr = compute_derived(eq_count_in, eq_mag_in, eq_depth_in) | |
| ca, cb, cc = st.columns(3) | |
| ca.metric("Frequency Score", f"{fs:.3f}") | |
| cb.metric("Energy Proxy", f"{ep:.2e}") | |
| cc.metric("Mag/Depth Ratio", f"{mdr:.3f}") | |
| st.markdown('<div class="section-title">๐ค Model</div>', unsafe_allow_html=True) | |
| model_choice = st.radio("Select Model", ["XGBoost (Best)", "Random Forest"], horizontal=True) | |
| model_name = "XGBoost" if "XGBoost" in model_choice else "Random Forest" | |
| predict_btn = st.button("๐ฎ Predict Risk", use_container_width=True, type="primary") | |
| with right: | |
| st.markdown('<div class="section-title">๐ฏ Prediction Result</div>', unsafe_allow_html=True) | |
| if predict_btn: | |
| with st.spinner("Running model..."): | |
| pred, prob_dict = predict( | |
| model_name, rf_model, xgb_model, scaler, label_enc, feat_cols, | |
| lat_in, lon_in, eq_count_in, eq_mag_in, eq_depth_in, | |
| has_seismic_in, continent_in | |
| ) | |
| st.session_state["pred"] = pred | |
| st.session_state["prob_dict"] = prob_dict | |
| st.session_state["lat_pred"] = lat_in | |
| st.session_state["lon_pred"] = lon_in | |
| st.session_state["model_used"] = model_name | |
| if "pred" in st.session_state: | |
| pred = st.session_state["pred"] | |
| prob_dict = st.session_state["prob_dict"] | |
| lat_p = st.session_state["lat_pred"] | |
| lon_p = st.session_state["lon_pred"] | |
| m_used = st.session_state["model_used"] | |
| badge_cls = BADGE_CLASS.get(pred, "badge-medium") | |
| conf = prob_dict.get(pred, 0) * 100 | |
| st.markdown(f""" | |
| <div class="result-box"> | |
| <div style="color:#90a4ae; font-size:0.85rem; margin-bottom:0.5rem">PREDICTED RISK LEVEL</div> | |
| <span class="badge {badge_cls}">{pred}</span> | |
| <div style="margin-top:1rem; color:#b0bec5; font-size:0.9rem"> | |
| Confidence: <strong style="color:#4fc3f7">{conf:.1f}%</strong> ยท Model: <strong style="color:#4fc3f7">{m_used}</strong> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Probability bar chart | |
| st.markdown('<div class="section-title">๐ Class Probabilities</div>', unsafe_allow_html=True) | |
| prob_vals = [prob_dict.get(c, 0) * 100 for c in RISK_ORDER] | |
| bar_colors = [RISK_COLORS[c] for c in RISK_ORDER] | |
| fig_bar = go.Figure(go.Bar( | |
| x=RISK_ORDER, | |
| y=prob_vals, | |
| marker_color=bar_colors, | |
| text=[f"{v:.1f}%" for v in prob_vals], | |
| textposition="outside", | |
| textfont=dict(color="#e0e0e0", size=11), | |
| )) | |
| fig_bar.update_layout( | |
| paper_bgcolor="#0e1117", | |
| plot_bgcolor="#1a2035", | |
| xaxis=dict(tickfont=dict(color="#b0bec5"), gridcolor="#2a3a5c"), | |
| yaxis=dict(tickfont=dict(color="#b0bec5"), gridcolor="#2a3a5c", | |
| title="Probability (%)", range=[0, 110]), | |
| margin=dict(l=10, r=10, t=10, b=10), | |
| height=260, | |
| showlegend=False, | |
| ) | |
| st.plotly_chart(fig_bar, use_container_width=True) | |
| # Location map | |
| st.markdown('<div class="section-title">๐ Predicted Location</div>', unsafe_allow_html=True) | |
| pin_color = RISK_COLORS.get(pred, "#f57f17") | |
| fig_loc = go.Figure(go.Scattergeo( | |
| lat=[lat_p], lon=[lon_p], | |
| mode="markers", | |
| marker=dict(size=14, color=pin_color, symbol="circle", | |
| line=dict(width=2, color="white")), | |
| text=[f"{pred} Risk<br>({lat_p}ยฐ, {lon_p}ยฐ)"], | |
| hoverinfo="text", | |
| )) | |
| fig_loc.update_layout( | |
| paper_bgcolor="#0e1117", | |
| geo=dict( | |
| bgcolor="#0e1117", | |
| showland=True, landcolor="#1a2035", | |
| showocean=True, oceancolor="#0d1b2a", | |
| showcoastlines=True, coastlinecolor="#2a3a5c", | |
| showframe=False, | |
| center=dict(lat=lat_p, lon=lon_p), | |
| projection_scale=3, | |
| ), | |
| margin=dict(l=0, r=0, t=0, b=0), | |
| height=240, | |
| ) | |
| st.plotly_chart(fig_loc, use_container_width=True) | |
| else: | |
| st.markdown(""" | |
| <div class="result-box" style="padding:3rem"> | |
| <div style="font-size:2.5rem">๐ฎ</div> | |
| <div style="color:#546e7a; margin-top:0.8rem">Fill in the values on the left<br>and click <strong>Predict Risk</strong></div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # TAB 3 โ MODEL PERFORMANCE | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| with tab3: | |
| st.markdown('<div class="section-title">Model Comparison</div>', unsafe_allow_html=True) | |
| perf_data = { | |
| "Model": ["Logistic Regression", "Random Forest", "XGBoost"], | |
| "Accuracy": ["58.5%", "76.7%", "80.5%"], | |
| "Macro F1": ["0.480", "0.650", "0.681"], | |
| "ROC-AUC": ["0.906", "0.959", "0.961"], | |
| "Status": ["Baseline", "Strong", "โญ Best"], | |
| } | |
| df_perf = pd.DataFrame(perf_data) | |
| # Colour the best row | |
| def style_row(row): | |
| if row["Status"] == "โญ Best": | |
| return ["background-color: #0d2137; color: #4fc3f7; font-weight:bold"] * len(row) | |
| return [""] * len(row) | |
| st.dataframe(df_perf.style.apply(style_row, axis=1), use_container_width=True, hide_index=True) | |
| st.markdown('<div class="section-title">Per-Class Results (XGBoost)</div>', unsafe_allow_html=True) | |
| cls_data = { | |
| "Class": ["Very Low", "Low", "Medium", "High", "Very High"], | |
| "Precision": [0.41, 0.90, 0.79, 0.67, 0.66], | |
| "Recall": [0.36, 0.88, 0.84, 0.63, 0.68], | |
| "F1-Score": [0.39, 0.89, 0.81, 0.65, 0.67], | |
| "Test Samples": [47, 1307, 909, 405, 255], | |
| } | |
| df_cls = pd.DataFrame(cls_data) | |
| fig_cls = px.bar( | |
| df_cls.melt(id_vars="Class", value_vars=["Precision","Recall","F1-Score"]), | |
| x="Class", y="value", color="variable", barmode="group", | |
| color_discrete_sequence=["#4fc3f7", "#81c784", "#ffb74d"], | |
| labels={"value": "Score", "variable": "Metric"}, | |
| title="", | |
| category_orders={"Class": RISK_ORDER}, | |
| ) | |
| fig_cls.update_layout( | |
| paper_bgcolor="#0e1117", plot_bgcolor="#1a2035", | |
| xaxis=dict(tickfont=dict(color="#b0bec5"), gridcolor="#2a3a5c"), | |
| yaxis=dict(tickfont=dict(color="#b0bec5"), gridcolor="#2a3a5c", range=[0, 1.1]), | |
| legend=dict(bgcolor="#1a2035", font=dict(color="#e0e0e0")), | |
| margin=dict(l=10, r=10, t=20, b=10), height=340, | |
| ) | |
| st.plotly_chart(fig_cls, use_container_width=True) | |
| st.markdown('<div class="section-title">Feature Importance (Random Forest)</div>', unsafe_allow_html=True) | |
| fi_data = { | |
| "Feature": ["lat_abs","neighbor_eq_count","continent_Europe","continent_South America", | |
| "continent_North America","continent_Oceania","continent_Asia", | |
| "lat_seismic_zone","eq_depth_mean","mag_depth_ratio","weighted_mag", | |
| "seismic_intensity","eq_count","eq_frequency_score","seismic_energy_proxy", | |
| "eq_mag_mean","depth_category","has_seismic_activity"], | |
| "Importance": [32.7,17.6,11.6,10.0,7.4,3.9,3.9,3.8,1.6,1.3,1.3,1.1,1.0,0.9,0.9,0.9,0.3,0.1], | |
| "Type": ["Geographic","Seismic","Geographic","Geographic","Geographic","Geographic","Geographic", | |
| "Geographic","Seismic","Seismic","Seismic","Seismic","Seismic","Seismic","Seismic", | |
| "Seismic","Seismic","Seismic"], | |
| } | |
| df_fi = pd.DataFrame(fi_data).sort_values("Importance", ascending=True) | |
| fig_fi = px.bar( | |
| df_fi, x="Importance", y="Feature", color="Type", orientation="h", | |
| color_discrete_map={"Geographic": "#4fc3f7", "Seismic": "#81c784"}, | |
| labels={"Importance": "Importance (%)"}, | |
| ) | |
| fig_fi.update_layout( | |
| paper_bgcolor="#0e1117", plot_bgcolor="#1a2035", | |
| xaxis=dict(tickfont=dict(color="#b0bec5"), gridcolor="#2a3a5c"), | |
| yaxis=dict(tickfont=dict(color="#b0bec5"), tickfont_size=11), | |
| legend=dict(bgcolor="#1a2035", font=dict(color="#e0e0e0")), | |
| margin=dict(l=10, r=10, t=10, b=10), height=480, | |
| ) | |
| st.plotly_chart(fig_fi, use_container_width=True) | |
| st.markdown('<div class="section-title">Dataset Summary</div>', unsafe_allow_html=True) | |
| c1, c2, c3, c4 = st.columns(4) | |
| stats = [ | |
| ("166,293", "Raw Earthquake Events"), | |
| ("14,613", "Grid Cells (1ยฐร1ยฐ)"), | |
| ("18", "Engineered Features"), | |
| ("80.5%", "Best Model Accuracy"), | |
| ] | |
| for col, (val, lbl) in zip([c1, c2, c3, c4], stats): | |
| col.markdown(f""" | |
| <div class="metric-card"> | |
| <div class="val">{val}</div> | |
| <div class="lbl">{lbl}</div> | |
| </div>""", unsafe_allow_html=True) | |