【问题标题】:How to sum appearances in columns with pandas without lambda如何在没有 lambda 的情况下使用 pandas 对列中的外观求和
【发布时间】:2017-11-05 16:40:08
【问题描述】:

我有以下数据框

    a    b    c    d    e 
0   0    0   -1    1   -1
1   0    1   -1    1   -1
2  -1    0   -1    1    1
3  -1    1    1   -1    1
4   1    0    1   -1    1  
5   1    0    0    0    1 
6   1    1    0    0   -1 
7   1    1   -1    0    0

对于出现在 a,b,c,d,e 中的每个数字,我想将其在一行中出现的次数求和并存储在列中,因此结果应该是这样的:

    a    b    c    d    e  Sum1  Sum0  Sum_1
0   0    0   -1    1   -1    1    2     2
1   0    1   -1    1   -1    2    1     2 
2  -1    0   -1    1    1    2    1     2
3  -1    1    1   -1    1    3    0     2
4   1    0    1   -1    1    3    1     1
5   1    0    0    0    1    2    3     0
6   1   -1    0    0   -1    1    2     2
7   1    1   -1   -1    0    2    1     2

所以在第一行中,数字“1”在 a,b,c,d,e 中出现了一次,因此我们将其存储在 Sum1 列中。然后,数字“0”出现两次,我们存储在Sum0中,数字“-1”出现2次,我们存储在Sum_1中。

如何在不使用 lambda 函数的情况下计算这些列(以获得更好的性能)?我猜这里涉及到 numpy,但我不知道该怎么做

【问题讨论】:

  • 只有-1、0、1吗?
  • 此刻,是的。虽然将来我可能想使用其他值,但只有 -1、0 和 1 的解决方案是完全可以接受的。

标签: python pandas numpy dataframe lambda


【解决方案1】:

使用get_dummies

df=df.astype(str)
pd.get_dummies(df.stack()).sum(level=0)
Out[667]: 
   -1  0  1
0   2  2  1
1   2  1  2
2   2  1  2
3   2  0  3
4   1  1  3
5   0  3  2
6   1  2  2
7   1  2  2

更多信息

pd.concat([df,pd.get_dummies(df.stack()).sum(level=0).add_prefix('Sum')],1)
Out[669]: 
    a  b   c   d   e  Sum-1  Sum0  Sum1
0   0  0  -1   1  -1      2     2     1
1   0  1  -1   1  -1      2     1     2
2  -1  0  -1   1   1      2     1     2
3  -1  1   1  -1   1      2     0     3
4   1  0   1  -1   1      1     1     3
5   1  0   0   0   1      0     3     2
6   1  1   0   0  -1      1     2     2
7   1  1  -1   0   0      1     2     2

另一种方法可能解决但不需要转换为str。

df.apply(lambda x : x.value_counts(),1).fillna(0)

Out[674]: 
    -1    0    1
0  2.0  2.0  1.0
1  2.0  1.0  2.0
2  2.0  1.0  2.0
3  2.0  0.0  3.0
4  1.0  1.0  3.0
5  0.0  3.0  2.0
6  1.0  2.0  2.0
7  1.0  2.0  2.0

【讨论】:

  • 这个答案最好的部分是它适用于一百万个值,就像它适用于 [-1, 0, 1]。
  • 很好,如果只对值的子集感兴趣,pd.get_dummies(df.stack()).sum(level=0).reindex(columns=[-1,0,1], fill_value=0)
  • 另外,还有一个问题;如果列 a、b、c、d、e 具有相同的名称,这会起作用吗? (因为dataframe是其他df的pd.concat,都是同一个列名)
  • @PandemicCode 同名栏总是会出现此类问题,最好重命名
  • @Wen 您的代码运行良好。虽然其他答案也不错,但这确实是一个优雅的答案。
【解决方案2】:

使用

In [62]: df.assign(**{'Sum{}'.format(v):df.eq(v).sum(1) for v in [1, 0, -1]})
Out[62]:
   a  b  c  d  e  Sum-1  Sum0  Sum1
0  0  0 -1  1 -1      2     2     1
1  0  1 -1  1 -1      2     1     2
2 -1  0 -1  1  1      2     1     2
3 -1  1  1 -1  1      2     0     3
4  1  0  1 -1  1      1     1     3
5  1  0  0  0  1      0     3     2
6  1  1  0  0 -1      1     2     2
7  1  1 -1  0  0      1     2     2

In [72]: df.join(pd.DataFrame({'Sum{}'.format(v):df.eq(v).sum(1) for v in [1, 0, -1]}))
Out[72]:
   a  b  c  d  e  Sum-1  Sum0  Sum1
0  0  0 -1  1 -1      2     2     1
1  0  1 -1  1 -1      2     1     2
2 -1  0 -1  1  1      2     1     2
3 -1  1  1 -1  1      2     0     3
4  1  0  1 -1  1      1     1     3
5  1  0  0  0  1      0     3     2
6  1  1  0  0 -1      1     2     2
7  1  1 -1  0  0      1     2     2

