根据可能在熊猫中包含Nan值的列创建列

时间:2018-09-25 15:59:30

标签: python pandas numpy dataframe

我有3列应该加权和求和。但是,有时这些列中有Nan值,这会影响要加权和求和的最后一组列。进一步的示例df:

import numpy as np
import pandas as pd

f = { 'A': [1, np.nan, 2, np.nan, 5, 6, np.nan],
'B': [np.nan, np.nan, 1, 1, 1, np.nan, 7], 
'C': [np.nan, 2, 3, 6, np.nan, 5, np.nan]}
fd = pd.DataFrame(data = f)
fd.head(10)

      A  B   C
0   1.0 NaN NaN
1   NaN NaN 2.0
2   2.0 1.0 3.0
3   NaN 1.0 6.0
4   5.0 1.0 NaN
5   6.0 NaN 5.0
6   NaN 7.0 NaN

此示例演示了列中Nan的所有可能组合。然后,我想创建列F,这是列A,B和C不是Nan时的加权总和。这是我的代码:

def scaler(df):
"Scaling and summing"
if (pd.notnull(df['A']) == True & pd.notnull(df['B']) == True & pd.notnull(df['C']) == True):
    return df['A']*0.5+df['B']*0.25+df['C']*0.25
elif (pd.notnull(df['A']) == True & pd.notnull(df['B']) == False & pd.notnull(df['C']) == False):
    return df['A']*1
elif (pd.notnull(df['A']) == True & pd.notnull(df['B']) == True & pd.notnull(df['C']) == False):
    return df['A']*0.75+df['B']*0.25
elif (pd.notnull(df['A']) == True & pd.notnull(df['B']) == False & pd.notnull(df['C']) == True):
    return df['A']*0.75+df['C']*0.25
elif (pd.notnull(df['A']) == False & pd.notnull(df['B']) == True & pd.notnull(df['C']) == True):
    return df['B']*0.5+df['C']*0.5
elif (pd.notnull(df['A']) == False & pd.notnull(df['B']) == True & pd.notnull(df['C']) == False):
    return df['B']*1
else: 
    return df['C']*1

fd['F'] =fd.apply(scaler, axis = 'columns')
fd.head(10)

     A   B   C   F
0   1.0 NaN NaN NaN
1   NaN NaN 2.0 NaN
2   2.0 1.0 3.0 2.0
3   NaN 1.0 6.0 6.0
4   5.0 1.0 NaN NaN
5   6.0 NaN 5.0 5.0
6   NaN 7.0 NaN 7.0

因此,我得到一个df,在其中正确加权,并且仅对所有三个非Nan值的列求和。如果其中一列中至少有一个Nan,则我在F列中得到Nan或错误的结果值。

为解决此问题,我用一些浮点数替换了原始df中的所有Nan值,该浮点数超出了所有列的范围,然后在上面展示了完美的代码逻辑。我的问题是:

1)为什么会发生(尽管包含这些值的列不直接参与重新调整的公式,但所有Nan值都会在结果周围翻转)?

2)我克服问题的方式发现自己有点草率。是否有更优雅的解决方案?

1 个答案:

答案 0 :(得分:1)

您误解了pd.DataFrame.apply的工作方式。沿着axis=1,每个都传递给函数,而不是整个数据帧。相应地命名函数参数很有用。

您正在函数中使用非序列的标量,因此应使用常规的and而不是&。还要注意,pd.isnullpd.notnull都存在。因此,您可以重写如下:

def scaler(row):
    "Scaling and summing"
    if pd.notnull(row['A']) and pd.notnull(row['B']) and pd.notnull(row['C']):
        return row['A']*0.5 + row['B']*0.25 + row['C']*0.25
    elif pd.notnull(row['A']) and pd.isnull(row['B']) and pd.isnull(row['C']):
        return row['A']
    elif pd.notnull(row['A']) and pd.notnull(row['B']) and pd.isnull(row['C']):
        return row['A']*0.75 + row['B']*0.25
    elif pd.notnull(row['A']) and pd.isnull(row['B']) and pd.notnull(row['C']):
        return row['A']*0.75 + row['C']*0.25
    elif pd.isnull(row['A']) and pd.notnull(row['B']) and pd.notnull(row['C']):
        return row['B']*0.5 + row['C']*0.5
    elif pd.isnull(row['A']) and pd.notnull(row['B']) and pd.isnull(row['C']):
        return row['B']
    else: 
        return row['C']

df['F'] = df.apply(scaler, axis=1)

但这对于大量的行而言效率低下。使用np.select的解决方案更有效,更易读。这些仅使用向量化操作。注意,我们只计算一次检查每个系列中的值是否为空。

a_null = df['A'].isnull()
b_null = df['B'].isnull()
c_null = df['C'].isnull()

conds = [~a_null & b_null & c_null,
         a_null & ~b_null & c_null,
         a_null & b_null & ~c_null,
         ~a_null & ~b_null & c_null,
         ~a_null & b_null & ~c_null,
         a_null & ~b_null & ~c_null,
         ~a_null & ~b_null & ~c_null]

choices = [df['A'], df['B'], df['C'],
           0.75 * df['A'] + 0.25 * df['B'],
           0.75 * df['A'] + 0.25 * df['C'],
           0.5 * df['B'] + 0.5 * df['C'],
           0.5 * df['A'] + 0.25 * df['B'] + 0.25 * df['C']]

df['F'] = np.select(conds, choices)

结果:

     A    B    C     F
0  1.0  NaN  NaN  1.00
1  NaN  NaN  2.0  2.00
2  2.0  1.0  3.0  2.00
3  NaN  1.0  6.0  3.50
4  5.0  1.0  NaN  4.00
5  6.0  NaN  5.0  5.75
6  NaN  7.0  NaN  7.00