【问题标题】:How to join two dataframes for which column values are within a certain range?如何连接列值在一定范围内的两个数据框?
【发布时间】:2018-03-13 12:58:36
【问题描述】:

给定两个数据框df_1df_2,如何将它们连接起来,使得日期时间列df_1 位于数据框end 中的startend 之间:

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

获取对应的event,其中df1.timestamp介于df_2.startdf2.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

【问题讨论】:

标签: python pandas datetime dataframe intervals


【解决方案1】:

一个简单的解决方案是从start and end 设置closed = both 创建interval index,然后使用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'])

输出:

时间戳 A B 事件 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

【讨论】:

  • 我知道你回答这个问题已经有一段时间了,但也许你可以详细说明\解释代码中的第二行?我遇到了类似的问题,不知道如何将其调整为我的代码。谢谢
  • @TaL,它只是映射数据。 df_2.index.get_loc(x)基本上会根据区间索引的上下界返回x的时间index,即index用于从表中获取事件。
  • @Bharath,我知道我们要回到旧帖子了。问题:如果我们有多个事件值怎么办。我可以使用 nunique() 来计算事件的数量吗?我无法根据您的输入调整代码。有什么建议吗?
  • @JoeFerndz 已经有一段时间了,您可以在 SO 中发布一个新问题来解释您的要求,这是一个旧答案,可能有更好的方法。
【解决方案2】:

首先使用 IntervalIndex 根据感兴趣的区间创建参考索引,然后使用 get_indexer 对包含感兴趣的离散事件的数据帧进行切片。

idx = pd.IntervalIndex.from_arrays(df_2['start'], df_2['end'], closed='both')
event = df_2.iloc[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.to_numpy()
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.

【讨论】:

  • 这很好用,如果间隔不重叠,否则您可能不得不恢复到 Bharath 的解决方案
【解决方案3】:

您可以使用模块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())

【讨论】:

  • 我不知道这是一个选项,谢谢!它解决了我的问题
  • 速度很慢。
  • this thread 演示仅使用 pandas 和 sqlite 的连接
【解决方案4】:

选项 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

