跳到内容

数据类型和结构

数据类型

Polars 支持多种数据类型,大致分为以下几类

  • 数值数据类型:有符号整数、无符号整数、浮点数和 Decimal 数。
  • 嵌套数据类型:列表、结构体和数组。
  • 时间类型:日期、日期时间、时间和时间差。
  • 杂项:字符串、二进制数据、布尔值、分类数据、枚举和对象。

所有类型都支持由特殊值 null 表示的缺失值。这不应与浮点数数据类型中的特殊值 NaN 混淆;有关更多信息,请参阅浮点数部分

您还可以在附录中找到包含所有支持的数据类型的完整表格,其中包含关于何时使用每种数据类型的说明以及指向文档相关部分的链接。

Series

Polars 提供的核心基础数据结构是 Series 和 Dataframe。Series 是一种一维同构数据结构。“同构”意味着 Series 中的所有元素都具有相同的数据类型。下面的代码片段展示了如何创建一个命名 Series。

Series

import polars as pl

s = pl.Series("ints", [1, 2, 3, 4, 5])
print(s)

Series

use polars::prelude::*;

let s = Series::new("ints".into(), &[1, 2, 3, 4, 5]);

println!("{s}");

shape: (5,)
Series: 'ints' [i64]
[
    1
    2
    3
    4
    5
]

创建 Series 时,Polars 会从您提供的值推断数据类型。您可以指定具体的数据类型来覆盖推断机制。

Series

s1 = pl.Series("ints", [1, 2, 3, 4, 5])
s2 = pl.Series("uints", [1, 2, 3, 4, 5], dtype=pl.UInt64)
print(s1.dtype, s2.dtype)

Series

let s1 = Series::new("ints".into(), &[1, 2, 3, 4, 5]);
let s2 = Series::new("uints".into(), &[1, 2, 3, 4, 5])
    .cast(&DataType::UInt64) // Here, we actually cast after inference.
    .unwrap();
println!("{} {}", s1.dtype(), s2.dtype()); // i32 u64

Int64 UInt64

Dataframe

Dataframe 是一种二维异构数据结构,包含唯一命名的 Series。通过将数据存储在 Dataframe 中,您将能够使用 Polars API 编写查询来操作数据。您可以通过使用我们接下来将讨论的 Polars 提供的上下文和表达式来实现这一点。

下面的代码片段展示了如何从列表字典创建 Dataframe

DataFrame

from datetime import date

df = pl.DataFrame(
    {
        "name": ["Alice Archer", "Ben Brown", "Chloe Cooper", "Daniel Donovan"],
        "birthdate": [
            date(1997, 1, 10),
            date(1985, 2, 15),
            date(1983, 3, 22),
            date(1981, 4, 30),
        ],
        "weight": [57.9, 72.5, 53.6, 83.1],  # (kg)
        "height": [1.56, 1.77, 1.65, 1.75],  # (m)
    }
)

print(df)

DataFrame

use chrono::prelude::*;

let df: DataFrame = df!(
    "name" => ["Alice Archer", "Ben Brown", "Chloe Cooper", "Daniel Donovan"],
    "birthdate" => [
        NaiveDate::from_ymd_opt(1997, 1, 10).unwrap(),
        NaiveDate::from_ymd_opt(1985, 2, 15).unwrap(),
        NaiveDate::from_ymd_opt(1983, 3, 22).unwrap(),
        NaiveDate::from_ymd_opt(1981, 4, 30).unwrap(),
    ],
    "weight" => [57.9, 72.5, 53.6, 83.1],  // (kg)
    "height" => [1.56, 1.77, 1.65, 1.75],  // (m)
)
.unwrap();
println!("{df}");

shape: (4, 4)
┌────────────────┬────────────┬────────┬────────┐
│ name           ┆ birthdate  ┆ weight ┆ height │
│ ---            ┆ ---        ┆ ---    ┆ ---    │
│ str            ┆ date       ┆ f64    ┆ f64    │
╞════════════════╪════════════╪════════╪════════╡
│ Alice Archer   ┆ 1997-01-10 ┆ 57.9   ┆ 1.56   │
│ Ben Brown      ┆ 1985-02-15 ┆ 72.5   ┆ 1.77   │
│ Chloe Cooper   ┆ 1983-03-22 ┆ 53.6   ┆ 1.65   │
│ Daniel Donovan ┆ 1981-04-30 ┆ 83.1   ┆ 1.75   │
└────────────────┴────────────┴────────┴────────┘

检查 Dataframe

在本小节中,我们将展示一些有用的方法来快速检查 Dataframe。我们将使用之前创建的 Dataframe 作为起点。

函数 head 显示 Dataframe 的前几行。默认情况下,您会获得前 5 行,但您也可以指定所需的行数。

head

print(df.head(3))

head

let df_head = df.head(Some(3));

println!("{df_head}");

