Update app.py
Browse files
app.py
CHANGED
|
@@ -225,8 +225,12 @@ def run_code(code: str, df: pd.DataFrame) -> dict:
|
|
| 225 |
except Exception:
|
| 226 |
err = traceback.format_exc(limit=3)
|
| 227 |
|
| 228 |
-
|
| 229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
plt.close("all")
|
| 231 |
|
| 232 |
return {"ok": err is None, "stdout": buf.getvalue(), "error": err,
|
|
@@ -247,12 +251,40 @@ def format_error(err: str, code: str) -> str:
|
|
| 247 |
# --------------------------------------------------------------------------- #
|
| 248 |
# 5. Deterministic facts and tables
|
| 249 |
# --------------------------------------------------------------------------- #
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
def make_tables(df: pd.DataFrame, max_card: int = 6) -> dict:
|
| 251 |
t = {}
|
| 252 |
n = df.isna().sum()
|
| 253 |
n = n[n > 0].sort_values(ascending=False)
|
| 254 |
-
|
| 255 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
|
| 257 |
bins = [c for c in df.select_dtypes(include="number") if df[c].nunique() == 2]
|
| 258 |
blocks = [
|
|
@@ -263,21 +295,18 @@ def make_tables(df: pd.DataFrame, max_card: int = 6) -> dict:
|
|
| 263 |
]
|
| 264 |
t["groups"] = "\n\n".join(blocks) if blocks else "_No low-cardinality groupings._"
|
| 265 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
num = df.select_dtypes(include="number")
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
key=abs, ascending=False)
|
| 270 |
-
seen, rows = set(), []
|
| 271 |
-
for (a, b), v in pr.items():
|
| 272 |
-
if (b, a) in seen:
|
| 273 |
-
continue
|
| 274 |
-
seen.add((a, b))
|
| 275 |
-
rows.append({"pair": f"{a} ~ {b}", "r": round(v, 3)})
|
| 276 |
-
if len(rows) >= 8:
|
| 277 |
-
break
|
| 278 |
-
t["corr"] = (pd.DataFrame(rows).to_markdown(index=False)
|
| 279 |
-
if rows else "_Too few numeric columns._")
|
| 280 |
-
t["describe"] = num.describe().round(2).to_markdown() if len(num.columns) else "_None._"
|
| 281 |
return t
|
| 282 |
|
| 283 |
|
|
@@ -304,14 +333,11 @@ def key_facts(df: pd.DataFrame, max_card: int = 6) -> dict:
|
|
| 304 |
b, c, k, v = best
|
| 305 |
f["group"] = f"the highest mean {b} is {v:.3f}, for {c} = {k}"
|
| 306 |
|
| 307 |
-
|
| 308 |
-
if
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
(a, b2), v = pr.index[0], pr.iloc[0]
|
| 313 |
-
f["corr"] = (f"the strongest correlation is {a} ~ {b2} at r = {v:+.3f} "
|
| 314 |
-
f"({'positive' if v > 0 else 'negative'})")
|
| 315 |
return f
|
| 316 |
|
| 317 |
|
|
@@ -415,6 +441,8 @@ def run_agent(instruction: str, dataset: str):
|
|
| 415 |
P += ["## Correlations", t["corr"], ""]
|
| 416 |
if "corr" in kf:
|
| 417 |
P += [one_liner(kf["corr"]), ""]
|
|
|
|
|
|
|
| 418 |
P += ["## Numeric Summary", t["describe"], ""]
|
| 419 |
|
| 420 |
take = generate("Rewrite each fact as one markdown bullet. Add nothing.",
|
|
|
|
| 225 |
except Exception:
|
| 226 |
err = traceback.format_exc(limit=3)
|
| 227 |
|
| 228 |
+
# safety net: model called plt.show(), or crashed with a figure still open
|
| 229 |
+
for i, num in enumerate(plt.get_fignums(), start=1):
|
| 230 |
+
fig = plt.figure(num)
|
| 231 |
+
if not fig.get_axes(): # skip blank figures
|
| 232 |
+
continue
|
| 233 |
+
fig.savefig(f"{PLOTS_DIR}/figure_{i}.png", bbox_inches="tight")
|
| 234 |
plt.close("all")
|
| 235 |
|
| 236 |
return {"ok": err is None, "stdout": buf.getvalue(), "error": err,
|
|
|
|
| 251 |
# --------------------------------------------------------------------------- #
|
| 252 |
# 5. Deterministic facts and tables
|
| 253 |
# --------------------------------------------------------------------------- #
|
| 254 |
+
REDUNDANT_R = 0.99 # |r| at or above this is a duplicate encoding, not a finding
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
def corr_pairs(df: pd.DataFrame):
|
| 258 |
+
"""Split correlations into real findings and near-duplicate columns."""
|
| 259 |
+
num = df.select_dtypes(include="number")
|
| 260 |
+
if len(num.columns) < 2:
|
| 261 |
+
return [], []
|
| 262 |
+
corr = num.corr()
|
| 263 |
+
pr = corr.where(~np.eye(len(corr), dtype=bool)).stack().sort_values(
|
| 264 |
+
key=abs, ascending=False)
|
| 265 |
+
seen, dup, real = set(), [], []
|
| 266 |
+
for (a, b), v in pr.items():
|
| 267 |
+
if (b, a) in seen:
|
| 268 |
+
continue
|
| 269 |
+
seen.add((a, b))
|
| 270 |
+
row = {"pair": f"{a} ~ {b}", "r": round(v, 3)}
|
| 271 |
+
(dup if abs(v) >= REDUNDANT_R else real).append(row)
|
| 272 |
+
return real, dup
|
| 273 |
+
|
| 274 |
+
|
| 275 |
def make_tables(df: pd.DataFrame, max_card: int = 6) -> dict:
|
| 276 |
t = {}
|
| 277 |
n = df.isna().sum()
|
| 278 |
n = n[n > 0].sort_values(ascending=False)
|
| 279 |
+
if len(n):
|
| 280 |
+
head = n.head(15)
|
| 281 |
+
tbl = pd.DataFrame({"missing": head,
|
| 282 |
+
"pct": (100 * head / len(df)).round(1)}).to_markdown()
|
| 283 |
+
if len(n) > 15:
|
| 284 |
+
tbl += f"\n\n_+{len(n) - 15} more columns with missing values._"
|
| 285 |
+
t["missing"] = tbl
|
| 286 |
+
else:
|
| 287 |
+
t["missing"] = "_No missing values._"
|
| 288 |
|
| 289 |
bins = [c for c in df.select_dtypes(include="number") if df[c].nunique() == 2]
|
| 290 |
blocks = [
|
|
|
|
| 295 |
]
|
| 296 |
t["groups"] = "\n\n".join(blocks) if blocks else "_No low-cardinality groupings._"
|
| 297 |
|
| 298 |
+
real, dup = corr_pairs(df)
|
| 299 |
+
t["corr"] = (pd.DataFrame(real[:8]).to_markdown(index=False)
|
| 300 |
+
if real else "_Too few numeric columns._")
|
| 301 |
+
if dup:
|
| 302 |
+
t["redundant"] = (
|
| 303 |
+
f"_{len(dup)} column pairs correlate at |r| >= {REDUNDANT_R} — likely duplicate "
|
| 304 |
+
"encodings rather than findings._\n\n"
|
| 305 |
+
+ pd.DataFrame(dup[:8]).to_markdown(index=False))
|
| 306 |
+
|
| 307 |
num = df.select_dtypes(include="number")
|
| 308 |
+
t["describe"] = (num.describe().T.round(2).to_markdown() # transposed
|
| 309 |
+
if len(num.columns) else "_None._")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
return t
|
| 311 |
|
| 312 |
|
|
|
|
| 333 |
b, c, k, v = best
|
| 334 |
f["group"] = f"the highest mean {b} is {v:.3f}, for {c} = {k}"
|
| 335 |
|
| 336 |
+
real, _ = corr_pairs(df)
|
| 337 |
+
if real:
|
| 338 |
+
r = real[0]
|
| 339 |
+
f["corr"] = (f"the strongest correlation is {r['pair']} at r = {r['r']:+.3f} "
|
| 340 |
+
f"({'positive' if r['r'] > 0 else 'negative'})")
|
|
|
|
|
|
|
|
|
|
| 341 |
return f
|
| 342 |
|
| 343 |
|
|
|
|
| 441 |
P += ["## Correlations", t["corr"], ""]
|
| 442 |
if "corr" in kf:
|
| 443 |
P += [one_liner(kf["corr"]), ""]
|
| 444 |
+
if "redundant" in t:
|
| 445 |
+
P += ["## Redundant Columns", t["redundant"], ""]
|
| 446 |
P += ["## Numeric Summary", t["describe"], ""]
|
| 447 |
|
| 448 |
take = generate("Rewrite each fact as one markdown bullet. Add nothing.",
|