如何连接列值在一定范围内的两个数据帧?

时间:2017-10-02 12:48:23

标签: python pandas datetime dataframe intervals

鉴于两个数据框df_1df_2,如何加入它们,使得日期时间列df_1位于数据框startend之间}}:

df_2

获取print df_1 timestamp A B 0 2016-05-14 10:54:33 0.020228 0.026572 1 2016-05-14 10:54:34 0.057780 0.175499 2 2016-05-14 10:54:35 0.098808 0.620986 3 2016-05-14 10:54:36 0.158789 1.014819 4 2016-05-14 10:54:39 0.038129 2.384590 print df_2 start end event 0 2016-05-14 10:54:31 2016-05-14 10:54:33 E1 1 2016-05-14 10:54:34 2016-05-14 10:54:37 E2 2 2016-05-14 10:54:38 2016-05-14 10:54:42 E3 eventdf1.timestamp之间的df_2.start对应的df2.end

  timestamp              A          B          event
0 2016-05-14 10:54:33    0.020228   0.026572   E1
1 2016-05-14 10:54:34    0.057780   0.175499   E2
2 2016-05-14 10:54:35    0.098808   0.620986   E2
3 2016-05-14 10:54:36    0.158789   1.014819   E2
4 2016-05-14 10:54:39    0.038129   2.384590   E3

6 个答案:

答案 0 :(得分:30)

一个简单的解决方案是从interval index设置start and end创建closed = both,然后使用get_loc来获取事件,即(希望所有日期时间都在时间戳dtype中)

df_2.index = pd.IntervalIndex.from_arrays(df_2['start'],df_2['end'],closed='both')
df_1['event'] = df_1['timestamp'].apply(lambda x : df_2.iloc[df_2.index.get_loc(x)]['event'])

输出:

            timestamp         A         B event
0 2016-05-14 10:54:33  0.020228  0.026572    E1
1 2016-05-14 10:54:34  0.057780  0.175499    E2
2 2016-05-14 10:54:35  0.098808  0.620986    E2
3 2016-05-14 10:54:36  0.158789  1.014819    E2
4 2016-05-14 10:54:39  0.038129  2.384590    E3

答案 1 :(得分:17)

idx = pd.IntervalIndex.from_arrays(df_2['start'], df_2['end'], closed='both')
event = df_2.loc[idx.get_indexer(df_1.timestamp), 'event']

event
0    E1
1    E2
1    E2
1    E2
2    E3
Name: event, dtype: object

df_1['event'] = event.values
df_1
            timestamp         A         B event
0 2016-05-14 10:54:33  0.020228  0.026572    E1
1 2016-05-14 10:54:34  0.057780  0.175499    E2
2 2016-05-14 10:54:35  0.098808  0.620986    E2
3 2016-05-14 10:54:36  0.158789  1.014819    E2
4 2016-05-14 10:54:39  0.038129  2.384590    E3

参考:A question on IntervalIndex.get_indexer.

答案 2 :(得分:8)

选项1

idx = pd.IntervalIndex.from_arrays(df_2['start'], df_2['end'], closed='both')
df_2.index=idx
df_1['event']=df_2.loc[df_1.timestamp,'event'].values

选项2

df_2['timestamp']=df_2['end']
pd.merge_asof(df_1,df_2[['timestamp','event']],on='timestamp',direction ='forward',allow_exact_matches =True)
Out[405]: 
            timestamp         A         B event
0 2016-05-14 10:54:33  0.020228  0.026572    E1
1 2016-05-14 10:54:34  0.057780  0.175499    E2
2 2016-05-14 10:54:35  0.098808  0.620986    E2
3 2016-05-14 10:54:36  0.158789  1.014819    E2
4 2016-05-14 10:54:39  0.038129  2.384590    E3

答案 3 :(得分:8)

您可以使用模块pandasql

import pandasql as ps

sqlcode = '''
select df_1.timestamp
,df_1.A
,df_1.B
,df_2.event
from df_1 
inner join df_2 
on d1.timestamp between df_2.start and df2.end
'''

newdf = ps.sqldf(sqlcode,locals())

答案 4 :(得分:4)

在这个方法中,我们假设使用了TimeStamp对象。

