iT邦幫忙

2025 iThome 鐵人賽

DAY 7
0
Software Development

Polars熊霸天下系列 第 7

[Day07] - Datatype:多種基本型別及缺失值處理

  • 分享至 

  • xImage
  •  

今天我們來認識Polars常用的基本型別及討論如何處理缺失值(pl.Null型別)。

本日大綱如下:

  1. 本日引入模組及準備工作
  2. pl.Intpl.UInt
  3. pl.Float
  4. pl.Bool
  5. 缺失值處理:pl.Null
  6. NaNpl.Null的注意事項
  7. codepanda

0. 本日引入模組及準備工作

import numpy as np
import pandas as pd
import polars as pl


data = {
    "int64": [0, 1, 2, 3, 500],
    "float64": [6.1, 7.2, 8.3, 9.4, 10.5],
    "bool": [True, False, False, True, True],
    "null": [5, 10, None, None, 30],
    "nan": [11.1, np.nan, float("nan"), 33.3, 22.2],
}
df = pl.DataFrame(data)
shape: (5, 5)
┌───────┬─────────┬───────┬──────┬──────┐
│ int64 ┆ float64 ┆ bool  ┆ null ┆ nan  │
│ ---   ┆ ---     ┆ ---   ┆ ---  ┆ ---  │
│ i64   ┆ f64     ┆ bool  ┆ i64  ┆ f64  │
╞═══════╪═════════╪═══════╪══════╪══════╡
│ 0     ┆ 6.1     ┆ true  ┆ 5    ┆ 11.1 │
│ 1     ┆ 7.2     ┆ false ┆ 10   ┆ NaN  │
│ 2     ┆ 8.3     ┆ false ┆ null ┆ NaN  │
│ 3     ┆ 9.4     ┆ true  ┆ null ┆ 33.3 │
│ 500   ┆ 10.5    ┆ true  ┆ 30   ┆ 22.2 │
└───────┴─────────┴───────┴──────┴──────┘

觀察df可以得知,在沒有指定schema時,Polars預設會將整數指定為pl.Int64型別,而將浮點數指定為pl.Float64型別。

1. pl.Intpl.UInt

整數分為帶有正負符號的pl.Int及不帶正負符號的pl.UInt。兩者皆有多種位元的型別可以使用,例如預設的pl.Int64型別,代表使用64位元且帶有正負符號的整數型別。

我們可以使用pl.Expr.cast()轉換型別,例如將「"int64"」列轉換為pl.Int32pl.UInt32型別。

(
    df.select(
        "int64",
        pl.col("int64").cast(pl.Int32).alias("Int32"),
        pl.col("int64").cast(pl.UInt32).alias("UInt32"),
    )
)
shape: (5, 3)
┌───────┬───────┬────────┐
│ int64 ┆ Int32 ┆ UInt32 │
│ ---   ┆ ---   ┆ ---    │
│ i64   ┆ i32   ┆ u32    │
╞═══════╪═══════╪════════╡
│ 0     ┆ 0     ┆ 0      │
│ 1     ┆ 1     ┆ 1      │
│ 2     ┆ 2     ┆ 2      │
│ 3     ┆ 3     ┆ 3      │
│ 500   ┆ 500   ┆ 500    │
└───────┴───────┴────────┘

這邊需注意,型別轉換是有可能會失敗的,例如如果我們將「"int64"」列轉換為pl.Int8即會報錯,因為其中有一行的數值為500,超出了pl.Int8的範圍(-128 ~ 127)。

❌
df.select("int64", pl.col("int64").cast(pl.Int8).alias("Int8"))
# InvalidOperationError: conversion from `i64` to `i8` failed 
# in column 'int64' for 1 out of 5 values: [500]

除此之外,我們也可以將「"int64"」列轉換為pl.Date型別:

df.select("int64", pl.col("int64").cast(pl.Date).alias("date"))
shape: (5, 2)
┌───────┬────────────┐
│ int64 ┆ date       │
│ ---   ┆ ---        │
│ i64   ┆ date       │
╞═══════╪════════════╡
│ 0     ┆ 1970-01-01 │
│ 1     ┆ 1970-01-02 │
│ 2     ┆ 1970-01-03 │
│ 3     ┆ 1970-01-04 │
│ 500   ┆ 1971-05-16 │
└───────┴────────────┘

這是因為Polars以pl.Int32型別,儲存自UNIX epoch(即UTC1970年1月1日0時0分0秒)以來的天數。關於時間相關的型別,我們將於[Day13]說明。

2. pl.Float

