如何计算Pandas系列中到前一个零的距离?

a14dhokn  于 2023-01-07  发布在  其他
关注(0)|答案(8)|浏览(142)

我有以下Pandas系列(以列表表示):

[7,2,0,3,4,2,5,0,3,4]

我想定义一个新的序列,返回到最后一个零的距离。这意味着我想得到以下输出:

[1,2,0,1,2,3,4,0,1,2]

如何以最有效的方式在Pandas身上做到这一点?

xxls0lw8

xxls0lw81#

复杂度是O(n) .在python中做一个for循环会使它慢下来.如果序列中有k个零,并且log k与序列的长度相比可以忽略不计,O(n log k)的解决方案是:

>>> izero = np.r_[-1, (ts == 0).nonzero()[0]]  # indices of zeros
>>> idx = np.arange(len(ts))
>>> idx - izero[np.searchsorted(izero - 1, idx) - 1]
array([1, 2, 0, 1, 2, 3, 4, 0, 1, 2])
nvbavucw

nvbavucw2#

Pandas中的一个解决方案有点棘手,但可能如下所示(s是您的Series):

>>> x = (s != 0).cumsum()
>>> y = x != x.shift()
>>> y.groupby((y != y.shift()).cumsum()).cumsum()
0    1
1    2
2    0
3    1
4    2
5    3
6    4
7    0
8    1
9    2
dtype: int64

对于最后一步,这里使用Pandas食谱中的“itertools.groupby”食谱。

50pmv0ei

50pmv0ei3#

一个解决方案可能性能不高(还没有真正检查),但在步骤方面更容易理解(至少对我来说),它是:

df = pd.DataFrame({'X': [7, 2, 0, 3, 4, 2, 5, 0, 3, 4]})
df

df['flag'] = np.where(df['X'] == 0, 0, 1)
df['cumsum'] = df['flag'].cumsum()
df['offset'] = df['cumsum']
df.loc[df.flag==1, 'offset'] = np.nan
df['offset'] = df['offset'].fillna(method='ffill').fillna(0).astype(int)
df['final'] = df['cumsum'] - df['offset']

df
nom7f22z

nom7f22z4#

使用Cython就能获得类似c的速度,这有时候会让人惊讶,假设列的.values给出arr,那么:

cdef int[:, :, :] arr_view = arr
ret = np.zeros_like(arr)
cdef int[:, :, :] ret_view = ret

cdef int i, zero_count = 0
for i in range(len(ret)):
    zero_count = 0 if arr_view[i] == 0 else zero_count + 1
    ret_view[i] = zero_count

注意typed memory views的使用,这是非常快的,你可以使用@cython.boundscheck(False)来进一步加快它的速度。

5tmbdcev

5tmbdcev5#

另一个选择

df = pd.DataFrame({'X': [7, 2, 0, 3, 4, 2, 5, 0, 3, 4]})
zeros = np.r_[-1, np.where(df.X == 0)[0]]

def d0(a):
    return np.min(a[a>=0])
    
df.index.to_series().apply(lambda i: d0(i - zeros))

或者用纯麻木

df = pd.DataFrame({'X': [7, 2, 0, 3, 4, 2, 5, 0, 3, 4]})
a = np.arange(len(df))[:, None] - np.r_[-1 , np.where(df.X == 0)[0]][None]

np.min(a, where=a>=0, axis=1, initial=len(df))
7xllpg7q

7xllpg7q6#

也许Pandas并不是最好的工具,正如@behzad.nouri的回答,然而这里有另一个变体:

df = pd.DataFrame({'X': [7, 2, 0, 3, 4, 2, 5, 0, 3, 4]})

z = df.ne(0).X
z.groupby((z != z.shift()).cumsum()).cumsum()

0    1
1    2
2    0
3    1
4    2
5    3
6    4
7    0
8    1
9    2
Name: X, dtype: int64
qqrboqgw

qqrboqgw7#

另一种方法是使用Numpy accumulate,唯一的问题是,要将计数器初始化为零,需要在序列值前面插入一个零。

import numpy as np

# Define Python function
f = lambda a, b: 0 if b == 0 else a + 1

# Convert to Numpy ufunc
npf = np.frompyfunc(f, 2, 1)

# Apply recursively over series values
x = npf.accumulate(np.r_[0, s.values])[1:]

print(x)
array([1, 2, 0, 1, 2, 3, 4, 0, 1, 2], dtype=object)
mitkmikd

mitkmikd8#

下面是不使用groupby的方法:

((v:=pd.Series([7,2,0,3,4,2,5,0,3,4]).ne(0))
.cumsum()
.where(v.eq(0)).ffill().fillna(0)
.rsub(v.cumsum())
.astype(int)
.tolist())

输出:

[1, 2, 0, 1, 2, 3, 4, 0, 1, 2]

相关问题