Skip to content

连接

连接操作将一个或多个数据框的列组合成一个新的数据框。不同类型的连接所使用的不同“连接策略”和匹配标准会影响列的组合方式,以及连接操作结果中包含哪些行。

最常见的连接类型是“等值连接”,其中行通过键表达式匹配。 Polars支持几种等值连接的连接策略,这些策略决定了我们如何处理行的匹配。 Polars还支持“非等值连接”,这是一种匹配标准不是等式的连接类型,以及一种通过键的接近度匹配行的连接类型,称为“asof连接”。

快速参考表

下表为知道自己在寻找什么的人提供了一个快速参考。如果你想了解连接的一般知识以及如何在Polars中使用它们,可以跳过表格继续阅读下面的内容。

Type Function Brief description
Equi inner join join(..., how="inner") Keeps rows that matched both on the left and right.
Equi left outer join join(..., how="left") Keeps all rows from the left plus matching rows from the right. Non-matching rows from the left have their right columns filled with null.
Equi right outer join join(..., how="right") Keeps all rows from the right plus matching rows from the left. Non-matching rows from the right have their left columns filled with null.
Equi full join join(..., how="full") Keeps all rows from either dataframe, regardless of whether they match or not. Non-matching rows from one side have the columns from the other side filled with null.
Equi semi join join(..., how="semi") Keeps rows from the left that have a match on the right.
Equi anti join join(..., how="anti") Keeps rows from the left that do not have a match on the right.
Non-equi inner join join_where Finds all possible pairings of rows from the left and right that satisfy the given predicate(s).
Asof join join_asof/join_asof_by Like a left outer join, but matches on the nearest key instead of on exact key matches.
Cartesian product join(..., how="cross") Computes the Cartesian product of the two dataframes.

等值连接

在等值连接中,通过检查键表达式的相等性来匹配行。您可以使用join函数通过指定用作键的列名来进行等值连接。对于示例,我们将加载一些(修改过的)大富翁属性数据。

首先,我们加载一个包含游戏中属性名称及其颜色组的数据框:

import polars as pl

props_groups = pl.read_csv("docs/assets/data/monopoly_props_groups.csv").head(5)
print(props_groups)
let props_groups = CsvReadOptions::default()
    .with_has_header(true)
    .try_into_reader_with_file_path(Some("docs/assets/data/monopoly_props_groups.csv".into()))?
    .finish()?
    .head(Some(5));
println!("{}", props_groups);
shape: (5, 2)
┌──────────────────────┬────────────┐
│ property_name        ┆ group      │
│ ---                  ┆ ---        │
│ str                  ┆ str        │
╞══════════════════════╪════════════╡
│ Old Ken Road         ┆ brown      │
│ Whitechapel Road     ┆ brown      │
│ The Shire            ┆ fantasy    │
│ Kings Cross Station  ┆ stations   │
│ The Angel, Islington ┆ light_blue │
└──────────────────────┴────────────┘

接下来,我们加载一个包含游戏中属性名称及其价格的数据框:

props_prices = pl.read_csv("docs/assets/data/monopoly_props_prices.csv").head(5)
print(props_prices)
let props_prices = CsvReadOptions::default()
    .with_has_header(true)
    .try_into_reader_with_file_path(Some("docs/assets/data/monopoly_props_prices.csv".into()))?
    .finish()?
    .head(Some(5));
println!("{}", props_prices);
shape: (5, 2)
┌──────────────────────┬──────┐
│ property_name        ┆ cost │
│ ---                  ┆ ---  │
│ str                  ┆ i64  │
╞══════════════════════╪══════╡
│ Old Ken Road         ┆ 60   │
│ Whitechapel Road     ┆ 60   │
│ Sesame Street        ┆ 100  │
│ Kings Cross Station  ┆ 200  │
│ The Angel, Islington ┆ 100  │
└──────────────────────┴──────┘

现在,我们将两个数据框连接起来,创建一个包含属性名称、颜色组和价格的数据框:

join

result = props_groups.join(props_prices, on="property_name")
print(result)

join

