Skip to content

基本操作

本节展示了如何对数据框列进行基本操作,如进行基本算术计算、执行比较以及其他通用操作。我们将使用以下数据框作为后续示例:

DataFrame

import polars as pl
import numpy as np

np.random.seed(42)  # 为了可重复性。

df = pl.DataFrame(
    {
        "nrs": [1, 2, 3, None, 5],
        "names": ["foo", "ham", "spam", "egg", "spam"],
        "random": np.random.rand(5),
        "groups": ["A", "A", "B", "A", "B"],
    }
)
print(df)

DataFrame

use polars::prelude::*;

let df = df! (
    "nrs" => &[Some(1), Some(2), Some(3), None, Some(5)],
    "names" => &["foo", "ham", "spam", "egg", "spam"],
    "random" => &[0.37454, 0.950714, 0.731994, 0.598658, 0.156019],
    "groups" => &["A", "A", "B", "A", "B"],
)?;

println!("{}", &df);

shape: (5, 4)
┌──────┬───────┬──────────┬────────┐
│ nrs  ┆ names ┆ random   ┆ groups │
│ ---  ┆ ---   ┆ ---      ┆ ---    │
│ i64  ┆ str   ┆ f64      ┆ str    │
╞══════╪═══════╪══════════╪════════╡
│ 1    ┆ foo   ┆ 0.37454  ┆ A      │
│ 2    ┆ ham   ┆ 0.950714 ┆ A      │
│ 3    ┆ spam  ┆ 0.731994 ┆ B      │
│ null ┆ egg   ┆ 0.598658 ┆ A      │
│ 5    ┆ spam  ┆ 0.156019 ┆ B      │
└──────┴───────┴──────────┴────────┘

基础算术

Polars 支持相同长度的序列之间,或序列与字面量之间的基本算术运算。 当字面量与序列混合使用时,字面量会被广播以匹配它们所使用的序列的长度。

operators

result = df.select(
    (pl.col("nrs") + 5).alias("nrs + 5"),
    (pl.col("nrs") - 5).alias("nrs - 5"),
    (pl.col("nrs") * pl.col("random")).alias("nrs * random"),
    (pl.col("nrs") / pl.col("random")).alias("nrs / random"),
    (pl.col("nrs") ** 2).alias("nrs ** 2"),
    (pl.col("nrs") % 3).alias("nrs % 3"),
)

print(result)

operators

let result = df
    .clone()
    .lazy()
    .select([
        (col("nrs") + lit(5)).alias("nrs + 5"),
        (col("nrs") - lit(5)).alias("nrs - 5"),
        (col("nrs") * col("random")).alias("nrs * random"),
        (col("nrs") / col("random")).alias("nrs / random"),
        (col("nrs").pow(lit(2))).alias("nrs ** 2"),
        (col("nrs") % lit(3)).alias("nrs % 3"),
    ])
    .collect()?;
println!("{}", result);

shape: (5, 6)
┌─────────┬─────────┬──────────────┬──────────────┬──────────┬─────────┐
│ nrs + 5 ┆ nrs - 5 ┆ nrs * random ┆ nrs / random ┆ nrs ** 2 ┆ nrs % 3 │
│ ---     ┆ ---     ┆ ---          ┆ ---          ┆ ---      ┆ ---     │
│ i64     ┆ i64     ┆ f64          ┆ f64          ┆ i64      ┆ i64     │
╞═════════╪═════════╪══════════════╪══════════════╪══════════╪═════════╡
│ 6       ┆ -4      ┆ 0.37454      ┆ 2.669941     ┆ 1        ┆ 1       │
│ 7       ┆ -3      ┆ 1.901429     ┆ 2.103681     ┆ 4        ┆ 2       │
│ 8       ┆ -2      ┆ 2.195982     ┆ 4.098395     ┆ 9        ┆ 0       │
│ null    ┆ null    ┆ null         ┆ null         ┆ null     ┆ null    │
│ 10      ┆ 0       ┆ 0.780093     ┆ 32.047453    ┆ 25       ┆ 2       │
└─────────┴─────────┴──────────────┴──────────────┴──────────┴─────────┘

