缺失值

本章节学习如何处理Polars中的缺失数据

nullNaN

  • 缺失数据使用null表示, 适用于所有的数据类型, 包括数值类型
  • NaN表示非数字的结果, 比如0/0
    • 不与任何值相等(包括自身)

    • 任何涉及NaN的运算都会返回NaN

1import polars as pl
2
3df = pl.DataFrame(
4    {
5        "value": [1, None],
6    },
7)
8print(df)
1shape: (2, 1)
2┌───────┐
3│ value │
4│ ---   │
5│ i64   │
6╞═══════╡
7│ 1     │
8│ null  │
9└───────┘
与pandas的区别

Pandas中, 用于表示缺失数据的值取决于的数据类型, 在Polars中, 始终使用null

缺失数据的元数据

这里的元数据是包含多少个缺失值等

可以通过null_count查询一列中缺少多少个值

1import polars as pl
2
3df = pl.DataFrame(
4    {
5        "value": [1, None],
6    },
7)
8null_count_df = df.null_count()
9print(null_count_df)
10print(df.get_column("value").null_count()) # 可以直接获得一个具体值: 1
1shape: (1, 1)
2┌───────┐
3│ value │
4│ ---   │
5│ u32   │
6╞═══════╡
7│ 1     │
8└───────┘
91

该函数可以在DataFrame上, DataFrame中的某一列上或直接在Series中调用, 并且这个结果是已知的, Polars会跟踪记录, 所以调用成本很低

Polars使用一种称为"有效位图"的东西来了解哪些值是缺失的. 采用位图编码, 内存效率较高. is_null通过有效性位图来报告哪些是null, 哪些不是

1is_null_series = df.select(
2    pl.col("value").is_null(),
3)
4print(is_null_series)
1shape: (2, 1)
2┌───────┐
3│ value │
4│ ---   │
5│ bool  │
6╞═══════╡
7│ false │
8│ true  │
9└───────┘

填充缺失数据

可以使用方法fill_null来填充缺失数据, 可以通过几种不同的方式来指定如何有效的填充

  • 指定填充值
  • Polars表达式, 例如另一列的值
  • 基于临近值的策略, 例如向前或向后填充
  • 插值 我们先定义一个简单的DataFrame用来演示, 其中第二列有两个缺失值
1df = pl.DataFrame(
2    {
3        "col1": [0.5, 1, 1.5, 2, 2.5],
4        "col2": [1, None, 3, None, 5],
5    },
6)
7print(df)
1shape: (5, 2)
2┌──────┬──────┐
3│ col1 ┆ col2 │
4│ ---  ┆ ---  │
5│ f64  ┆ i64  │
6╞══════╪══════╡
7│ 0.5  ┆ 1    │
8│ 1.0  ┆ null │
9│ 1.5  ┆ 3    │
10│ 2.0  ┆ null │
11│ 2.5  ┆ 5    │
12└──────┴──────┘

使用指定的值填充

使用fill_null方法, 可以使用一个值来填充所有缺失数据

1fill_literal_df = df.with_columns(
2    pl.col("col2").fill_null(3).alias("fill_col2"),
3)
4print(fill_literal_df)

新增一列用来对比, 可以看下面的结果

1shape: (5, 3)
2┌──────┬──────┬───────────┐
3│ col1 ┆ col2 ┆ fill_col2 │
4│ ---  ┆ ---  ┆ ---       │
5│ f64  ┆ i64  ┆ i64       │
6╞══════╪══════╪═══════════╡
7│ 0.5  ┆ 1    ┆ 1         │
8│ 1.0  ┆ null ┆ 3         │
9│ 1.5  ┆ 3    ┆ 3         │
10│ 2.0  ┆ null ┆ 3         │
11│ 2.5  ┆ 5    ┆ 5         │
12└──────┴──────┴───────────┘

用表达式填充

我们可以将第二列的缺失值用第一列的值得2倍来填充

1fill_expression_df = df.with_columns(
2    pl.col("col2").fill_null((2 * pl.col("col1")).cast(pl.Int64)).alias("fill_col2"),
3)
4print(fill_expression_df)
1shape: (5, 3)
2┌──────┬──────┬───────────┐
3│ col1 ┆ col2 ┆ fill_col2 │
4│ ---  ┆ ---  ┆ ---       │
5│ f64  ┆ i64  ┆ i64       │
6╞══════╪══════╪═══════════╡
7│ 0.5  ┆ 1    ┆ 1         │
8│ 1.0  ┆ null ┆ 2         │
9│ 1.5  ┆ 3    ┆ 3         │
10│ 2.0  ┆ null ┆ 4         │
11│ 2.5  ┆ 5    ┆ 5         │
12└──────┴──────┴───────────┘