// 在 Rust 中,我们不能使用只指定一次公共列名的简写形式。
let result = props_groups
    .clone()
    .lazy()
    .join(
        props_prices.clone().lazy(),
        [col("property_name")],
        [col("property_name")],
        JoinArgs::default(),
    )
    .collect()?;
println!("{}", result);

shape: (4, 3)
┌──────────────────────┬────────────┬──────┐
│ property_name        ┆ group      ┆ cost │
│ ---                  ┆ ---        ┆ ---  │
│ str                  ┆ str        ┆ i64  │
╞══════════════════════╪════════════╪══════╡
│ Old Ken Road         ┆ brown      ┆ 60   │
│ Whitechapel Road     ┆ brown      ┆ 60   │
│ Kings Cross Station  ┆ stations   ┆ 200  │
│ The Angel, Islington ┆ light_blue ┆ 100  │
└──────────────────────┴────────────┴──────┘

结果有四行,但操作中使用的两个数据框都有五行。Polars 使用一种连接策略来确定如何处理有多重匹配的行或没有匹配的行。默认情况下,Polars 计算“内连接”,但还有其他连接策略我们接下来会展示

在上面的例子中,两个数据框方便地拥有我们希望用作键的列,列名相同且值的格式完全相同。假设为了讨论,其中一个数据框有一个不同名称的列,而另一个数据框的属性名是小写的:

str namespace

props_groups2 = props_groups.with_columns(
    pl.col("property_name").str.to_lowercase(),
)
print(props_groups2)

str namespace · 在功能 strings 上可用

let props_groups2 = props_groups
    .clone()
    .lazy()
    .with_column(col("property_name").str().to_lowercase())
    .collect()?;
println!("{}", props_groups2);

shape: (5, 2)
┌──────────────────────┬────────────┐
│ property_name        ┆ group      │
│ ---                  ┆ ---        │
│ str                  ┆ str        │
╞══════════════════════╪════════════╡
│ old ken road         ┆ brown      │
│ whitechapel road     ┆ brown      │
│ the shire            ┆ fantasy    │
│ kings cross station  ┆ stations   │
│ the angel, islington ┆ light_blue │
└──────────────────────┴────────────┘
props_prices2 = props_prices.select(
    pl.col("property_name").alias("name"), pl.col("cost")
)
print(props_prices2)
let props_prices2 = props_prices
    .clone()
    .lazy()
    .select([col("property_name").alias("name"), col("cost")])
    .collect()?;
println!("{}", props_prices2);
shape: (5, 2)
┌──────────────────────┬──────┐
│ name                 ┆ cost │
│ ---                  ┆ ---  │
│ str                  ┆ i64  │
╞══════════════════════╪══════╡
│ Old Ken Road         ┆ 60   │
│ Whitechapel Road     ┆ 60   │
│ Sesame Street        ┆ 100  │
│ Kings Cross Station  ┆ 200  │
│ The Angel, Islington ┆ 100  │
└──────────────────────┴──────┘

在这种情况下,我们可能希望执行与之前相同的连接,我们可以利用join的灵活性,并指定任意表达式来计算左侧和右侧的连接键,从而允许动态计算行键:

join · str namespace

result = props_groups2.join(
    props_prices2,
    left_on="property_name",
    right_on=pl.col("name").str.to_lowercase(),
)
print(result)

join · str namespace · 在功能 strings 上可用

let result = props_groups2
    .clone()
    .lazy()
    .join(
        props_prices2.clone().lazy(),
        [col("property_name")],
        [col("name").str().to_lowercase()],
        JoinArgs::default(),
    )
    .collect()?;
println!("{}", result);

shape: (4, 4)
┌──────────────────────┬────────────┬──────────────────────┬──────┐
│ property_name        ┆ group      ┆ name                 ┆ cost │
│ ---                  ┆ ---        ┆ ---                  ┆ ---  │
│ str                  ┆ str        ┆ str                  ┆ i64  │
╞══════════════════════╪════════════╪══════════════════════╪══════╡
│ old ken road         ┆ brown      ┆ Old Ken Road         ┆ 60   │
│ whitechapel road     ┆ brown      ┆ Whitechapel Road     ┆ 60   │
│ kings cross station  ┆ stations   ┆ Kings Cross Station  ┆ 200  │
│ the angel, islington ┆ light_blue ┆ The Angel, Islington ┆ 100  │
└──────────────────────┴────────────┴──────────────────────┴──────┘

