毎回忘れる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. おわりに
これで毎回忘れても毎回思い出せるようになりました。