熊猫时间序列事件之间的时间

时间:2015-03-17 04:48:17

标签: python 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 个答案:

答案 0 :(得分:5)

继续改进这个答案,并希望有人参与'' 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()

答案 1 :(得分:2)

一次通过解决方案当然是理想的,但这是一个只使用(大概)cythonized 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

或许这种比较中的一些细微差别并不能使其公平,但无论哪种方式,无定制功能的版本肯定不会慢很多,如果在所有

答案 2 :(得分:1)

我最近遇到groupby().diff(),可以提供以下方法:

  1. 使用groupby.diff计算上次True天的日期:

    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
    

    给你:

                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. 使用tshift()last和之前的True设置正确的False值:

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

    你会得到:

                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. 最后线性插值NaN值以得到最终结果

    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
    

答案 3 :(得分:0)

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

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

答案 4 :(得分:0)

这是另一种方法,将日期与查找表进行比较。


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

我相信它可以更漂亮或更小,但是您明白了。

希望有帮助!