根据临近值策略进行填充

可以指定填充策略, 填充策略有:

  • "forward": 填充缺失值, 使用第一个非缺失值
  • "backward": 填充缺失值, 使用最后一个非缺失值
  • "min": 填充缺失值, 使用最小值
  • "max": 填充缺失值, 使用最大值
  • "mean": 填充缺失值, 使用平均值
  • "zero": 填充缺失值, 使用0
  • "one": 填充缺失值, 使用1
1fill_forward_df = df.with_columns(
2    pl.col("col2").fill_null(strategy="forward").alias("forward"),
3    pl.col("col2").fill_null(strategy="backward").alias("backward"),
4)
5print(fill_forward_df)
1shape: (5, 4)
2┌──────┬──────┬─────────┬──────────┐
3│ col1 ┆ col2 ┆ forward ┆ backward │
4│ ---  ┆ ---  ┆ ---     ┆ ---      │
5│ f64  ┆ i64  ┆ i64     ┆ i64      │
6╞══════╪══════╪═════════╪══════════╡
7│ 0.5  ┆ 1    ┆ 1       ┆ 1        │
8│ 1.0  ┆ null ┆ 1       ┆ 3        │
9│ 1.5  ┆ 3    ┆ 3       ┆ 3        │
10│ 2.0  ┆ null ┆ 3       ┆ 5        │
11│ 2.5  ┆ 5    ┆ 5       ┆ 5        │
12└──────┴──────┴─────────┴──────────┘

插值填充

插值填充就是找一个中间值来填充, 函数具体信息可以查看文档, 默认是线性插值, 这里需要注意的是, 开头和结尾的缺失值仍然是空

1fill_interpolation_df = df.with_columns(
2    pl.col("col2").interpolate(),
3)
4print(fill_interpolation_df)
1shape: (5, 2)
2┌──────┬──────┐
3│ col1 ┆ col2 │
4│ ---  ┆ ---  │
5│ f64  ┆ f64  │
6╞══════╪══════╡
7│ 0.5  ┆ 1.0  │
8│ 1.0  ┆ 2.0  │
9│ 1.5  ┆ 3.0  │
10│ 2.0  ┆ 4.0  │
11│ 2.5  ┆ 5.0  │
12└──────┴──────┘

非数字或者NaN

浮点类型的列可能有值是NaN, 可能会与null混淆.

我们可以直接创建NaN

1import numpy as np
2
3nan_df = pl.DataFrame(
4    {
5        "value": [1.0, np.nan, float("nan"), 3.0],
6    },
7)
8print(nan_df)
1shape: (4, 1)
2┌───────┐
3│ value │
4│ ---   │
5│ f64   │
6╞═══════╡
7│ 1.0   │
8│ NaN   │
9│ NaN   │
10│ 3.0   │
11└───────┘

也可能作为计算的结果出现

1df = pl.DataFrame(
2    {
3        "dividend": [1, 0, -1],
4        "divisor": [1, 0, -1],
5    }
6)
7result = df.select(pl.col("dividend") / pl.col("divisor"))
8print(result)
1shape: (3, 1)
2┌──────────┐
3│ dividend │
4│ ---      │
5│ f64      │
6╞══════════╡
7│ 1.0      │
8│ NaN      │
9│ 1.0      │
10└──────────┘

在Polars中, NaN值被视为浮点数据类型, 不被视为缺失数据, 这意味着

  • null_count()不统计NaN
  • 可以使用fill_nan()填充, 而fill_null()不会填充

Polars不会保存与NaN值相关的元数据, 因此is_nan()是需要计算的

nullNaN之间的另一个区别是, 数值聚合函数在计算时会跳过缺失值, 而值NaN会被计入计算中, 并且通常传递到结果中. 如果需要, 可以将NaN替换为null来避免此行为

1nan_df = pl.DataFrame(
2    {
3        "value": [1.0, np.nan, float("nan"), 3.0],
4    },
5)
6mean_nan_df = nan_df.with_columns(
7    pl.col("value").fill_nan(None).alias("replaced"),
8).select(
9    pl.all().mean(),
10    pl.all().sum(),
11)
12print(mean_nan_df)
1shape: (1, 4)
2┌────────────┬───────────────┬───────────┬──────────────┐
3│ value_mean ┆ replaced_mean ┆ value_sum ┆ replaced_sum │
4│ ---        ┆ ---           ┆ ---       ┆ ---          │
5│ f64        ┆ f64           ┆ f64       ┆ f64          │
6╞════════════╪═══════════════╪═══════════╪══════════════╡
7│ NaN        ┆ 2.0           ┆ NaN       ┆ 4.0          │
8└────────────┴───────────────┴───────────┴──────────────┘