ビジネスアナリスト・データエンジニア・データサイエンティストがDatabricksでCOVID-19データ分析に取り組んだ1日

こちらのマニュアルを翻訳しているときに以下の一文が目に入りました。

https://qiita.com/taka_yayoi/items/883b29d4808874427464

> Delta Lakeを用いてデータを格納することで、データが処理されるとすぐに皆様のコアのETLワークロードをサポートし、後段のデータサイエンティスト、アナリスト、機械学習エンジニアは同一のプロダクションデータを活用できるようになります。

自分でもよく話している内容ではありますが、これって具体的にどういうことなんだろうか？と今更ながらと自分の中でモヤモヤした訳です。

と言うことで、仮想的なシナリオに沿って上の話を再現してみました。

# ある企業におけるCOVID-19分析

[データからわかる－新型コロナウイルス感染症情報－](https://covid19.mhlw.go.jp/?lang=ja)にあるようなダッシュボードを作ってみたいというシナリオからスタートします。

**登場人物**
- **偉い人**: そのままです。
- **DE君**: データエンジニア
- **BA君**: ビジネスアナリスト
- **DS君**: データサイエンティスト

**ある日のオフィス**
- **偉い人**: やあ、BA君。
- **BA君**: はい。
- **偉い人**: コロナなかなか落ち着かないね。
- **BA君**: そうですね。
- **偉い人**: ニュースで感染者数はわかるけど、これまでどのようなトレンドだったのかを知りたいのだけど、ダッシュボードをチャチャっと作ってくれない？
- **BA君**: えっ。[データからわかる－新型コロナウイルス感染症情報－](https://covid19.mhlw.go.jp/?lang=ja)を見ればいいじゃないですか？
- **偉い人**: いやいや、これだと足りない観点があるから独自に作ってほしいんだ。
- **BA君**: はあ。
- **偉い人**: じゃ、頼んだよ！
- **BA君**: 参ったな。データをどうにかしないと。DE君に相談しよう。

# (データエンジニア)DE君の取り組み

- BA君からの相談を受けたDE君は作業に取り掛かります。
- ダッシュボードを作るにはまずデータが必要です。
- [オープンデータ｜厚生労働省](https://www.mhlw.go.jp/stf/covid-19/open-data.html)で公開されているデータを使用します。
    - [新規陽性者数の推移（日別）](https://covid19.mhlw.go.jp/public/opendata/newly_confirmed_cases_daily.csv)
    
いわゆるETL(Extract-Transform-Load)処理を行なっていきます。最初はデータの抽出(Extract)からです。

## データの抽出(Extract)

[Databricksノートブック](https://qiita.com/taka_yayoi/items/c306161906d6d34e8bd5)を使ってETLロジックを記述していきます。

```py:Python
import pandas as pd

# pandasを使ってURLから直接データを取得します
pdf = pd.read_csv("https://covid19.mhlw.go.jp/public/opendata/newly_confirmed_cases_daily.csv")
# データを表示します
display(pdf)
```

:::note info
**注意**
上のコマンドは外部サイトにアクセスしますので、アクセス頻度に関しては注意してください。
:::

![Screen Shot 2022-08-31 at 16.46.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/d85556b2-4450-a36d-62b5-4ba10d9300ec.png)

## データの変換(Transform)

### データ構造の変換

このデータは、そのままでも都道府県ごとの日々の感染者数の推移を確認することはできます。しかし、このデータは各列が都道府県名となっている**横持ちのデータフレーム(Wide dataframe)** となっています。特定の県のデータを取り出すためには、列名を指定しなくてはならず、where句を使うSQLとの相性は良いとはいえません。

そこで、ここではいろいろな切り口で分析をしやすくなるように、**縦持ちのデータフレーム(Long dataframe)** に変換します。

![](https://www.salesanalytics.co.jp/wp-content/uploads/2021/04/795316b92fc766b0181f6fef074f03fa-5.jpg)

上の図は以下の記事から引用させていただきました。

https://www.salesanalytics.co.jp/datascience/datascience021/#melt

```py:Python
# 縦長に変換
# id_vars: Dateをidとして、変換後のデータフレームにそのまま残します
# var_name: variable変数の列名をPrefectureにします
# value_name: value_name変数はCaseとします
long_pdf = pdf.melt(id_vars=["Date"], var_name="Prefecture", value_name="Cases")
display(long_pdf)
```
![Screen Shot 2022-08-31 at 16.47.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/36121407-ca8c-1af4-37e8-6aa66b913fba.png)

### データ型の確認

次にデータ型を確認します。

[pandasのデータ型dtype一覧とastypeによる変換（キャスト） \| note\.nkmk\.me](https://note.nkmk.me/python-pandas-dtype-astype/)

```py:Python
long_pdf.dtypes
```
![Screen Shot 2022-08-31 at 16.48.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/d51f63d9-ccc4-5998-eb90-6d72f485d7b5.png)

### フレームワークの選択、データ型の変換、フィルタリング

ここでは、データエンジニアの立場に立って以下の検討を行うものとします。

- pandasでは豊富なメソッドが提供されていますが、大量データの取り扱いには不得手です。このため、ここからは大量データを容易に取り扱うことができるSparkデータフレームで操作を行なっていきます。
- `object`はオブジェクト型であることを示しています。日付を示す`Date`がオブジェクト型だと後程の処理で都合が悪くなるのでタイムスタンプ型に変換します。
- `Prefecture`が`ALL`は全国の感染者数を示しています。全都道府県のデータの合計を取れば全国分は計算できるので除外します。
- 全期間のデータは不要なので2022年以降のデータに限定します。

このように、データエンジニアリングの過程では、後段での処理で求められる要件を踏まえてデータを加工していきます。これと並行して、性能要件も考慮してどのようなアプローチを取ったらいいのかも検討していきます。

```py:Python
# pandasデータフレームをSparkデータフレームに変換します
sdf = spark.createDataFrame(long_pdf)
```

```py:Python
from pyspark.sql.functions import *

# PythonのSpark API(pyspark)を用いて、データ型の変換を行います
# 変換後に元のDate列は削除します
# Prefecture ALLを除外します
# 2022年以降のデータに限定します
df = (sdf
       .withColumn("date_timestamp", to_timestamp(col("Date"), "yyyy/M/d"))
       .drop("Date")
       .filter("Prefecture != 'ALL' AND date_timestamp >= '2022-1-1'")
     )

display(df)
```
![Screen Shot 2022-08-31 at 16.49.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/a15ceda4-2952-958c-ff43-c8fa53168b7f.png)

### データの拡張

これで変換処理は概ね完了しましたが、都道府県がどの地方に属しているのかが分かった方が分析の幅が広がるので、地方名などでデータを拡張します。

以下のサイトのデータをお借りして、補強するデータを準備します。

https://xenonhyx.com/excel-tihoumei/

以下のCSVファイルを準備します。

```
pref_no,Prefecture,Area
1,Hokkaido,北海道地方
2,Aomori,東北地方
3,Iwate,東北地方
4,Miyagi,東北地方
5,Akita,東北地方
6,Yamagata,東北地方
7,Fukushima,東北地方
8,Ibaraki,関東地方
9,Tochigi,関東地方
10,Gunma,関東地方
11,Saitama,関東地方
12,Chiba,関東地方
13,Tokyo,関東地方
14,Kanagawa,関東地方
15,Niigata,中部地方
16,Toyama,中部地方
17,Ishikawa,中部地方
18,Fukui,中部地方
19,Yamanashi,中部地方
20,Nagano,中部地方
21,Gifu,中部地方
22,Shizuoka,中部地方
23,Aichi,中部地方
24,Mie,関西地方
25,Shiga,関西地方
26,Kyoto,関西地方
27,Osaka,関西地方
28,Hyogo,関西地方
29,Nara,関西地方
30,Wakayama,関西地方
31,Tottori,中国地方
32,Shimane,中国地方
33,Okayama,中国地方
34,Hiroshima,中国地方
35,Yamaguchi,中国地方
36,Tokushima,四国地方
37,Kagawa,四国地方
38,Ehime,四国地方
39,Kochi,四国地方
40,Fukuoka,九州地方
41,Saga,九州地方
42,Nagasaki,九州地方
43,Kumamoto,九州地方
44,Oita,九州地方
45,Miyazaki,九州地方
46,Kagoshima,九州地方
47,Okinawa,沖縄地方
```

上のCSVを[DBFSにアップロード](https://docs.databricks.com/dbfs/file-browser.html)します。

```py:Python
# 拡張用のデータを読み込みます
augment_df = spark.read.csv("dbfs:/FileStore/shared_uploads/takaaki.yayoi@databricks.com/pref_augment.csv", header=True)
display(augment_df)
```
![Screen Shot 2022-08-31 at 16.51.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/4a012e6d-911b-fd52-6abe-96a31945e0bc.png)

元のデータと拡張用のデータを結合(join)します。

```py:Python
# 結合キーはPrefecture、内部結合(inner join)します
final_df = df.join(augment_df, on="Prefecture", how="inner")
display(final_df)
```
![Screen Shot 2022-08-31 at 16.52.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/cb7d6f2d-cf3f-aa5e-5abf-7c7dd8d528dd.png)

## データのロード(Load)

これでデータの準備が整いました。ETL(Extract-Transform-Load)で言うところのTransformまでが終わった形になります。最後にBIなどでこのデータを活用できるようにデータベースにロードします。

```py:Python
import re
from pyspark.sql.types import * 

# ログインIDからUsernameを取得
username_raw = dbutils.notebook.entry_point.getDbutils().notebook().getContext().tags().apply('user')
# Username の英数字以外を除去し、全て小文字化。Username をファイルパスやデータベース名の一部で使用可能にするため。
username = re.sub('[^A-Za-z0-9]+', '', username_raw).lower()

# データベース名
db_name = f"japan_covid_{username}"

# Hiveメタストアのデータベースの準備:データベースの作成
spark.sql(f"DROP DATABASE IF EXISTS {db_name} CASCADE")
spark.sql(f"CREATE DATABASE IF NOT EXISTS {db_name}")
# Hiveメタストアのデータベースの選択
spark.sql(f"USE {db_name}")

print("database name: " + db_name)
```
![Screen Shot 2022-08-31 at 16.52.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/0eea0b93-c1af-9533-a8ea-f3f4398738d2.png)

```py:Python
# データベースに登録
final_df.write.format("delta").mode("overwrite").saveAsTable("covid_cases")
```

データベースに登録すると、SQLで簡単にアクセスできるようになります。

```sql:SQL
%sql
SELECT * FROM japan_covid_takaakiyayoidatabrickscom.covid_cases
```
![Screen Shot 2022-08-31 at 16.53.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/b2393628-e711-a47f-a0c9-b58c23914271.png)

:::note info
**注意**
上のコマンドでは[可視化の設定](https://qiita.com/taka_yayoi/items/36a307e79e9433121c38)を行なっています。Databricksノートブックでは取り扱っているデータを簡単に可視化することができます。
:::

これでDE君はBA君の必要としているデータを準備できました。

- **DE君**: ふう、これでBA君の要望に応えられるかな。
- **BA君**: ありがとう！これでダッシュボードを作れるよ！

:::note info
**注意**
今回は一度限りの処理を行いましたが、当然データは日々更新されます。入力データの更新に合わせて、上の処理を行うようにジョブ化して定期実行することを検討することになります。Databricksでは[ジョブもサポート](https://qiita.com/taka_yayoi/items/b3275a1983c51a8bbe1a)しています。
:::

# (ビジネスアナリスト)BA君の取り組み

- **BA君**: さすがDE君、あっという間にETLを終わらせてくれた。今度は自分の番だな。まずは、[Databricks SQL](https://qiita.com/taka_yayoi/items/143ee2b53c2a9696add7)にアクセスしてと。
- (以降はBA君のセリフ) よしよし。DE君が準備してくれた`covid_cases`テーブルがあるな。
![Screen Shot 2022-08-31 at 17.00.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/8679f8a8-212c-471e-243f-cd1c0029d935.png)
- これに対するSQLクエリーを書こう。ちょっと年月でも集計したいからロジックを追加してと。

    ```sql:SQL
    SELECT
      *,
      date(date_timestamp) AS Date,
      month(date_timestamp) AS Month,
      year(date_timestamp) AS Year,
      concat(
        year(date_timestamp),
        "-",
        RIGHT(concat("0", month(date_timestamp)), 2)
      ) AS YearMonth
    FROM
      japan_covid_takaakiyayoidatabrickscom.covid_cases
    ORDER BY
      pref_no ASC;
    ```
- よし、結果も問題ないな。
![Screen Shot 2022-08-31 at 17.03.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/76e6b97d-bb8f-65e9-6740-d8e7b1ad2f29.png)
- 時系列変化から見ていこうか。都道府県別に集計して棒グラフにしてと。
![Screen Shot 2022-08-31 at 17.05.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/0db62b92-0ba2-872a-3565-9bfe5f512ce0.png)
- うーん、都道府県レベルだと細かすぎるか。地方レベルにしてみよう。
![Screen Shot 2022-08-31 at 17.06.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/a59df876-e200-80a4-91c2-eda05ecef7e7.png)
- こっちの方が傾向がわかりやすいな。
- 次は地図にマッピングしてみよう。
![Screen Shot 2022-08-31 at 17.07.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/cf222443-2a81-dea6-40e0-23b7a1a7781e.png)
- まあ、傾向はニュースの通りだな。
- 他にはヒートマップも作ってみよう。
![Screen Shot 2022-08-31 at 17.07.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/ea9a6325-0a4a-b177-8b01-551b881a65a4.png)
- これで大体パーツは揃ったかな。これらをダッシュボードにまとめよう。
![Screen Shot 2022-08-31 at 17.09.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/d4cb8413-d183-9f36-606d-84fa841ab8bb.png)
![Screen Shot 2022-08-31 at 17.10.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/b2a86c65-b24c-cb32-6dfb-df0d2cd81cd5.png)
- よし、これを偉い人に見せにいこう！

このように、ビジネスアナリストはデータ集計を通じて様々な観点からデータを可視化することで洞察を得ることになります。

- **BA君**: これでどうでしょう？
- **偉い人**: おおー、素晴らしい！私が見たかった観点も盛り込まれているよ。さすがだね！
- **BA君**: ありがとうございます。
- **偉い人**: ....
- **BA君**: どうしました？
- **偉い人**: こうやって傾向が分かるとさ、今後どうなっていくか気にならない？
- **BA君**: えっ！まあ、確かに。
- **偉い人**: ちょっとさ、DS君やDE君と相談して予測結果を出してくれない？
- **BA君**: ええーー！！

# データエンジニアリング再び

上長から「予測をできないか」と言われたビジネスアナリストから相談を受けて、「それならDS君にAutoMLを実施してもらおう！」となりました。

ここでは、デモのため予測に用いるデータを東京都に限定します。この際もデータをコピーするのではなくビューを作成します。これによってビュー経由でも最新のデータを参照することができます。

```sql:SQL
%sql
CREATE VIEW japan_covid_takaakiyayoidatabrickscom.covid_cases_for_forecast 
AS SELECT * FROM japan_covid_takaakiyayoidatabrickscom.covid_cases WHERE Prefecture="Tokyo"
```

# (データサイエンティスト)DS君の取り組み

- **DS君**: BA君も追加の要求が出てきて災難だったな。ここは、自分が[Databricks AutoML](https://qiita.com/taka_yayoi/items/e9e34a418c6acca3ab05)を使ってチャチャっと予測をしてしまおう。
- (以降はDS君のセリフ) 今回解く問題は予測なので予測を選択してと。
![Screen Shot 2022-08-31 at 17.21.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/b5075e06-dcb5-beda-292e-b82f16e67ab7.png)
- 今回は東京都の予測だけで良いそうなので、DE君が作ってくれたビュー`covid_cases_for_forecast`を使おう。
![Screen Shot 2022-08-31 at 17.22.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/c6497357-f270-e40f-4eff-e4251455ccc3.png)
- その他のパラメータを指定して、**AutoMLを開始**っと。
![Screen Shot 2022-08-31 at 17.23.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/6f978901-ddb9-74d7-3598-8663b3b6749d.png)

    :::note info
    **注意**
    ここではデモのため、トレーニング時間を短く設定しています。実際にご利用する際には、精度を高めるためにデフォルト値など十分に長い時間を設けてください。
:::

- これでトレーニング、ハイパーパラメータチューニング、モデルのロギングなど全ての処理が自動で行われるから便利な世の中になったものだ。 
![Screen Shot 2022-08-31 at 17.26.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/e25e6526-fa2f-e0b4-76b7-78b8ff285905.png)
- よし、AutoMLの処理が完了した。
![Screen Shot 2022-08-31 at 17.34.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/d55c6dc5-1271-5183-7741-a3823dd31aa0.png)
![Screen Shot 2022-08-31 at 17.34.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/cafc4916-1bbf-4c4b-9136-be502586afe8.png)
- **最適なモデルのノートブックを表示**をクリックすれば、一番パフォーマンスが良かったモデルがどのように作られたのかも確認できるんだよな。これがガラスボックスアプローチのAutoMLの良いところだ。途中の処理が把握できるし、仮にカスタマイズが必要になってもPythonコードを書き換えるだけで良いんだから。
![Screen Shot 2022-08-31 at 17.36.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/40e2ba42-ff8b-5d36-9055-b0570267f593.png)
![Screen Shot 2022-08-31 at 17.38.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/73ec15cb-3839-e243-fe46-6a41c2f13bc9.png)
- この結果を見ると減少傾向が続きそうだな。BA君と一緒に偉い人に報告に行こう。予測結果もデータベースに格納されているから、ダッシュボードにしてほしいと言われても大丈夫だし。

:::note info
**注意**
今回はAutoMLを活用しましたが、もちろんノートブック上で独自のロジックを記述して分析や機械学習モデルの構築を行うことも可能です。
:::

# まとめ

このように、データや機械学習に対する様々な要件に対して、ビジネスアナリスト・データエンジニア・データサイエンティストが一つのプラットフォーム、一つのデータを活用して色々な取り組みを行えるのがDatabricksのレイクハウスです。

ETL処理、BIダッシュボードの構築、機械学習モデルの活用すべてのケースにおいて、さまざまなペルソナが一貫性を持って物事を進めることができると感じていただけたら幸いです。

こちらのシナリオでは、データエンジニアリング、ビジネスアナリティクス(BI)、データサイエンスという一連のプロセスを説明しましたが、このプロセス自体一回きりということではありません。データは日々生成されるので、このプロセスもそれに合わせて繰り返すのが普通です。レイクハウスなら、このプロセス全体を完全に自動化することすら可能なのです。

- [Databricksレイクハウスとは何か？](https://qiita.com/taka_yayoi/items/883b29d4808874427464)
- [Databricksのメダリオンレイクハウスアーキテクチャとは？](https://qiita.com/taka_yayoi/items/f3d9028cf533e2c78b9d)
- [信頼できる唯一の情報源\(single source of truth\)の構築は何を意味するのか？](https://qiita.com/taka_yayoi/items/a8d6fce746688dbf2306)
- [レイクハウスはどのようにしてディスカバリーとコラボレーションを改善するのか？](https://qiita.com/taka_yayoi/items/e40311b4a1ef3deeb6e6)

上でウォークスルーしたノートブックはこちらです。

https://github.com/taka-yayoi/public_repo/blob/main/covid-19%20analytics%20with%20lakehouse/COVID-19%E6%84%9F%E6%9F%93%E8%80%85%E6%95%B0%E5%88%86%E6%9E%90.py

### Databricks 無料トライアル

[Databricks 無料トライアル](https://databricks.com/jp/try-databricks)