列表和数组
Polars 对两种同质容器数据类型提供了优先支持:List 和 Array。Polars 支持对这两种数据类型的多种操作,并且它们的 API 有重叠,因此本用户指南部分的目的是阐明在什么情况下应选择一种数据类型而不是另一种。
列表与数组
数据类型 List
数据类型列表适用于那些值是不同长度的同质一维容器的列。
下面的数据框包含三个数据类型为List的列示例:
from datetime import datetime
import polars as pl
df = pl.DataFrame(
{
"names": [
["Anne", "Averill", "Adams"],
["Brandon", "Brooke", "Borden", "Branson"],
["Camila", "Campbell"],
["Dennis", "Doyle"],
],
"children_ages": [
[5, 7],
[],
[],
[8, 11, 18],
],
"medical_appointments": [
[],
[],
[],
[datetime(2022, 5, 22, 16, 30)],
],
}
)
print(df)
// 通过提交PR来贡献Python示例的Rust翻译。
shape: (4, 3)
┌─────────────────────────────────┬───────────────┬───────────────────────┐
│ names ┆ children_ages ┆ medical_appointments │
│ --- ┆ --- ┆ --- │
│ list[str] ┆ list[i64] ┆ list[datetime[μs]] │
╞═════════════════════════════════╪═══════════════╪═══════════════════════╡
│ ["Anne", "Averill", "Adams"] ┆ [5, 7] ┆ [] │
│ ["Brandon", "Brooke", … "Brans… ┆ [] ┆ [] │
│ ["Camila", "Campbell"] ┆ [] ┆ [] │
│ ["Dennis", "Doyle"] ┆ [8, 11, 18] ┆ [2022-05-22 16:30:00] │
└─────────────────────────────────┴───────────────┴───────────────────────┘
请注意,数据类型 List 与 Python 的类型 list 不同,后者的元素可以是任何类型。如果你想在列中存储真正的 Python 列表,可以使用数据类型 Object,但你的列将不具备我们即将讨论的列表操作功能。
数据类型 Array
数据类型 Array 适用于那些值是已知且固定形状的任意维度同质容器的列。
下面的数据框包含两个数据类型为Array的列示例。
df = pl.DataFrame(
{
"bit_flags": [
[True, True, True, True, False],
[False, True, True, True, True],
],
"tic_tac_toe": [
[
[" ", "x", "o"],
[" ", "x", " "],
["o", "x", " "],
],
[
["o", "x", "x"],
[" ", "o", "x"],
[" ", " ", "o"],
],
],
},
schema={
"bit_flags": pl.Array(pl.Boolean, 5),
"tic_tac_toe": pl.Array(pl.String, (3, 3)),
},
)
print(df)
// 通过提交 PR 来贡献 Python 示例的 Rust 翻译。
shape: (2, 2)
┌───────────────────────┬─────────────────────────────────┐
│ bit_flags ┆ tic_tac_toe │
│ --- ┆ --- │
│ array[bool, 5] ┆ array[str, (3, 3)] │
╞═══════════════════════╪═════════════════════════════════╡
│ [true, true, … false] ┆ [[" ", "x", "o"], [" ", "x", "… │
│ [false, true, … true] ┆ [["o", "x", "x"], [" ", "o", "… │
└───────────────────────┴─────────────────────────────────┘
上面的例子展示了如何指定列“bit_flags”和“tic_tac_toe”具有数据类型Array,该数据类型由包含的元素的数据类型和每个数组的形状参数化。
通常,出于性能考虑,Polars 不会推断列具有数据类型 Array,而是默认为数据类型 List 的适当变体。在 Python 中,此规则的一个例外是当你提供 NumPy 数组来构建列时。在这种情况下,Polars 从 NumPy 中保证所有子数组具有相同的形状,因此一个 \(n + 1\) 维的数组将生成一个 \(n\) 维数组的列:
import numpy as np
array = np.arange(0, 120).reshape((5, 2, 3, 4)) # 4D数组
print(pl.Series(array).dtype) # 包含3D子数组的列
// 通过提交PR来贡献Python示例的Rust翻译。
Array(Int64, shape=(2, 3, 4))
何时使用每种方法
简而言之,优先选择数据类型Array而不是List,因为它更节省内存且性能更好。如果无法使用Array,则使用List:
- 当列中的值没有固定形状时;或者
- 当你需要仅在列表API中可用的函数时。
处理列表
命名空间 list
Polars 提供了许多函数来处理数据类型为 List 的值,这些函数被分组在命名空间 list 中。我们现在将稍微探索一下这个命名空间。
arr 然后,list 现在
在Polars的早期版本中,列表操作的命名空间曾经是arr。
arr现在是数据类型Array的命名空间。
如果你在StackOverflow或其他来源上找到关于命名空间arr的引用,请注意这些来源可能已经过时。
下面定义的weather数据框包含了来自该地区不同气象站的数据。
当气象站无法获取结果时,会记录一个错误代码,而不是当时的实际温度。
weather = pl.DataFrame(
{
"station": [f"Station {idx}" for idx in range(1, 6)],
"temperatures": [
"20 5 5 E1 7 13 19 9 6 20",
"18 8 16 11 23 E2 8 E2 E2 E2 90 70 40",
"19 24 E9 16 6 12 10 22",
"E2 E0 15 7 8 10 E1 24 17 13 6",
"14 8 E0 16 22 24 E1",
],
}
)
print(weather)
// Contribute the Rust translation of the Python example by opening a PR.
shape: (5, 2)
┌───────────┬─────────────────────────────────┐
│ station ┆ temperatures │
│ --- ┆ --- │
│ str ┆ str │
╞═══════════╪═════════════════════════════════╡
│ Station 1 ┆ 20 5 5 E1 7 13 19 9 6 20 │
│ Station 2 ┆ 18 8 16 11 23 E2 8 E2 E2 E2 90… │
│ Station 3 ┆ 19 24 E9 16 6 12 10 22 │
│ Station 4 ┆ E2 E0 15 7 8 10 E1 24 17 13 6 │
│ Station 5 ┆ 14 8 E0 16 22 24 E1 │
└───────────┴─────────────────────────────────┘
以编程方式创建列表
给定之前定义的weather数据框,我们很可能需要对每个站点捕获的温度进行一些分析。为了实现这一点,我们首先需要能够获取单个温度测量值。我们可以使用命名空间str来实现这一点:
shape: (5, 2)
┌───────────┬──────────────────────┐
│ station ┆ temperatures │
│ --- ┆ --- │
│ str ┆ list[str] │
╞═══════════╪══════════════════════╡
│ Station 1 ┆ ["20", "5", … "20"] │
│ Station 2 ┆ ["18", "8", … "40"] │
│ Station 3 ┆ ["19", "24", … "22"] │
│ Station 4 ┆ ["E2", "E0", … "6"] │
│ Station 5 ┆ ["14", "8", … "E1"] │
└───────────┴──────────────────────┘
一个自然的后续步骤是将温度列表展开,使每个测量值都在其自己的行中:
shape: (49, 2)
┌───────────┬──────────────┐
│ station ┆ temperatures │
│ --- ┆ --- │
│ str ┆ str │
╞═══════════╪══════════════╡
│ Station 1 ┆ 20 │
│ Station 1 ┆ 5 │
│ Station 1 ┆ 5 │
│ Station 1 ┆ E1 │
│ Station 1 ┆ 7 │
│ … ┆ … │
│ Station 5 ┆ E0 │
│ Station 5 ┆ 16 │
│ Station 5 ┆ 22 │
│ Station 5 ┆ 24 │
│ Station 5 ┆ E1 │
└───────────┴──────────────┘
然而,在Polars中,我们通常不需要这样做来操作列表元素。
操作列表
Polars 提供了几种对具有 List 数据类型的列进行标准操作的方法。
类似于你可以对字符串进行的操作,列表可以使用
函数 head、tail 和 slice 进行切片:
result = weather.with_columns(
pl.col("temperatures").list.head(3).alias("head"),
pl.col("temperatures").list.tail(3).alias("tail"),
pl.col("temperatures").list.slice(-3, 2).alias("two_next_to_last"),
)
print(result)
// 通过提交PR来贡献Python示例的Rust翻译。
shape: (5, 5)
┌───────────┬──────────────────────┬────────────────────┬────────────────────┬──────────────────┐
│ station ┆ temperatures ┆ head ┆ tail ┆ two_next_to_last │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ list[str] ┆ list[str] ┆ list[str] ┆ list[str] │
╞═══════════╪══════════════════════╪════════════════════╪════════════════════╪══════════════════╡
│ Station 1 ┆ ["20", "5", … "20"] ┆ ["20", "5", "5"] ┆ ["9", "6", "20"] ┆ ["9", "6"] │
│ Station 2 ┆ ["18", "8", … "40"] ┆ ["18", "8", "16"] ┆ ["90", "70", "40"] ┆ ["90", "70"] │
│ Station 3 ┆ ["19", "24", … "22"] ┆ ["19", "24", "E9"] ┆ ["12", "10", "22"] ┆ ["12", "10"] │
│ Station 4 ┆ ["E2", "E0", … "6"] ┆ ["E2", "E0", "15"] ┆ ["17", "13", "6"] ┆ ["17", "13"] │
│ Station 5 ┆ ["14", "8", … "E1"] ┆ ["14", "8", "E0"] ┆ ["22", "24", "E1"] ┆ ["22", "24"] │
└───────────┴──────────────────────┴────────────────────┴────────────────────┴──────────────────┘
列表中的元素级计算
如果我们需要识别出产生最多错误的站点,我们需要
- 尝试将测量值转换为数字;
- 按行计算列表中非数值(即
null值)的数量;以及 - 将此输出列重命名为“errors”,以便我们能够轻松识别站点。
要执行这些步骤,我们需要对列表中的每个测量值执行类型转换操作。函数eval被用作对列表元素执行操作的入口点。在其中,您可以使用上下文element来单独引用列表中的每个元素,然后您可以在该元素上使用任何Polars表达式:
shape: (5, 3)
┌───────────┬──────────────────────┬────────┐
│ station ┆ temperatures ┆ errors │
│ --- ┆ --- ┆ --- │
│ str ┆ list[str] ┆ u32 │
╞═══════════╪══════════════════════╪════════╡
│ Station 1 ┆ ["20", "5", … "20"] ┆ 1 │
│ Station 2 ┆ ["18", "8", … "40"] ┆ 4 │
│ Station 3 ┆ ["19", "24", … "22"] ┆ 1 │
│ Station 4 ┆ ["E2", "E0", … "6"] ┆ 3 │
│ Station 5 ┆ ["14", "8", … "E1"] ┆ 2 │
└───────────┴──────────────────────┴────────┘
另一种选择是使用正则表达式来检查测量值是否以字母开头:
True
如果你不熟悉命名空间 str 或正则表达式中的符号 (?i),现在是时候
查看如何在 Polars 中使用字符串和正则表达式。
行计算
函数 eval 使我们能够访问列表元素,pl.element 指的是每个单独的元素,但我们也可以使用 pl.all() 来引用列表中的所有元素。
为了展示这一点,我们将首先创建另一个包含更多天气数据的dataframe:
weather_by_day = pl.DataFrame(
{
"station": [f"Station {idx}" for idx in range(1, 11)],
"day_1": [17, 11, 8, 22, 9, 21, 20, 8, 8, 17],
"day_2": [15, 11, 10, 8, 7, 14, 18, 21, 15, 13],
"day_3": [16, 15, 24, 24, 8, 23, 19, 23, 16, 10],
}
)
print(weather_by_day)
// Contribute the Rust translation of the Python example by opening a PR.
shape: (10, 4)
┌────────────┬───────┬───────┬───────┐
│ station ┆ day_1 ┆ day_2 ┆ day_3 │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ i64 ┆ i64 ┆ i64 │
╞════════════╪═══════╪═══════╪═══════╡
│ Station 1 ┆ 17 ┆ 15 ┆ 16 │
│ Station 2 ┆ 11 ┆ 11 ┆ 15 │
│ Station 3 ┆ 8 ┆ 10 ┆ 24 │
│ Station 4 ┆ 22 ┆ 8 ┆ 24 │
│ Station 5 ┆ 9 ┆ 7 ┆ 8 │
│ Station 6 ┆ 21 ┆ 14 ┆ 23 │
│ Station 7 ┆ 20 ┆ 18 ┆ 19 │
│ Station 8 ┆ 8 ┆ 21 ┆ 23 │
│ Station 9 ┆ 8 ┆ 15 ┆ 16 │
│ Station 10 ┆ 17 ┆ 13 ┆ 10 │
└────────────┴───────┴───────┴───────┘
现在,我们将计算每天各站点测量的温度的百分比排名。 Polars 没有直接提供执行此操作的函数,但由于表达式非常灵活,我们可以为最高温度创建自己的百分比排名表达式。让我们试试看:
rank_pct = (pl.element().rank(descending=True) / pl.all().count()).round(2)
result = weather_by_day.with_columns(
# 创建同质数据列表
pl.concat_list(pl.all().exclude("station")).alias("all_temps")
).select(
# 选择除中间列表外的所有列
pl.all().exclude("all_temps"),
# 通过调用 `list.eval` 计算排名
pl.col("all_temps").list.eval(rank_pct, parallel=True).alias("temps_rank"),
)
print(result)
shape: (10, 5)
┌────────────┬───────┬───────┬───────┬────────────────────┐
│ station ┆ day_1 ┆ day_2 ┆ day_3 ┆ temps_rank │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ i64 ┆ i64 ┆ i64 ┆ list[f64] │
╞════════════╪═══════╪═══════╪═══════╪════════════════════╡
│ Station 1 ┆ 17 ┆ 15 ┆ 16 ┆ [0.33, 1.0, 0.67] │
│ Station 2 ┆ 11 ┆ 11 ┆ 15 ┆ [0.83, 0.83, 0.33] │
│ Station 3 ┆ 8 ┆ 10 ┆ 24 ┆ [1.0, 0.67, 0.33] │
│ Station 4 ┆ 22 ┆ 8 ┆ 24 ┆ [0.67, 1.0, 0.33] │
│ Station 5 ┆ 9 ┆ 7 ┆ 8 ┆ [0.33, 1.0, 0.67] │
│ Station 6 ┆ 21 ┆ 14 ┆ 23 ┆ [0.67, 1.0, 0.33] │
│ Station 7 ┆ 20 ┆ 18 ┆ 19 ┆ [0.33, 1.0, 0.67] │
│ Station 8 ┆ 8 ┆ 21 ┆ 23 ┆ [1.0, 0.67, 0.33] │
│ Station 9 ┆ 8 ┆ 15 ┆ 16 ┆ [1.0, 0.67, 0.33] │
│ Station 10 ┆ 17 ┆ 13 ┆ 10 ┆ [0.33, 0.67, 1.0] │
└────────────┴───────┴───────┴───────┴────────────────────┘
处理数组
创建一个数组列
正如我们上面所看到的,Polars通常不会自动推断数据类型Array。在创建系列/数据框或显式转换列时,您必须指定数据类型Array,除非您从NumPy数组创建该列。
命名空间 arr
数据类型 Array 最近被引入,并且在其提供的功能方面仍然相当初级。尽管如此,命名空间 arr 聚合了几个函数,您可以使用这些函数来处理数组。
arr 然后,list 现在
在Polars的早期版本中,列表操作的命名空间曾经是arr。
arr现在是数据类型Array的命名空间。
如果你在StackOverflow或其他来源上找到关于命名空间arr的引用,请注意这些来源可能已经过时。
API文档应该能让你很好地了解命名空间arr中的函数,我们在这里介绍几个:
df = pl.DataFrame(
{
"first_last": [
["Anne", "Adams"],
["Brandon", "Branson"],
["Camila", "Campbell"],
["Dennis", "Doyle"],
],
"fav_numbers": [
[42, 0, 1],
[2, 3, 5],
[13, 21, 34],
[73, 3, 7],
],
},
schema={
"first_last": pl.Array(pl.String, 2),
"fav_numbers": pl.Array(pl.Int32, 3),
},
)
result = df.select(
pl.col("first_last").arr.join(" ").alias("name"),
pl.col("fav_numbers").arr.sort(),
pl.col("fav_numbers").arr.max().alias("largest_fav"),
pl.col("fav_numbers").arr.sum().alias("summed"),
pl.col("fav_numbers").arr.contains(3).alias("likes_3"),
)
print(result)
`arr namespace` · 在功能 dtype-array 上可用
// 通过提交 PR 来贡献 Python 示例的 Rust 翻译。
shape: (4, 5)
┌─────────────────┬───────────────┬─────────────┬────────┬─────────┐
│ name ┆ fav_numbers ┆ largest_fav ┆ summed ┆ likes_3 │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ array[i32, 3] ┆ i32 ┆ i32 ┆ bool │
╞═════════════════╪═══════════════╪═════════════╪════════╪═════════╡
│ Anne Adams ┆ [0, 1, 42] ┆ 42 ┆ 43 ┆ false │
│ Brandon Branson ┆ [2, 3, 5] ┆ 5 ┆ 10 ┆ true │
│ Camila Campbell ┆ [13, 21, 34] ┆ 34 ┆ 68 ┆ false │
│ Dennis Doyle ┆ [3, 7, 73] ┆ 73 ┆ 83 ┆ true │
└─────────────────┴───────────────┴─────────────┴────────┴─────────┘