簡體   English   中英

自上一次在Pandas DataFrame中發生以來還沒有幾天?

[英]Getting days since last occurence in Pandas DataFrame?

假設我有一個Pandas DataFrame 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

對於每一行,我想有效地計算自上次出現Value=1以來的天數。

這樣df

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

但是,對於龐大的數據集而言,效率似乎很低,而且可能還是不合適。

這是NumPy的方法-

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

讓我們使用cumsumcumcountgroupby嘗試一下:

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

您不必更新值last for循環中的每一步。 在循環外啟動變量

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

並僅在Value列中出現1時更新它。

請注意,無論您選擇哪種方法,都必須對整個表進行一次迭代。

您可以使用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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM