我有以下Pandas系列(以列表表示):
[7,2,0,3,4,2,5,0,3,4]
我想定义一个新的序列,返回到最后一个零的距离。这意味着我想得到以下输出:
[1,2,0,1,2,3,4,0,1,2]
如何以最有效的方式在Pandas身上做到这一点?
xxls0lw81#
复杂度是O(n) .在python中做一个for循环会使它慢下来.如果序列中有k个零,并且log k与序列的长度相比可以忽略不计,O(n log k)的解决方案是:
O(n)
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])
nvbavucw2#
Pandas中的一个解决方案有点棘手,但可能如下所示(s是您的Series):
s
>>> 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”食谱。
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
nom7f22z4#
使用Cython就能获得类似c的速度,这有时候会让人惊讶,假设列的.values给出arr,那么:
.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)来进一步加快它的速度。
@cython.boundscheck(False)
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))
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
qqrboqgw7#
另一种方法是使用Numpy accumulate,唯一的问题是,要将计数器初始化为零,需要在序列值前面插入一个零。
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)
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]
8条答案
按热度按时间xxls0lw81#
复杂度是
O(n)
.在python中做一个for
循环会使它慢下来.如果序列中有k
个零,并且log k
与序列的长度相比可以忽略不计,O(n log k)
的解决方案是:nvbavucw2#
Pandas中的一个解决方案有点棘手,但可能如下所示(
s
是您的Series):对于最后一步,这里使用Pandas食谱中的“itertools.groupby”食谱。
50pmv0ei3#
一个解决方案可能性能不高(还没有真正检查),但在步骤方面更容易理解(至少对我来说),它是:
nom7f22z4#
使用Cython就能获得类似c的速度,这有时候会让人惊讶,假设列的
.values
给出arr
,那么:注意typed memory views的使用,这是非常快的,你可以使用
@cython.boundscheck(False)
来进一步加快它的速度。5tmbdcev5#
另一个选择
或者用纯麻木
7xllpg7q6#
也许Pandas并不是最好的工具,正如@behzad.nouri的回答,然而这里有另一个变体:
qqrboqgw7#
另一种方法是使用Numpy
accumulate
,唯一的问题是,要将计数器初始化为零,需要在序列值前面插入一个零。mitkmikd8#
下面是不使用groupby的方法:
输出: