Polars按最接近日期连接

2 投票
2 回答
79 浏览
提问于 2025-04-12 00:22

我有一个数据框(叫做events),我想把它和另一个数据框(叫做fr)合并,合并的依据是日期和符号。这里的日期不一定会有重叠。events中的日期只会和fr中同一天或更晚的日期的第一次出现匹配,所以如果事件的日期是2010年12月1日,它会在同一天合并,如果那天没有,就会合并到下一个可用的日期(比如2010年12月2日)。

我试过用search_sorted和join_asof来实现这个,但我还想按符号这一列进行分组,而且这也不是一个标准的合并方式。这种方法对于单个符号来说有点效果。

fr = pl.DataFrame(
    {
        'Symbol': ['A']*5,
        'Date': ['2010-08-29', '2010-09-01', '2010-09-05',
                 '2010-11-30', '2010-12-02'],
    }
).with_columns(pl.col('Date').str.strptime(pl.Date, '%Y-%m-%d')).with_row_index().set_sorted("Date")

events = pl.DataFrame(
    {
         'Symbol': ['A']*3,
         'Earnings_Date': ['2010-06-01', '2010-09-01', '2010-12-01'],
         'Event': [1, 4, 7],
     }
).with_columns(pl.col('Earnings_Date').str.strptime(pl.Date, '%Y-%m-%d')).set_sorted("Earnings_Date")

idx = fr["Date"].search_sorted(events["Earnings_Date"], "left")

fr = fr.with_columns(
    pl.when(
        pl.col("index").is_in(idx)
    )
    .then(True)
    .otherwise(False)
    .alias("Earnings")
)

fr = fr.join_asof(events, by="Symbol", left_on="Date", right_on="Earnings_Date")
fr = fr.with_columns(
    pl.when(
       pl.col("Earnings") == True
    )
    .then(pl.col("Event"))
    .otherwise(False)
    .alias("Event")
)

2 个回答

2

听起来你在使用 pl.DataFrame.join_asof 的方向上是对的。要按符号分组,可以使用 by 这个参数。

(
    fr
    .join_asof(
        events,
        left_on="Date", right_on="Earnings_Date",
        by="Symbol",
    )
)
shape: (5, 5)
┌───────┬────────┬────────────┬───────────────┬───────┐
│ index ┆ Symbol ┆ Date       ┆ Earnings_Date ┆ Event │
│ ---   ┆ ---    ┆ ---        ┆ ---           ┆ ---   │
│ u32   ┆ str    ┆ date       ┆ date          ┆ i64   │
╞═══════╪════════╪════════════╪═══════════════╪═══════╡
│ 0     ┆ A      ┆ 2010-08-29 ┆ 2010-06-01    ┆ 1     │
│ 1     ┆ A      ┆ 2010-09-01 ┆ 2010-09-01    ┆ 4     │
│ 2     ┆ A      ┆ 2010-09-05 ┆ 2010-09-01    ┆ 4     │
│ 3     ┆ A      ┆ 2010-11-30 ┆ 2010-09-01    ┆ 4     │
│ 4     ┆ A      ┆ 2010-12-02 ┆ 2010-12-01    ┆ 7     │
└───────┴────────┴────────────┴───────────────┴───────┘

现在,我明白你希望每个事件最多只匹配一次。我觉得仅靠 join_asof 是做不到的。不过,我们可以把所有与前一行相等的事件行设置为 Null。为此,可以使用 pl.when().then() 这个结构。

(
    fr
    .join_asof(
        events,
        left_on="Date", right_on="Earnings_Date",
        by="Symbol",
    )
    .with_columns(
        pl.when(
            pl.col("Earnings_Date", "Event").is_first_distinct()
        ).then(
            pl.col("Earnings_Date", "Event")
        ).over("Symbol")
    )
)
shape: (5, 5)
┌───────┬────────┬────────────┬───────────────┬───────┐
│ index ┆ Symbol ┆ Date       ┆ Earnings_Date ┆ Event │
│ ---   ┆ ---    ┆ ---        ┆ ---           ┆ ---   │
│ u32   ┆ str    ┆ date       ┆ date          ┆ i64   │
╞═══════╪════════╪════════════╪═══════════════╪═══════╡
│ 0     ┆ A      ┆ 2010-08-29 ┆ 2010-06-01    ┆ 1     │
│ 1     ┆ A      ┆ 2010-09-01 ┆ 2010-09-01    ┆ 4     │
│ 2     ┆ A      ┆ 2010-09-05 ┆ null          ┆ null  │
│ 3     ┆ A      ┆ 2010-11-30 ┆ null          ┆ null  │
│ 4     ┆ A      ┆ 2010-12-02 ┆ 2010-12-01    ┆ 7     │
└───────┴────────┴────────────┴───────────────┴───────┘

撰写回答