按月对pandas DataFrame进行降采样,保持目标比例不变

1 投票
2 回答
62 浏览
提问于 2025-04-12 17:47

我有一个名为 df 的 pandas 数据框,其中有一列叫 'TARGET',它的值是 01,还有一列叫 'MONTH',记录了不同的月份:

月份 目标为0的观察数 目标为1的观察数
202207 44619 52960
202208 48093 55399
202209 50161 56528

我想把这个数据框进行下采样,使得每个月的 TARGET = 0TARGET = 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)

撰写回答