跳到内容

类型转换

类型转换将列的底层数据类型转换为新的数据类型。类型转换可以通过函数 cast 实现。

函数 cast 包含一个参数 strict,它决定了 Polars 在遇到无法从源数据类型转换为目标数据类型的值时的行为。默认行为是 strict=True,这意味着 Polars 将抛出错误,通知用户转换失败,同时提供无法转换的值的详细信息。另一方面,如果 strict=False,任何无法转换为目标数据类型的值将被静默转换为 null

基本示例

让我们来看看下面包含整数和浮点数的 DataFrame

import polars as pl

df = pl.DataFrame(
    {
        "integers": [1, 2, 3],
        "big_integers": [10000002, 2, 30000003],
        "floats": [4.0, 5.8, -6.3],
    }
)

print(df)
use polars::prelude::*;

let df = df! (
    "integers"=> [1, 2, 3],
    "big_integers"=> [10000002, 2, 30000003],
    "floats"=> [4.0, 5.8, -6.3],
)?;

println!("{df}");
shape: (3, 3)
┌──────────┬──────────────┬────────┐
│ integers ┆ big_integers ┆ floats │
│ ---      ┆ ---          ┆ ---    │
│ i64      ┆ i64          ┆ f64    │
╞══════════╪══════════════╪════════╡
│ 1        ┆ 10000002     ┆ 4.0    │
│ 2        ┆ 2            ┆ 5.8    │
│ 3        ┆ 30000003     ┆ -6.3   │
└──────────┴──────────────┴────────┘

为了在浮点数和整数之间进行类型转换,反之亦然,我们使用函数 cast

cast

result = df.select(
    pl.col("integers").cast(pl.Float32).alias("integers_as_floats"),
    pl.col("floats").cast(pl.Int32).alias("floats_as_integers"),
)
print(result)

cast

let result = df
    .clone()
    .lazy()
    .select([
        col("integers")
            .cast(DataType::Float32)
            .alias("integers_as_floats"),
        col("floats")
            .cast(DataType::Int32)
            .alias("floats_as_integers"),
    ])
    .collect()?;
println!("{result}");

shape: (3, 2)
┌────────────────────┬────────────────────┐
│ integers_as_floats ┆ floats_as_integers │
│ ---                ┆ ---                │
│ f32                ┆ i32                │
╞════════════════════╪════════════════════╡
│ 1.0                ┆ 4                  │
│ 2.0                ┆ 5                  │
│ 3.0                ┆ -6                 │
└────────────────────┴────────────────────┘

请注意,将浮点数转换为整数数据类型时,浮点数会被截断。

向下转换数值数据类型

通过更改与列的数值数据类型相关的精度,可以减少列的内存占用。例如,以下代码演示了如何通过将 Int64 转换为 Int16 以及将 Float64 转换为 Float32 来降低内存使用量

cast · estimated_size

print(f"Before downcasting: {df.estimated_size()} bytes")
result = df.with_columns(
    pl.col("integers").cast(pl.Int16),
    pl.col("floats").cast(pl.Float32),
)
print(f"After downcasting: {result.estimated_size()} bytes")

cast · estimated_size

println!("Before downcasting: {} bytes", df.estimated_size());
let result = df
    .clone()
    .lazy()
    .with_columns([
        col("integers").cast(DataType::Int16),
        col("floats").cast(DataType::Float32),
    ])
    .collect()?;
println!("After downcasting: {} bytes", result.estimated_size());

Before downcasting: 72 bytes
After downcasting: 42 bytes

在执行向下转换时,务必确保所选的位数(例如 64、32 或 16)足以容纳列中的最大和最小数字。例如,一个 32 位有符号整数(Int32)表示包含 -2147483648 到 2147483647 之间的整数,而一个 8 位有符号整数仅表示包含 -128 到 127 之间的整数。尝试向下转换为精度不足的数据类型将导致 Polars 抛出错误。

cast

from polars.exceptions import InvalidOperationError

try:
    result = df.select(pl.col("big_integers").cast(pl.Int8))
    print(result)
except InvalidOperationError as err:
    print(err)

cast

let result = df
    .clone()
    .lazy()
    .select([col("big_integers").strict_cast(DataType::Int8)])
    .collect();
if let Err(e) = result {
    println!("{e}")
};

conversion from `i64` to `i8` failed in column 'big_integers' for 2 out of 3 values: [10000002, 30000003]

如果您将参数 strict 设置为 False,则溢出/下溢的值将转换为 null

cast

result = df.select(pl.col("big_integers").cast(pl.Int8, strict=False))
print(result)

cast