上面的例子表明,当算术运算将null作为其操作数之一时,结果是null

Polars 使用运算符重载,允许你在表达式中使用你语言的原生算术运算符。如果你愿意,在 Python 中你可以使用相应的命名函数,如下面的代码片段所示:

# Python only:
result_named_operators = df.select(
    (pl.col("nrs").add(5)).alias("nrs + 5"),
    (pl.col("nrs").sub(5)).alias("nrs - 5"),
    (pl.col("nrs").mul(pl.col("random"))).alias("nrs * random"),
    (pl.col("nrs").truediv(pl.col("random"))).alias("nrs / random"),
    (pl.col("nrs").pow(2)).alias("nrs ** 2"),
    (pl.col("nrs").mod(3)).alias("nrs % 3"),
)

print(result.equals(result_named_operators))
True

比较

与算术操作类似,Polars 支持通过重载运算符或命名函数进行比较:

operators

result = df.select(
    (pl.col("nrs") > 1).alias("nrs > 1"),  # .gt
    (pl.col("nrs") >= 3).alias("nrs >= 3"),  # ge
    (pl.col("random") < 0.2).alias("random < .2"),  # .lt
    (pl.col("random") <= 0.5).alias("random <= .5"),  # .le
    (pl.col("nrs") != 1).alias("nrs != 1"),  # .ne
    (pl.col("nrs") == 1).alias("nrs == 1"),  # .eq
)
print(result)

operators

let result = df
    .clone()
    .lazy()
    .select([
        col("nrs").gt(1).alias("nrs > 1"),
        col("nrs").gt_eq(3).alias("nrs >= 3"),
        col("random").lt_eq(0.2).alias("random < .2"),
        col("random").lt_eq(0.5).alias("random <= .5"),
        col("nrs").neq(1).alias("nrs != 1"),
        col("nrs").eq(1).alias("nrs == 1"),
    ])
    .collect()?;
println!("{}", result);

shape: (5, 6)
┌─────────┬──────────┬─────────────┬──────────────┬──────────┬──────────┐
│ nrs > 1 ┆ nrs >= 3 ┆ random < .2 ┆ random <= .5 ┆ nrs != 1 ┆ nrs == 1 │
│ ---     ┆ ---      ┆ ---         ┆ ---          ┆ ---      ┆ ---      │
│ bool    ┆ bool     ┆ bool        ┆ bool         ┆ bool     ┆ bool     │
╞═════════╪══════════╪═════════════╪══════════════╪══════════╪══════════╡
│ false   ┆ false    ┆ false       ┆ true         ┆ false    ┆ true     │
│ true    ┆ false    ┆ false       ┆ false        ┆ true     ┆ false    │
│ true    ┆ true     ┆ false       ┆ false        ┆ true     ┆ false    │
│ null    ┆ null     ┆ false       ┆ false        ┆ null     ┆ null     │
│ true    ┆ true     ┆ true        ┆ true         ┆ true     ┆ false    │
└─────────┴──────────┴─────────────┴──────────────┴──────────┴──────────┘

布尔和位操作

根据语言的不同,您可以使用运算符 &|~ 分别表示布尔操作“与”、“或”和“非”,或者使用同名的函数:

operators

# 布尔运算符 & | ~
result = df.select(
    ((~pl.col("nrs").is_null()) & (pl.col("groups") == "A")).alias(
        "number not null and group A"
    ),
    ((pl.col("random") < 0.5) | (pl.col("groups") == "B")).alias(
        "random < 0.5 or group B"
    ),
)

print(result)

# 对应的命名函数 `and_`, `or_`, 和 `not_`。
result2 = df.select(
    (pl.col("nrs").is_null().not_().and_(pl.col("groups") == "A")).alias(
        "number not null and group A"
    ),
    ((pl.col("random") < 0.5).or_(pl.col("groups") == "B")).alias(
        "random < 0.5 or group B"
    ),
)
print(result.equals(result2))

operators

let result = df
    .clone()
    .lazy()
    .select([
        ((col("nrs").is_null()).not().and(col("groups").eq(lit("A"))))
            .alias("数字不为空且组为A"),
        (col("random").lt(lit(0.5)).or(col("groups").eq(lit("B"))))
            .alias("随机数小于0.5或组为B"),
    ])
    .collect()?;
