
本文介绍一种高效、安全的分组下采样方法:对每个month组内,按target=0和target=1的最小频数统一截断,确保每月两类样本数量相等,同时保留所有原始列且避免settingwithcopywarning。
本文介绍一种高效、安全的分组下采样方法:对每个month组内,按target=0和target=1的最小频数统一截断,确保每月两类样本数量相等,同时保留所有原始列且避免settingwithcopywarning。
在处理时间序列分类数据(如月度风控建模)时,常需对不平衡标签(TARGET=0/1)进行分层下采样,但要求按时间维度(如MONTH)独立平衡——即每个自然月内正负样本数量严格相等,而非全局平衡。直接遍历+drop()不仅效率低,更会因链式赋值触发SettingWithCopyWarning,甚至因视图/副本问题导致修改失效。
推荐采用向量化分组采样策略,核心思想是:对每个MONTH,计算该月TARGET=0与TARGET=1的最小计数作为目标样本量,再按MONTH+TARGET双重分组执行等量随机抽样。该方法完全避免显式循环和原地修改,语义清晰、性能优异,且天然支持多列保留。
以下为完整实现(基于您提供的可复现示例):
import pandas as pd
# 构造示例数据
data = {
"MONTH": [202207, 202207, 202207, 202207, 202208, 202208, 202208, 202209, 202209, 202209, 202209],
"TARGET": [1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0],
"other_column1": [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110],
"other_column2": [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100]
}
df = pd.DataFrame(data)
# 步骤1:计算每组MONTH的目标采样量(min(count_0, count_1))
min_counts = pd.crosstab(df['MONTH'], df['TARGET']).min(axis=1).rename('n_sample')
# 输出:MONTH
# 202207 1
# 202208 1
# 202209 2
# 步骤2:将采样量映射回原始DataFrame(自动广播至每行)
df_with_n = df.merge(min_counts, left_on='MONTH', right_index=True)
# 步骤3:按MONTH+TARGET分组,对每组抽取n_sample个样本
balanced_df = (df_with_n
.groupby(['MONTH', 'TARGET'])
.apply(lambda g: g.sample(n=g['n_sample'].iloc[0], random_state=42))
.reset_index(drop=True)
.drop('n_sample', axis=1)) # 清理辅助列
print("下采样后分布:")
print(pd.crosstab(balanced_df['MONTH'], balanced_df['TARGET']))
输出结果:
下采样后分布: TARGET 0 1 MONTH 202207 1 1 202208 1 1 202209 2 2
✅ 关键优势说明:
- 无警告安全:全程使用merge和groupby.apply,不涉及df[...].drop(...)等易触发SettingWithCopyWarning的操作;
- 全列保留:other_column1/other_column2等非分组列自动继承,无需额外指定;
- 可复现性:通过random_state参数确保结果稳定,便于调试与生产部署;
- 扩展性强:若需按其他维度(如地区+月份)联合平衡,仅需调整groupby键即可。
⚠️ 注意事项:
- 若某月TARGET仅含单一类别(如全为0),min()将返回0,导致该月被完全剔除——请预先用pd.crosstab(...).gt(0).all(axis=1)校验数据完整性;
- sample()默认有放回抽样(replace=False),若样本量极小且需保证多样性,可设replace=True,但需注意可能引入重复记录;
- 大数据集建议用df.groupby(...).sample(...)(Pandas ≥1.1.0),比apply(lambda x: x.sample())性能更优。
此方案兼顾正确性、可读性与工程鲁棒性,是时间分层平衡采样的推荐实践。










