如何计算熊猫时间序列中“事件”之间的时间(天数)?例如,如果我有以下时间序列,我想知道系列中的每一天,自上次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
答案 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()
,可以提供以下方法:
使用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
使用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
最后线性插值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
我相信它可以更漂亮或更小,但是您明白了。
希望有帮助!