Pandas,检查时间戳值是否存在于datetimeindex重新采样的30分钟时间段中

3
我已经在 Pandas 中创建了一个包含 DateTimeIndex 的重采样数据框 DF1。我有另一个数据框 DF2,它也包含 DateTimeIndex 和时间列。如果 DF2 中的时间实例落在 DF1 中 DateTimeIndex 的 30 分钟间隔内,我想要用 DF1 中 30 分钟间隔的适当“速度”标记 DF2 中的每个时间实例。

DF1:

                   boat_id      speed
time                                      
2015-01-13 09:00:00   28.000000   0.000000
2015-01-13 09:30:00   28.000000   0.723503
2015-01-13 10:00:00   28.000000   2.239399

DF2

                      id  boat_id                 time  state     
time                                                                          
2015-01-18 16:09:03   319437       28  2015-01-18 16:09:03      2    
2015-01-18 16:18:43   319451       28  2015-01-18 16:18:43      0    
2015-03-01 09:39:51   507108       31  2015-03-01 09:39:51      1    
2015-03-01 09:40:58   507109       31  2015-03-01 09:40:58      0 

期望结果

                      id  boat_id                 time      state   speed
time                                                                          
2015-01-18 16:09:03   319437       28  2015-01-18 16:09:03      2 nan   
2015-01-18 16:18:43   319451       28  2015-01-18 16:18:43      0 nan   
2015-03-01 09:39:51   507108       31  2015-03-01 09:39:51      1 2.239399   
2015-03-01 09:40:58   507109       31  2015-03-01 09:40:58      0 2.239399

我创建了这个脚本尝试实现这个目标,但我认为它失败了,因为DF1的datetimeindex是不可变的,所以我的timedelta请求无法为块创建起始点。我想过一个想法,是否可能将DF1的datetimeindex复制到一个新列中,其中对象是可变的,但我还没有成功,所以对逻辑不是100%确定。我很乐意尝试,但目前我已经陷入停滞状态一段时间了,希望其他人能有一些想法。
for row in DF1.iterrows():
    for dfrow in DF2.iterrows():
        if dfrow[0] > row[0] - dt.timedelta(minutes=30) and dfrow[0] < row[0]:
            df['test'] =  row[1]

为什么不尝试使用between_time()函数呢? - Kartik
@Kartik,感谢您的建议。我尝试了以下代码:for row in DF1.iterrows(): for dfrow in DF2.iterrows(): if dfrow['time'] == DF1[pd.DataFrame.between_time(row[0] - dt.timedelta(minutes=15), row[0])]: df['test'] = row[1]但是出现了TypeError: tuple indices must be integers, not str的结果。如果我使用整数而不是字符串,我会得到IndexError: tuple index out of range或者TypeError: unbound method between_time() must be called with DataFrame instance as first argument (got Timestamp instance instead)。我是否漏掉了什么? - hselbie
我想知道这是否与数据框中的标题有关? - hselbie
兄弟!那是错的!等一下,让我回答... - Kartik
2个回答

1

迭代的性能非常低。更好的方法是使用向量化的解决方案。我使用了两次函数 merge文档

输入:

print df1
                     boat_id     speed
time                                  
2015-03-01 09:00:00       28  0.000000
2015-03-01 09:30:00       28  0.723503
2015-03-01 10:00:00       28  2.239399

print df2
                         id  boat_id                time  state
time                                                           
2015-01-18 16:09:03  319437       28 2015-01-18 16:09:03      2
2015-01-18 16:18:43  319451       28 2015-01-18 16:18:43      0
2015-03-01 09:39:51  507108       31 2015-03-01 09:39:51      1
2015-03-01 09:40:58  507109       31 2015-03-01 09:40:58      0

我重置了两个数据框的索引,并创建了一个辅助列 i ,其中填充了1
df1 = df1.reset_index()
df2 = df2.reset_index(drop=True)
df1['i'] =  df2['i'] = 1
print df1
                 time  boat_id     speed  i
0 2015-03-01 09:00:00       28  0.000000  1
1 2015-03-01 09:30:00       28  0.723503  1
2 2015-03-01 10:00:00       28  2.239399  1
print df2
       id  boat_id                time  state  i
0  319437       28 2015-01-18 16:09:03      2  1
1  319451       28 2015-01-18 16:18:43      0  1
2  507108       31 2015-03-01 09:39:51      1  1
3  507109       31 2015-03-01 09:40:58      0  1

然后我通过辅助列i合并了这两个数据框。

df = df2.merge(df1, on='i', how='left')
df = df.rename(columns={'time_y':'Bin_time', 'time_x':'time'})
print df
        id  boat_id_x                time  state  i            Bin_time  \
