Spaces:
Sleeping
Sleeping
| """Fit the FeatureBuilder on the training split and persist all artifacts. | |
| Outputs: | |
| artifacts/feature_pipeline.pkl - fitted FeatureBuilder | |
| artifacts/feature_list.json - selected feature names | |
| artifacts/train_holdout.parquet - raw training rows (+target) | |
| artifacts/test_holdout.parquet - raw test rows (+target), used by eval & simulator | |
| reports/feature_selection.md - what was selected and why | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import joblib | |
| import pandas as pd | |
| from src import config | |
| from src.data.load import load_raw, train_test | |
| from src.features.builder import ANOMALY_COL, FeatureBuilder | |
| def main() -> None: | |
| config.ensure_dirs() | |
| df = load_raw() | |
| X_tr, X_te, y_tr, y_te = train_test(df) | |
| # Persist raw holdouts so training / eval / simulator share identical splits. | |
| train_raw = X_tr.copy(); train_raw[config.TARGET] = y_tr.values | |
| test_raw = X_te.copy(); test_raw[config.TARGET] = y_te.values | |
| train_raw.to_parquet(config.ARTIFACTS_DIR / "train_holdout.parquet") | |
| test_raw.to_parquet(config.TEST_SPLIT_PATH) | |
| builder = FeatureBuilder() | |
| builder.fit(X_tr, y_tr) | |
| joblib.dump(builder, config.PIPELINE_PATH) | |
| config.FEATURE_LIST_PATH.write_text(json.dumps(builder.selected_features_, indent=2)) | |
| # ---- selection report -------------------------------------------------- | |
| freq = pd.Series(builder.selection_freq_).sort_values(ascending=False) | |
| priors = set(config.KNOWN_IMPORTANT) | {"F3888_age_days", "F3889_recency_ord"} | |
| lines = ["# Feature Selection — MuleGuard\n"] | |
| lines.append(f"- Full feature space after preprocessing: **{len(builder.feature_names_full_)}** columns") | |
| lines.append(f"- Selected for modeling: **{len(builder.selected_features_)}**") | |
| lines.append(f"- Includes the fused **{ANOMALY_COL}** (Isolation Forest) and retained domain priors.\n") | |
| lines.append("## Top selected features by CV selection frequency\n") | |
| lines.append("| Feature | Selection freq | Domain prior? |") | |
| lines.append("|---|---|---|") | |
| for feat in builder.selected_features_: | |
| f = freq.get(feat, 0.0) | |
| base = feat.split("_")[0] | |
| prior = "✅" if (feat in priors or base in priors or feat == ANOMALY_COL) else "" | |
| lines.append(f"| {feat} | {f:.2f} | {prior} |") | |
| (config.REPORTS_DIR / "feature_selection.md").write_text("\n".join(lines)) | |
| print(f"Selected {len(builder.selected_features_)} features " | |
| f"(of {len(builder.feature_names_full_)}). Saved pipeline + reports.") | |
| print("Anomaly score included:", ANOMALY_COL in builder.selected_features_) | |
| if __name__ == "__main__": | |
| main() | |