println!("{}", result);

shape: (5, 2)
┌─────────────────────────────┬─────────────────────────┐
│ number not null and group A ┆ random < 0.5 or group B │
│ ---                         ┆ ---                     │
│ bool                        ┆ bool                    │
╞═════════════════════════════╪═════════════════════════╡
│ true                        ┆ true                    │
│ true                        ┆ false                   │
│ false                       ┆ true                    │
│ false                       ┆ false                   │
│ false                       ┆ true                    │
└─────────────────────────────┴─────────────────────────┘
True
Python trivia

Python函数被称为and_or_not_,因为单词andornot是Python中的保留关键字。 同样,我们不能使用关键字andornot作为布尔运算符,因为这些Python关键字将通过dunder方法__bool__在Truthy和Falsy的上下文中解释它们的操作数。 因此,我们将位运算符&|~重载为布尔运算符,因为它们是次优选择。

这些运算符/函数也可以用于相应的位操作,与位运算符 ^ / 函数 xor 一起使用:

result = df.select(
    pl.col("nrs"),
    (pl.col("nrs") & 6).alias("nrs & 6"),
    (pl.col("nrs") | 6).alias("nrs | 6"),
    (~pl.col("nrs")).alias("not nrs"),
    (pl.col("nrs") ^ 6).alias("nrs ^ 6"),
)

print(result)
let result = df
    .clone()
    .lazy()
    .select([
        col("nrs"),
        col("nrs").and(lit(6)).alias("nrs & 6"),
        col("nrs").or(lit(6)).alias("nrs | 6"),
        col("nrs").not().alias("not nrs"),
        col("nrs").xor(lit(6)).alias("nrs ^ 6"),
    ])
    .collect()?;
println!("{}", result);
shape: (5, 5)
┌──────┬─────────┬─────────┬─────────┬─────────┐
│ nrs  ┆ nrs & 6 ┆ nrs | 6 ┆ not nrs ┆ nrs ^ 6 │
│ ---  ┆ ---     ┆ ---     ┆ ---     ┆ ---     │
│ i64  ┆ i64     ┆ i64     ┆ i64     ┆ i64     │
╞══════╪═════════╪═════════╪═════════╪═════════╡
│ 1    ┆ 0       ┆ 7       ┆ -2      ┆ 7       │
│ 2    ┆ 2       ┆ 6       ┆ -3      ┆ 4       │
│ 3    ┆ 2       ┆ 7       ┆ -4      ┆ 5       │
│ null ┆ null    ┆ null    ┆ null    ┆ null    │
│ 5    ┆ 4       ┆ 7       ┆ -6      ┆ 3       │
└──────┴─────────┴─────────┴─────────┴─────────┘

计数(唯一)值

Polars 有两个函数用于计算系列中唯一值的数量。函数 n_unique 可用于计算系列中唯一值的精确数量。然而,对于非常大的数据集,此操作可能会非常慢。在这些情况下,如果近似值足够好,您可以使用函数 approx_n_unique,该函数使用算法 HyperLogLog++ 来估计结果。

下面的示例展示了一个示例系列,其中approx_n_unique估计值误差为0.9%:

n_unique · approx_n_unique

long_df = pl.DataFrame({"numbers": np.random.randint(0, 100_000, 100_000)})

result = long_df.select(
    pl.col("numbers").n_unique().alias("n_unique"),
    pl.col("numbers").approx_n_unique().alias("approx_n_unique"),
)

print(result)

n_unique · approx_n_unique · 在功能 approx_unique 上可用

use rand::distributions::{Distribution, Uniform};
use rand::thread_rng;

let mut rng = thread_rng();
let between = Uniform::new_inclusive(0, 100_000);
let arr: Vec<u32> = between.sample_iter(&mut rng).take(100_100).collect();

let long_df = df!(
    "numbers" => &arr
)?;

let result = long_df
    .clone()
    .lazy()
    .select([
        col("numbers").n_unique().alias("n_unique"),
        col("numbers").approx_n_unique().alias("approx_n_unique"),
    ])
    .collect()?;
