Pandas:追加行的副本,仅更改多列中大于允许拆分 bin 值的最大值的值

Pandas: Append copy of rows changing only values in multiple columns larger than max allowed to split bin values

问题: 我有一个数据框需要根据特定列的值进行修改。如果任何列值的值大于允许的最大值,则将根据分配到大小相等的 bin 中创建一个新行(在数据值和最大允许值之间进行整数除法)

Table 和解释:

原文:

Index Data 1 Data 2 Max. Allowed
1 1 2 3
2 10 5 8
3 7 12 5

必填:

Index 括号中的值指的是原始索引值

Index Data 1 Data 2 Max. Allowed
1 (1) 1 2 3
2 (2) 8 5 8
3 2 0 8
4 (3) 5 5 5
5 2 5 5
6 0 2 5

由于原始索引 = 2,Data1 = 10 大于允许的最大值 = 8。此行已分为两行,如上所示 table。

尝试: 我能够找到那些值大于允许的最大值和要插入的行数的列。但是我很困惑,如果两列的值都大于最大允许值(如索引 = 3 的情况),那么这种方法是否可行。这些值指示要为特定列的每个索引值插入多少行。

Index Data 1 Data 2
1 0 0
2 1 0
3 1 2

让我们按照以下步骤进行处理:

第一步:准备分割值:

定义自定义 lambda 函数,将 Data 1Data 2 转换为值列表,如果大于 Max. Allowed,则用 Max. Allowed 拆分。将扩展列表保存在 2 个新列中 Data 1xData 2x:

f = lambda x, y, z: [z] * (x // z) + [x % z] + [0] * (max(x//z, y//z) - x//z)

df['Data 1x'] = df.apply(lambda x: f(x['Data 1'], x['Data 2'], x['Max. Allowed'])  , axis=1)
df['Data 2x'] = df.apply(lambda x: f(x['Data 2'], x['Data 1'], x['Max. Allowed'])  , axis=1)

lambda函数的设计目的是在列表中加0,使同一行列表中的元素个数长度相同

中间结果:

print(df)

   Index  Data 1  Data 2  Max. Allowed    Data 1x    Data 2x
0      1       1       2             3        [1]        [2]
1      2      10       5             8     [8, 2]     [5, 0]
2      3       7      12             5  [5, 2, 0]  [5, 5, 2]

第 2 步:将拆分值分解成单独的行:

情况一:如果你的Pandas版本是1.3或以上

我们使用DataFrame.explode()展开2个新列:(这部分展开多个列的功能需要Pandas1.3或以上版本)

df = df.explode(['Data 1x', 'Data 2x'])

案例2: Pandas 1.3以下的版本,尝试以下方式爆破:

df = df.apply(pd.Series.explode)

案例3:如果以上2种爆破方式在你的编程环境中不起作用,请使用:

df_exp = df.explode('Data 1x')[['Index', 'Data 1', 'Data 2', 'Max. Allowed']].reset_index(drop=True)
df_1x = df.explode('Data 1x')[['Data 1x']].reset_index(drop=True)
df_2x = df.explode('Data 2x')[['Data 2x']].reset_index(drop=True)

df = df_exp.join([df_1x, df_2x])

结果:

print(df)

   Index  Data 1  Data 2  Max. Allowed Data 1x Data 2x
0      1       1       2             3       1       2
1      2      10       5             8       8       5
1      2      10       5             8       2       0
2      3       7      12             5       5       5
2      3       7      12             5       2       5
2      3       7      12             5       0       2

第 3 步:格式化为所需的输出:

# select and rename columns
df = (df[['Index', 'Data 1x',  'Data 2x', 'Max. Allowed']]
        .rename({'Data 1x': 'Data 1', 'Data 2x': 'Data 2'}, axis=1)
        .reset_index(drop=True)
     )

# reset the `Index` values
df['Index'] = df.index + 1  

最终结果:

print(df)


   Index Data 1 Data 2  Max. Allowed
0      1      1      2             3
1      2      8      5             8
2      3      2      0             8
3      4      5      5             5
4      5      2      5             5
5      6      0      2             5

假设您愿意逐行处理数据框,那么您可以在 while 循环中检查最大值并用新行填充新数据框。

import pandas as pd
df = pd.DataFrame({"Index" : [1, 2, 3], "Data 1" : [1,10,7], "Data 2" : [2,5,12], "Max_Allowed" : [3,8,5]})
print(df)
# create a new data frame that we can populate with rows of data
dfz = pd.DataFrame(columns=("Index", "Data 1","Data 2","Max_Allowed"))

iz = 0
for(_, col1, col2, col3, col4) in df.itertuples(name=None):
    
    if col2<=col4 and col3<=col4:             
        dfz.loc[iz] = [str(iz+1)+"("+str(col1)+")", col2, col3, col4]
        iz += 1
    else:
        iz_orig = iz  # keep the index we are at currently
        while col2>0 or col3>0:
            
            if col2>col4:   # check if more than maximum value for Data 1
                col2p=col4
                col2 -= col4  # minus the maximum value from current value
            else:
                col2p=col2
                col2 = 0   # set the value to zero
                
            if col3>col4:  # check if more than maximum value for Data 2
                col3p=col4
                col3 -= col4
            else:
                col3p=col3
                col3 = 0

            if iz_orig == iz:
                # enter with the original Index in parenthesis
                dfz.loc[iz] = [str(iz+1)+"("+str(col1)+")", col2p, col3p, col4]
            else:
                # enter row with just the new Index
                dfz.loc[iz] = [str(iz+1), col2p, col3p, col4]
                
            iz += 1

print(dfz)