Skip to content

欠損データ(Missing data)

このページでは、Polars における欠損データの表現方法と、欠損データの補完方法について説明します。

nullNaN

DataFrame(または同等の Series)の各カラムは、Arrow 配列または Arrow 配列の集合です(Apache Arrow 形式に基づいています)。欠損データは、Arrow および Polars で null 値として表されます。この null 値は、数値を含むすべてのデータタイプに適用されます。

Polars は、浮動小数点カラムに対して NotaNumber または NaN 値を許容しています。これらの NaN 値は、欠損データではなく浮動小数点データの一種と見なされます。NaN 値については後述します。

Python の None 値を使用して、手動で欠損値を定義することができます:

DataFrame

df = pl.DataFrame(
    {
        "value": [1, None],
    },
)
print(df)

DataFrame

let df = df! (
    "value" => &[Some(1), None],
)?;

println!("{}", &df);

shape: (2, 1)
┌───────┐
│ value │
│ ---   │
│ i64   │
╞═══════╡
│ 1     │
│ null  │
└───────┘

Info

pandas では、カラムの dtype によって欠損データの値が異なります。Polars では、欠損データは常に null 値として表されます。

欠損データのメタデータ

Polars で使用される各 Arrow 配列は、欠損データに関連する二種類のメタデータを格納しています。このメタデータにより、Polars は欠損値の数とどの値が欠損しているかを迅速に示すことができます。

最初のメタデータは null_count で、これはカラム内の null 値を持つ行の数です:

null_count

null_count_df = df.null_count()
print(null_count_df)

null_count

let null_count_df = df.null_count();
println!("{}", &null_count_df);

shape: (1, 1)
┌───────┐
│ value │
│ ---   │
│ u32   │
╞═══════╡
│ 1     │
└───────┘

null_count メソッドは DataFrameDataFrame のカラム、または Series に対して呼び出すことができます。null_count メソッドは、基本的な Arrow 配列で null_count がすでに計算されているため、低コストの操作です。

もう一つのメタデータは、各データ値が有効か欠損かを示す validity bitmap と呼ばれる配列です。 validity bitmap はメモリ効率が良いです。なぜなら、ビットエンコードされているからです(各値は 0 または 1)。このビットエンコードにより、配列ごとのメモリオーバーヘッドは(配列の長さ / 8)バイトのみです。validity bitmap は Polars の is_null メソッドで使用されます。

DataFrame または Series のカラムに対する validity bitmap を基に Series を返すことが、is_null メソッドで可能です:

is_null

is_null_series = df.select(
    pl.col("value").is_null(),
)
print(is_null_series)

is_null

let is_null_series = df
    .clone()
    .lazy()
    .select([col("value").is_null()])
    .collect()?;
println!("{}", &is_null_series);

shape: (2, 1)
┌───────┐
│ value │
│ ---   │
│ bool  │
╞═══════╡
│ false │
│ true  │
└───────┘

is_null メソッドは、null 値を完全にスキャンする必要がないため、低コストの操作です。これは、validity bitmap がすでに存在し、Boolean 配列として返されるためです。

欠損データの補完

Series の欠損データは、fill_null メソッドで補完することができます。欠損データをどのように補完するかを指定する必要があります。これを行う主な方法は次のとおりです:

  • リテラル(0 や "0" など)で補完
  • 戦略(前方に補完するなど)で補完
  • 別のカラムからの値で置換するなどのエクスプレッションで補完
  • 補間

欠損値がある col2 を持つシンプルな DataFrame を定義して、null を補完する方法を示します:

DataFrame

df = pl.DataFrame(
    {
        "col1": [1, 2, 3],
        "col2": [1, None, 3],
    },
)
print(df)

DataFrame

let df = df!(
        "col1" => &[Some(1), Some(2), Some(3)],
        "col2" => &[Some(1), None, Some(3)],

)?;
println!("{}", &df);

shape: (3, 2)
┌──────┬──────┐
│ col1 ┆ col2 │
│ ---  ┆ ---  │
│ i64  ┆ i64  │
╞══════╪══════╡
│ 1    ┆ 1    │
│ 2    ┆ null │
│ 3    ┆ 3    │
└──────┴──────┘

指定されたリテラル値で補完

指定されたリテラル値で欠損データを補完することができます。例えば pl.lit を使います:

fill_null

fill_literal_df = df.with_columns(
    pl.col("col2").fill_null(pl.lit(2)),
)
print(fill_literal_df)

fill_null

let fill_literal_df = df
    .clone()
    .lazy()
    .with_columns([col("col2").fill_null(lit(2))])
    .collect()?;
println!("{}", &fill_literal_df);

shape: (3, 2)
┌──────┬──────┐
│ col1 ┆ col2 │
│ ---  ┆ ---  │
│ i64  ┆ i64  │
╞══════╪══════╡
│ 1    ┆ 1    │
│ 2    ┆ 2    │
│ 3    ┆ 3    │
└──────┴──────┘

