缺失值
本章节学习如何处理Polars中的缺失数据
null
和NaN
- 缺失数据使用
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()
是需要计算的
null
与NaN
之间的另一个区别是, 数值聚合函数在计算时会跳过缺失值, 而值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└────────────┴───────────────┴───────────┴──────────────┘