我們可以將「"float64"」列轉換為pl.Float32型別,但一樣要注意數值不能超過其範圍:

(
    df.select(
        "float64",
        pl.col("float64").cast(pl.Float32).alias("float32"),
    )
)
shape: (5, 2)
┌─────────┬─────────┐
│ float64 ┆ float32 │
│ ---     ┆ ---     │
│ f64     ┆ f32     │
╞═════════╪═════════╡
│ 6.1     ┆ 6.1     │
│ 7.2     ┆ 7.2     │
│ 8.3     ┆ 8.3     │
│ 9.4     ┆ 9.4     │
│ 10.5    ┆ 10.5    │
└─────────┴─────────┘

在Polars有一種特別的NaN(Not a Number),被歸類於pl.Float型別中。其可以由使用者生成,例如np.nanfloat("nan"),又或者於計算中產生:

(
    df.select(
        "int64",
        pl.col("int64").truediv(pl.col("int64")).alias("int64 / int64"),
    )
)
shape: (5, 2)
┌───────┬───────────────┐
│ int64 ┆ int64 / int64 │
│ ---   ┆ ---           │
│ i64   ┆ f64           │
╞═══════╪═══════════════╡
│ 0     ┆ NaN           │
│ 1     ┆ 1.0           │
│ 2     ┆ 1.0           │
│ 3     ┆ 1.0           │
│ 500   ┆ 1.0           │
└───────┴───────────────┘

需要注意的是NaN屬於pl.Float型別,而不是pl.Null型別(缺失值型別)。

Polars提供有pl.Expr.is_nan()pl.Expr.fill_nan()兩個expr,幫助使用者操作。舉例來說,我們可以:

  • 使用pl.Expr.is_nan()判斷「"nan"」列各行是否為NaN
  • 使用pl.Expr.fill_nan()NaN值取代為10。
  • 使用pl.Expr.fill_nan()NaN值取代為「"nan"」列乘以100。
(
    df.select(
        "nan",
        pl.col("nan").is_nan().alias("is_nan"),
        pl.col("nan").fill_nan(10).alias("fill_10"),
        pl.col("nan")
        .fill_nan(pl.col("int64").mul(100))
        .alias("fill_expr"),
    )
)
shape: (5, 4)
┌──────┬────────┬─────────┬───────────┐
│ nan  ┆ is_nan ┆ fill_10 ┆ fill_expr │
│ ---  ┆ ---    ┆ ---     ┆ ---       │
│ f64  ┆ bool   ┆ f64     ┆ f64       │
╞══════╪════════╪═════════╪═══════════╡
│ 11.1 ┆ false  ┆ 11.1    ┆ 11.1      │
│ NaN  ┆ true   ┆ 10.0    ┆ 100.0     │
│ NaN  ┆ true   ┆ 10.0    ┆ 200.0     │
│ 33.3 ┆ false  ┆ 33.3    ┆ 33.3      │
│ 22.2 ┆ false  ┆ 22.2    ┆ 22.2      │
└──────┴────────┴─────────┴───────────┘

3. pl.Bool

pl.bool只有TrueFalse兩種元素。以下列舉幾個常用的操作:

  • 使用pl.Expr.not_()TrueFalse互換。
  • pl.bool轉換為整數型別,即False會變成0,而True會變成1。
  • 進行加總運算。
(
    df.select(
        "bool",
        pl.col("bool").not_().alias("~bool"),
        pl.col("bool").cast(pl.UInt8).alias("bool_to_uint8"),
        pl.col("bool").sum().alias("sum"),
    )
)
shape: (5, 4)
┌───────┬───────┬───────────────┬─────┐
│ bool  ┆ ~bool ┆ bool_to_uint8 ┆ sum │
│ ---   ┆ ---   ┆ ---           ┆ --- │
│ bool  ┆ bool  ┆ u8            ┆ u32 │
╞═══════╪═══════╪═══════════════╪═════╡
│ true  ┆ false ┆ 1             ┆ 3   │
│ false ┆ true  ┆ 0             ┆ 3   │
│ false ┆ true  ┆ 0             ┆ 3   │
│ true  ┆ false ┆ 1             ┆ 3   │
│ true  ┆ false ┆ 1             ┆ 3   │
└───────┴───────┴───────────────┴─────┘

4. 缺失值處理:pl.Null

