Pandas时间序列事件间隔

18

如何在 Pandas 时间序列中计算“事件”之间的时间(天数)?例如,如果我有以下时间序列,我想知道在系列中的每一天距上一个 TRUE 事件过去了多少天。

            event
2010-01-01  False
2010-01-02   True
2010-01-03  False
2010-01-04  False
2010-01-05   True
2010-01-06  False

我现在的做法看起来过于复杂,所以希望能有更加优雅的方法。显然用for循环迭代每一行是可行的,但我希望找到一个向量化(可扩展)的解决方案。以下是我目前的尝试:

date_range = pd.date_range('2010-01-01', '2010-01-06')
df = pd.DataFrame([False, True, False, False, True, False], index=date_range, columns=['event'])
event_dates = df.index[df['event']]
df2 = pd.DataFrame(event_dates, index=event_dates, columns=['max_event_date'])
df = df.join(df2)
df['max_event_date'] = df['max_event_date'].cummax(axis=0, skipna=False)
df['days_since_event'] = df.index - df['max_event_date']

            event max_event_date  days_since_event
2010-01-01  False            NaT               NaT
2010-01-02   True     2010-01-02            0 days
2010-01-03  False     2010-01-02            1 days
2010-01-04  False     2010-01-02            2 days
2010-01-05   True     2010-01-05            0 days
2010-01-06  False     2010-01-05            1 days
5个回答

8

继续改进这个答案,希望有人提供'最'pythonic的方法。在此之前,我认为这个最终更新是最好的。

last = pd.to_datetime(np.nan)
def elapsed(row):
    if not row.event:
        return row.name - last
    else:
        global last
        last = row.name
        return row.name-last

df['elapsed'] = df.apply(elapsed,axis=1)

df
            event  elapsed
2010-01-01  False      NaT
2010-01-02   True   0 days
2010-01-03  False   1 days
2010-01-04  False   2 days
2010-01-05   True   0 days
2010-01-06  False   1 days

:::::::::::::

以下是之前的回答,虽然不够理想

:::::::

与其进行多次循环,似乎更容易只通过索引进行循环

df['elapsed'] = 0
for i in df.index[1:]:
    if not df['event'][i]:
        df['elapsed'][i] = df['elapsed'][i-1] + 1

假设“对”的事件是您感兴趣的事件。
trues = df[df.event==True]
trues.Dates = trues.index #need this because .diff() doesn't work on the index
trues.Elapsed = trues.Dates.diff()

3

一次性解决方案当然是理想的,但这里提供了一个多次通过只使用(可能)cython化的pandas函数的解决方案:

def get_delay(ds):
    x1 = (~ds).cumsum()
    x2 = x1.where(ds, np.nan).ffill()
    return x1 - x2

date_range = pd.date_range('2010-01-01', '2010-01-06')
ds = pd.Series([False, True, False, False, True, False], index=date_range)
pd.concat([ds, get_delay(ds)], axis=1)


            Event   Last
2010-01-01  False   NaN
2010-01-02  True    0
2010-01-03  False   1
2010-01-04  False   2
2010-01-05  True    0
2010-01-06  False   1

有趣的是,在一些快速基准测试中,它似乎表现得更好一些,可能是因为避免了逐行操作:

%%timeit -n 1000

    def get_delay(ds):
        x1 = (~ds).cumsum()
        x2 = x1.where(ds, np.nan).ffill()
        return x1 - x2

    n = 100
    events = np.random.choice([True, False], size=n)
    date_range = pd.date_range('2010-01-01', periods=n)
    df = pd.DataFrame(events, index=date_range, columns=['event'])
    get_delay(df['event'])

1000 loops, best of 3: 1.09 ms per loop

与全局变量的单循环方法相比:
%%timeit -n 1000

last = pd.to_datetime(np.nan)
def elapsed(row):
    if not row.event:
        return row.name - last
    else:
        global last
        last = row.name
        return row.name-last


n = 100
events = np.random.choice([True, False], size=n)
date_range = pd.date_range('2010-01-01', periods=n)
df = pd.DataFrame(events, index=date_range, columns=['event'])
df.apply(elapsed, axis=1)

1000 loops, best of 3: 2.4 ms per loop