shape: (3, 4)
┌──────────────┬────────────┬────────┬────────┐
│ name         ┆ birthdate  ┆ weight ┆ height │
│ ---          ┆ ---        ┆ ---    ┆ ---    │
│ str          ┆ date       ┆ f64    ┆ f64    │
╞══════════════╪════════════╪════════╪════════╡
│ Alice Archer ┆ 1997-01-10 ┆ 57.9   ┆ 1.56   │
│ Ben Brown    ┆ 1985-02-15 ┆ 72.5   ┆ 1.77   │
│ Chloe Cooper ┆ 1983-03-22 ┆ 53.6   ┆ 1.65   │
└──────────────┴────────────┴────────┴────────┘

Glimpse

函数 glimpse 是另一个显示 Dataframe 前几行值的函数,但其输出格式与 head 不同。在这里,输出的每一行对应一个单独的列,这使得检查更宽的 Dataframe 变得更容易。

glimpse

print(df.glimpse(return_as_string=True))
Rows: 4
Columns: 4
$ name       <str> 'Alice Archer', 'Ben Brown', 'Chloe Cooper', 'Daniel Donovan'
$ birthdate <date> 1997-01-10, 1985-02-15, 1983-03-22, 1981-04-30
$ weight     <f64> 57.9, 72.5, 53.6, 83.1
$ height     <f64> 1.56, 1.77, 1.65, 1.75

信息

glimpse 仅适用于 Python 用户。

Tail

函数 tail 显示 Dataframe 的最后几行。默认情况下,您会获得最后 5 行,但您也可以指定所需的行数,类似于 head 的工作方式。

tail

print(df.tail(3))

tail

let df_tail = df.tail(Some(3));

println!("{df_tail}");

shape: (3, 4)
┌────────────────┬────────────┬────────┬────────┐
│ name           ┆ birthdate  ┆ weight ┆ height │
│ ---            ┆ ---        ┆ ---    ┆ ---    │
│ str            ┆ date       ┆ f64    ┆ f64    │
╞════════════════╪════════════╪════════╪════════╡
│ Ben Brown      ┆ 1985-02-15 ┆ 72.5   ┆ 1.77   │
│ Chloe Cooper   ┆ 1983-03-22 ┆ 53.6   ┆ 1.65   │
│ Daniel Donovan ┆ 1981-04-30 ┆ 83.1   ┆ 1.75   │
└────────────────┴────────────┴────────┴────────┘

Sample

如果您认为 Dataframe 的前几行或最后几行不能代表您的数据,您可以使用 sample 从 Dataframe 中获取任意数量的随机选择行。请注意,这些行不一定按它们在 Dataframe 中出现的相同顺序返回。

sample

import random

random.seed(42)  # For reproducibility.

print(df.sample(2))

sample_n

let n = Series::new("".into(), &[2]);
let sampled_df = df.sample_n(&n, false, false, None).unwrap();

println!("{sampled_df}");

shape: (2, 4)
┌────────────────┬────────────┬────────┬────────┐
│ name           ┆ birthdate  ┆ weight ┆ height │
│ ---            ┆ ---        ┆ ---    ┆ ---    │
│ str            ┆ date       ┆ f64    ┆ f64    │
╞════════════════╪════════════╪════════╪════════╡
│ Daniel Donovan ┆ 1981-04-30 ┆ 83.1   ┆ 1.75   │
│ Chloe Cooper   ┆ 1983-03-22 ┆ 53.6   ┆ 1.65   │
└────────────────┴────────────┴────────┴────────┘

Describe

您还可以使用 describe 计算 Dataframe 中所有列的汇总统计信息。

describe

print(df.describe())

describe · 在 feature describe 上可用

// Not available in Rust

shape: (9, 5)
┌────────────┬────────────────┬─────────────────────┬───────────┬──────────┐
│ statistic  ┆ name           ┆ birthdate           ┆ weight    ┆ height   │
│ ---        ┆ ---            ┆ ---                 ┆ ---       ┆ ---      │
│ str        ┆ str            ┆ str                 ┆ f64       ┆ f64      │
╞════════════╪════════════════╪═════════════════════╪═══════════╪══════════╡
│ count      ┆ 4              ┆ 4                   ┆ 4.0       ┆ 4.0      │
│ null_count ┆ 0              ┆ 0                   ┆ 0.0       ┆ 0.0      │
│ mean       ┆ null           ┆ 1986-09-04 00:00:00 ┆ 66.775    ┆ 1.6825   │
│ std        ┆ null           ┆ null                ┆ 13.560082 ┆ 0.097082 │
│ min        ┆ Alice Archer   ┆ 1981-04-30          ┆ 53.6      ┆ 1.56     │
│ 25%        ┆ null           ┆ 1983-03-22          ┆ 57.9      ┆ 1.65     │
│ 50%        ┆ null           ┆ 1985-02-15          ┆ 72.5      ┆ 1.75     │
│ 75%        ┆ null           ┆ 1985-02-15          ┆ 72.5      ┆ 1.75     │
│ max        ┆ Daniel Donovan ┆ 1997-01-10          ┆ 83.1      ┆ 1.77     │
└────────────┴────────────────┴─────────────────────┴───────────┴──────────┘

