Python 熊猫数据帧中上次出现后的天数?

Python 熊猫数据帧中上次出现后的天数?,python,performance,date,pandas,numpy,Python,Performance,Date,Pandas,Numpy,假设我有一个熊猫数据帧df: Date Value 01/01/17 0 01/02/17 0 01/03/17 1 01/04/17 0 01/05/17 0 01/06/17 0 01/07/17 1 01/08/17 0 01/09/17 0 Date Value Last_Occurence 01/01/17 0 NaN 01/02/17 0 NaN 01/03/17 1 0 01/04/17

假设我有一个熊猫数据帧
df

Date      Value
01/01/17  0
01/02/17  0
01/03/17  1
01/04/17  0
01/05/17  0
01/06/17  0
01/07/17  1
01/08/17  0
01/09/17  0
Date      Value    Last_Occurence
01/01/17  0        NaN
01/02/17  0        NaN
01/03/17  1        0
01/04/17  0        1
01/05/17  0        2
01/06/17  0        3
01/07/17  1        0
01/08/17  0        1
01/09/17  0        2
对于每一行,我希望有效地计算自上次出现
Value=1
以来的天数

因此,
df

Date      Value
01/01/17  0
01/02/17  0
01/03/17  1
01/04/17  0
01/05/17  0
01/06/17  0
01/07/17  1
01/08/17  0
01/09/17  0
Date      Value    Last_Occurence
01/01/17  0        NaN
01/02/17  0        NaN
01/03/17  1        0
01/04/17  0        1
01/05/17  0        2
01/06/17  0        3
01/07/17  1        0
01/08/17  0        1
01/09/17  0        2
我可以做一个循环:

for i in range(0, len(df)):
    last = np.where(df.loc[0:i,'Value']==1)
    df.loc[i, 'Last_Occurence'] = i-last

但是对于非常大的数据集,它似乎效率非常低,而且可能无论如何都不正确。

您不必在for循环中的每一步都将值更新为
last
。在循环外启动一个变量

last = np.nan
for i in range(len(df)):
    if df.loc[i, 'Value'] == 1:
        last = i
    df.loc[i, 'Last_Occurence'] = i - last
并且仅当列
中出现
1
时才更新它


请注意,无论选择何种方法,都不可避免地要对整个表进行一次迭代。

让我们使用
cumsum
cumcount
groupby
尝试一下:

mask = df.Value.cumsum().replace(0,False).astype(bool) #Mask starting zeros as NaN
df_out = df.assign(Last_Occurance=df.groupby(df.Value.astype(bool).cumsum()).cumcount().where(mask))
print(df_out)
输出:

       Date  Value  Last_Occurance
0  01/01/17      0             NaN
1  01/02/17      0             NaN
2  01/03/17      1             0.0
3  01/04/17      0             1.0
4  01/05/17      0             2.0
5  01/06/17      0             3.0
6  01/07/17      1             0.0
7  01/08/17      0             1.0
8  01/09/17      0             2.0

这里有一个简单的方法-

def intervaled_cumsum(a, trigger_val=1, start_val = 0, invalid_specifier=-1):
    out = np.ones(a.size,dtype=int)    
    idx = np.flatnonzero(a==trigger_val)
    if len(idx)==0:
        return np.full(a.size,invalid_specifier)
    else:
        out[idx[0]] = -idx[0] + 1
        out[0] = start_val
        out[idx[1:]] = idx[:-1] - idx[1:] + 1
        np.cumsum(out, out=out)
        out[:idx[0]] = invalid_specifier
        return out
很少有示例在阵列数据上运行,以展示触发器和启动值的各种场景的用法:

In [120]: a
Out[120]: array([0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0])

In [121]: p1 = intervaled_cumsum(a, trigger_val=1, start_val=0)
     ...: p2 = intervaled_cumsum(a, trigger_val=1, start_val=1)
     ...: p3 = intervaled_cumsum(a, trigger_val=0, start_val=0)
     ...: p4 = intervaled_cumsum(a, trigger_val=0, start_val=1)
     ...: 

In [122]: np.vstack(( a, p1, p2, p3, p4 ))
Out[122]: 
array([[ 0,  1,  1,  1,  0,  0,  1,  0,  0,  1,  1,  1,  1,  1,  0],
       [-1,  0,  0,  0,  1,  2,  0,  1,  2,  0,  0,  0,  0,  0,  1],
       [-1,  1,  1,  1,  2,  3,  1,  2,  3,  1,  1,  1,  1,  1,  2],
       [ 0,  1,  2,  3,  0,  0,  1,  0,  0,  1,  2,  3,  4,  5,  0],
       [ 1,  2,  3,  4,  1,  1,  2,  1,  1,  2,  3,  4,  5,  6,  1]])
用它来解决我们的问题:

df['Last_Occurence'] = intervaled_cumsum(df.Value.values)
样本输出-

In [181]: df
Out[181]: 
       Date  Value  Last_Occurence
0  01/01/17      0              -1
1  01/02/17      0              -1
2  01/03/17      1               0
3  01/04/17      0               1
4  01/05/17      0               2
5  01/06/17      0               3
6  01/07/17      1               0
7  01/08/17      0               1
8  01/09/17      0               2
运行时测试

接近-

# @Scott Boston's soln
def pandas_groupby(df):
    mask = df.Value.cumsum().replace(0,False).astype(bool)
    return df.assign(Last_Occurance=df.groupby(df.Value.astype(bool).\
                                    cumsum()).cumcount().where(mask))

# Proposed in this post
def numpy_based(df):
    df['Last_Occurence'] = intervaled_cumsum(df.Value.values)
时间安排-

In [33]: df = pd.DataFrame((np.random.rand(10000000)>0.7).astype(int), columns=[['Value']])

In [34]: %timeit pandas_groupby(df)
1 loops, best of 3: 1.06 s per loop

In [35]: %timeit numpy_based(df)
10 loops, best of 3: 103 ms per loop

In [36]: df = pd.DataFrame((np.random.rand(100000000)>0.7).astype(int), columns=[['Value']])

In [37]: %timeit pandas_groupby(df)
1 loops, best of 3: 11.1 s per loop

In [38]: %timeit numpy_based(df)
1 loops, best of 3: 1.03 s per loop
您可以使用argmax:

df.apply(lambda x: np.argmax(df.iloc[x.name::-1].Value.tolist()),axis=1)
Out[85]: 
0    0
1    0
2    0
3    1
4    2
5    3
6    0
7    1
8    2
dtype: int64
如果前两行必须使用nan,请使用:

df.apply(lambda x: np.argmax(df.iloc[x.name::-1].Value.tolist()) \
                   if 1 in df.iloc[x.name::-1].Value.values \
                   else np.nan,axis=1)
Out[86]: 
0    NaN
1    NaN
2    0.0
3    1.0
4    2.0
5    3.0
6    0.0
7    1.0
8    2.0
dtype: float64

是否可以在开始时填充-1而不是NaN,或者其他一些int支持的数字作为无效的说明符?int也可以,NaN并不是完全必要的。在速度方面,与numpy不同。我很欣赏这些比较