因为我们正在使用表达式进行右连接,Polars 保留了左侧的列“property_name”和右侧的列“name”,以便我们可以访问应用键表达式的原始值。

连接策略

当使用df1.join(df2, ...)计算连接时,我们可以指定多种不同的连接策略。连接策略指定了基于它们是否与另一个数据框的行匹配,从每个数据框中保留哪些行。

内连接

在内连接中,结果数据帧仅包含来自左和右数据帧中匹配的行。这是join使用的默认策略,上面我们可以看到这样的一个例子。我们在这里重复这个例子,并明确指定连接策略:

join

result = props_groups.join(props_prices, on="property_name", how="inner")
print(result)

join

let result = props_groups
    .clone()
    .lazy()
    .join(
        props_prices.clone().lazy(),
        [col("property_name")],
        [col("property_name")],
        JoinArgs::new(JoinType::Inner),
    )
    .collect()?;
println!("{}", result);

shape: (4, 3)
┌──────────────────────┬────────────┬──────┐
│ property_name        ┆ group      ┆ cost │
│ ---                  ┆ ---        ┆ ---  │
│ str                  ┆ str        ┆ i64  │
╞══════════════════════╪════════════╪══════╡
│ Old Ken Road         ┆ brown      ┆ 60   │
│ Whitechapel Road     ┆ brown      ┆ 60   │
│ Kings Cross Station  ┆ stations   ┆ 200  │
│ The Angel, Islington ┆ light_blue ┆ 100  │
└──────────────────────┴────────────┴──────┘

结果不包括来自props_groups的包含“The Shire”的行,结果也不包括来自props_prices的包含“Sesame Street”的行。

左连接

左外连接是一种连接,其结果包含来自左数据帧的所有行以及右数据帧中与左数据帧任何行匹配的行。

join

result = props_groups.join(props_prices, on="property_name", how="left")
print(result)

join

let result = props_groups
    .clone()
    .lazy()
    .join(
        props_prices.clone().lazy(),
        [col("property_name")],
        [col("property_name")],
        JoinArgs::new(JoinType::Left),
    )
    .collect()?;
println!("{}", result);

shape: (5, 3)
┌──────────────────────┬────────────┬──────┐
│ property_name        ┆ group      ┆ cost │
│ ---                  ┆ ---        ┆ ---  │
│ str                  ┆ str        ┆ i64  │
╞══════════════════════╪════════════╪══════╡
│ Old Ken Road         ┆ brown      ┆ 60   │
│ Whitechapel Road     ┆ brown      ┆ 60   │
│ The Shire            ┆ fantasy    ┆ null │
│ Kings Cross Station  ┆ stations   ┆ 200  │
│ The Angel, Islington ┆ light_blue ┆ 100  │
└──────────────────────┴────────────┴──────┘

如果左数据框中有任何行在右数据框中没有匹配的行,它们在新列中将获得null值。

右连接

从计算的角度来看,右外连接与左外连接完全相同,只是参数交换了。以下是一个示例:

join

result = props_groups.join(props_prices, on="property_name", how="right")
print(result)

join

let result = props_groups
    .clone()
    .lazy()
    .join(
        props_prices.clone().lazy(),
        [col("property_name")],
        [col("property_name")],
        JoinArgs::new(JoinType::Right),
    )
    .collect()?;
println!("{}", result);

shape: (5, 3)
┌────────────┬──────────────────────┬──────┐
│ group      ┆ property_name        ┆ cost │
│ ---        ┆ ---                  ┆ ---  │
│ str        ┆ str                  ┆ i64  │
╞════════════╪══════════════════════╪══════╡
│ brown      ┆ Old Ken Road         ┆ 60   │
│ brown      ┆ Whitechapel Road     ┆ 60   │
│ null       ┆ Sesame Street        ┆ 100  │
│ stations   ┆ Kings Cross Station  ┆ 200  │
│ light_blue ┆ The Angel, Islington ┆ 100  │
└────────────┴──────────────────────┴──────┘

