跳到内容

基本操作

本节将展示如何在 DataFrame 列上执行基本操作,例如进行基本算术计算、执行比较以及其他通用操作。以下示例将使用以下 DataFrame

DataFrame

import polars as pl
import numpy as np

np.random.seed(42)  # For reproducibility.

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 支持长度相同的 Series 之间或 Series 与字面量之间的基本算术运算。当字面量与 Series 混合使用时,字面量会被广播以匹配其所使用的 Series 的长度。

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

# Boolean 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)

# Corresponding named functions `and_`, `or_`, and `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("number not null and group A"),
        (col("random").lt(lit(0.5)).or(col("groups").eq(lit("B"))))
            .alias("random < 0.5 or group 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 小知识

Python 函数名为 and_or_not_,因为 andornot 在 Python 中是保留关键字。同样,我们不能使用关键字 andornot 作为布尔运算符,因为这些 Python 关键字会通过其 __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 有两个函数可以计算 Series 中唯一值的数量。函数 n_unique 可用于计算 Series 中唯一值的精确数量。但是,对于非常大的数据集,此操作可能会非常慢。在这种情况下,如果近似值足够好,您可以使用函数 approx_n_unique,它使用 HyperLogLog++ 算法来估算结果。

下面的示例展示了一个 Series,其中 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    ┆ 64141           │
└──────────┴─────────────────┘

您可以使用 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]    │
╞══════════════╡
│ {"spam",2}   │
│ {"ham",1}    │
│ {"foo",1}    │
│ {"egg",1}    │
└──────────────┘

函数 value_counts结构体(一种我们将在后续章节中探讨的数据类型)的形式返回结果。

或者,如果您只需要包含唯一值的 Series 或包含唯一计数的 Series,只需一个函数即可实现。

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

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

when

result = df.select(
    pl.col("nrs"),
    pl.when(pl.col("nrs") % 2 == 1)  # Is the number odd?
    .then(3 * pl.col("nrs") + 1)  # If so, multiply by 3 and add 1.
    .otherwise(pl.col("nrs") // 2)  # If not, divide by 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 将只考虑链中更深层的替换表达式。