戦略で補完

欠損データを戦略で補完することができます。例えば、前方に補完する戦略です:

fill_null

fill_forward_df = df.with_columns(
    pl.col("col2").fill_null(strategy="forward"),
)
print(fill_forward_df)

fill_null

let fill_forward_df = df
    .clone()
    .lazy()
    .with_columns([col("col2").forward_fill(None)])
    .collect()?;
println!("{}", &fill_forward_df);

shape: (3, 2)
┌──────┬──────┐
│ col1 ┆ col2 │
│ ---  ┆ ---  │
│ i64  ┆ i64  │
╞══════╪══════╡
│ 1    ┆ 1    │
│ 2    ┆ 1    │
│ 3    ┆ 3    │
└──────┴──────┘

API ドキュメントで他の補完戦略を見つけることができます。

エクスプレッションで補完

より柔軟性を持って欠損データを補完することができます。 例えば、そのカラムの中央値で null を補完します:

fill_null

fill_median_df = df.with_columns(
    pl.col("col2").fill_null(pl.median("col2")),
)
print(fill_median_df)

fill_null

let fill_median_df = df
    .clone()
    .lazy()
    .with_columns([col("col2").fill_null(median("col2"))])
    .collect()?;
println!("{}", &fill_median_df);

shape: (3, 2)
┌──────┬──────┐
│ col1 ┆ col2 │
│ ---  ┆ ---  │
│ i64  ┆ f64  │
╞══════╪══════╡
│ 1    ┆ 1.0  │
│ 2    ┆ 2.0  │
│ 3    ┆ 3.0  │
└──────┴──────┘

この場合、中央値が浮動小数点統計であるため、カラムは整数から浮動小数点にキャストされます。

補間で補完

さらに、補間を使用して(fill_null 関数を使用せずに)null を補完することもできます:

interpolate

fill_interpolation_df = df.with_columns(
    pl.col("col2").interpolate(),
)
print(fill_interpolation_df)

interpolate

let fill_interpolation_df = df
    .clone()
    .lazy()
    .with_columns([col("col2").interpolate(InterpolationMethod::Linear)])
    .collect()?;
println!("{}", &fill_interpolation_df);

shape: (3, 2)
┌──────┬──────┐
│ col1 ┆ col2 │
│ ---  ┆ ---  │
│ i64  ┆ f64  │
╞══════╪══════╡
│ 1    ┆ 1.0  │
│ 2    ┆ 2.0  │
│ 3    ┆ 3.0  │
└──────┴──────┘

NotaNumber または NaN

Series の欠損データには null 値があります。しかし、浮動小数点データ型のカラムでは NotaNumber または NaN 値を使用することができます。これらの NaN 値は、Numpy の np.nan またはネイティブ Python の float('nan') から作成することができます:

DataFrame

nan_df = pl.DataFrame(
    {
        "value": [1.0, np.nan, float("nan"), 3.0],
    },
)
print(nan_df)

DataFrame

let nan_df = df!(
        "value" => [1.0, f64::NAN, f64::NAN, 3.0],
)?;
println!("{}", &nan_df);

shape: (4, 1)
┌───────┐
│ value │
│ ---   │
│ f64   │
╞═══════╡
│ 1.0   │
│ NaN   │
│ NaN   │
│ 3.0   │
└───────┘

Info

pandas では、整数カラムの NaN 値がデフォルトでカラムを浮動小数点にキャストします。これは Polars では起こらず、代わりに例外が発生します。

NaN 値は浮動小数点データの一種と見なされ、Polars では欠損データとは見なされません。つまり:

  • NaN 値は null_count メソッドでカウントされません
  • NaN 値は fill_nan メソッドで補完されますが、fill_null メソッドでは補完されません

Polars には is_nanfill_nan のメソッドがあり、is_nullfill_null のメソッドと同様に動作します。NaN 値には事前計算された validity bitmap がないため、is_nan メソッド用にこれを計算する必要があります。

nullNaN 値のもう一つの違いは、null 値を含むカラムの平均を取る場合、null 値は計算から除外されますが、NaN 値を含む場合、平均を取ると NaN になります。この挙動は、NaN 値を null 値に置き換えることで回避することができます:

fill_nan

mean_nan_df = nan_df.with_columns(
    pl.col("value").fill_nan(None).alias("value"),
).mean()
print(mean_nan_df)

fill_nan

let mean_nan_df = nan_df
    .clone()
    .lazy()
    .with_columns([col("value").fill_nan(lit(NULL)).alias("value")])
    .mean()
    .collect()?;
println!("{}", &mean_nan_df);

shape: (1, 1)
┌───────┐
│ value │
│ ---   │
│ f64   │
╞═══════╡
│ 2.0   │
└───────┘