【问题标题】:pandas use cumsum over columns but reset count熊猫在列上使用 cumsum 但重置计数
【发布时间】:2018-05-31 11:04:27
【问题描述】:

This post 和 this post 让我很接近,但我无法解决我的问题。

我有一个看起来像这样的 df:

     2017-04-03    2017-04-04    2017-04-05    2017-04-06
id                                                                         
0           0.0        active           0.0           0.0   
1           0.0        active           0.0        active   
2           0.0           0.0           0.0           0.0 

我想计算每一行的零并将它们放入一个字符串中以对数据进行编码,但只要没有连续的零,计数就需要重置。

对于上述 df,输出 df 如下所示:

     2017-04-03    2017-04-04    2017-04-05    2017-04-06
id                                                                         
0    inactive_1        active    inactive_1    inactive_2   
1    inactive_1        active    inactive_1        active   
2    inactive_1    inactive_2    inactive_3    inactive_4

这个函数让我非常接近,但不考虑重置 cumsum,它只是对行中所有为零的实例求和。

def inactive(s):
     np.where(s == 0, 'inactive_' + (s.eq(0).cumsum()).astype(str), s)

df.apply(inactive, 1)

【问题讨论】:

  • 真实数据中的索引数和列数是多少?
  • ~100,000 行乘~300 列。 @coldspeed 下面的解决方案效果很好
  • @Matt 欢呼。您可以对所有答案进行投票,即使您只能接受一个。
  • 我比较解决方案并与我的样本数据和100000, 300 行、列 - cᴏʟᴅsᴘᴇᴇᴅ 解决方案是 3.5me 慢 ;),检查 timings

标签: python pandas cumsum


【解决方案1】:

你可以使用:

#convert to numeric, NaNs for non numeric
df1 = df.apply(pd.to_numeric, errors='coerce')
#count consecutive values with reset
a = df1 == 0
b = a.cumsum(axis=1)
c = b-b.where(~a, axis=1).ffill(axis=1).fillna(0).astype(int)

print (c)
    2017-04-03  2017-04-04  2017-04-05  2017-04-06
id                                                
0            1           0           1           2
1            1           0           1           0
2            1           2           3           4


#replace by mask 
df = df.mask(c != 0, 'inactive_' + c.astype(str))
print (df)
    2017-04-03  2017-04-04  2017-04-05  2017-04-06
id                                                
0   inactive_1      active  inactive_1  inactive_2
1   inactive_1      active  inactive_1      active
2   inactive_1  inactive_2  inactive_3  inactive_4

时间安排

np.random.seed(425)
df = pd.DataFrame(np.random.choice([0, 'active'], size=(100000, 300)))

In [4]: %timeit (jez(df))
1 loop, best of 3: 1min 40s per loop

In [5]: %timeit col(df)
1 loop, best of 3: 5min 54s per loop

def jez(df):
    df1 = df.apply(pd.to_numeric, errors='coerce')
    #count consecutive values
    a = df1 == 0
    b = a.cumsum(axis=1)
    c = b-b.where(~a, axis=1).ffill(axis=1).fillna(0).astype(int)
    #replace by mask 
    return df.mask(c != 0, 'inactive_' + c.astype(str))

def f(x):
    return x.groupby(x.ne(x.shift()).cumsum()).cumcount() + 1

def col(df):

    i = df.apply(pd.to_numeric, errors='coerce')
    j = 'inactive_' + i.apply(f, axis=1).astype(str)

    df[:] = np.where(i.ne(0), df.values, j)

    return(df)

警告

性能真的取决于数据。

【讨论】:

  • 这些时间不是决定性的。这真的取决于数据。我希望 OP 的数据采用宽格式,列多于行,因此您会看到更好的性能。
  • 我也可以为它添加计时,但是 apply + groupby 真的很慢,所以我认为它也会更慢。
  • 确实如此,但差别可能会比现在小;)
  • 嗯,这就是我的想法。可能值得提出一个解决方案,首先转置数据,然后执行所有这些操作,然后 OP 可以根据他们的实际数据使用最好的。
  • 我测试过,但是In [82]: %timeit (jez(df.T).T) 1 loop, best of 3: 6.9 s per loop
【解决方案2】:

有点迂回,但这可以通过对每一行应用groupby 操作来完成,然后使用np.where 有选择地将您的值应用于原始数据。

def f(x):
    return x.groupby(x.ne(x.shift()).cumsum()).cumcount() + 1

i = df.apply(pd.to_numeric, errors='coerce')
j = 'inactive_' + i.apply(f, axis=1).astype(str)

df[:] = np.where(i.ne(0), df.values, j)

df

    2017-04-03  2017-04-04  2017-04-05  2017-04-06
id                                                
0   inactive_1      active  inactive_1  inactive_2
1   inactive_1      active  inactive_1      active
2   inactive_1  inactive_2  inactive_3  inactive_4

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-02-04
    • 2023-04-03
    • 2019-07-22
    • 2020-03-22
    • 2017-10-28
    • 2018-11-03
    • 2020-02-02
    • 1970-01-01
    相关资源
    最近更新 更多