println!("{}", result);

shape: (1, 2)
┌──────────┬─────────────────┐
│ n_unique ┆ approx_n_unique │
│ ---      ┆ ---             │
│ u32      ┆ u32             │
╞══════════╪═════════════════╡
│ 63218    ┆ 63784           │
└──────────┴─────────────────┘

您可以使用Polars也提供的value_counts函数获取有关唯一值及其计数的更多信息:

value_counts

result = df.select(
    pl.col("names").value_counts().alias("value_counts"),
)

print(result)

value_counts · 在功能 dtype-struct 上可用

let result = df
    .clone()
    .lazy()
    .select([col("names")
        .value_counts(false, false, "count", false)
        .alias("value_counts")])
    .collect()?;
println!("{}", result);

shape: (4, 1)
┌──────────────┐
│ value_counts │
│ ---          │
│ struct[2]    │
╞══════════════╡
│ {"foo",1}    │
│ {"spam",2}   │
│ {"egg",1}    │
│ {"ham",1}    │
└──────────────┘

函数 value_counts 返回的结果是 结构体,一种我们将在后续章节中探讨的数据类型

或者,如果你只需要一个包含唯一值的系列或一个包含唯一计数的系列, 它们只需一个函数即可实现:

unique · unique_counts

result = df.select(
    pl.col("names").unique(maintain_order=True).alias("unique"),
    pl.col("names").unique_counts().alias("unique_counts"),
)

print(result)

unique · unique_counts · 在功能 unique_counts 上可用

let result = df
    .clone()
    .lazy()
    .select([
        col("names").unique_stable().alias("unique"),
        col("names").unique_counts().alias("unique_counts"),
    ])
    .collect()?;
println!("{}", result);

shape: (4, 2)
┌────────┬───────────────┐
│ unique ┆ unique_counts │
│ ---    ┆ ---           │
│ str    ┆ u32           │
╞════════╪═══════════════╡
│ foo    ┆ 1             │
│ ham    ┆ 1             │
│ spam   ┆ 2             │
│ egg    ┆ 1             │
└────────┴───────────────┘

请注意,我们需要在函数unique中指定maintain_order=True,以便结果的顺序与unique_counts中的结果顺序一致。有关更多信息,请参阅API参考。

条件语句

Polars 支持类似于三元运算符的功能,通过函数 when 实现,其后跟随一个函数 then 和一个可选的函数 otherwise

函数 when 接受一个谓词表达式。评估为 True 的值会被替换为函数 then 内部表达式的相应值。评估为 False 的值会被替换为函数 otherwise 内部表达式的相应值,或者如果未提供 otherwise,则替换为 null

下面的示例将Collatz猜想的一步应用于“nrs”列中的数字:

when

result = df.select(
    pl.col("nrs"),
    pl.when(pl.col("nrs") % 2 == 1)  # 数字是奇数吗?
    .then(3 * pl.col("nrs") + 1)  # 如果是,乘以3并加1。
    .otherwise(pl.col("nrs") // 2)  # 如果不是,除以2。
    .alias("Collatz"),
)

print(result)

when

let result = df
    .clone()
    .lazy()
    .select([
        col("nrs"),
        when((col("nrs") % lit(2)).eq(lit(1)))
            .then(lit(3) * col("nrs") + lit(1))
            .otherwise(col("nrs") / lit(2))
            .alias("Collatz"),
    ])
    .collect()?;
println!("{}", result);

shape: (5, 2)
┌──────┬─────────┐
│ nrs  ┆ Collatz │
│ ---  ┆ ---     │
│ i64  ┆ i64     │
╞══════╪═════════╡
│ 1    ┆ 4       │
│ 2    ┆ 1       │
│ 3    ┆ 10      │
│ null ┆ null    │
│ 5    ┆ 16      │
└──────┴─────────┘

你也可以通过链接任意数量的连续.when(...).then(...)块来模拟任意数量的条件链,类似于Python的elif语句。在这些情况下,对于每个给定的值,Polars只有在之前的所有谓词都失败时才会考虑链中更深层的替换表达式。