求和 vs np.nansum 在 pandas 数据帧上对具有相同名称的列求和时的怪异 - python

sum vs np.nansum weirdness while summing columns with same name on a pandas dataframe - python

从这里关于 SO (Merge Columns within a DataFrame that have the Same Name) 的讨论中获得灵感,我尝试了建议的方法,虽然它在使用函数 sum() 时有效,但在我使用 np.nansum

import pandas as pd
import numpy as np

df = pd.DataFrame(np.random.rand(100,4), columns=['a', 'a','b','b'], index=pd.date_range('2011-1-1', periods=100))
print(df.head(3))

sum() 案例:

print(df.groupby(df.columns, axis=1).apply(sum, axis=1).head(3))
                   a         b
2011-01-01  1.328933  1.678469
2011-01-02  1.878389  1.343327
2011-01-03  0.964278  1.302857

np.nansum() 案例:

print(df.groupby(df.columns, axis=1).apply(np.nansum, axis=1).head(3))

a    [1.32893299939, 1.87838886222, 0.964278430632,...
b    [1.67846885234, 1.34332662587, 1.30285727348, ...
dtype: object

知道为什么吗?

问题在于 np.nansum 将其输入转换为 numpy 数组,因此它实际上丢失了列信息(sum 不会这样做)。因此,groupby 在构造输出时不会返回任何列信息,因此输出只是一系列 numpy 数组。

具体来说,the source code for np.nansum calls the _replace_nan function. In turn, the source code for _replace_nan 检查输入是否为数组,如果不是,则将其转换为 1。

虽然所有的希望都没有消失。您可以使用 Pandas 函数轻松复制 np.nansum。具体使用 sum 后跟 fillna:

df.groupby(df.columns, axis=1).sum().fillna(0)

sum 应该忽略 NaN 并且只对非空值求和。您会得到 NaN 的唯一情况是所有试图求和的值都是 NaN,这就是为什么需要 fillna 的原因。请注意,您也可以在 groupby 之前执行 fillna,即 df.fillna(0).groupby....

如果真的要用np.nansum,可以重铸为pd.Series。这可能会影响性能,因为构建一个 Series 可能相对昂贵,而且您会多次这样做:

df.groupby(df.columns, axis=1).apply(lambda x: pd.Series(np.nansum(x, axis=1), x.index))

示例计算

对于一些示例计算,我将使用以下简单的 DataFrame,其中包含 NaN 个值(您的示例数据不包含):

df = pd.DataFrame([[1,2,2,np.nan,4],[np.nan,np.nan,np.nan,3,3],[np.nan,np.nan,-1,2,np.nan]], columns=list('aaabb'))

     a    a    a    b    b
0  1.0  2.0  2.0  NaN  4.0
1  NaN  NaN  NaN  3.0  3.0
2  NaN  NaN -1.0  2.0  NaN

使用 sum 不使用 fillna:

df.groupby(df.columns, axis=1).sum()

     a    b
0  5.0  4.0
1  NaN  6.0
2 -1.0  2.0

使用 sumfillna:

df.groupby(df.columns, axis=1).sum().fillna(0)

     a    b
0  5.0  4.0
1  0.0  6.0
2 -1.0  2.0

与固定的np.nansum方法相比:

df.groupby(df.columns, axis=1).apply(lambda x: pd.Series(np.nansum(x, axis=1), x.index))
     a    b
0  5.0  4.0
1  0.0  6.0
2 -1.0  2.0