列表和数组
Polars 对两种同构容器数据类型有第一优先级的支持:List
和 Array
。Polars 支持对这两种数据类型进行许多操作,并且它们的 API 有重叠,因此本用户指南的目标是阐明何时选择一种数据类型而不是另一种。
列表 vs 数组
List
数据类型
List 数据类型适用于值是长度可变的同构一维容器的列。
下面的 DataFrame 包含三个 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)
// Contribute the Rust translation of the Python example by opening a PR.
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] │
└─────────────────────────────────┴───────────────┴───────────────────────┘
请注意,Polars 的 List
数据类型不同于 Python 的 list
类型,后者中的元素可以是任何类型。如果您想在列中存储真正的 Python 列表,可以使用 Object
数据类型,但您的列将不具备我们将要讨论的列表操作功能。
Array
数据类型
Array
数据类型适用于值是具有已知固定形状的任意维度的同构容器的列。
下面的 DataFrame 包含两个 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)
// Contribute the Rust translation of the Python example by opening a PR.
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 array
print(pl.Series(array).dtype) # Column with the 3D subarrays
// Contribute the Rust translation of the Python example by opening a PR.
Array(Int64, shape=(2, 3, 4))
何时使用哪种
简而言之,优先使用 Array
数据类型而不是 List
,因为它更节省内存且性能更高。如果不能使用 Array
,则使用 List
- 当列中的值没有固定形状时;或者
- 当您需要仅在列表 API 中可用的函数时。
使用列表
list
命名空间
Polars 提供了许多用于处理 List
数据类型值的功能,这些功能都分组在 list
命名空间中。我们现在将稍微探索一下这个命名空间。
以前是 arr
,现在是 list
在 Polars 的早期版本中,列表操作的命名空间曾是 arr
。现在 arr
是 Array
数据类型的命名空间。如果您在 StackOverflow 或其他来源上找到对 arr
命名空间的引用,请注意这些来源*可能*已过时。
下面定义的 weather
DataFrame 包含来自某个区域不同气象站的数据。当气象站无法获取结果时,会记录错误代码而不是当时的实际温度。
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
DataFrame,我们很可能需要对每个站点捕获的温度进行一些分析。为了实现这一点,我们首先需要能够获取单个温度测量值。我们可以使用 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)
// Contribute the Rust translation of the Python example by opening a PR.
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.element().count()).round(2)
result = weather_by_day.with_columns(
# create the list of homogeneous data
pl.concat_list(pl.all().exclude("station")).alias("all_temps")
).select(
# select all columns except the intermediate list
pl.all().exclude("all_temps"),
# compute the rank by calling `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
数据类型。您必须在创建 Series/DataFrame 时指定 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 特性标志下
// Contribute the Rust translation of the Python example by opening a PR.
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 │
└─────────────────┴───────────────┴─────────────┴────────┴─────────┘