| """Appendix A3 — Feature-group cumulative F1 contribution (waterfall). |
| |
| Each bar is the validation-F1 gain from adding that group on top of the previous |
| stage (incremental ablation). The gains sum to 0.0283, exactly LightGCN 0.9386 -> 0.9669. |
| """ |
| from pathlib import Path |
| import matplotlib.pyplot as plt |
| from style import apply, save, PALETTE as C, COL1 |
|
|
| KEY = "figA3_feature_importance" |
| TITLE = "Appendix Figure A3. Feature-group contribution" |
|
|
| GROUPS = [ |
| ("graph / meta-path stacking", 0.0174, C[0]), |
| ("7-block random-walk ensemble", 0.0028, C[1]), |
| ("DeepWalk + Node2Vec", 0.0022, C[2]), |
| ("higher-order propagation", 0.0020, C[3]), |
| ("BPR-MF", 0.0017, C[4]), |
| ("neg / topk / variant scores", 0.0011, C[5]), |
| ("rich content (18-d)", 0.0006, C[6]), |
| ("content mean-cos", 0.0005, C[7]), |
| ] |
|
|
|
|
| def make(root, out): |
| apply() |
| names = [g[0] for g in GROUPS]; gains = [g[1] for g in GROUPS]; cols = [g[2] for g in GROUPS] |
| total = sum(gains) |
| fig, ax = plt.subplots(figsize=(COL1 * 1.5, 3.4)) |
| ax.barh(range(len(GROUPS)), gains, color=cols) |
| ax.set_yticks(range(len(GROUPS))); ax.set_yticklabels(names, fontsize=7.5) |
| ax.invert_yaxis() |
| for i, g in enumerate(gains): |
| ax.text(g + 0.0003, i, f"+{g:.4f}", va="center", fontsize=7) |
| ax.set_xlabel("$\\Delta$F1 (cumulative, seed=202)"); ax.set_xlim(0, 0.020) |
| ax.set_title(f"Feature-group contribution ($\\Sigma\\Delta$={total:.4f})", fontsize=9) |
| save(fig, KEY, out) |
| return dict(key=KEY, title=TITLE, status="ok", files=[f"{KEY}.pdf", f"{KEY}.png", f"{KEY}.svg"], |
| sources=["post95/extra/content_rich/node2vec/randomwalk/high_order incremental ablations"], |
| caption=( |
| "Cumulative F1 contribution by feature group (incremental ablation on seed=202). The graph/" |
| "meta-path stacking with the LightGBM stacker dominates (+0.0174); the random-walk blocks, " |
| "DeepWalk/Node2Vec and higher-order propagation form the second tier. Gains sum to 0.0283, " |
| "exactly the LightGCN 0.9386 -> 0.9669 improvement, confirming internal consistency.")) |
|
|
|
|
| if __name__ == "__main__": |
| from style import ensure_dirs |
| r = make(Path("."), ensure_dirs(Path("."))) |
| print(r["key"], r["status"]) |
|
|