Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
import io, json, os, base64
|
| 2 |
from pathlib import Path
|
| 3 |
import streamlit as st
|
|
@@ -5,14 +6,12 @@ import pandas as pd
|
|
| 5 |
import numpy as np
|
| 6 |
import joblib
|
| 7 |
|
| 8 |
-
#
|
| 9 |
import matplotlib
|
| 10 |
matplotlib.use("Agg")
|
| 11 |
import matplotlib.pyplot as plt
|
| 12 |
|
| 13 |
-
# Plotly for the main interactive charts
|
| 14 |
import plotly.graph_objects as go
|
| 15 |
-
|
| 16 |
from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error
|
| 17 |
|
| 18 |
# =========================
|
|
@@ -98,9 +97,9 @@ def find_sheet(book, names):
|
|
| 98 |
if nm.lower() in low2orig: return low2orig[nm.lower()]
|
| 99 |
return None
|
| 100 |
|
| 101 |
-
# ----------
|
| 102 |
def cross_plot_interactive(actual, pred, size=(3.9, 3.9)):
|
| 103 |
-
"""Interactive cross-plot: blue points, dashed 1:1, equal axes, no title, numeric ticks."""
|
| 104 |
a = pd.Series(actual).astype(float)
|
| 105 |
p = pd.Series(pred).astype(float)
|
| 106 |
lo = float(np.nanmin([a.min(), p.min()]))
|
|
@@ -115,14 +114,14 @@ def cross_plot_interactive(actual, pred, size=(3.9, 3.9)):
|
|
| 115 |
x=a, y=p, mode="markers",
|
| 116 |
marker=dict(size=6, color=COLORS["pred"]),
|
| 117 |
hovertemplate="Actual: %{x:.2f}<br>Pred: %{y:.2f}<extra></extra>",
|
| 118 |
-
showlegend=False
|
| 119 |
))
|
| 120 |
|
| 121 |
# 1:1 line (dashed grey)
|
| 122 |
fig.add_trace(go.Scatter(
|
| 123 |
x=[x0, x1], y=[x0, x1], mode="lines",
|
| 124 |
line=dict(color=COLORS["ref"], width=1.2, dash="dash"),
|
| 125 |
-
hoverinfo="skip", showlegend=False
|
| 126 |
))
|
| 127 |
|
| 128 |
fig.update_layout(
|
|
@@ -130,17 +129,18 @@ def cross_plot_interactive(actual, pred, size=(3.9, 3.9)):
|
|
| 130 |
margin=dict(l=50, r=10, t=10, b=36),
|
| 131 |
hovermode="closest", font=dict(size=13)
|
| 132 |
)
|
|
|
|
| 133 |
fig.update_xaxes(
|
| 134 |
title_text="<b>Actual UCS</b>",
|
| 135 |
range=[x0, x1], ticks="outside",
|
| 136 |
-
showline=True, linewidth=1.2, linecolor="#444",
|
| 137 |
showgrid=True, gridcolor="rgba(0,0,0,0.12)",
|
| 138 |
tickformat=",.0f", automargin=True
|
| 139 |
)
|
| 140 |
fig.update_yaxes(
|
| 141 |
title_text="<b>Predicted UCS</b>",
|
| 142 |
range=[x0, x1], ticks="outside",
|
| 143 |
-
showline=True, linewidth=1.2, linecolor="#444",
|
| 144 |
showgrid=True, gridcolor="rgba(0,0,0,0.12)",
|
| 145 |
tickformat=",.0f", scaleanchor="x", scaleratio=1,
|
| 146 |
automargin=True
|
|
@@ -150,7 +150,7 @@ def cross_plot_interactive(actual, pred, size=(3.9, 3.9)):
|
|
| 150 |
return fig
|
| 151 |
|
| 152 |
def depth_or_index_track_interactive(df, title=None, include_actual=True):
|
| 153 |
-
"""Interactive UCS track: blue solid pred, yellow dotted actual, legend inside; x on top;
|
| 154 |
depth_col = next((c for c in df.columns if 'depth' in str(c).lower()), None)
|
| 155 |
if depth_col is not None:
|
| 156 |
y = df[depth_col]; y_label = depth_col
|
|
@@ -187,21 +187,22 @@ def depth_or_index_track_interactive(df, title=None, include_actual=True):
|
|
| 187 |
width=int(3.1 * 100),
|
| 188 |
height=int((7.6 if depth_col is not None else 7.2) * 100),
|
| 189 |
)
|
|
|
|
| 190 |
fig.update_xaxes(
|
| 191 |
title_text="<b>UCS</b>", side="top",
|
| 192 |
-
ticks="outside", showline=True, linewidth=1.2, linecolor="#444",
|
| 193 |
showgrid=True, gridcolor="rgba(0,0,0,0.12)",
|
| 194 |
tickformat=",.0f", automargin=True
|
| 195 |
)
|
| 196 |
fig.update_yaxes(
|
| 197 |
title_text=f"<b>{y_label}</b>", autorange="reversed",
|
| 198 |
-
ticks="outside", showline=True, linewidth=1.2, linecolor="#444",
|
| 199 |
showgrid=True, gridcolor="rgba(0,0,0,0.12)",
|
| 200 |
automargin=True
|
| 201 |
)
|
| 202 |
return fig
|
| 203 |
|
| 204 |
-
# ---------- Preview modal helpers (matplotlib) ----------
|
| 205 |
def make_index_tracks(df: pd.DataFrame, cols: list[str]):
|
| 206 |
cols = [c for c in cols if c in df.columns]
|
| 207 |
n = len(cols)
|
|
@@ -304,12 +305,19 @@ if meta_path.exists():
|
|
| 304 |
FEATURES = meta.get("features", FEATURES); TARGET = meta.get("target", TARGET)
|
| 305 |
except Exception: pass
|
| 306 |
else:
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 313 |
if infer: FEATURES = infer
|
| 314 |
|
| 315 |
# =========================
|
|
@@ -397,13 +405,13 @@ if st.session_state.app_step == "dev":
|
|
| 397 |
file_bytes = train_test_file.getvalue()
|
| 398 |
size = len(file_bytes)
|
| 399 |
except Exception:
|
| 400 |
-
file_bytes = b""
|
| 401 |
-
size = 0
|
| 402 |
sig = (train_test_file.name, size)
|
| 403 |
if sig != st.session_state.dev_file_signature and size > 0:
|
| 404 |
st.session_state.dev_file_signature = sig
|
| 405 |
st.session_state.dev_file_name = train_test_file.name
|
| 406 |
st.session_state.dev_file_bytes = file_bytes
|
|
|
|
| 407 |
_book_tmp = read_book_bytes(file_bytes)
|
| 408 |
if _book_tmp:
|
| 409 |
first_df = next(iter(_book_tmp.values()))
|
|
@@ -448,7 +456,7 @@ if st.session_state.app_step == "dev":
|
|
| 448 |
else:
|
| 449 |
st.write("**Upload your data to build a case, then run the model to review development performance.**")
|
| 450 |
|
| 451 |
-
# If user clicked preview, open modal after helper
|
| 452 |
if st.session_state.dev_preview_request and st.session_state.dev_file_bytes:
|
| 453 |
_book = read_book_bytes(st.session_state.dev_file_bytes)
|
| 454 |
st.session_state.dev_previewed = True
|
|
@@ -538,15 +546,17 @@ if st.session_state.app_step == "dev":
|
|
| 538 |
summary_df = pd.DataFrame(rows) if rows else None
|
| 539 |
try:
|
| 540 |
buf = io.BytesIO()
|
| 541 |
-
import openpyxl # noqa
|
| 542 |
with pd.ExcelWriter(buf, engine="openpyxl") as xw:
|
| 543 |
for name, frame in sheets.items():
|
| 544 |
frame.to_excel(xw, sheet_name=name[:31], index=False)
|
| 545 |
if summary_df is not None:
|
| 546 |
summary_df.to_excel(xw, sheet_name="Summary", index=False)
|
| 547 |
-
st.download_button(
|
| 548 |
-
|
| 549 |
-
|
|
|
|
|
|
|
|
|
|
| 550 |
except Exception as e:
|
| 551 |
st.warning(str(e))
|
| 552 |
|
|
@@ -618,9 +628,19 @@ if st.session_state.app_step == "predict":
|
|
| 618 |
if sv["oor_pct"] > 0:
|
| 619 |
st.warning("Some validation inputs fall outside the **training min–max** ranges. Interpret predictions with caution.")
|
| 620 |
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 624 |
|
| 625 |
left, right = st.columns([0.9, 0.55])
|
| 626 |
with left:
|
|
@@ -658,15 +678,17 @@ if st.session_state.app_step == "predict":
|
|
| 658 |
summary_df = pd.DataFrame(rows) if rows else None
|
| 659 |
try:
|
| 660 |
buf = io.BytesIO()
|
| 661 |
-
import openpyxl # noqa
|
| 662 |
with pd.ExcelWriter(buf, engine="openpyxl") as xw:
|
| 663 |
-
for
|
| 664 |
-
|
| 665 |
if summary_df is not None:
|
| 666 |
summary_df.to_excel(xw, sheet_name="Summary", index=False)
|
| 667 |
-
st.download_button(
|
| 668 |
-
|
| 669 |
-
|
|
|
|
|
|
|
|
|
|
| 670 |
except Exception as e:
|
| 671 |
st.warning(str(e))
|
| 672 |
|
|
|
|
| 1 |
+
# app.py
|
| 2 |
import io, json, os, base64
|
| 3 |
from pathlib import Path
|
| 4 |
import streamlit as st
|
|
|
|
| 6 |
import numpy as np
|
| 7 |
import joblib
|
| 8 |
|
| 9 |
+
# keep matplotlib ONLY for the preview modal (static thumbnails)
|
| 10 |
import matplotlib
|
| 11 |
matplotlib.use("Agg")
|
| 12 |
import matplotlib.pyplot as plt
|
| 13 |
|
|
|
|
| 14 |
import plotly.graph_objects as go
|
|
|
|
| 15 |
from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error
|
| 16 |
|
| 17 |
# =========================
|
|
|
|
| 97 |
if nm.lower() in low2orig: return low2orig[nm.lower()]
|
| 98 |
return None
|
| 99 |
|
| 100 |
+
# ---------- Interactive plotting (full outline, bold axis titles) ----------
|
| 101 |
def cross_plot_interactive(actual, pred, size=(3.9, 3.9)):
|
| 102 |
+
"""Interactive cross-plot: blue points, dashed 1:1, equal axes, no title, numeric ticks, full box outline."""
|
| 103 |
a = pd.Series(actual).astype(float)
|
| 104 |
p = pd.Series(pred).astype(float)
|
| 105 |
lo = float(np.nanmin([a.min(), p.min()]))
|
|
|
|
| 114 |
x=a, y=p, mode="markers",
|
| 115 |
marker=dict(size=6, color=COLORS["pred"]),
|
| 116 |
hovertemplate="Actual: %{x:.2f}<br>Pred: %{y:.2f}<extra></extra>",
|
| 117 |
+
showlegend=False
|
| 118 |
))
|
| 119 |
|
| 120 |
# 1:1 line (dashed grey)
|
| 121 |
fig.add_trace(go.Scatter(
|
| 122 |
x=[x0, x1], y=[x0, x1], mode="lines",
|
| 123 |
line=dict(color=COLORS["ref"], width=1.2, dash="dash"),
|
| 124 |
+
hoverinfo="skip", showlegend=False
|
| 125 |
))
|
| 126 |
|
| 127 |
fig.update_layout(
|
|
|
|
| 129 |
margin=dict(l=50, r=10, t=10, b=36),
|
| 130 |
hovermode="closest", font=dict(size=13)
|
| 131 |
)
|
| 132 |
+
# FULL OUTLINE via mirror=True (top/right) + showline
|
| 133 |
fig.update_xaxes(
|
| 134 |
title_text="<b>Actual UCS</b>",
|
| 135 |
range=[x0, x1], ticks="outside",
|
| 136 |
+
showline=True, linewidth=1.2, linecolor="#444", mirror=True,
|
| 137 |
showgrid=True, gridcolor="rgba(0,0,0,0.12)",
|
| 138 |
tickformat=",.0f", automargin=True
|
| 139 |
)
|
| 140 |
fig.update_yaxes(
|
| 141 |
title_text="<b>Predicted UCS</b>",
|
| 142 |
range=[x0, x1], ticks="outside",
|
| 143 |
+
showline=True, linewidth=1.2, linecolor="#444", mirror=True,
|
| 144 |
showgrid=True, gridcolor="rgba(0,0,0,0.12)",
|
| 145 |
tickformat=",.0f", scaleanchor="x", scaleratio=1,
|
| 146 |
automargin=True
|
|
|
|
| 150 |
return fig
|
| 151 |
|
| 152 |
def depth_or_index_track_interactive(df, title=None, include_actual=True):
|
| 153 |
+
"""Interactive UCS track: blue solid pred, yellow dotted actual, legend inside; x on top; full box outline."""
|
| 154 |
depth_col = next((c for c in df.columns if 'depth' in str(c).lower()), None)
|
| 155 |
if depth_col is not None:
|
| 156 |
y = df[depth_col]; y_label = depth_col
|
|
|
|
| 187 |
width=int(3.1 * 100),
|
| 188 |
height=int((7.6 if depth_col is not None else 7.2) * 100),
|
| 189 |
)
|
| 190 |
+
# FULL OUTLINE via mirror=True (adds bottom/right sides)
|
| 191 |
fig.update_xaxes(
|
| 192 |
title_text="<b>UCS</b>", side="top",
|
| 193 |
+
ticks="outside", showline=True, linewidth=1.2, linecolor="#444", mirror=True,
|
| 194 |
showgrid=True, gridcolor="rgba(0,0,0,0.12)",
|
| 195 |
tickformat=",.0f", automargin=True
|
| 196 |
)
|
| 197 |
fig.update_yaxes(
|
| 198 |
title_text=f"<b>{y_label}</b>", autorange="reversed",
|
| 199 |
+
ticks="outside", showline=True, linewidth=1.2, linecolor="#444", mirror=True,
|
| 200 |
showgrid=True, gridcolor="rgba(0,0,0,0.12)",
|
| 201 |
automargin=True
|
| 202 |
)
|
| 203 |
return fig
|
| 204 |
|
| 205 |
+
# ---------- Preview modal helpers (matplotlib static) ----------
|
| 206 |
def make_index_tracks(df: pd.DataFrame, cols: list[str]):
|
| 207 |
cols = [c for c in cols if c in df.columns]
|
| 208 |
n = len(cols)
|
|
|
|
| 305 |
FEATURES = meta.get("features", FEATURES); TARGET = meta.get("target", TARGET)
|
| 306 |
except Exception: pass
|
| 307 |
else:
|
| 308 |
+
def infer_features_from_model(m):
|
| 309 |
+
try:
|
| 310 |
+
if hasattr(m, "feature_names_in_") and len(getattr(m, "feature_names_in_")):
|
| 311 |
+
return [str(x) for x in m.feature_names_in_]
|
| 312 |
+
except Exception: pass
|
| 313 |
+
try:
|
| 314 |
+
if hasattr(m, "steps") and len(m.steps):
|
| 315 |
+
last = m.steps[-1][1]
|
| 316 |
+
if hasattr(last, "feature_names_in_") and len(last.feature_names_in_):
|
| 317 |
+
return [str(x) for x in last.feature_names_in_]
|
| 318 |
+
except Exception: pass
|
| 319 |
+
return None
|
| 320 |
+
infer = infer_features_from_model(model)
|
| 321 |
if infer: FEATURES = infer
|
| 322 |
|
| 323 |
# =========================
|
|
|
|
| 405 |
file_bytes = train_test_file.getvalue()
|
| 406 |
size = len(file_bytes)
|
| 407 |
except Exception:
|
| 408 |
+
file_bytes = b""; size = 0
|
|
|
|
| 409 |
sig = (train_test_file.name, size)
|
| 410 |
if sig != st.session_state.dev_file_signature and size > 0:
|
| 411 |
st.session_state.dev_file_signature = sig
|
| 412 |
st.session_state.dev_file_name = train_test_file.name
|
| 413 |
st.session_state.dev_file_bytes = file_bytes
|
| 414 |
+
# Inspect first sheet for rows/cols
|
| 415 |
_book_tmp = read_book_bytes(file_bytes)
|
| 416 |
if _book_tmp:
|
| 417 |
first_df = next(iter(_book_tmp.values()))
|
|
|
|
| 456 |
else:
|
| 457 |
st.write("**Upload your data to build a case, then run the model to review development performance.**")
|
| 458 |
|
| 459 |
+
# If user clicked preview, open modal *after* helper so helper stays on top
|
| 460 |
if st.session_state.dev_preview_request and st.session_state.dev_file_bytes:
|
| 461 |
_book = read_book_bytes(st.session_state.dev_file_bytes)
|
| 462 |
st.session_state.dev_previewed = True
|
|
|
|
| 546 |
summary_df = pd.DataFrame(rows) if rows else None
|
| 547 |
try:
|
| 548 |
buf = io.BytesIO()
|
|
|
|
| 549 |
with pd.ExcelWriter(buf, engine="openpyxl") as xw:
|
| 550 |
for name, frame in sheets.items():
|
| 551 |
frame.to_excel(xw, sheet_name=name[:31], index=False)
|
| 552 |
if summary_df is not None:
|
| 553 |
summary_df.to_excel(xw, sheet_name="Summary", index=False)
|
| 554 |
+
st.download_button(
|
| 555 |
+
"Export Development Results to Excel",
|
| 556 |
+
data=buf.getvalue(),
|
| 557 |
+
file_name="UCS_Dev_Results.xlsx",
|
| 558 |
+
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
| 559 |
+
)
|
| 560 |
except Exception as e:
|
| 561 |
st.warning(str(e))
|
| 562 |
|
|
|
|
| 628 |
if sv["oor_pct"] > 0:
|
| 629 |
st.warning("Some validation inputs fall outside the **training min–max** ranges. Interpret predictions with caution.")
|
| 630 |
|
| 631 |
+
# Metrics like Development (R² / RMSE / MAE)
|
| 632 |
+
metrics_val = st.session_state.results.get("metrics_val")
|
| 633 |
+
if metrics_val is not None:
|
| 634 |
+
c1, c2, c3 = st.columns(3)
|
| 635 |
+
c1.metric("R²", f"{metrics_val['R2']:.4f}")
|
| 636 |
+
c2.metric("RMSE", f"{metrics_val['RMSE']:.4f}")
|
| 637 |
+
c3.metric("MAE", f"{metrics_val['MAE']:.4f}")
|
| 638 |
+
else:
|
| 639 |
+
# Fallback only if actual UCS isn't provided
|
| 640 |
+
c1, c2, c3 = st.columns(3)
|
| 641 |
+
c1.metric("# points", f"{sv['n_points']}")
|
| 642 |
+
c2.metric("Pred min", f"{sv['pred_min']:.2f}")
|
| 643 |
+
c3.metric("Pred max", f"{sv['pred_max']:.2f}")
|
| 644 |
|
| 645 |
left, right = st.columns([0.9, 0.55])
|
| 646 |
with left:
|
|
|
|
| 678 |
summary_df = pd.DataFrame(rows) if rows else None
|
| 679 |
try:
|
| 680 |
buf = io.BytesIO()
|
|
|
|
| 681 |
with pd.ExcelWriter(buf, engine="openpyxl") as xw:
|
| 682 |
+
for name, frame in sheets.items():
|
| 683 |
+
frame.to_excel(xw, sheet_name=name[:31], index=False)
|
| 684 |
if summary_df is not None:
|
| 685 |
summary_df.to_excel(xw, sheet_name="Summary", index=False)
|
| 686 |
+
st.download_button(
|
| 687 |
+
"Export Validation Results to Excel",
|
| 688 |
+
data=buf.getvalue(),
|
| 689 |
+
file_name="UCS_Validation_Results.xlsx",
|
| 690 |
+
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
| 691 |
+
)
|
| 692 |
except Exception as e:
|
| 693 |
st.warning(str(e))
|
| 694 |
|