我们展示了df1.join(df2, how="right", ...)df2.join(df1, how="left", ...)在结果列的顺序上是相同的,通过以下计算:

join

print(
    result.equals(
        props_prices.join(
            props_groups,
            on="property_name",
            how="left",
            # 重新排列列以匹配上面的顺序。
        ).select(pl.col("group"), pl.col("property_name"), pl.col("cost"))
    )
)

join

// 需要使用 `equals_missing` 而不是 `equals`
// 以便缺失值可以比较为相等。
let dfs_match = result.equals_missing(
    &props_prices
        .clone()
        .lazy()
        .join(
            props_groups.clone().lazy(),
            [col("property_name")],
            [col("property_name")],
            JoinArgs::new(JoinType::Left),
        )
        .select([
            // 重新排列列以匹配 `result` 的顺序。
            col("group"),
            col("property_name"),
            col("cost"),
        ])
        .collect()?,
);
println!("{}", dfs_match);

True

全连接

全外连接将保留左侧和右侧数据框中的所有行,即使它们在另一个数据框中没有匹配的行:

join

result = props_groups.join(props_prices, on="property_name", how="full")
print(result)

join

let result = props_groups
    .clone()
    .lazy()
    .join(
        props_prices.clone().lazy(),
        [col("property_name")],
        [col("property_name")],
        JoinArgs::new(JoinType::Full),
    )
    .collect()?;
println!("{}", result);

shape: (6, 4)
┌──────────────────────┬────────────┬──────────────────────┬──────┐
│ property_name        ┆ group      ┆ property_name_right  ┆ cost │
│ ---                  ┆ ---        ┆ ---                  ┆ ---  │
│ str                  ┆ str        ┆ str                  ┆ i64  │
╞══════════════════════╪════════════╪══════════════════════╪══════╡
│ Old Ken Road         ┆ brown      ┆ Old Ken Road         ┆ 60   │
│ Whitechapel Road     ┆ brown      ┆ Whitechapel Road     ┆ 60   │
│ null                 ┆ null       ┆ Sesame Street        ┆ 100  │
│ Kings Cross Station  ┆ stations   ┆ Kings Cross Station  ┆ 200  │
│ The Angel, Islington ┆ light_blue ┆ The Angel, Islington ┆ 100  │
│ The Shire            ┆ fantasy    ┆ null                 ┆ null │
└──────────────────────┴────────────┴──────────────────────┴──────┘

在这种情况下,我们看到我们得到了两列 property_nameproperty_name_right,以弥补我们在两个数据框的 property_name 列上进行匹配时存在一些没有匹配的名称的事实。这两列有助于区分每行数据的来源。如果我们想要强制 join 将两列 property_name 合并为一列,我们可以显式设置 coalesce=True

join

result = props_groups.join(
    props_prices,
    on="property_name",
    how="full",
    coalesce=True,
)
print(result)

join

let result = props_groups
    .clone()
    .lazy()
    .join(
        props_prices.clone().lazy(),
        [col("property_name")],
        [col("property_name")],
        JoinArgs::new(JoinType::Full).with_coalesce(JoinCoalesce::CoalesceColumns),
    )
    .collect()?;
println!("{}", result);

shape: (6, 3)
┌──────────────────────┬────────────┬──────┐
│ property_name        ┆ group      ┆ cost │
│ ---                  ┆ ---        ┆ ---  │
│ str                  ┆ str        ┆ i64  │
╞══════════════════════╪════════════╪══════╡
│ Old Ken Road         ┆ brown      ┆ 60   │
│ Whitechapel Road     ┆ brown      ┆ 60   │
│ Sesame Street        ┆ null       ┆ 100  │
│ Kings Cross Station  ┆ stations   ┆ 200  │
│ The Angel, Islington ┆ light_blue ┆ 100  │
│ The Shire            ┆ fantasy    ┆ null │
└──────────────────────┴────────────┴──────┘

当未设置时,参数coalesce会根据连接策略和指定的键自动确定,这就是为什么内连接、左连接和右连接表现得好像coalesce=True,即使我们没有设置它。

半连接

半连接将返回左数据框中在右数据框中有匹配的行,但我们实际上并不连接匹配的行:

join

