按月对pandas DataFrame进行降采样,保持目标比例不变
我有一个名为 df
的 pandas 数据框,其中有一列叫 'TARGET'
,它的值是 0
或 1
,还有一列叫 'MONTH'
,记录了不同的月份:
月份 | 目标为0的观察数 | 目标为1的观察数 |
---|---|---|
202207 | 44619 | 52960 |
202208 | 48093 | 55399 |
202209 | 50161 | 56528 |
我想把这个数据框进行下采样,使得每个月的 TARGET = 0
和 TARGET = 1
的观察数相同:
月份 | 目标为0的观察数 | 目标为1的观察数 |
---|---|---|
202207 | 44619 | 44619 |
202208 | 48093 | 48093 |
202209 | 50161 | 50161 |
我尝试了以下方法:
for m in df['MONTH'].unique():
number_of_ones = len(df[(df['MONTH']==m) & (df['TARGET']==1)])
number_of_zeros = len(df[(df['MONTH']==m) & (df['TARGET']==0)])
n_obs_to_drop = number_of_ones - number_of_zeros
df[df['MONTH']==m].drop(df[(df['MONTH']==m) & (df['TARGET']==1)].sample(n_obs_to_drop).index, inplace = True)
但显然没有删除任何东西,我收到了以下警告(编辑2):
/opt/conda/envs/librerias_cbi/lib/python3.9/site-packages/pandas/core/frame.py:4901: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
return super().drop(
我该怎么做呢?另外,欢迎提供不同的方法。
请注意,在不同的 MONTH
值中,索引有重复的值。数据框 df
中还有很多其他列,这些列在下采样后的数据框中也应该保留。
编辑1:
我添加了一个可复现的例子:
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], # Example additional columns
"other_column2": [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100]
}
df = pd.DataFrame(data)
pd.crosstab(df['MONTH'],df['TARGET'])
TARGET 0 1
MONTH
202207 1 3
202208 1 2
202209 2 2
2 个回答
0
我想问题出在对每一部分的 df
使用了 .drop()
,或者是跟索引中的重复值有关(对于不同的 MONTH
值)。我这样解决了这个问题:
df_temp = pd.DataFrame() # create an empty df that will be filled in each iteration of the loop
for mes in df['MONTH'].unique():
df_slice = df[df["MONTH"]==mes].copy() # create a temporary df where the index is unique
number_of_ones = len(df_slice[df_slice['TARGET']==1])
number_of_zeros = len(df_slice[df_slice['TARGET']==0])
n_obs_to_drop = number_of_ones - number_of_zeros
df_slice.drop(df_slice[df_slice['TARGET']==1].sample(n_obs_to_drop).index, inplace = True) # drop rows only for the partition
df_temp = pd.concat([df_temp,df_slice]) # concatenate the partitions
df = df_temp
del df_temp
del df_slice
2
我不确定这是不是最好的方法,但我就这样做吧。
获取每个月的样本数量
grp = pd.crosstab(df['MONTH'],df['TARGET'])\
.min(1)\
.reset_index(name='size')
MONTH size
0 202207 1
1 202208 1
2 202209 2
与原始数据合并
df1 = pd.merge(df, grp)
根据之前定义的大小进行抽样
df2 = df1.groupby(['MONTH', 'TARGET'])\
.apply(lambda x: x.sample(n=x['size'].iloc[0]))\
.reset_index(drop=True)