0   319437         28 2015-01-18 16:09:03      2  1 2015-03-01 09:00:00   
1   319437         28 2015-01-18 16:09:03      2  1 2015-03-01 09:30:00   
2   319437         28 2015-01-18 16:09:03      2  1 2015-03-01 10:00:00   
3   319451         28 2015-01-18 16:18:43      0  1 2015-03-01 09:00:00   
4   319451         28 2015-01-18 16:18:43      0  1 2015-03-01 09:30:00   
5   319451         28 2015-01-18 16:18:43      0  1 2015-03-01 10:00:00   
6   507108         31 2015-03-01 09:39:51      1  1 2015-03-01 09:00:00   
7   507108         31 2015-03-01 09:39:51      1  1 2015-03-01 09:30:00   
8   507108         31 2015-03-01 09:39:51      1  1 2015-03-01 10:00:00   
9   507109         31 2015-03-01 09:40:58      0  1 2015-03-01 09:00:00   
10  507109         31 2015-03-01 09:40:58      0  1 2015-03-01 09:30:00   
11  507109         31 2015-03-01 09:40:58      0  1 2015-03-01 10:00:00   

    boat_id_y     speed  
0          28  0.000000  
1          28  0.723503  
2          28  2.239399  
3          28  0.000000  
4          28  0.723503  
5          28  2.239399  
6          28  0.000000  
7          28  0.723503  
8          28  2.239399  
9          28  0.000000  
10         28  0.723503  
11         28  2.239399  

输出被二进制时间过滤:

df = df[((df.time >= (df.Bin_time - dt.timedelta(minutes=30))) & (df.time <= df.Bin_time ))]
df = df.drop(['Bin_time', 'id', 'boat_id_x', 'boat_id_y','state', 'i' ], axis=1 )
print df
                  time     speed
8  2015-03-01 09:39:51  2.239399
11 2015-03-01 09:40:58  2.239399

并且使用列 timedf 与数据框 df2 合并。

df = df2.merge(df, on='time', how='left').reset_index(drop=True)
df = df.drop([ 'i' ], axis=1 )
print df
       id  boat_id                time  state     speed
0  319437       28 2015-01-18 16:09:03      2       NaN
1  319451       28 2015-01-18 16:18:43      0       NaN
2  507108       31 2015-03-01 09:39:51      1  2.239399
3  507109       31 2015-03-01 09:40:58      0  2.239399

在类似的答案这里中,比较向量化和索引方法。


谢谢你提供的解决方案,我只是试图让它能够工作。当您删除索引时,时间列会恢复为 pandas 对象,而不是 datetime64,因此 timedelta 无法使用。但是,我理解你的逻辑并对解决方案充满希望。 - hselbie
太棒了,正如我之前所述的那样,这是timedelta问题。 - hselbie
我发现的一个问题是在i上使用合并函数需要很长时间和大量内存。使用df.info,我可以看到大小约为50mb=df2和1mb=df1。您能否建议一种替代连接方法,以创建相同的连接?我已经尝试过concatappend - hselbie
这个链接显示了当在单个值上合并时,DF的大小会膨胀并且需要很长时间。我将上面的代码更改为在ID字段上合并,速度快了一百万倍...大约是这样。 - hselbie

0

谢谢@kartik,我尝试了一下,收到了这个错误信息:ValueError: Location based indexing can only have [integer, integer slice (START point is INCLUDED, END point is EXCLUDED), listlike of integers, boolean array] types。我已经尝试使用between_time来选择索引,不知道它是否只适用于时间而非日期。 - hselbie
不,我认为在iloc中逗号后面去掉冒号就可以解决问题了。它可能会抱怨在冒号的两侧都没有找到任何整数。尝试使用.iloc[i-1,].iloc[i,]代替.iloc[i-1,:].iloc[i,:]... - Kartik
我想通了...很抱歉一开始没有好好思考。当只选择一行时,.loc[]将返回一个Series,并且该Series的索引将作为名称。但是有一种更简单的方法,而不是使用.iloc...我正在相应地编辑我的答案。请看一下并告诉我是否有效。 - Kartik
还有,这真的很尴尬。我把 'speed' 传递给了 .iloc[],这就是错误的原因。对不起。 - Kartik
仍未将速度附加到DF2中,我已查看了两个数据框并导出了两个CSV文件以确保没有遗漏。我开始使用此代码取得一些成功:for i in range(1, len(fi.index)): if df.index[i] > fi.index[i-1] and df.index[i] < fi.index[1]: df['speed'] = fi['speed'] 这是一个更简单的形式,可以实现您一直在尝试做的事情,而不使用between_time。 - hselbie
显示剩余4条评论

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