result = props_groups.join(props_prices, on="property_name", how="semi")
print(result)

join · 功能 semi_anti_join 可用

let result = props_groups
    .clone()
    .lazy()
    .join(
        props_prices.clone().lazy(),
        [col("property_name")],
        [col("property_name")],
        JoinArgs::new(JoinType::Semi),
    )
    .collect()?;
println!("{}", result);

shape: (4, 2)
┌──────────────────────┬────────────┐
│ property_name        ┆ group      │
│ ---                  ┆ ---        │
│ str                  ┆ str        │
╞══════════════════════╪════════════╡
│ Old Ken Road         ┆ brown      │
│ Whitechapel Road     ┆ brown      │
│ Kings Cross Station  ┆ stations   │
│ The Angel, Islington ┆ light_blue │
└──────────────────────┴────────────┘

半连接作为一种基于第二个数据框的行过滤器。

反连接

相反,反连接将返回左数据框中在右数据框中没有匹配的行:

join

result = props_groups.join(props_prices, on="property_name", how="anti")
print(result)

join · 在功能 semi_anti_join 上可用

let result = props_groups
    .clone()
    .lazy()
    .join(
        props_prices.clone().lazy(),
        [col("property_name")],
        [col("property_name")],
        JoinArgs::new(JoinType::Anti),
    )
    .collect()?;
println!("{}", result);

shape: (1, 2)
┌───────────────┬─────────┐
│ property_name ┆ group   │
│ ---           ┆ ---     │
│ str           ┆ str     │
╞═══════════════╪═════════╡
│ The Shire     ┆ fantasy │
└───────────────┴─────────┘

非等值连接

在非等值连接中,左右数据框之间的匹配计算方式不同。我们不是寻找键表达式上的匹配,而是提供一个单一的谓词,用于确定左数据框的哪些行可以与右数据框的哪些行配对。

例如,考虑以下大富翁玩家及其当前的现金:

players = pl.DataFrame(
    {
        "name": ["Alice", "Bob"],
        "cash": [78, 135],
    }
)
print(players)
let players = df!(
    "name" => ["Alice", "Bob"],
    "cash" => [78, 135],
)?;
println!("{}", players);
shape: (2, 2)
┌───────┬──────┐
│ name  ┆ cash │
│ ---   ┆ ---  │
│ str   ┆ i64  │
╞═══════╪══════╡
│ Alice ┆ 78   │
│ Bob   ┆ 135  │
└───────┴──────┘

使用非等值连接,我们可以轻松构建一个包含每个玩家可能感兴趣的购买属性的数据框。我们使用函数join_where来计算非等值连接:

join_where

result = players.join_where(props_prices, pl.col("cash") > pl.col("cost"))
print(result)

join_where · 在功能 iejoin 上可用

let result = players
    .clone()
    .lazy()
    .join_builder()
    .with(props_prices.clone().lazy())
    .join_where(vec![col("cash").cast(DataType::Int64).gt(col("cost"))])
    .collect()?;
println!("{}", result);

shape: (6, 4)
┌───────┬──────┬──────────────────────┬──────┐
│ name  ┆ cash ┆ property_name        ┆ cost │
│ ---   ┆ ---  ┆ ---                  ┆ ---  │
│ str   ┆ i64  ┆ str                  ┆ i64  │
╞═══════╪══════╪══════════════════════╪══════╡
│ Alice ┆ 78   ┆ Old Ken Road         ┆ 60   │
│ Alice ┆ 78   ┆ Whitechapel Road     ┆ 60   │
│ Bob   ┆ 135  ┆ Old Ken Road         ┆ 60   │
│ Bob   ┆ 135  ┆ Whitechapel Road     ┆ 60   │
│ Bob   ┆ 135  ┆ Sesame Street        ┆ 100  │
│ Bob   ┆ 135  ┆ The Angel, Islington ┆ 100  │
└───────┴──────┴──────────────────────┴──────┘

您可以提供多个表达式作为谓词,但它们都必须使用评估为布尔结果的比较运算符,并且必须引用来自两个数据框的列。

注意

join_where 仍然是实验性的,尚不支持任意布尔表达式作为谓词。

Asof 连接

