强制类型转换

强制类型转换会将列的底层数据类型转换为新的数据类型, 可以通过cast函数进行

该函数包含一个参数strict, 用于确定Polars在遇到无法从源数据类型转换为目标数据类型的值时的行为

默认为strict=True, Polars会抛出错误,通知用户转换失败, 同时提供无法转换的值得相信信息. 否则任何无法转换的值都会被静默转换为null

基本示例

1import polars as pl
2
3df = pl.DataFrame(
4    {
5        "integers": [1, 2, 3],
6        "big_integers": [10000002, 2, 30000003],
7        "floats": [4.0, 5.8, -6.3],
8    }
9)
10
11print(df)
1shape: (3, 3)
2┌──────────┬──────────────┬────────┐
3│ integers ┆ big_integers ┆ floats │
4│ ---      ┆ ---          ┆ ---    │
5│ i64      ┆ i64          ┆ f64    │
6╞══════════╪══════════════╪════════╡
7│ 1        ┆ 10000002     ┆ 4.0    │
8│ 2        ┆ 2            ┆ 5.8    │
9│ 3        ┆ 30000003     ┆ -6.3   │
10└──────────┴──────────────┴────────┘

下面代码将i64类型的数据转换为f32, 将f64转换为i32, 只需要使用.cast()方法即可, 需要注意浮点数转换为整数时, 丢失精度

1result = df.select(
2    pl.col("integers").cast(pl.Float32).alias("integers_as_floats"),
3    pl.col("floats").cast(pl.Int32).alias("floats_as_integers"),
4)
5print(result)
1shape: (3, 2)
2┌────────────────────┬────────────────────┐
3│ integers_as_floats ┆ floats_as_integers │
4│ ---                ┆ ---                │
5│ f32                ┆ i32                │
6╞════════════════════╪════════════════════╡
7│ 1.0                ┆ 4                  │
8│ 2.0                ┆ 5                  │
9│ 3.0                ┆ -6                 │
10└────────────────────┴────────────────────┘

转换为低容量类型以减少内存占用

将i64->i16, f64->f32可以减少内存占用, 又不影响数据精度

WARNING

请确保转换后的数据类型可以容纳数据, 否则Polars会报错

1print(f"类型转换之前内存占用: {df.estimated_size()} bytes")
2
3result = df.with_columns(
4    pl.col("integers").cast(pl.Int16),
5    pl.col("floats").cast(pl.Float32),
6)
7print(result)
8print(f"类型转换之后内存占用: {result.estimated_size()} bytes")
1类型转换之前内存占用: 72 bytes
2shape: (3, 3)
3┌──────────┬──────────────┬────────┐
4│ integers ┆ big_integers ┆ floats │
5│ ---      ┆ ---          ┆ ---    │
6│ i16      ┆ i64          ┆ f32    │
7╞══════════╪══════════════╪════════╡
8│ 1        ┆ 10000002     ┆ 4.0    │
9│ 2        ┆ 2            ┆ 5.8    │
10│ 3        ┆ 30000003     ┆ -6.3   │
11└──────────┴──────────────┴────────┘
12类型转换之后内存占用: 42 bytes

转换时类型不匹配报错

如果转换时数据类型不匹配, Polars会抛出异常, 比如下面的例子中big_integers列转换成i8时, i8类型显然无法容纳第一个值和第三个值

TIP

自己在使用时, 可以先试用max函数看下最大值, 或者使用try except包裹处理错误

1from polars.exceptions import InvalidOperationError
2
3try:
4    result = df.select(pl.col("big_integers").cast(pl.Int8))
5    print(result)
6except InvalidOperationError as err:
7    print(err)
1conversion from `i64` to `i8` failed in column 'big_integers' for 2 out of 3 values: [10000002, 30000003]

使用strict=False将溢出值转换为null

下面的例子中将i64转换为i8, 第一个和第三个值显然会溢出, 使用strict=False可以将溢出值转换为null

1result = df.select(pl.col("big_integers").cast(pl.Int8, strict=False))
2print(result)
1shape: (3, 1)
2┌──────────────┐
3│ big_integers │
4│ ---          │
5│ i8           │
6╞══════════════╡
7│ null         │
8│ 2            │
9│ null         │
10└──────────────┘

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

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

1df = pl.DataFrame(
2    {
3        "integers_as_strings": ["1", "2", "3"],
4        "floats_as_strings": ["4.0", "5.8", "-6.3"],
5        "floats": [4.0, 5.8, -6.3],
6    }
7)
8
9result = df.select(
10    pl.col("integers_as_strings").cast(pl.Int32),
11    pl.col("floats_as_strings").cast(pl.Float64),
12    pl.col("floats").cast(pl.String),
13)
14print(result)
1shape: (3, 3)
2┌─────────────────────┬───────────────────┬────────┐
3│ integers_as_strings ┆ floats_as_strings ┆ floats │
4│ ---                 ┆ ---               ┆ ---    │
5│ i32                 ┆ f64               ┆ str    │
6╞═════════════════════╪═══════════════════╪════════╡
7│ 1                   ┆ 4.0               ┆ 4.0    │
8│ 2                   ┆ 5.8               ┆ 5.8    │
9│ 3                   ┆ -6.3              ┆ -6.3   │
10└─────────────────────┴───────────────────┴────────┘

如果列包含非数字值或者格式错误, Polars会抛出错误并给出详细的错误信息, 可以设置strict=False绕过错误获取null值

