雑記 in hibernation

頭の整理と備忘録

PySparkでfillna()

毎回忘れるPySparkでの欠損処理の書き方と注意点について、個人的な備忘録です。

1. 前提

こちら相当の準備ができていることを前提にします

Google ColaboratoryでPySpark環境構築(v3.2.1) - 雑記 in hibernation


2. PySparkの欠損補完

こんな感じの適当な欠損データがあったとします。

sdf_na = (
    sdf_input
    # 適当に欠損を作る
    .withColumn("weight", fn.when(fn.col("weight")>60, fn.lit(None)).otherwise(fn.col("weight")))
    .withColumn("height", fn.when(fn.col("height")>160, fn.lit(None)).otherwise(fn.col("height")))
    .withColumn("time", fn.when(fn.col("time")>75, fn.lit(None)).otherwise(fn.col("time")))
)
sdf_na.show()
+----+-----+---+------+------+----+
|name|class|sex|weight|height|time|
+----+-----+---+------+------+----+
| aaa|    A|  F|    45|   150|null|
| bbb|    A|  M|    50|   160|null|
| ccc|    A|  F|    55|   155|  74|
| ddd|    B|  M|  null|  null|null|
| eee|    B|  F|    51|   158|  65|
| fff|    B|  M|    40|   155|  68|
| ggg|    C|  F|  null|  null|null|
| hhh|    C|  M|  null|  null|null|
|iiii|    C|  F|    52|  null|  73|
+----+-----+---+------+------+----+


一番シンプルなパターン

fillna()でまるっと欠損補完できます。

sdf_na.fillna(0).show()
+----+-----+---+------+------+----+
|name|class|sex|weight|height|time|
+----+-----+---+------+------+----+
| aaa|    A|  F|    45|   150|   0|
| bbb|    A|  M|    50|   160|   0|
| ccc|    A|  F|    55|   155|  74|
| ddd|    B|  M|     0|     0|   0|
| eee|    B|  F|    51|   158|  65|
| fff|    B|  M|    40|   155|  68|
| ggg|    C|  F|     0|     0|   0|
| hhh|    C|  M|     0|     0|   0|
|iiii|    C|  F|    52|     0|  73|
+----+-----+---+------+------+----+


カラムを指定して欠損補完したい

subsetでカラムを指定できます。便利ですね。

fill_list = ["weight", "time"]
sdf_na.fillna(0, subset=fill_list).show()
+----+-----+---+------+------+----+
|name|class|sex|weight|height|time|
+----+-----+---+------+------+----+
| aaa|    A|  F|    45|   150|   0|
| bbb|    A|  M|    50|   160|   0|
| ccc|    A|  F|    55|   155|  74|
| ddd|    B|  M|     0|  null|   0|
| eee|    B|  F|    51|   158|  65|
| fff|    B|  M|    40|   155|  68|
| ggg|    C|  F|     0|  null|   0|
| hhh|    C|  M|     0|  null|   0|
|iiii|    C|  F|    52|  null|  73|
+----+-----+---+------+------+----+


カラムごとに埋める数値を指定して欠損補完したい

辞書を渡すこと実現できます。これまた便利ですね。

fill_dict = {"weight":-1, "time":999}
sdf_na.fillna(fill_dict).show()
+----+-----+---+------+------+----+
|name|class|sex|weight|height|time|
+----+-----+---+------+------+----+
| aaa|    A|  F|    45|   150| 999|
| bbb|    A|  M|    50|   160| 999|
| ccc|    A|  F|    55|   155|  74|
| ddd|    B|  M|    -1|  null| 999|
| eee|    B|  F|    51|   158|  65|
| fff|    B|  M|    40|   155|  68|
| ggg|    C|  F|    -1|  null| 999|
| hhh|    C|  M|    -1|  null| 999|
|iiii|    C|  F|    52|  null|  73|
+----+-----+---+------+------+----+


3. たまに引っかかるパターン

先程のデータにちょこっと細工を加えた、こんなデータがありまして、、、

sdf_na_kozaiku.show()
+----+-----+---+------+------+----+
|name|class|sex|weight|height|time|
+----+-----+---+------+------+----+
| aaa|    A|  F|    45|   150|null|
| bbb|    A|  M|    50|   160|null|
| ccc|    A|  F|    55|   155|  74|
| ddd|    B|  M|  null|  null|null|
| eee|    B|  F|    51|   158|  65|
| fff|    B|  M|    40|   155|  68|
| ggg|    C|  F|  null|  null|null|
| hhh|    C|  M|  null|  null|null|
|iiii|    C|  F|    52|  null|  73|
+----+-----+---+------+------+----+


fillnaします

できてないんだが。

fill_list = ["weight", "time"]
sdf_na_kozaiku.fillna(0, subset=fill_list).show()
+----+-----+---+------+------+----+
|name|class|sex|weight|height|time|
+----+-----+---+------+------+----+
| aaa|    A|  F|    45|   150|null|
| bbb|    A|  M|    50|   160|null|
| ccc|    A|  F|    55|   155|  74|
| ddd|    B|  M|  null|  null|null|
| eee|    B|  F|    51|   158|  65|
| fff|    B|  M|    40|   155|  68|
| ggg|    C|  F|  null|  null|null|
| hhh|    C|  M|  null|  null|null|
|iiii|    C|  F|    52|  null|  73|
+----+-----+---+------+------+----+


よくみたら文字型になってました

文字型に数値の0を埋めようとしていたので、ちゃんと置換できてなかったわけですね。

sdf_na_kozaiku.printSchema()
root
 |-- name: string (nullable = true)
 |-- class: string (nullable = true)
 |-- sex: string (nullable = true)
 |-- weight: string (nullable = true)
 |-- height: string (nullable = true)
 |-- time: string (nullable = true)


前回の記事と同じオチでした。

【あるある】数値をちゃんとソートできないと思ったら文字型になってた - 雑記 in hibernation


4. おわりに

これで毎回忘れても毎回思い出せるようになりました。