Skip to content

数据类型和结构

数据类型

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

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

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

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

系列

Polars 提供的核心基础数据结构是系列和数据框。系列是一个一维同质数据结构。所谓“同质”,我们指的是系列中的所有元素都具有相同的数据类型。下面的代码片段展示了如何创建一个命名的系列:

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
]

在创建系列时,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) // 在这里,我们实际上在推断后进行类型转换。
    .unwrap();
println!("{} {}", s1.dtype(), s2.dtype()); // i32 u64

Int64 UInt64

数据框

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

下面的代码片段展示了如何从列表字典创建数据框:

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],  # (公斤)
        "height": [1.56, 1.77, 1.65, 1.75],  # (米)
    }
)

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],  // (公斤)
    "height" => [1.56, 1.77, 1.65, 1.75],  // (米)
)
.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   │
└────────────────┴────────────┴────────┴────────┘

检查数据框

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

函数 head 显示数据框的前几行。默认情况下,你会得到前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 是另一个显示数据框前几行值的函数,但其输出格式与 head 不同。在这里,输出的每一行对应一个单独的列,使得检查更宽的数据框变得更加容易:

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 显示数据框的最后几行。默认情况下,你会得到最后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从数据框中获取任意数量的随机选择的行。请注意,这些行不一定按照它们在数据框中出现的顺序返回:

sample

import random

random.seed(42)  # 为了可重复性。

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

print(df.describe())

describe · 在功能 describe 上可用

// 在 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检查数据框的结构:

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

与系列类似,Polars 在创建数据框时会推断其模式,但您可以在需要时覆盖推断系统。

在Python中,您可以通过使用字典将列名映射到数据类型来指定显式模式。如果您不希望覆盖给定列的推断,可以使用值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位浮点数更大的内部表示),因此总是预期会有一些误差。

附录:完整数据类型表

Type(s) Details
Boolean Boolean type that is bit packed efficiently.
Int8, Int16, Int32, Int64 Varying-precision signed integer types.
UInt8, UInt16, UInt32, UInt64 Varying-precision unsigned integer types.
Float32, Float64 Varying-precision signed floating point numbers.
Decimal Decimal 128-bit type with optional precision and non-negative scale. Use this if you need fine-grained control over the precision of your floats and the operations you make on them. See Python's decimal.Decimal for documentation on what a decimal data type is.
String Variable length UTF-8 encoded string data, typically Human-readable.
Binary Stores arbitrary, varying length raw binary data.
Date Represents a calendar date.
Time Represents a time of day.
Datetime Represents a calendar date and time of day.
Duration Represents a time duration.
Array Arrays with a known, fixed shape per series; akin to numpy arrays. Learn more about how arrays and lists differ and how to work with both.
List Homogeneous 1D container with variable length. Learn more about how arrays and lists differ and how to work with both.
Object Wraps arbitrary Python objects.
Categorical Efficient encoding of string data where the categories are inferred at runtime. Learn more about how categoricals and enums differ and how to work with both.
Enum Efficient ordered encoding of a set of predetermined string categories. Learn more about how categoricals and enums differ and how to work with both.
Struct Composite product type that can store multiple fields. Learn more about the data type Struct in its dedicated documentation section..
Null Represents null values.