SPB Git
3.9 KB · 88 lines python
1"""Predictive model: which projects are 'mega-projects'?23Target: among projects with a disclosed dollar amount, those above the median4(>$718m). Features are text-derived structural attributes only (project type,5sector, status, #technologies, #filings, #mentions, AI flag, has-location,6announcement year) -- no leakage of the amount. Gradient-boosted trees with7cross-validated ROC-AUC and permutation importance.8"""9from __future__ import annotations1011import warnings12import numpy as np13import pandas as pd14from sklearn.ensemble import HistGradientBoostingClassifier15from sklearn.model_selection import cross_val_predict, StratifiedKFold16from sklearn.metrics import roc_auc_score, roc_curve, average_precision_score17from sklearn.inspection import permutation_importance1819from common import DATA, set_style, savefig, write_table, save_results, esc20import matplotlib.pyplot as plt2122warnings.filterwarnings("ignore")23set_style()2425p = pd.read_parquet(DATA / "projects.parquet")26d = p[p["has_amount"]].copy()27thr = d["total_amount_usd"].median()28d["mega"] = (d["total_amount_usd"] > thr).astype(int)29d["has_loc"] = (d["canonical_location"].fillna("") != "").astype(int)30d["ann_year"] = d["ann_year"].astype(int)3132feat_num = ["n_tech", "n_filings", "n_mentions", "is_ai", "has_loc", "ann_year", "avg_confidence"]33d["is_ai"] = d["is_ai"].astype(int)34Xcat = pd.get_dummies(d[["project_type", "sector", "status"]].astype(str), dtype=float)35X = pd.concat([d[feat_num].reset_index(drop=True), Xcat.reset_index(drop=True)], axis=1)36y = d["mega"].values3738clf = HistGradientBoostingClassifier(max_iter=350, learning_rate=0.06,39                                     max_leaf_nodes=31, l2_regularization=1.0,40                                     random_state=0)41cv = StratifiedKFold(5, shuffle=True, random_state=0)42proba = cross_val_predict(clf, X, y, cv=cv, method="predict_proba")[:, 1]43auc = roc_auc_score(y, proba)44ap = average_precision_score(y, proba)4546# fit on full for importance47clf.fit(X, y)48pi = permutation_importance(clf, X, y, n_repeats=8, random_state=0, scoring="roc_auc")49imp = pd.Series(pi.importances_mean, index=X.columns).sort_values(ascending=False)50# collapse one-hot back to groups51def group(col):52    for g in ("project_type_", "sector_", "status_"):53        if col.startswith(g):54            return g.rstrip("_")55    return col56gimp = imp.groupby(group).sum().sort_values(ascending=False)5758# ROC + importance figure59fpr, tpr, _ = roc_curve(y, proba)60fig, axes = plt.subplots(1, 2, figsize=(11.0, 4.2))61ax = axes[0]62ax.plot(fpr, tpr, color="#1b3a5b", lw=2, label=f"GBT (AUC={auc:.3f})")63ax.plot([0, 1], [0, 1], "--", color="grey", lw=0.9)64ax.set_xlabel("False positive rate"); ax.set_ylabel("True positive rate")65ax.set_title("Mega-project classifier (5-fold CV ROC)"); ax.legend(loc="lower right")66ax2 = axes[1]67gg = gimp.head(8)[::-1]68LBL = {"project_type": "Project type", "sector": "Sector", "status": "Status",69       "n_tech": "\\# technologies", "n_filings": "\\# filings", "n_mentions": "\\# mentions",70       "is_ai": "AI flag", "has_loc": "Has location", "ann_year": "Year", "avg_confidence": "Confidence"}71ax2.barh([LBL.get(i, i).replace("\\#", "#") for i in gg.index], gg.values, color="#c44e52")72ax2.set_xlabel("Permutation importance (AUC drop)")73ax2.set_title("Feature importance")74savefig(fig, "fig_ml_roc.pdf")7576lt = [r"\begin{tabular}{lr}", r"\toprule", r"Feature group & Importance (AUC drop) \\", r"\midrule"]77for i, v in gimp.head(8).items():78    lt.append(f"{LBL.get(i, esc(i))} & {v:.4f} \\\\")79lt += [r"\midrule", f"CV ROC-AUC & {auc:.3f} \\\\", f"Avg. precision & {ap:.3f} \\\\",80       f"Observations & {len(d):,} \\\\", r"\bottomrule", r"\end{tabular}"]81write_table("tab_ml", "\n".join(lt))8283save_results("ml", {84    "n": int(len(d)), "threshold_usd": float(thr), "auc": float(auc), "avg_precision": float(ap),85    "top_features": {str(k): float(v) for k, v in gimp.head(6).items()},86})87print(f"ML done | AUC={auc:.3f}, AP={ap:.3f}")88
Files · main
Menu

Theme