df2  start                end                  event    
   0 2016-05-14 10:54:31  2016-05-14 10:54:33  E1
   1 2016-05-14 10:54:34  2016-05-14 10:54:37  E2
   2 2016-05-14 10:54:38  2016-05-14 10:54:42  E3

event_num = len(df2.event)

def get_event(t):    
    event_idx = ((t >= df2.start) & (t <= df2.end)).dot(np.arange(event_num))
    return df2.event[event_idx]

df1["event"] = df1.timestamp.transform(get_event)

get_event

的说明

对于df1中的每个时间戳,请说t0 = 2016-05-14 10:54:33

(t0 >= df2.start) & (t0 <= df2.end)将包含1个true。 (见例1)。然后,使用带有np.arange(event_num)的点积来获取t0所属事件的索引。

<强>示例:

示例1

    t0 >= df2.start    t0 <= df2.end     After &     np.arange(3)    
0     True                True         ->  T              0        event_idx
1    False                True         ->  F              1     ->     0
2    False                True         ->  F              2

t2 = 2016-05-14 10:54:35换取另一个例子

    t2 >= df2.start    t2 <= df2.end     After &     np.arange(3)    
0     True                False        ->  F              0        event_idx
1     True                True         ->  T              1     ->     1
2    False                True         ->  F              2

我们最终使用transform将每个时间戳转换为一个事件。

答案 5 :(得分:0)

在解决方案 by firelynx here on StackOverflow 中,这表明多态性不起作用。我必须同意 firelynx(经过广泛测试)。然而,将多态的想法与 the numpy broadcasting solution of piRSquared 结合起来,它可以工作!

唯一的问题是,最后,在幕后,numpy 广播确实做了某种交叉连接,我们过滤所有相等的元素,给出 O(n1*n2) 内存和 {{1} } 性能下降。可能有人可以从一般意义上提高效率。

我在这里发帖的原因是 firelynx 的解决方案问题作为这个问题的副本而关闭,我倾向于不同意。因为当你有多个点属于多个区间时,这个问题和其中的答案并没有给出解决方案,而只是针对属于多个区间的一个点。我在下面提出的解决方案确实会处理这些 n-m 关系。

基本上,为多态创建以下两个类 O(n1*n2)PointInTime

Timespan

顺便说一句,如果你不希望使用 ==,而是使用其他运算符(例如 !=、<、>、<=、>=),你可以为它们创建相应的函数(from datetime import datetime class PointInTime(object): doPrint = True def __init__(self, year, month, day): self.dt = datetime(year, month, day) def __eq__(self, other): if isinstance(other, self.__class__): r = (self.dt == other.dt) if self.doPrint: print(f'{self.__class__}: comparing {self} to {other} (equals) gives {r}') return (r) elif isinstance(other, Timespan): r = (other.start_date < self.dt < other.end_date) if self.doPrint: print(f'{self.__class__}: comparing {self} to {other} (Timespan in PointInTime) gives {r}') return (r) else: if self.doPrint: print(f'Not implemented... (PointInTime)') return NotImplemented def __repr__(self): return "{}-{}-{}".format(self.dt.year, self.dt.month, self.dt.day) class Timespan(object): doPrint = True def __init__(self, start_date, end_date): self.start_date = start_date self.end_date = end_date def __eq__(self, other): if isinstance(other, self.__class__): r = ((self.start_date == other.start_date) and (self.end_date == other.end_date)) if self.doPrint: print(f'{self.__class__}: comparing {self} to {other} (equals) gives {r}') return (r) elif isinstance (other, PointInTime): r = self.start_date < other.dt < self.end_date if self.doPrint: print(f'{self.__class__}: comparing {self} to {other} (PointInTime in Timespan) gives {r}') return (r) else: if self.doPrint: print(f'Not implemented... (Timespan)') return NotImplemented def __repr__(self): return "{}-{}-{} -> {}-{}-{}".format(self.start_date.year, self.start_date.month, self.start_date.day, self.end_date.year, self.end_date.month, self.end_date.day) 、{{ 1}}、__ne____lt____gt__)。

结合广播的方式如下。

__le__

这给出了预期的输出。

__ge__

与基本 Python 类型相比,拥有这些类的开销可能会造成额外的性能损失,但我没有研究过。

以上是我们如何创建“内部”连接。创建“(外)左”、“(外)右”和“(全)外”连接应该很简单。