Schema

在讨论数据(在 Dataframe 或其他地方)时,我们可以引用其 Schema。Schema 是列名或 Series 名称到这些列或 Series 数据类型的映射。

您可以使用 schema 检查 Dataframe 的 Schema。

print(df.schema)
println!("{:?}", df.schema());
Schema({'name': String, 'birthdate': Date, 'weight': Float64, 'height': Float64})

与 Series 类似,Polars 会在您创建 Dataframe 时推断其 Schema,但如果需要,您可以覆盖推断系统。

在 Python 中,您可以使用字典将列名映射到数据类型来指定显式 Schema。如果您不希望覆盖给定列的推断,则可以使用值 None

df = pl.DataFrame(
    {
        "name": ["Alice", "Ben", "Chloe", "Daniel"],
        "age": [27, 39, 41, 43],
    },
    schema={"name": None, "age": pl.UInt8},
)

print(df)
shape: (4, 2)
┌────────┬─────┐
│ name   ┆ age │
│ ---    ┆ --- │
│ str    ┆ u8  │
╞════════╪═════╡
│ Alice  ┆ 27  │
│ Ben    ┆ 39  │
│ Chloe  ┆ 41  │
│ Daniel ┆ 43  │
└────────┴─────┘

如果您只需要覆盖某些列的推断,参数 schema_overrides 通常更方便,因为它允许您省略不想覆盖推断的列。

df = pl.DataFrame(
    {
        "name": ["Alice", "Ben", "Chloe", "Daniel"],
        "age": [27, 39, 41, 43],
    },
    schema_overrides={"age": pl.UInt8},
)

print(df)
shape: (4, 2)
┌────────┬─────┐
│ name   ┆ age │
│ ---    ┆ --- │
│ str    ┆ u8  │
╞════════╪═════╡
│ Alice  ┆ 27  │
│ Ben    ┆ 39  │
│ Chloe  ┆ 41  │
│ Daniel ┆ 43  │
└────────┴─────┘

数据类型内部结构

Polars 利用 Arrow 列式格式进行数据定向。遵循此规范使得 Polars 能够以极小的开销与其他也使用 Arrow 规范的工具传输数据。

Polars 的大部分性能来自于其查询引擎、对查询计划执行的优化以及在运行表达式时采用的并行化。

浮点数

Polars 通常遵循 IEEE 754 浮点标准处理 Float32Float64,但有一些例外情况:

  • 任何 NaN 都与任何其他 NaN 比较相等,并且大于任何非 NaN 值。
  • 操作不保证零或 NaN 的符号,也不保证 NaN 值的负载有任何特定行为。这不仅限于算术运算,例如,排序或分组操作可能会将所有零规范化为 +0,将所有 NaN 规范化为无负载的正 NaN,以实现高效的相等性检查。

Polars 始终尝试为浮点计算提供合理准确的结果,但除非另有说明,否则不保证误差。一般来说,实现 100% 准确的结果在经济上是不可行的(需要比 64 位浮点数大得多的内部表示),因此总会存在一些误差。

附录:完整数据类型表

类型 详情
布尔型 高效位打包的布尔类型。
Int8, Int16, Int32, Int64 可变精度有符号整数类型。
UInt8, UInt16, UInt32, UInt64 可变精度无符号整数类型。
Float32, Float64 可变精度有符号浮点数。
Decimal 具有可选精度和非负刻度的 Decimal 128 位类型。如果您需要对浮点数的精度及其操作进行精细控制,请使用此类型。有关十进制数据类型是什么的文档,请参阅 Python 的 decimal.Decimal
字符串 可变长度 UTF-8 编码的字符串数据,通常是人类可读的。
二进制 存储任意、可变长度的原始二进制数据。
日期 表示一个日历日期。
时间 表示一天中的时间。
日期时间 表示一个日历日期和一天中的时间。
持续时间 表示一个时间持续。
数组 每个 Series 具有已知固定形状的数组;类似于 NumPy 数组。了解更多关于数组和列表的区别以及如何使用它们
列表 可变长度的同构一维容器。了解更多关于数组和列表的区别以及如何使用它们
对象 封装任意 Python 对象。
分类 在运行时推断类别的字符串数据的有效编码。了解更多关于分类和枚举的区别以及如何使用它们
枚举 预定字符串类别集的有效有序编码。了解更多关于分类和枚举的区别以及如何使用它们
结构体 可以存储多个字段的复合产品类型。在其专门的文档部分了解更多关于数据类型 Struct 的信息。
空值 表示空值。