【遇见Pandas】详解pd.merge_asof

2,340 阅读3分钟

作用

pd.merge_asof() ,是用来进行时序数据合并的。这个函数比较难也不常用,我们来简单做下学习笔记。

右边的数据,会找左边最接近的日期去合并。backward往上找,forward往下找,nearest最近

它会基于关键字段和时间阈值,将行连接在一起。如果:

  1. 右侧 DataFrame 的时间,晚于左侧 DataFrame 的时间
  2. 差值小于等于阈值

则连接两行

阈值

通过 tolerance 参数传递阈值。

该参数指定了两个时间序列中,所允许的最大时间差。

如果不指定 tolerance 参数,则默认为 pd.Timedelta(seconds=1)。

与pd.merge的关系

pd.merge_asof() 函数适用于时间序列数据;而 pd.merge() 适用于普通的关系型数据

示例1

import pandas as pd

quotes = pd.DataFrame(
    {
        "time": [
            pd.Timestamp("2016-05-25 13:30:00.023"),
            pd.Timestamp("2016-05-25 13:30:00.023"),
            pd.Timestamp("2016-05-25 13:30:00.030"),
            pd.Timestamp("2016-05-25 13:30:00.041"),
            pd.Timestamp("2016-05-25 13:30:00.048"),
            pd.Timestamp("2016-05-25 13:30:00.049"),
            pd.Timestamp("2016-05-25 13:30:00.072"),
            pd.Timestamp("2016-05-25 13:30:00.075")
        ],
        "ticker": [
            "GOOG",
            "MSFT",
            "MSFT",
            "MSFT",
            "GOOG",
            "AAPL",
            "GOOG",
            "MSFT"
        ],
        "bid": [720.50, 51.95, 51.97, 51.99, 720.50, 97.99, 720.50, 52.01],
        "ask": [720.93, 51.96, 51.98, 52.00, 720.93, 98.01, 720.88, 52.03]
    }
)

trades = pd.DataFrame(
    {
        "time": [
            pd.Timestamp("2016-05-25 13:30:00.023"),
            pd.Timestamp("2016-05-25 13:30:00.038"),
            pd.Timestamp("2016-05-25 13:30:00.048"),
            pd.Timestamp("2016-05-25 13:30:00.048"),
            pd.Timestamp("2016-05-25 13:30:00.048")
        ],
        "ticker": ["MSFT", "MSFT", "GOOG", "GOOG", "AAPL"],
        "price": [51.95, 51.95, 720.77, 720.92, 98.0],
        "quantity": [75, 155, 100, 100, 100]
    }
)

merged = pd.merge_asof(
    trades,
    quotes,
    on="time",
    by="ticker",
    tolerance=pd.Timedelta("2ms")
)
print(merged)

结果

                     time ticker   price  quantity     bid     ask
0 2016-05-25 13:30:00.023   MSFT   51.95        75   51.95   51.96
1 2016-05-25 13:30:00.038   MSFT   51.95       155     NaN     NaN
2 2016-05-25 13:30:00.048   GOOG  720.77       100  720.50  720.93
3 2016-05-25 13:30:00.048   GOOG  720.92       100  720.50  720.93
4 2016-05-25 13:30:00.048   AAPL   98.00       100     NaN     NaN

可以看到,第二个MSFT无法合并进去。

这是因为,quotes的第三行数据time为2016-05-25 13:30:00.030,比trade的第2行数据的time2016-05-25 13:30:00.038相差8毫秒。将tolerance调大为8ms,就可以合并了。

merged = pd.merge_asof(
    trades,
    quotes,
    on="time",
    by="ticker",
    tolerance=pd.Timedelta("8ms")
)

结果

                     time ticker   price  quantity     bid     ask
0 2016-05-25 13:30:00.023   MSFT   51.95        75   51.95   51.96
1 2016-05-25 13:30:00.038   MSFT   51.95       155   51.97   51.98
2 2016-05-25 13:30:00.048   GOOG  720.77       100  720.50  720.93
3 2016-05-25 13:30:00.048   GOOG  720.92       100  720.50  720.93
4 2016-05-25 13:30:00.048   AAPL   98.00       100     NaN     NaN

当然,也可以调整direction为forward,可以改变对时间做差时候的方向。

示例2

import pandas as pd

left = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'],
                     'left': ['L0', 'L1', 'L2', 'L3'],
                     'time': pd.to_datetime(['2021-01-01', '2021-01-02', '2021-01-05', '2021-01-06'])})

right = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'],
                      'right': ['R0', 'R1', 'R2', 'R3'],
                      'time': pd.to_datetime(['2021-01-01', '2021-01-03', '2021-01-04', '2021-01-05'])})

merged = pd.merge_asof(left, right, by='key', on='time', tolerance=pd.Timedelta(days=1), direction='forward')
print(merged)

结果

  key left       time right
0  K0   L0 2021-01-01    R0
1  K1   L1 2021-01-02    R1
2  K2   L2 2021-01-05   NaN
3  K3   L3 2021-01-06   NaN

可以看到,forward是left比right早,才可以合并进去

将direction改为backward,结果为:

  key left       time right
0  K0   L0 2021-01-01    R0
1  K1   L1 2021-01-02   NaN
2  K2   L2 2021-01-05    R2
3  K3   L3 2021-01-06    R3

可以猜测,合并的时候,它应该是逐行进行的,满足条件就匹配

补充

这个函数,要求两个df的time列,必须是排过序的

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情