pl.Null一般是由Python的None而來。與NaN類似,Polars也提供了pl.Expr.is_null()pl.Expr.fill_null()兩種expr。以下列舉幾個常見用來填補缺失值的方法:

  • 使用pl.Expr.fill_null()填補數字10。
  • 使用pl.Expr.fill_null(),以「"float64"」列填補缺失值。
  • 使用pl.Expr.fill_null(),以strategy="forward"填補缺失值。
  • 使用pl.Expr.fill_null(),以strategy="backward"填補缺失值。
  • 使用pl.Expr.interpolate()填補缺失值。
(
    df.select(
        pl.col("null", "float64"),
        pl.col("null").fill_null(10).alias("fill_10"),
        pl.col("null").fill_null(pl.col("float64")).alias("from_float64"),
        pl.col("null").fill_null(strategy="forward").alias("forward"),
        pl.col("null").fill_null(strategy="backward").alias("backward"),
        pl.col("null").interpolate().alias("interpolate"),
    )
)
shape: (5, 7)
┌──────┬─────────┬─────────┬──────────────┬─────────┬──────────┬─────────────┐
│ null ┆ float64 ┆ fill_10 ┆ from_float64 ┆ forward ┆ backward ┆ interpolate │
│ ---  ┆ ---     ┆ ---     ┆ ---          ┆ ---     ┆ ---      ┆ ---         │
│ i64  ┆ f64     ┆ i64     ┆ f64          ┆ i64     ┆ i64      ┆ f64         │
╞══════╪═════════╪═════════╪══════════════╪═════════╪══════════╪═════════════╡
│ 5    ┆ 6.1     ┆ 5       ┆ 5.0          ┆ 5       ┆ 5        ┆ 5.0         │
│ 10   ┆ 7.2     ┆ 10      ┆ 10.0         ┆ 10      ┆ 10       ┆ 10.0        │
│ null ┆ 8.3     ┆ 10      ┆ 8.3          ┆ 10      ┆ 30       ┆ 16.666667   │
│ null ┆ 9.4     ┆ 10      ┆ 9.4          ┆ 10      ┆ 30       ┆ 23.333333   │
│ 30   ┆ 10.5    ┆ 30      ┆ 30.0         ┆ 30      ┆ 30       ┆ 30.0        │
└──────┴─────────┴─────────┴──────────────┴─────────┴──────────┴─────────────┘

5. NaNpl.Null的注意事項

聚合計算

在進行聚合運算,如求平均值時,pl.Null是會被排除在計算之外,而NaN則會進行傳遞。例如,對「"null"」列求取平均值時,僅會使用「5」、「10」及「30」三個元素,所以會得到45/3=15。而對「"nan"」列求取平均值時,由於其中存有NaN,所以平均值仍為NaN

df.select(pl.col("null", "nan").mean())
shape: (1, 2)
┌──────┬─────┐
│ null ┆ nan │
│ ---  ┆ --- │
│ f64  ┆ f64 │
╞══════╪═════╡
│ 15.0 ┆ NaN │
└──────┴─────┘

metadata

Polars內部使用一種名叫「"validity bitmap"」的技術,來儲存缺失值的資訊。雖然會耗費一點點儲存空間,可是大幅提升了判斷元素是否為缺失值及計算缺失值數量的效率。也就是說當使用pl.Expr.is_null()或是pl.Expr.null_count()時,與其說是計算,Polars更像是在針對metadata進行查找並返回結果。

但是Polars並沒有針對NaN儲存資訊,也就是說在使用pl.Expr.is_nan()時,Polars會真正進行計算,不如pl.Expr.is_null()般快速。

6. codepanda

Pandas經過多年開發後,其型別系統可以用眼花撩亂來形容。大致上可以分為v0時代的numpy型別、v1時代引入的nullable型別及v2時代引入的pyarrow型別。

以64bit整數為例,可以有下面三種表達方式:

  • v0時代,為int64
  • v1時代,為Int64
  • v2時代,為int64[pyarrow]
df_pd = pd.DataFrame({"v0_int64": [0]}, dtype="int64").assign(
    v1_Int64=lambda df_: df_.v0_int64.astype({"v0_int64": "Int64"}),
    v2_int64pyarrow=lambda df_: df_.v0_int64.astype(
        {"v0_int64": "int64[pyarrow]"}
    ),
)

print(df_pd.dtypes)
v0_int64                    int64
v1_Int64                    Int64
v2_int64pyarrow    int64[pyarrow]
dtype: object

我個人會建議如果是以v2在做開發的朋友,可以試試更有效率的int64[pyarrow]型別。

Code

本日程式碼傳送門


上一篇
[Day06] - pl.Expr與selectors
下一篇
[Day08] - Datatype:String
系列文
Polars熊霸天下8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言