【讨论】:

    【解决方案5】:

    在此方法中,我们假设使用了 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 &gt;= df2.start) &amp; (t0 &lt;= df2.end) 将包含 1 个真值。 (参见示例 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 将每个时间戳转换为一个事件。

    【讨论】:

      【解决方案6】:

      您可以通过将df_1 的索引设置为时间戳字段的权宜之计,使pandas 索引对齐为您工作

      import pandas as pd
      
      df_1 = pd.DataFrame(
          columns=["timestamp", "A", "B"],
          data=[
              (pd.Timestamp("2016-05-14 10:54:33"), 0.020228, 0.026572),
              (pd.Timestamp("2016-05-14 10:54:34"), 0.057780, 0.175499),
              (pd.Timestamp("2016-05-14 10:54:35"), 0.098808, 0.620986),
              (pd.Timestamp("2016-05-14 10:54:36"), 0.158789, 1.014819),
              (pd.Timestamp("2016-05-14 10:54:39"), 0.038129, 2.384590),
          ],
      )
      df_2 = pd.DataFrame(
          columns=["start", "end", "event"],
          data=[
              (
                  pd.Timestamp("2016-05-14 10:54:31"),
                  pd.Timestamp("2016-05-14 10:54:33"),
                  "E1",
              ),
              (
                  pd.Timestamp("2016-05-14 10:54:34"),
                  pd.Timestamp("2016-05-14 10:54:37"),
                  "E2",
              ),
              (
                  pd.Timestamp("2016-05-14 10:54:38"),
                  pd.Timestamp("2016-05-14 10:54:42"),
                  "E3",
              ),
          ],
      )
      df_2.index = pd.IntervalIndex.from_arrays(df_2["start"], df_2["end"], closed="both")
      

      只需将df_1["event"] 设置为df_2["event"]

      df_1["event"] = df_2["event"]
      

      然后瞧

      df_1["event"]
      
      timestamp
      2016-05-14 10:54:33    E1
      2016-05-14 10:54:34    E2
      2016-05-14 10:54:35    E2
      2016-05-14 10:54:36    E2
      2016-05-14 10:54:39    E3
      Name: event, dtype: object
      

      【讨论】:

        【解决方案7】:

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

        唯一的问题是,最终,在后台,numpy 广播确实做了某种交叉连接,我们过滤了所有相等的元素,给O(n1*n2) 内存和O(n1*n2) 性能带来了打击。大概,有人可以在一般意义上使这更有效。

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

        基本上,为多态创建以下两个类PointInTimeTimespan

        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)
        

        顺便说一句,如果您不希望使用 ==,而希望使用其他运算符(例如 !=、、=),您可以为它们创建相应的函数(__ne____lt____gt____le____ge__)。

        你可以将它与广播结合使用的方式如下。

        import pandas as pd
        import numpy as np
        
        df1 = pd.DataFrame({"pit":[(x) for x in [PointInTime(2015,1,1), PointInTime(2015,2,2), PointInTime(2015,3,3), PointInTime(2015,4,4)]], 'vals1':[1,2,3,4]})
        df2 = pd.DataFrame({"ts":[(x) for x in [Timespan(datetime(2015,2,1), datetime(2015,2,5)), Timespan(datetime(2015,2,1), datetime(2015,4,1)), Timespan(datetime(2015,2,1), datetime(2015,2,5))]], 'vals2' : ['a', 'b', 'c']})
        a = df1['pit'].values
        b = df2['ts'].values
        i, j = np.where((a[:,None] == b))
        
        res = pd.DataFrame(
            np.column_stack([df1.values[i], df2.values[j]]),
            columns=df1.columns.append(df2.columns)
        )
        print(df1)
        print(df2)
        print(res)
        

        这给出了预期的输出。

        <class '__main__.PointInTime'>: comparing 2015-1-1 to 2015-2-1 -> 2015-2-5 (Timespan in PointInTime) gives False
        <class '__main__.PointInTime'>: comparing 2015-1-1 to 2015-2-1 -> 2015-4-1 (Timespan in PointInTime) gives False
        <class '__main__.PointInTime'>: comparing 2015-1-1 to 2015-2-1 -> 2015-2-5 (Timespan in PointInTime) gives False
        <class '__main__.PointInTime'>: comparing 2015-2-2 to 2015-2-1 -> 2015-2-5 (Timespan in PointInTime) gives True
        <class '__main__.PointInTime'>: comparing 2015-2-2 to 2015-2-1 -> 2015-4-1 (Timespan in PointInTime) gives True
        <class '__main__.PointInTime'>: comparing 2015-2-2 to 2015-2-1 -> 2015-2-5 (Timespan in PointInTime) gives True
        <class '__main__.PointInTime'>: comparing 2015-3-3 to 2015-2-1 -> 2015-2-5 (Timespan in PointInTime) gives False
        <class '__main__.PointInTime'>: comparing 2015-3-3 to 2015-2-1 -> 2015-4-1 (Timespan in PointInTime) gives True
        <class '__main__.PointInTime'>: comparing 2015-3-3 to 2015-2-1 -> 2015-2-5 (Timespan in PointInTime) gives False
        <class '__main__.PointInTime'>: comparing 2015-4-4 to 2015-2-1 -> 2015-2-5 (Timespan in PointInTime) gives False
        <class '__main__.PointInTime'>: comparing 2015-4-4 to 2015-2-1 -> 2015-4-1 (Timespan in PointInTime) gives False
        <class '__main__.PointInTime'>: comparing 2015-4-4 to 2015-2-1 -> 2015-2-5 (Timespan in PointInTime) gives False
                pit  vals1
        0  2015-1-1      1
        1  2015-2-2      2
        2  2015-3-3      3
        3  2015-4-4      4
                             ts vals2
        0  2015-2-1 -> 2015-2-5     a
        1  2015-2-1 -> 2015-4-1     b
        2  2015-2-1 -> 2015-2-5     c
                pit vals1                    ts vals2
        0  2015-2-2     2  2015-2-1 -> 2015-2-5     a
        1  2015-2-2     2  2015-2-1 -> 2015-4-1     b
        2  2015-2-2     2  2015-2-1 -> 2015-2-5     c
        3  2015-3-3     3  2015-2-1 -> 2015-4-1     b
        

        与基本的 Python 类型相比,拥有类的开销可能会带来额外的性能损失,但我没有对此进行研究。

        以上是我们如何创建“内部”连接。创建“(outer) left”、“(outer) right”和“(full) outer”连接应该很简单。

        【讨论】:

          猜你喜欢
          • 2021-11-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-04-30
          相关资源
          最近更新 更多