2 · 哪些特征有区分度,而且足够稳定?¶
本批保留 Home Credit 两张 application 表的全部 356,255 条记录。TARGET 来自原比赛训练表,比赛测试表的 48,744 条记录保持 null。APPLY_DATE 是按 ID 稳定散列生成的演示日期。0train 用于拟合和 PSI 基准,1val 用于验证或筛选,2test 与 3oot 用于固定结果的独立复核,4_oot_undue 仅表示比赛未公开标签。日期、元数据和角色映射见输入方案与分区说明。
本批实际结果¶
TARGET;KS 为 0–100 点,PSI 无量纲。0train 拟合,3oot 的日期为模拟日期。
| 字段 | Train KS | Val KS | Test KS | OOT KS | 无标签 PSI |
|---|---|---|---|---|---|
| EXT_SOURCE_2 | 21.1206 | 21.5852 | 21.8119 | 21.9017 | 0.0067 |
| EXT_SOURCE_3 | 22.0037 | 23.3587 | 21.3300 | 23.0991 | 0.0051 |
| AMT_INCOME_TOTAL | 3.7852 | 3.9803 | 3.2401 | 3.0711 | 0.0196 |
| DAYS_BIRTH | 11.3724 | 13.4295 | 12.3029 | 12.7862 | 0.0004 |
只在 0train 拟合固定分箱,并以它作为 PSI 参考分布;五组复用同一分箱。 主图是 EXT_SOURCE_2 的 MARS 原生趋势图,完整展示一次。EXT_SOURCE_3、AMT_INCOME_TOTAL、DAYS_BIRTH 可在报告中继续查询。 完整候选 fcols 与有限展示字段分别记录在 manifest;有限图表不代表只分析了这些字段。 无标签组保留箱占比与适用 PSI,IV、KS、AUC 和已观测 Bad Rate 按实际未观测状态解释。 count、observed_count 与未观测人数以报告字段为准;本例不设权重,KS 为 0–100,Bad Rate 为 0–1,IV/PSI 无量纲。 模拟月趋势另存 binning-time.marsreport;模拟时间差异不能证明真实业务 OOT。
下载 300 dpi PNG · 查看 SVG · 原生图参数与查询来源
图由 MarsBinningReport.save_risk_trend_images() 直接导出,保留原生布局、坐标与标签。AMT_CREDIT 是贷款金额,使用公开数据的原始金额单位,不解释为利润或损失。
从报告查看真实表与图,按实际分母判断。完整可运行代码使用共享准备入口,复用同一宽表与固定参考样本。
def _binning(inputs: HomeCreditInputs, output: Path) -> None:
"""Train 决定所有固定切点与参考分布,五分区和模拟月趋势复用。"""
display = [name for name in DEFAULT_DISPLAY_FEATURES if name in inputs.fcols]
run = profile_risk(
inputs.df, target="TARGET", features=inputs.fcols, benchmark_df=inputs.benchmark_df,
method="quantile", n_bins=4, group_col="dataset_flag", time_col="APPLY_DATE",
time_grain="month", amount_col="AMT_CREDIT",
feature_metadata=inputs.feature_metadata, business_context=inputs.business_context,
n_jobs=1, batch_size=30,
)
report = run.report
report.save(output / "binning.marsreport", overwrite=True)
report.write_html(str(output / "binning.html"), max_plots=len(display), include_charts=True,
chart_embed_mode="inline", report_name="Home Credit 固定分箱与五分区风险")
load_report(output / "binning.marsreport").write_excel(str(output / "binning.xlsx"))
time_report = MarsBinEvaluator().evaluate(
inputs.df, target="TARGET", features=display, binner=run.binner,
benchmark_df=inputs.benchmark_df, time_col="APPLY_DATE", time_grain="month",
amount_col="AMT_CREDIT", feature_metadata=inputs.feature_metadata,
business_context=inputs.business_context,
).report
time_report.save(output / "binning-time.marsreport", overwrite=True)
time_report.write_html(str(output / "binning-time.html"), max_plots=4, chart_embed_mode="inline")
load_report(output / "binning-time.marsreport").write_excel(str(output / "binning-time.xlsx"))
# 特殊码只应用于 DAYS_EMPLOYED 单字段,完整候选报告同时保留原始未改写值。
employment = profile_risk(
inputs.df, target="TARGET", features=["DAYS_EMPLOYED"], benchmark_df=inputs.benchmark_df,
method="quantile", n_bins=4, special_values=[365243], group_col="dataset_flag",
time_col="APPLY_DATE", time_grain="month",
amount_col="AMT_CREDIT", feature_metadata=inputs.feature_metadata,
business_context={**inputs.business_context, "special_value_processing": {"DAYS_EMPLOYED": [365243]}}, n_jobs=1,
).report
employment.save(output / "days-employed-special.marsreport", overwrite=True)
employment.write_html(str(output / "days-employed-special.html"), max_plots=1)
load_report(output / "days-employed-special.marsreport").write_excel(str(output / "days-employed-special.xlsx"))
actual_parameters = {"features": "EXT_SOURCE_2", "target": "TARGET", "dpi": 300, "show_risk": "both"}
for image_format in ("png", "svg"):
for native, stem in ((report, "binning-native-home-credit"), (time_report, "binning-native-time-home-credit")):
generated = native.save_risk_trend_images(output, **actual_parameters,
image_format=image_format, filename_prefix="native-export")
shutil.copyfile(generated[0], output / f"{stem}.{image_format}")
generated[0].unlink()
queries = [
_query(report, "summary", "外部来源分 2 与 3 的已观测区分度", features=list(DIRECTIONS), limit=2),
_query(report, "detail", "主图五组固定箱人数和已观测坏率", features="EXT_SOURCE_2", limit=30),
_query(report, "calculation_status", "无标签组监督指标真实状态", filters={"group": "4_oot_undue"}, features=list(DIRECTIONS), limit=10),
_query(report, "detail", "收入与年龄天数原始分析入口", features=["AMT_INCOME_TOTAL", "DAYS_BIRTH"], limit=8),
]
metrics: dict[str, Any] = {}
for metric in ("iv", "ks", "auc", "psi"):
evidence = _query(report, f"trend.{metric}", f"五分区 {metric} 固定分箱比较", features=FEATURES, limit=4)
queries.append(evidence)
metrics[metric] = evidence["rows"]
_write_json(output / "binning-native-evidence.json", {
"report_id": report.report_id, "time_report_id": time_report.report_id,
"feature": "EXT_SOURCE_2", "target": "TARGET", "chart_png": "binning-native-home-credit.png",
"chart_svg": "binning-native-home-credit.svg", "source_api": "MarsBinningReport.save_risk_trend_images",
"parameters": actual_parameters, "layout": "existing native figure; no crop/redraw",
"group": "dataset_flag", "time_col": "APPLY_DATE", "time_grain": "month",
"scope": "five partitions; separate simulated monthly view", "queries": queries[:3],
})
_case(output, 2, report, "固定分箱怎样比较五分区与无标签分布?", queries, {
"summary_rows": queries[0]["rows"], "status_rows": queries[2]["rows"], "split_metrics": metrics,
"reference_row_count": inputs.benchmark_df.height, "targets": ["TARGET"],
"candidate_features": inputs.fcols, "analyzed_features": inputs.fcols, "display_features": display,
"primary_feature": "EXT_SOURCE_2", "time_report_id": time_report.report_id,
"units": {"ks": "0-100 percentage points", "bad_rate": "fraction", "iv": "dimensionless", "psi": "dimensionless"},
}, "模拟日期不能证明真实跨期泛化;无标签组只有分布/PSI,没有可观测坏率或 KS。")
新进程按目录读取已保存统计;实际有限 JSON 中的引用可重放。
from mars.reporting import load_report
report = load_report("binning.marsreport")
print(report.describe())
page = report.query_page("summary", features="EXT_SOURCE_2", limit=3)
print(page["reference"], page["data"])
实际查询结果与分页引用
{
"case": 2,
"report_id": "d73396cc-4ad6-493e-a69a-0cd2a0a435e6",
"consumer": "deterministic public API queries; no LLM",
"question": "固定分箱怎样比较五分区与无标签分布?",
"query": {
"question": "外部来源分 2 与 3 的已观测区分度",
"reference": {
"report_id": "d73396cc-4ad6-493e-a69a-0cd2a0a435e6",
"table": "summary",
"query": {
"offset": 0,
"limit": 2,
"features": [
"EXT_SOURCE_2",
"EXT_SOURCE_3"
]
}
},
"total_rows": 2,
"returned_rows": 2,
"omitted_rows": 0,
"next_offset": null,
"rows": [
{
"feature": "EXT_SOURCE_3",
"display_name": "外部来源分 3",
"dtype": "Float64",
"data_source": "Home Credit 2018 application_{train|test}.csv",
"iv": 0.2917010188102722,
"ks": 22.186461924601865,
"auc": 0.6482176971618914,
"psi_max": 0.00506146451724824,
"rc_min": 1.0,
"lift_min": 0.439180232030532,
"lift_max": 1.8693434599007182,
"missing": 0.19545830935706138,
"missing_min": 0.17782701460692235,
"missing_max": 0.19945768755745727,
"mono": -1.0
},
{
"feature": "EXT_SOURCE_2",
"display_name": "外部来源分 2",
"dtype": "Float64",
"data_source": "Home Credit 2018 application_{train|test}.csv",
"iv": 0.2703412175178528,
"ks": 21.25443262874279,
"auc": 0.6414238444445256,
"psi_max": 0.006740861911165587,
"rc_min": 1.0,
"lift_min": 0.4715535313877019,
"lift_max": 1.7708857175047088,
"missing": 0.0018750614026469777,
"missing_min": 0.00016412276382733948,
"missing_max": 0.0028283485045512732,
"mono": -1.0
}
]
},
"unavailable": "模拟日期不能证明真实跨期泛化;无标签组只有分布/PSI,没有可观测坏率或 KS。"
}
运行与下载¶
python docs/snippets/task_cases.py --case 2 --data-dir examples/home-credit-2018/data/raw_data --output-dir output/task-cases
首次生成全部关联证据用 --case all。文件缺失会明确失败,正式入口不回退合成数据。 数据下载与输入准备 · 完整共享源码 · 共享包 · 来源和哈希 · 案例索引