let result = df
    .clone()
    .lazy()
    .select([col("big_integers").cast(DataType::Int8)])
    .collect()?;
println!("{result}");

shape: (3, 1)
┌──────────────┐
│ big_integers │
│ ---          │
│ i8           │
╞══════════════╡
│ null         │
│ 2            │
│ null         │
└──────────────┘

将字符串转换为数值数据类型

表示数字的字符串可以通过类型转换转换为相应的数据类型。反向转换也是可行的。

cast

df = pl.DataFrame(
    {
        "integers_as_strings": ["1", "2", "3"],
        "floats_as_strings": ["4.0", "5.8", "-6.3"],
        "floats": [4.0, 5.8, -6.3],
    }
)

result = df.select(
    pl.col("integers_as_strings").cast(pl.Int32),
    pl.col("floats_as_strings").cast(pl.Float64),
    pl.col("floats").cast(pl.String),
)
print(result)

cast

let df = df! (
    "integers_as_strings" => ["1", "2", "3"],
    "floats_as_strings" => ["4.0", "5.8", "-6.3"],
    "floats" => [4.0, 5.8, -6.3],
)?;

let result = df
    .clone()
    .lazy()
    .select([
        col("integers_as_strings").cast(DataType::Int32),
        col("floats_as_strings").cast(DataType::Float64),
        col("floats").cast(DataType::String),
    ])
    .collect()?;
println!("{result}");

shape: (3, 3)
┌─────────────────────┬───────────────────┬────────┐
│ integers_as_strings ┆ floats_as_strings ┆ floats │
│ ---                 ┆ ---               ┆ ---    │
│ i32                 ┆ f64               ┆ str    │
╞═════════════════════╪═══════════════════╪════════╡
│ 1                   ┆ 4.0               ┆ 4.0    │
│ 2                   ┆ 5.8               ┆ 5.8    │
│ 3                   ┆ -6.3              ┆ -6.3   │
└─────────────────────┴───────────────────┴────────┘

如果列中包含非数值或格式不正确的值,Polars 将抛出错误,并提供转换错误的详细信息。您可以设置 strict=False 来避免错误并获取 null 值。

cast

df = pl.DataFrame(
    {
        "floats": ["4.0", "5.8", "- 6 . 3"],
    }
)
try:
    result = df.select(pl.col("floats").cast(pl.Float64))
except InvalidOperationError as err:
    print(err)

cast

let df = df! ("floats" => ["4.0", "5.8", "- 6 . 3"])?;

let result = df
    .clone()
    .lazy()
    .select([col("floats").strict_cast(DataType::Float64)])
    .collect();
if let Err(e) = result {
    println!("{e}")
};

conversion from `str` to `f64` failed in column 'floats' for 1 out of 3 values: ["- 6 . 3"]

布尔值

布尔值可以表示为 1 (True) 或 0 (False)。数值数据类型和布尔值之间可以进行类型转换操作,反之亦然。

将数字转换为布尔值时,数字 0 将转换为 False,所有其他数字将转换为 True,这与 Python 中数字的 Truthy 和 Falsy 值保持一致。

cast

df = pl.DataFrame(
    {
        "integers": [-1, 0, 2, 3, 4],
        "floats": [0.0, 1.0, 2.0, 3.0, 4.0],
        "bools": [True, False, True, False, True],
    }
)

result = df.select(
    pl.col("integers").cast(pl.Boolean),
    pl.col("floats").cast(pl.Boolean),
    pl.col("bools").cast(pl.Int8),
)
print(result)

cast

let df = df! (
        "integers"=> [-1, 0, 2, 3, 4],
        "floats"=> [0.0, 1.0, 2.0, 3.0, 4.0],
        "bools"=> [true, false, true, false, true],
)?;

let result = df
    .clone()
    .lazy()
    .select([
        col("integers").cast(DataType::Boolean),
        col("floats").cast(DataType::Boolean),
        col("bools").cast(DataType::UInt8),
    ])
    .collect()?;
println!("{result}");

shape: (5, 3)
┌──────────┬────────┬───────┐
│ integers ┆ floats ┆ bools │
│ ---      ┆ ---    ┆ ---   │
│ bool     ┆ bool   ┆ i8    │
╞══════════╪════════╪═══════╡
│ true     ┆ false  ┆ 1     │
│ false    ┆ true   ┆ 0     │
│ true     ┆ true   ┆ 1     │
│ true     ┆ true   ┆ 0     │
│ true     ┆ true   ┆ 1     │
└──────────┴────────┴───────┘

解析/格式化时间数据类型

