根据其他列创建新列:DRY并运行

时间:2017-03-02 10:23:11

标签: python pandas dataframe functional-programming currying

首先:也许这在代码审查中更合适,但我认为这里有更多的熊猫仿冒用户。如果您不这么认为,请随意移动

通常情况下,人们想要从现有列中计算新列: 请考虑以下pandas Dataframe

df = pd.DataFrame({'ItemNumber':np.arange(1, 10), 
     'A':[1,1,1,2,2,2,3,3,3], 'B': [1,2,3]*3})
print(df)
   A  B  ItemNumber
0  1  1           1
1  1  2           2
2  1  3           3
3  2  1           4
4  2  2           5
5  2  3           6
6  3  1           7
7  3  2           8
8  3  3           9

假设我们通过

计算新列'C'
df.loc[(df["A"] == 1) & (df["B"] > 1), 'C'] = 1
df.loc[(df["B"] == 1) & (df["A"] > 1), 'C'] = 2 
df.loc[(df["A"] > 1) &  (df["B"] > 1), 'C' ] = 3
df.loc[(df["A"] == 1) & (df["B"] == 1), 'C' ] = 4

与呈现here的迭代方法相比,这对大型数据帧的执行速度也非常快。 特别是,这种方法的性能问题引导我看到上面提到的代码。

但是,此代码违反了DRY原则。复制粘贴感觉很难。

所以让我们更具功能性并定义两个curried函数:

def col_equals_value(col, value):
    def filter_df(df):
        return df[col] == value
    return filter_df

def col_greater_value(col, value):
    def filter_df(df):
        return df[col] > value
    return filter_df

从那时起,我们定义了比较:

a1 = col_equals_value('A', 1)
b1 = col_equals_value('B', 1)
agt1 = col_greater_value('A', 1)
bgt1 = col_greater_value('B', 1)

需要另一个函数来将值分配给列:

def assign_value(cond_1, cond_2, value):
    def assign_col_value(df, col):
        df.loc[df.apply(cond_1, axis=1) & df.apply(cond_2, axis=1), col] =value
    return assign_col_value

最后,我们可以将condtion-to-values映射定义为

mapping = [(a1, b1, 4), 
           (a1, bgt1, 1), 
           (agt1, b1, 2), 
           (agt1, bgt1, 3)]

构造assign_value_functions

m = [assign_value(x, y, z) for (x,y,z) in mapping]

并将每个函数应用于数据框:

for f in m:
    f(df, 'C')
print(df)

   A  B  ItemNumber
0  1  1           1
1  1  2           2
2  1  3           3
3  2  1           4
4  2  2           5
5  2  3           6
6  3  1           7
7  3  2           8
8  3  3           9

那么问题是什么? 这种方法似乎不太可扩展。对于每个比较运算符,我似乎需要定义一个全新的函数。比较运算符可以是变量吗? 目前,我只支持与& amp;运营商。如何概括呢? 我不确定调用apply方法。我觉得应该有一个更简单的方法。

欢迎任何帮助

1 个答案:

答案 0 :(得分:1)

您可以在此处使用pandas.DataFrame.eval。首先,定义一个字典trans,其中包含您要应用的转换。其次,使用辅助函数apply,它利用了eval:

trans = {"C": {"A == 1 and B > 1": 1,
               "B == 1 and A > 1": 2,
               "A > 1 and B > 1": 3,
               "A == 1 and B == 1": 4}}

def apply(sub_df, trans_dict):
    # sub_df = sub_df.copy() # in case you don't want change original df
    for column, transforms in trans_dict.items():
        for transform, value in transforms.items():
            sub_df.loc[sub_df.eval(transform), column] = value

    return sub_df

apply(df, trans)

    A   B   ItemNumber  C
0   1   1   1           4.0
1   1   2   2           1.0
2   1   3   3           1.0
3   2   1   4           2.0
4   2   2   5           3.0
5   2   3   6           3.0
6   3   1   7           2.0
7   3   2   8           3.0
8   3   3   9           3.0

我认为使用熊猫是合理的。此处 eval 以提高可读性。您现在可以向trans字典提供任何列条件值组合。

然而,我们仍然有点违反DRY,因为像A == 1这样的每个原子条件都会被多次评估,而不是像你提供的例子那样只评估一次。但我想有效地记住那些布尔系列会有变通方法。