基本操作
本节将展示如何在 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)
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 的长度。
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)
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 支持通过重载运算符或命名函数进行比较。
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)
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 │
└─────────┴──────────┴─────────────┴──────────────┴──────────┴──────────┘
布尔和位操作
根据所用语言,您可以使用运算符 &
、|
和 ~
分别进行布尔运算“与”、“或”和“非”,或者使用同名的函数
# 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))
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_
,因为 and
、or
和 not
在 Python 中是保留关键字。同样,我们不能使用关键字 and
、or
和 not
作为布尔运算符,因为这些 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%
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
获取有关唯一值及其计数的信息
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,只需一个函数即可实现。
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”列中的数字
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)
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 将只考虑链中更深层的替换表达式。