一个asof连接类似于左连接,除了我们匹配的是最近的键而不是相等的键。在Polars中,我们可以使用join_asof方法进行asof连接。

对于asof连接,我们将考虑一个受股市启发的场景。假设一个股票市场经纪人有一个名为df_trades的数据框,显示它为不同股票进行的交易。

from datetime import datetime

df_trades = pl.DataFrame(
    {
        "time": [
            datetime(2020, 1, 1, 9, 1, 0),
            datetime(2020, 1, 1, 9, 1, 0),
            datetime(2020, 1, 1, 9, 3, 0),
            datetime(2020, 1, 1, 9, 6, 0),
        ],
        "stock": ["A", "B", "B", "C"],
        "trade": [101, 299, 301, 500],
    }
)
print(df_trades)
use chrono::prelude::*;

let df_trades = df!(
    "time" => [
        NaiveDate::from_ymd_opt(2020, 1, 1).unwrap().and_hms_opt(9, 1, 0).unwrap(),
        NaiveDate::from_ymd_opt(2020, 1, 1).unwrap().and_hms_opt(9, 1, 0).unwrap(),
        NaiveDate::from_ymd_opt(2020, 1, 1).unwrap().and_hms_opt(9, 3, 0).unwrap(),
        NaiveDate::from_ymd_opt(2020, 1, 1).unwrap().and_hms_opt(9, 6, 0).unwrap(),
    ],
    "stock" => ["A", "B", "B", "C"],
    "trade" => [101, 299, 301, 500],
)?;
println!("{}", df_trades);
shape: (4, 3)
┌─────────────────────┬───────┬───────┐
│ time                ┆ stock ┆ trade │
│ ---                 ┆ ---   ┆ ---   │
│ datetime[μs]        ┆ str   ┆ i64   │
╞═════════════════════╪═══════╪═══════╡
│ 2020-01-01 09:01:00 ┆ A     ┆ 101   │
│ 2020-01-01 09:01:00 ┆ B     ┆ 299   │
│ 2020-01-01 09:03:00 ┆ B     ┆ 301   │
│ 2020-01-01 09:06:00 ┆ C     ┆ 500   │
└─────────────────────┴───────┴───────┘

经纪人还有另一个名为 df_quotes 的数据框,显示它为这些股票报价的价格:

df_quotes = pl.DataFrame(
    {
        "time": [
            datetime(2020, 1, 1, 9, 0, 0),
            datetime(2020, 1, 1, 9, 2, 0),
            datetime(2020, 1, 1, 9, 4, 0),
            datetime(2020, 1, 1, 9, 6, 0),
        ],
        "stock": ["A", "B", "C", "A"],
        "quote": [100, 300, 501, 102],
    }
)

print(df_quotes)
let df_quotes = df!(
    "time" => [
        NaiveDate::from_ymd_opt(2020, 1, 1).unwrap().and_hms_opt(9, 1, 0).unwrap(),
        NaiveDate::from_ymd_opt(2020, 1, 1).unwrap().and_hms_opt(9, 2, 0).unwrap(),
        NaiveDate::from_ymd_opt(2020, 1, 1).unwrap().and_hms_opt(9, 4, 0).unwrap(),
        NaiveDate::from_ymd_opt(2020, 1, 1).unwrap().and_hms_opt(9, 6, 0).unwrap(),
    ],
    "stock" => ["A", "B", "C", "A"],
    "quote" => [100, 300, 501, 102],
)?;
println!("{}", df_quotes);
shape: (4, 3)
┌─────────────────────┬───────┬───────┐
│ time                ┆ stock ┆ quote │
│ ---                 ┆ ---   ┆ ---   │
│ datetime[μs]        ┆ str   ┆ i64   │
╞═════════════════════╪═══════╪═══════╡
│ 2020-01-01 09:00:00 ┆ A     ┆ 100   │
│ 2020-01-01 09:02:00 ┆ B     ┆ 300   │
│ 2020-01-01 09:04:00 ┆ C     ┆ 501   │
│ 2020-01-01 09:06:00 ┆ A     ┆ 102   │
└─────────────────────┴───────┴───────┘

