跳到内容

连接

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

最常见的连接类型是“等值连接”,其中行通过键表达式进行匹配。Polars 支持多种等值连接策略,它们精确地决定了如何处理行的匹配。Polars 还支持“非等值连接”,这是一种匹配条件不是相等关系的连接类型;以及一种通过键的近似性进行行匹配的连接类型,称为“近似连接”。

快速参考表

下表是为那些知道自己要找什么的人提供的快速参考。如果您想了解连接的总体概念以及如何在 Polars 中使用它们,可以跳过该表并继续阅读下文。

类型 函数 简要描述
等值内连接 join(..., how="inner") 保留左右两侧都匹配的行。
等值左外连接 join(..., how="left") 保留左侧所有行以及右侧匹配的行。左侧不匹配的行,其右侧列用 null 填充。
等值右外连接 join(..., how="right") 保留右侧所有行以及左侧匹配的行。右侧不匹配的行,其左侧列用 null 填充。
等值全连接 join(..., how="full") 保留两个数据框中的所有行,无论它们是否匹配。不匹配的行,其另一侧的列用 null 填充。
等值半连接 join(..., how="semi") 保留左侧与右侧有匹配的行。
等值反连接 join(..., how="anti") 保留左侧与右侧没有匹配的行。
非等值内连接 join_where 查找左侧和右侧所有满足给定谓词(或多个谓词)的可能行配对。
近似连接 (Asof join) join_asof/join_asof_by 类似于左外连接,但匹配的是最接近的键,而非精确匹配的键。
笛卡尔积 join(..., how="cross") 计算两个数据框的 笛卡尔积

等值连接

在等值连接中,通过检查键表达式的相等性来匹配行。您可以使用 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

// In Rust, we cannot use the shorthand of specifying a common
// column name just once.
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 · 适用于 feature 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 · 适用于 feature 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",
            # Reorder the columns to match the order from above.
        ).select(pl.col("group"), pl.col("property_name"), pl.col("cost"))
    )
)

join

// `equals_missing` is needed instead of `equals`
// so that missing values compare as equal.
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([
            // Reorder the columns to match the order of `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 · 适用于 feature 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 · 适用于 feature 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 · 适用于 feature 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  │
╞═══════╪══════╪══════════════════════╪══════╡
│ Bob   ┆ 135  ┆ Sesame Street        ┆ 100  │
│ Bob   ┆ 135  ┆ The Angel, Islington ┆ 100  │
│ Bob   ┆ 135  ┆ Old Ken Road         ┆ 60   │
│ Bob   ┆ 135  ┆ Whitechapel Road     ┆ 60   │
│ Alice ┆ 78   ┆ Old Ken Road         ┆ 60   │
│ Alice ┆ 78   ┆ Whitechapel Road     ┆ 60   │
└───────┴──────┴──────────────────────┴──────┘

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

注意

join_where 仍在实验中,尚不支持任意布尔表达式作为谓词。

近似连接 (Asof join)

近似连接(asof join)类似于左连接,但我们匹配的是最接近的键而非相等的键。在 Polars 中,我们可以使用 join_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 · 适用于 feature asof_join

let result = df_trades.join_asof_by(
    &df_quotes,
    "time",
    "time",
    ["stock"],
    ["stock"],
    AsofStrategy::Backward,
    None,
    true,
    true,
)?;
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 · 适用于 feature asof_join

let result = df_trades.join_asof_by(
    &df_quotes,
    "time",
    "time",
    ["stock"],
    ["stock"],
    AsofStrategy::Backward,
    Some(AnyValue::Duration(60000, TimeUnit::Milliseconds)),
    true,
    true,
)?;
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 · 适用于 feature 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           │
└───────┴────────────────┘