所有时间数据类型在内部都表示为自某个参考时刻(通常称为纪元)以来经过的时间单位数。例如,Date 数据类型的值存储为自纪元以来的天数。对于 Datetime 数据类型,时间单位是微秒 (us),对于 Time 数据类型,时间单位是纳秒 (ns)。

数值类型和时间数据类型之间允许进行类型转换,这揭示了它们之间的关系。

cast

from datetime import date, datetime, time

df = pl.DataFrame(
    {
        "date": [
            date(1970, 1, 1),  # epoch
            date(1970, 1, 10),  # 9 days later
        ],
        "datetime": [
            datetime(1970, 1, 1, 0, 0, 0),  # epoch
            datetime(1970, 1, 1, 0, 1, 0),  # 1 minute later
        ],
        "time": [
            time(0, 0, 0),  # reference time
            time(0, 0, 1),  # 1 second later
        ],
    }
)

result = df.select(
    pl.col("date").cast(pl.Int64).alias("days_since_epoch"),
    pl.col("datetime").cast(pl.Int64).alias("us_since_epoch"),
    pl.col("time").cast(pl.Int64).alias("ns_since_midnight"),
)
print(result)

cast

use chrono::prelude::*;

let df = df!(
    "date" => [
        NaiveDate::from_ymd_opt(1970, 1, 1).unwrap(),  // epoch
        NaiveDate::from_ymd_opt(1970, 1, 10).unwrap(),  // 9 days later
    ],
    "datetime" => [
        NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(),  // epoch
        NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 1, 0).unwrap(),  // 1 minute later
    ],
    "time" => [
        NaiveTime::from_hms_opt(0, 0, 0).unwrap(),  // reference time
        NaiveTime::from_hms_opt(0, 0, 1).unwrap(),  // 1 second later
    ]
)
.unwrap()
.lazy()
// Make the time unit match that of Python's for the same results.
.with_column(col("datetime").cast(DataType::Datetime(TimeUnit::Microseconds, None)))
.collect()?;

let result = df
    .clone()
    .lazy()
    .select([
        col("date").cast(DataType::Int64).alias("days_since_epoch"),
        col("datetime")
            .cast(DataType::Int64)
            .alias("us_since_epoch"),
        col("time").cast(DataType::Int64).alias("ns_since_midnight"),
    ])
    .collect()?;
println!("{result}");

shape: (2, 3)
┌──────────────────┬────────────────┬───────────────────┐
│ days_since_epoch ┆ us_since_epoch ┆ ns_since_midnight │
│ ---              ┆ ---            ┆ ---               │
│ i64              ┆ i64            ┆ i64               │
╞══════════════════╪════════════════╪═══════════════════╡
│ 0                ┆ 0              ┆ 0                 │
│ 9                ┆ 60000000       ┆ 1000000000        │
└──────────────────┴────────────────┴───────────────────┘

要将时间数据类型格式化为字符串,我们可以使用函数 dt.to_string;要从字符串解析时间数据类型,我们可以使用函数 str.to_datetime。这两个函数都采用 chrono 格式语法进行格式化。

dt.to_string · str.to_date

df = pl.DataFrame(
    {
        "date": [date(2022, 1, 1), date(2022, 1, 2)],
        "string": ["2022-01-01", "2022-01-02"],
    }
)

result = df.select(
    pl.col("date").dt.to_string("%Y-%m-%d"),
    pl.col("string").str.to_datetime("%Y-%m-%d"),
)
print(result)

dt.to_string · str.replace_all · 在功能 `dtype-date` 中可用 · 在功能 `temporal` 中可用

let df = df! (
        "date" => [
            NaiveDate::from_ymd_opt(2022, 1, 1).unwrap(),
            NaiveDate::from_ymd_opt(2022, 1, 2).unwrap(),
        ],
        "string" => [
            "2022-01-01",
            "2022-01-02",
        ],
)?;

let result = df
    .clone()
    .lazy()
    .select([
        col("date").dt().to_string("%Y-%m-%d"),
        col("string").str().to_datetime(
            Some(TimeUnit::Microseconds),
            None,
            StrptimeOptions::default(),
            lit("raise"),
        ),
    ])
    .collect()?;
println!("{result}");

shape: (2, 2)
┌────────────┬─────────────────────┐
│ date       ┆ string              │
│ ---        ┆ ---                 │
│ str        ┆ datetime[μs]        │
╞════════════╪═════════════════════╡
│ 2022-01-01 ┆ 2022-01-01 00:00:00 │
│ 2022-01-02 ┆ 2022-01-02 00:00:00 │
└────────────┴─────────────────────┘

值得注意的是,str.to_datetime 具有支持时区功能的附加选项。请参阅 API 文档了解更多信息。