你想要生成一个数据框,显示每笔交易时提供的最新报价在或之前交易时间。你可以使用join_asof(使用默认的strategy = "backward")来实现这一点。为了避免将一只股票的交易与另一只股票的报价进行连接,你必须在股票列上使用by="stock"指定一个精确的初步连接。

join_asof

df_asof_join = df_trades.join_asof(df_quotes, on="time", by="stock")
print(df_asof_join)

join_asof_by · 在功能 asof_join 上可用

let result = df_trades.join_asof_by(
    &df_quotes,
    "time",
    "time",
    ["stock"],
    ["stock"],
    AsofStrategy::Backward,
    None,
)?;
println!("{}", result);

shape: (4, 4)
┌─────────────────────┬───────┬───────┬───────┐
│ time                ┆ stock ┆ trade ┆ quote │
│ ---                 ┆ ---   ┆ ---   ┆ ---   │
│ datetime[μs]        ┆ str   ┆ i64   ┆ i64   │
╞═════════════════════╪═══════╪═══════╪═══════╡
│ 2020-01-01 09:01:00 ┆ A     ┆ 101   ┆ 100   │
│ 2020-01-01 09:01:00 ┆ B     ┆ 299   ┆ null  │
│ 2020-01-01 09:03:00 ┆ B     ┆ 301   ┆ 300   │
│ 2020-01-01 09:06:00 ┆ C     ┆ 500   ┆ 501   │
└─────────────────────┴───────┴───────┴───────┘

如果你想确保只有特定时间范围内的报价被加入到交易中,你可以指定tolerance参数。在这种情况下,我们希望确保最后一个前导报价在交易的1分钟内,所以我们设置tolerance = "1m"

join_asof

df_asof_tolerance_join = df_trades.join_asof(
    df_quotes, on="time", by="stock", tolerance="1m"
)
print(df_asof_tolerance_join)

join_asof_by · 在功能 asof_join 上可用

let result = df_trades.join_asof_by(
    &df_quotes,
    "time",
    "time",
    ["stock"],
    ["stock"],
    AsofStrategy::Backward,
    Some(AnyValue::Duration(60000, TimeUnit::Milliseconds)),
)?;
println!("{}", result);

shape: (4, 4)
┌─────────────────────┬───────┬───────┬───────┐
│ time                ┆ stock ┆ trade ┆ quote │
│ ---                 ┆ ---   ┆ ---   ┆ ---   │
│ datetime[μs]        ┆ str   ┆ i64   ┆ i64   │
╞═════════════════════╪═══════╪═══════╪═══════╡
│ 2020-01-01 09:01:00 ┆ A     ┆ 101   ┆ 100   │
│ 2020-01-01 09:01:00 ┆ B     ┆ 299   ┆ null  │
│ 2020-01-01 09:03:00 ┆ B     ┆ 301   ┆ 300   │
│ 2020-01-01 09:06:00 ┆ C     ┆ 500   ┆ null  │
└─────────────────────┴───────┴───────┴───────┘

笛卡尔积

Polars 允许你计算两个数据帧的 笛卡尔积,生成一个 数据帧,其中左数据帧的所有行与右数据帧的所有行配对。要计算两个数据帧的笛卡尔积,你可以将策略 how="cross" 传递给函数 join,而不指定任何 onleft_onright_on

join

tokens = pl.DataFrame({"monopoly_token": ["hat", "shoe", "boat"]})

result = players.select(pl.col("name")).join(tokens, how="cross")
print(result)

cross_join · 可在功能 cross_join 上使用

let tokens = df!(
    "monopoly_token" => ["hat", "shoe", "boat"],
)?;

let result = players
    .clone()
    .lazy()
    .select([col("name")])
    .cross_join(tokens.clone().lazy(), None)
    .collect()?;
println!("{}", result);

shape: (6, 2)
┌───────┬────────────────┐
│ name  ┆ monopoly_token │
│ ---   ┆ ---            │
│ str   ┆ str            │
╞═══════╪════════════════╡
│ Alice ┆ hat            │
│ Alice ┆ shoe           │
│ Alice ┆ boat           │
│ Bob   ┆ hat            │
│ Bob   ┆ shoe           │
│ Bob   ┆ boat           │
└───────┴────────────────┘