【讨论】:

    【解决方案3】:

    创建布尔掩码并计算它 - Trues 是类似 1 的过程:

    m1 = df == 1
    m0 = df == 0
    m_1 = df == -1
    df['Sum1'] = m1.sum(1)
    df['Sum0'] = m0.sum(1)
    df['Sum_1'] = m_1.sum(1)
    print (df)
       a  b  c  d  e  Sum1  Sum0  Sum_1
    0  0  0 -1  1 -1     1     2      2
    1  0  1 -1  1 -1     2     1      2
    2 -1  0 -1  1  1     2     1      2
    3 -1  1  1 -1  1     3     0      2
    4  1  0  1 -1  1     3     1      1
    5  1  0  0  0  1     2     3      0
    6  1  1  0  0 -1     2     2      1
    7  1  1 -1  0  0     2     2      1
    

    get_dummies的通用解决方案:

    df1 = (pd.get_dummies(df.astype(str), prefix='', prefix_sep='')
             .sum(level=0, axis=1)
             .add_prefix('Sum'))
    print (df1)
       Sum-1  Sum0  Sum1
    0      2     2     1
    1      2     1     2
    2      2     1     2
    3      2     0     3
    4      1     1     3
    5      0     3     2
    6      1     2     2
    7      1     2     2
    
    df = df.join(df1)
    print (df)
       a  b  c  d  e  Sum-1  Sum0  Sum1
    0  0  0 -1  1 -1      2     2     1
    1  0  1 -1  1 -1      2     1     2
    2 -1  0 -1  1  1      2     1     2
    3 -1  1  1 -1  1      2     0     3
    4  1  0  1 -1  1      1     1     3
    5  1  0  0  0  1      0     3     2
    6  1  1  0  0 -1      1     2     2
    7  1  1 -1  0  0      1     2     2
    

    Zero 解决方案的更好性能的想法 - 比较 numpy array,而静态值可以使用 numpy.unique 的唯一值:

    all_vals = np.unique(df.values)
    arr = df.values
    df1 = df.join(pd.DataFrame({'Sum{}'.format(v):(arr == v).sum(1) for v in all_vals})) 
    print (df1)
       a  b  c  d  e  Sum-1  Sum0  Sum1
    0  0  0 -1  1 -1      2     2     1
    1  0  1 -1  1 -1      2     1     2
    2 -1  0 -1  1  1      2     1     2
    3 -1  1  1 -1  1      2     0     3
    4  1  0  1 -1  1      1     1     3
    5  1  0  0  0  1      0     3     2
    6  1  1  0  0 -1      1     2     2
    7  1  1 -1  0  0      1     2     2
    

    时间

    np.random.seed(234)
    N = 100000
    df = pd.DataFrame(np.random.randint(3, size=(N,5)), columns=list('abcde')) - 1
    print (df)
    
    #wen's solution 1
    In [49]: %timeit pd.concat([df,pd.get_dummies(df.astype(str).stack()).sum(level=0).add_prefix('Sum')],1)
    1 loop, best of 3: 2.21 s per loop
    
    #wen's solution 2
    In [56]: %timeit df.apply(lambda x : x.value_counts(),1).fillna(0)
    1 loop, best of 3: 1min 35s per loop
    

    #jezrael's solution 2
    In [50]: %timeit df.join((pd.get_dummies(df.astype(str), prefix='', prefix_sep='').sum(level=0, axis=1).add_prefix('Sum')))
    1 loop, best of 3: 2.14 s per loop
    
    #jezrael's solution 1
    In [55]: %%timeit
        ...: m1 = df == 1
        ...: m0 = df == 0
        ...: m_1 = df == -1
        ...: df['Sum1'] = m1.sum(1)
        ...: df['Sum0'] = m0.sum(1)
        ...: df['Sum_1'] = m_1.sum(1)
        ...: 
    10 loops, best of 3: 50.6 ms per loop
    

    #zero's solution1
    In [51]: %timeit df.assign(**{'Sum{}'.format(v):df.eq(v).sum(1) for v in [1, 0, -1]})
    10 loops, best of 3: 39.8 ms per loop
    
    #zero's solution2    
    In [52]: %timeit df.join(pd.DataFrame({'Sum{}'.format(v):df.eq(v).sum(1) for v in [1, 0, -1]}))
    10 loops, best of 3: 39.6 ms per loop
    

    #zero&jezrael's solution1 
    In [53]: %timeit df.join(pd.DataFrame({'Sum{}'.format(v):(df.values == v).sum(1) for v in np.unique(df.values)}))
    10 loops, best of 3: 23.8 ms per loop
    
    #zero&jezrael's solution2    
    In [54]: %timeit df.join(pd.DataFrame({'Sum{}'.format(v):(df.values == v).sum(1) for v in [0, 1, -1]}))
    100 loops, best of 3: 12.8 ms per loop
    
    #if many columns and more unique values is possible convert to numpy array outside loop
    
    def f1(df):
        all_vals = np.unique(df.values)
        arr = df.values
        return df.join(pd.DataFrame({'Sum{}'.format(v):(arr == v).sum(1) for v in all_vals}))
    
    def f2(df):
        arr = df.values
        return df.join(pd.DataFrame({'Sum{}'.format(v):(arr == v).sum(1) for v in [0, 1, -1]}))
    
    print (f1(df))
    print (f2(df))
    
    #zero&jezrael's solution3
    In [58]: %timeit (f1(df))
    10 loops, best of 3: 25.8 ms per loop
    
    #zero&jezrael's solution4
    In [59]: %timeit (f2(df))
    100 loops, best of 3: 13 ms per loop
    

    【讨论】:

    • 如果您运行Sum0,然后运行Sum1,这可能不起作用。由于df 被覆盖并且对所有df 进行了值检查
    • 请记住,如果您需要对 3 个以上的值求和,这将变得不方便。 Zero 的回答是对此的一种概括。
    • 新的?看起来很像 Wen 的回答,唯一的区别是加入... ;-)
    • @cᴏʟᴅsᴘᴇᴇᴅ - 并删除堆栈 ;)
    • 我怀疑,这将是低效的,字符串转换,然后 mxn 爆炸值,..?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-02
    • 2012-08-08
    • 2017-06-14
    • 2017-11-17
    • 1970-01-01
    相关资源
    最近更新 更多