也许这个比较有一些细微差别,不太公平,但无论如何,没有自定义功能的版本肯定不会慢太多,甚至可能完全没差别。


3

我最近接触到了 groupby().diff() 方法,它可以提供以下功能:

  1. Use groupby.diff to calculate the days to last True day:

    df.loc[df.index[-1]+pd.Timedelta(days=1), 'event'] = True  # add an artificial True day for interpolation
    df['last']=df.index
    df['last']=df.groupby('event')['last'].diff()
    df.loc[df['event']==False, 'last'] = None
    

    which gives you:

                event   last
    2010-01-01  False   NaT
    2010-01-02  True    NaT
    2010-01-03  False   NaT
    2010-01-04  False   NaT
    2010-01-05  True    3 days
    2010-01-06  False   NaT
    2010-01-07  True    2 days
    
  2. Use tshift() to set correct last value for True and the False before:

    df['last'] = (df['last']-pd.Timedelta(days=1)).tshift(periods=-1, freq='D')
    df.loc[df['event'], ['last']] = pd.Timedelta(days=0)
    

    you will get:

                event   last
    2010-01-01  False   NaT
    2010-01-02  True    0 days
    2010-01-03  False   NaT
    2010-01-04  False   2 days
    2010-01-05  True    0 days
    2010-01-06  False   1 days
    2010-01-07  True    0 days
    
  3. Lastly interpolate the NaN values linearly to get the final result

    df['last'] /= np.timedelta64(1, 'D')
    df.interpolate(method='linear', axis=0, inplace=True)
    df.drop(df.index[-1], inplace=True)  # erase the artificial row
    df['last'] *= np.timedelta64(1, 'D')
    
                event   last
    2010-01-01  False   NaN
    2010-01-02  True    0 days
    2010-01-03  False   1 days
    2010-01-04  False   2 days
    2010-01-05  True    0 days
    2010-01-06  False   1 days
    

1

这里是另一种方法,使用查找表比较日期。


import pandas as pd
import io

data=io.StringIO('''
date,event
2010-01-01,False
2010-01-02,True
2010-01-03,False
2010-01-04,False
2010-01-05,True
2010-01-06,False
''')

df = pd.read_csv( data, parse_dates=['date'] )
df.set_index( 'date', inplace=True )
print( df )

            event
date             
2010-01-01  False
2010-01-02   True
2010-01-03  False
2010-01-04  False
2010-01-05   True
2010-01-06  False

我首先列出事件发生的日期:

when_events = df[ (df['event']==True) ].index
when_events = pd.Series( when_events )
print( when_events )

0   2010-01-02
1   2010-01-05
Name: date, dtype: datetime64[ns]

然后使用它来查找不大于我的索引的最大日期:

df[ 'last' ] = df.index
df[ 'last' ] = df['last'].apply( lambda x: when_events[ when_events<=x ].max() )
df[ 'elapsed' ] = df.index.values - df[ 'last' ] 
print( df )

            event       last elapsed
date                                
2010-01-01  False        NaT     NaT
2010-01-02   True 2010-01-02  0 days
2010-01-03  False 2010-01-02  1 days
2010-01-04  False 2010-01-02  2 days
2010-01-05   True 2010-01-05  0 days
2010-01-06  False 2010-01-05  1 days

我相信它可以更漂亮、更小,但你已经明白了意思。

希望能对你有所帮助!



0

如果有人正在寻找一个易读、简单的解决方案,也许在处理大型数据集时不够高效,我刚刚做了以下工作。在我的设置中,我想要计算对话中主题更改之间发言者话语(轮次)的数量。coder指的是特定的研究助理(许多研究助理编码了每个对话,因此每个对话都有自己的1和0列,表示主题更改或主题继续)。在我的情况下,相邻的行总是相差一个时间步长,因此我不需要访问日期时间索引--我可以在数据集中的每一行(话语/轮次)上递增(并在主题更改时重置)turns_since_last计数器:

def turns_since_last_topic(coder):
    turns_since_last = 0
    coding['turns_since_last_{}'.format(coder)] = np.nan    
    for idx, row in coding.iterrows():
        if not row[coder]:
            turns_since_last += 1
        else:
            turns_since_last += 1
            coding.loc[idx, 'turns_since_last_{}'.format(coder)] = turns_since_last
            turns_since_last = 0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接