布尔类型转换

  • 数值转换为布尔类型时, 0转换为false, 非0转换为true

  • 布尔转换为数值类型时, true转换为1, false转换为0

1df = pl.DataFrame(
2    {
3        "integers": [-1, 0, 2, 3, 4],
4        "floats": [0.0, 1.0, 2.0, 3.0, 4.1],
5        "bools": [True, False, True, False, True],
6    }
7)
8
9result = df.select(
10    pl.col("integers").cast(pl.Boolean),
11    pl.col("floats").cast(pl.Boolean),
12    pl.col("bools").cast(pl.Int8),
13)
14print(result)
1shape: (5, 3)
2┌──────────┬────────┬───────┐
3│ integers ┆ floats ┆ bools │
4│ ---      ┆ ---    ┆ ---   │
5│ bool     ┆ bool   ┆ i8    │
6╞══════════╪════════╪═══════╡
7│ true     ┆ false  ┆ 1     │
8│ false    ┆ true   ┆ 0     │
9│ true     ┆ true   ┆ 1     │
10│ true     ┆ true   ┆ 0     │
11│ true     ┆ true   ┆ 1     │
12└──────────┴────────┴───────┘

时间数据类型处理

所有时间数据类型的数据在内部都表示为: 从参考时间(纪元)到现在所经过的时间单位数, Unix纪元: 1970 年 1 月 1 日 00:00:00 UTC

  • Date: 存储自纪元以来的天数
  • Datetime: 存储自纪元以来的毫秒数(ms)
  • Time: 时间单位是纳秒(ns)

Polars允许在数字类型和事件类型数据之间进行转换

标准库datetime基本示例

1from datetime import date, datetime, time
2
3print(date(1970,1,1))
4print(datetime(1970,1,1,0,0,0))
5print(time(0,0,1))
11970-01-01
21970-01-01 00:00:00
300:00:01

时间转换为数字

1from datetime import date, datetime, time
2import polars as pl
3df = pl.DataFrame(
4    {
5        "date": [
6            date(1970, 1, 1),  # 转换之后是0, 因为是从这天开始计算的
7            date(1970, 1, 10),  # 转换之后是9, 10-1=9, 第九天
8        ],
9        "datetime": [
10            datetime(1970, 1, 1, 0, 0, 0),  # 转换之后是0
11            datetime(1970, 1, 1, 0, 1, 0),  # 纪元1分钟之后, 1分钟60秒, 一共是60_000毫秒, 60_000_000毫秒
12        ],
13        "time": [
14            time(0, 0, 0),  # time计算起点是每天的午夜零点, 转换之后是0
15            time(0, 0, 1),  # 1s之后, 一共是1_000_000_000纳秒
16        ],
17    }
18)
19print(df)
20result = df.select(
21    pl.col("date").cast(pl.Int64).alias("days_since_epoch"),
22    pl.col("datetime").cast(pl.Int64).alias("us_since_epoch"),
23    pl.col("time").cast(pl.Int64).alias("ns_since_midnight"),
24)
25print(result)
1shape: (2, 3)
2┌────────────┬─────────────────────┬──────────┐
3│ date       ┆ datetime            ┆ time     │
4│ ---        ┆ ---                 ┆ ---      │
5│ date       ┆ datetime[μs]        ┆ time     │
6╞════════════╪═════════════════════╪══════════╡
7│ 1970-01-01 ┆ 1970-01-01 00:00:00 ┆ 00:00:00 │
8│ 1970-01-10 ┆ 1970-01-01 00:01:00 ┆ 00:00:01 │
9└────────────┴─────────────────────┴──────────┘
10shape: (2, 3)
11┌──────────────────┬────────────────┬───────────────────┐
12│ days_since_epoch ┆ us_since_epoch ┆ ns_since_midnight │
13│ ---              ┆ ---            ┆ ---               │
14│ i64              ┆ i64            ┆ i64               │
15╞══════════════════╪════════════════╪═══════════════════╡
16│ 0                ┆ 0              ┆ 0                 │
17│ 9                ┆ 60000000       ┆ 1000000000        │
18└──────────────────┴────────────────┴───────────────────┘

时间类型和字符串互转

  • 时间类型转换为字符串: .str.to_string()
  • 字符串转换为时间类型: .str.to_datetime()
1df = pl.DataFrame(
2    {
3        "date": [date(2022, 1, 1), date(2022, 1, 2)],
4        "string": ["2022-01-01", "2022-01-02"],
5        "time":[time(0,0,0), time(1,0,0)]
6    }
7)
8
9result = df.select(
10    pl.col("date").dt.to_string("%Y-%m-%d"),
11    pl.col("string").str.to_datetime("%Y-%m-%d"),
12    pl.col("time").dt.to_string("%H:%M:%S")
13)
14print(result)
1shape: (2, 3)
2┌────────────┬─────────────────────┬──────────┐
3│ date       ┆ string              ┆ time     │
4│ ---        ┆ ---                 ┆ ---      │
5│ str        ┆ datetime[μs]        ┆ str      │
6╞════════════╪═════════════════════╪══════════╡
7│ 2022-01-01 ┆ 2022-01-01 00:00:00 ┆ 00:00:00 │
8│ 2022-01-02 ┆ 2022-01-02 00:00:00 ┆ 01:00:00 │
9└────────────┴─────────────────────┴──────────┘
TIP

str.to_datetime具有支持时区功能的其他选项, 更多信息可以参考官方API文档