Azure Synapse - Data Flow で pandas の ffill や bfill 的な欠損値埋めをする方法

# はじめに

Azure Data Factory や Azure Synapse Pipeline の Data Flow (GUIとノーコードでデータ加工できるツール）を使ってデータ集計や予測モデル用のデータ加工をするときのテクニックを紹介しています

Azureのサービス使ってデータ分析をする際に、予測モデルの構築や運用は Azure Machine Learning を使いますが、
モデル用のデータセットをゴリゴリデータ加工する際は、Azure Synapse Pipeline 側に処理を寄せることをお勧めしております

既存で Python でモデル用のデータセット作成コードを実装していたけど、Azure への移行を機に Azure Synapse Pipeline の Data Flow で実装再現するクライアントもたくさん支援してきました
たいていの処理は、Azure Synapse Pipeline の Data Flowでシンプルに実装できるのですが、今まで唯一全く同じ処理が再現できなかったものがあるので、その内容と代替方法を紹介します

それは、<b>pandasにおけるffill（forward fill）や bfill（backward fill）で欠損値(null)を前後の値で埋める処理</b>です
これに該当する関数は、Azure Data Factory や Azure Synapse Pipeline の Data Flow にはありません
もっと言うと、Data Flow の裏側は Spark で処理されていまして、pyspark（Spark SQL）のコード実装にもありません

厳密には全く同じ処理ではないですが、限りなく近い処理は実装できますので、その方法を紹介します

# やりたいこと

顧客の架電オプトアウト情報を月ごとにスナップしているデータがあって、
欠損の場合は前後の月の情報で埋めたいという例で説明します

以下のロジックで欠損値埋めしたいとします
①「電話可否」が欠損値(null)でなければ、その値を採用
②「電話可否」が欠損値(null)の場合、ffill 補完値 がnullでなければ、ffill 補完値を採用
③②の補完結果が欠損値(null)の場合、 bfill 補完値 がnullでなければ、bfill 補完値を採用
④③の補完結果が欠損値(null)の場合、"制限なし"を代入する

Python のpandas の ffill（forward fill）や bfill（backward fill）だとこんな感じで実装できますね
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/d2640cc9-baab-0e62-52af-c2e40b4d98cf.png)

<b>＜データイメージ＞</b>
意図した欠損補完が実装できるか確認するためにいくつかのパターンのテストデータで確認します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/cdfbc92a-4282-8d41-9745-c396bfd13399.png)


# Data Flow で実装再現方法

Window モジュールと 派生列 モジュールを組み合わせて実装します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/b6dba214-3769-2f20-9e03-012c336d5b0a.png)

## １．Window モジュールで、ffill 補完値列を作成する

https://learn.microsoft.com/ja-jp/azure/data-factory/data-flow-window

① Window モジュールを繋げて、[1. Over] タブで「顧客CD」を選択
※本ケースでは、顧客毎に前後の年月の値で欠損補完を行いたいため
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/3022b5b8-35d3-2838-f632-706b794eff29.png)

② [2. 並べ替え] タブで、「年月」の「降順」を設定
※ffill の再現のため、最近の年月の値で欠損補完を行いたいため
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/a2a2fdd1-92f8-4e0d-4841-ca94e16de1d6.png)

③ [3. 範囲] タブで、「無制限」にチェック
※本ケースでは、「年月」が文字型のため、列値による範囲指定はできない
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/3e6296e1-6d72-41f7-54a6-968dd9b0d009.png)

④ [4. ウィンドwの列] タブで、以下関数式で欠損補完を設定する
<b>first({電話可否}, true())</b>
※列の最初＝年月の最降順の値が欠損でなければその値で補完するというロジック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/c19150e9-0f16-d510-9180-8e30f792c0a2.png)


※集計関数及び式関数は、リファレンスページを参照して引数等の使い方を確認しながら設定しましょう

https://learn.microsoft.com/ja-jp/azure/data-factory/data-flow-expressions-usage#first

## ２．Windowモジュールで、bfill 補完値列を作成する
もうひとつ Window モジュールを繋げて、
１と同じ要領で「年月」を「昇順」に並べて、bfillを再現する
※ [2. 並べ替え] 以外は、１と全く同じ内容を設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/178d8f46-4dd1-ca5c-a389-aac443159416.png)

## ３．派生列モジュールで、欠損埋めの優先順位ロジックを実装し、欠損埋め完成
① 派生列モジュールを繋げて、以下の式関数列を作成する
<b>iif(isNull({電話可否}),
    coalesce({電話可否_ffill},{電話可否_bfill},'制限なし'),
    {電話可否})</b>
※「電話可否」が欠損の場合、ffill, bfillの順で欠損でない値で補完し、それでも欠損の場合は、'制限なし'の値で補完するというロジック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/986771c3-c2a6-1588-d1b5-d7d4221e848a.png)

結果は、このように上述のデータイメージと同じ欠損補完が再現できました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482258/25f3c285-b855-d968-7820-c058afb15897.png)

# さいごに
本ケースでは、Python でたった 3行 で処理できることが、Synapse Data Flow だと 3モジュールも使って面倒くさいと思うかもしれませんが、他の多くの処理では Data Flow で1モジュールで一気に処理できてシンプルかつ結果も確認しやすいですよ