気象データの細かすぎる天気概況をなんとかまとめてみる

# はじめに
この記事は [スタンバイ Advent Calendar 2021](https://qiita.com/advent-calendar/2021/stanby) の15日目の記事です。
昨日の記事は @Nisi  さんの[【空冷自作PC】Ryzen7 5700G + ASRock DeskMini X300をノクチュア NH-P1でファンレスにした話](https://qiita.com/Nisi/items/24bc1fb90d23b03f64aa)でした。

# 概要

気象庁では、全国の観測点で記録した過去の気象データを公開しています。
一度に入手可能なデータ量に制限はありますが、
ユーザー登録無しで全国のアメダスのデータが無償で使えるのはとても素晴らしいです。

しかし、天気概況（晴、快晴、雨、大雨など）の種類が非常に細かく
（2020年東京の6時〜18時の気象概況では年間58種類使われている）
例えば、晴、雨、その中間の３分類程度で売上を比較したい、といった用途にはいささか不便です。
そこで、本記事では、気圧等の数値データに基づいて、一日の天気概況表現を再分類する方法を検討しました。

# Contents

- データの入手
- データの概要
- データの加工

# データの入手

[過去の気象データ](https://www.data.jma.go.jp/gmd/risk/obsdl/index.php)は無料で入手出来ます。以下のようなフォームが用意されているので、

- 地点
- 項目（気温、気圧など）
- 期間
- 表示オプション

を選択し、**CSVファイルをダウンロード**でデータ入手完了です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1972562/22b53448-3093-e532-92ec-45b89001e562.png)

なお、本記事では、以下の条件で取得したデータを使用します

- 地点:東京
- 項目:日平均気温、最高気温、最低気温、降水量の日合計、日照時間、日最深積雪、日平均風速、日平均現地気圧、日平均相対湿度、天気概況（昼：06時～18時）
- 期間:　2020年1月1日〜2020年12月31日

:::note warn
ファイルの文字コードがShiftJISなので、必要に応じて文字コードを変換して下さい。
:::

# データの概要
早速、Pandasデータフレームに読み込んでみます。

```
# 必要なライブラリのインポート
import pandas as pd

# データの読み込み
df_weather = pd.read_csv('path/do/datadir/data.csv')
df_head() 
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1972562/6693547b-5355-b318-8750-693a65b87588.png)

2020年はうるう年なので366行、問題なくデータを読み込めています。

次に、天気概況の分類をみてみます。

```
df_weather['天気概況(昼：06時〜18時)'].unique()

array(['晴', '晴一時曇', '快晴', '曇時々晴', '晴後一時曇', '曇後雨', '曇一時雨', '曇後晴', '曇後一時雨',
       '雨一時晴', '曇', '雨時々みぞれ一時雪', '曇後一時晴', '雨', '雨後曇', '晴一時雨', '晴時々曇',
       '薄曇時々晴', '薄曇後一時晴', '曇一時雨後晴', '薄曇', '雨時々曇', '晴後薄曇', '晴後一時薄曇',
       '曇時々晴後一時雨', '曇時々雨', '曇一時晴', '晴一時薄曇', '晴後一時雨', '雨後雪時々みぞれ',
       '曇一時雨後時々晴', '雨時々雪後一時曇、みぞれを伴う', '曇後雨一時晴', '晴後曇', '晴後時々曇', '大雨',
       '薄曇一時晴', '晴後曇時々雨', '晴時々薄曇', '曇後一時雨、雷を伴う', '曇時々雨、雷を伴う', '雨一時曇',
       '曇時々雨後晴', '曇時々晴一時雨、雷を伴う', '曇、雷を伴う', '曇一時雨後一時晴', '雨後時々曇', '曇時々雨一時晴',
       '曇後時々雨', '晴後曇一時雨、雷を伴う', '晴後時々曇一時雨、雷を伴う', '晴、雷を伴う', '曇時々大雨、雷を伴う',
       '晴時々曇一時雨', '曇時々雨一時晴、雷を伴う', '曇時々晴一時雨', '曇一時晴後雨', '曇後時々晴'],
      dtype=object)
```
「時々」、「一時」等と組み合わさって、こんな分類になるのね。

種類の数は

```
len(df_weather['天気概況(昼：06時〜18時)'].unique())
58
```
58種類も！！　細かすぎる、、、。

:::note info
天気予報でよく出てくる「時々」と「一時」の定義について
時々: 断続的かつその時間が予報期間の１／２未満の場合
一時: 連続的かつその時間が予報期間の１／４未満の場合
:::

# データの加工

いよいよ天気概況を再分類します。
今回は、雨、晴、その中間の３つ程度に再分類したいと思います。
この程度の分類であれば、以下の予想をもとに、再分類を進めれそう（な気がします）。

- 天候を左右するのは、気圧と湿度、風速ではないか
- 似たような天気のときは、似たような値の組み合わせになるはず

似たようなものの分類、そう、クラスター分析の出番ですね。

## クラスター分析 (Try1)

今回は、k-means法を使います。分析コードは以下の通り。

```
# 必要なライブラリを読み込む
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler

# クラスター分析に使用するカラムだけ抜き出す
df_analytics = df_weather[['平均現地気圧(hPa)','平均湿度(％)','平均風速(m/s)']]

# パラメータ間のオーダーをあわせるために値を標準化する
sc = StandardScalar()
df_analytics_std = sc.transform(df_analytics)

# k-means法によるクラスタ分析
kmeans = KMeans(init='random', n_clusters=3, random_state=0)
kmeans.fit(df_analytics_std)
labels = pd.Series(kmeans.labels_, name='cluster_number')

# 各クラスタのメンバー数を確認
print(labels.value_counts(sort=False))
1    136
2    168
0     62
Name: cluster_number, dtype: int64

# クラスター番号を結合
df_weather_lbl = pd.concat([df_weather, labels], axis=1)
```

各クラスターの天気概況(昼：06時〜18時)で使われている表現を見てみます。

```
# クラスター番号0
df_weather_class0 = df_weather_lbl[df_weather_lbl['cluster_number'] == 0]['天気概況(昼：06時〜18時)'].unique()
df_weather_class0

array(['晴後一時曇', '晴', '快晴', '曇時々晴後一時雨', '晴一時曇', '晴後一時雨', '薄曇時々晴', '曇',
       '曇時々雨', '晴一時薄曇', '晴時々薄曇', '薄曇', '雨一時曇', '曇後雨', '曇後晴', '雨時々曇',
       '晴時々曇一時雨', '曇時々雨、雷を伴う', '曇一時晴', '曇一時雨'], dtype=object)

# クラスター番号1
df_weather_class1 = df_weather_lbl[df_weather_lbl['cluster_number'] == 1]['天気概況(昼：06時〜18時)'].unique()
df_weather_class1

array(['晴', '晴一時曇', '快晴', '曇時々晴', '晴後一時曇', '曇後雨', '曇後晴', '曇', '曇後一時晴',
       '雨', '雨後曇', '晴時々曇', '薄曇時々晴', '薄曇後一時晴', '曇一時雨後晴', '曇後一時雨', '薄曇',
       '晴後薄曇', '晴後一時薄曇', '曇一時晴', '晴一時薄曇', '曇時々雨', '曇一時雨後時々晴', '晴後曇',
       '晴後時々曇', '薄曇一時晴', '曇一時晴後雨', '曇後時々晴', '雨後時々曇', '曇一時雨'], dtype=object)

# クラスター番号2
df_weather_class2 = df_weather_lbl[df_weather_lbl['cluster_number'] == 2]['天気概況(昼：06時〜18時)'].unique()
df_weather_class2

array(['曇一時雨', '曇後一時雨', '快晴', '雨一時晴', '雨時々みぞれ一時雪', '雨', '晴一時雨', '晴',
       '雨時々曇', '曇後晴', '曇時々雨', '曇一時晴', '雨後雪時々みぞれ', '晴後一時曇',
       '雨時々雪後一時曇、みぞれを伴う', '曇後雨一時晴', '大雨', '曇', '晴後曇時々雨', '曇後雨',
       '曇後一時雨、雷を伴う', '雨後曇', '曇時々晴', '曇時々雨、雷を伴う', '晴時々曇', '曇時々雨後晴', '晴一時曇',
       '薄曇', '曇時々晴一時雨、雷を伴う', '曇後一時晴', '薄曇時々晴', '曇、雷を伴う', '曇一時雨後一時晴',
       '雨一時曇', '雨後時々曇', '曇時々雨一時晴', '曇後時々雨', '晴後曇一時雨、雷を伴う',
       '晴後時々曇一時雨、雷を伴う', '晴、雷を伴う', '晴時々薄曇', '曇時々大雨、雷を伴う', '曇時々雨一時晴、雷を伴う',
       '晴後一時薄曇', '曇時々晴一時雨', '曇時々晴後一時雨', '晴後薄曇'], dtype=object)
```

ぱっと見た感じ、各クラスターがどのような天気概況の集団なのか、良くわからないです、、、。
ここで、分類性能を簡易的に評価するため、クラスター間の重複の数を数えてみましょう。

```
# 重複個数を確認
# 3クラスター重複
len(set(df_weather_class0).intersection(set(df_weather_class1), set(df_weather_class2)))
12

# クラスター0,1重複
len(set(df_weather_class0).intersection(set(df_weather_class1)))
13

# クラスター1,2重複
len(set(df_weather_class1).intersection(set(df_weather_class2)))
21

# クラスター2,0重複
len(set(df_weather_class2).intersection(set(df_weather_class0)))
17
```

どのクラスターも半分以上他のクラスターと重複しており、うまく分類出来ているとは言い難いです。
改良の余地がありそうです。


## クラスター分析 (Try2)

気圧、湿度、風速の値だけで分類が難しい、であれば、パラメータを増やしてみることにします。
試しに、降水量の合計、日照時間を加えてみます。

```
# クラスター分析に使用するカラムだけ抜き出す
df_analytics = df_weather[['降水量の合計(mm)','日照時間(時間)','平均現地気圧(hPa)','平均湿度(％)','平均風速(m/s)']]

# 先程と同様

# 各クラスタのメンバー数を確認
print(labels.value_counts(sort=False))
0    181
1    149
2     36
Name: cluster_number, dtype: int64
```
再び、各クラスターの天気概況(昼：06時〜18時)で使われている表現を見てみます。

```
# クラスター番号0
df_weather_class0 = df_weather_lbl[df_weather_lbl['cluster_number'] == 0]['天気概況(昼：06時〜18時)'].unique()
df_weather_class0

array(['晴', '晴一時曇', '快晴', '曇時々晴', '晴後一時曇', '曇', '晴時々曇', '薄曇時々晴', '薄曇後一時晴',
       '曇一時雨後晴', '晴後薄曇', '薄曇', '晴後一時薄曇', '曇時々晴後一時雨', '曇一時晴', '晴一時薄曇',
       '晴後一時雨', '晴後曇', '晴後時々曇', '薄曇一時晴', '晴時々薄曇', '曇後晴', '晴、雷を伴う',
       '曇後一時晴', '晴一時雨', '曇後時々晴'], dtype=object)

# クラスター番号1
df_weather_class1 = df_weather_lbl[df_weather_lbl['cluster_number'] == 1]['天気概況(昼：06時〜18時)'].unique()
df_weather_class1

array(['曇後雨', '曇一時雨', '曇後晴', '曇後一時雨', '雨一時晴', '雨時々みぞれ一時雪', '曇後一時晴', '雨',
       '曇', '雨後曇', '薄曇', '雨時々曇', '曇時々雨', '曇一時晴', '曇一時雨後時々晴', '曇後雨一時晴',
       '晴後曇時々雨', '曇後一時雨、雷を伴う', '曇時々雨、雷を伴う', '曇時々雨後晴', '曇時々晴一時雨、雷を伴う',
       '曇、雷を伴う', '曇一時雨後一時晴', '晴一時曇', '曇後時々雨', '晴', '晴時々曇', '晴後曇一時雨、雷を伴う',
       '晴後時々曇一時雨、雷を伴う', '曇時々晴', '晴時々曇一時雨', '曇時々晴一時雨', '晴一時雨', '曇一時晴後雨',
       '曇時々晴後一時雨', '雨後時々曇', '晴後薄曇', '薄曇一時晴'], dtype=object)

# クラスター番号2
df_weather_class2 = df_weather_lbl[df_weather_lbl['cluster_number'] == 2]['天気概況(昼：06時〜18時)'].unique()
df_weather_class2

array(['雨', '晴一時雨', '雨時々曇', '雨後雪時々みぞれ', '曇時々雨', '雨時々雪後一時曇、みぞれを伴う', '大雨',
       '雨一時曇', '曇後雨', '雨後曇', '雨後時々曇', '曇時々雨一時晴', '曇時々大雨、雷を伴う',
       '曇時々雨一時晴、雷を伴う', '曇時々雨、雷を伴う', '曇'], dtype=object)
```

ぱっと見た感じ、おおよそ

- クラスター番号0 : 晴
- クラスター番号2 : 雨
- クラスター番号1 : 中間

のように再分類出来ていそうです。
先程と同じように、クラスター間の重複をカウントしてみます。

```
# 重複個数を確認
# 3クラスター重複
len(set(df_weather_class0).intersection(set(df_weather_class1), set(df_weather_class2)))
2

# クラスター0,1重複
len(set(df_weather_class0).intersection(set(df_weather_class1)))
13

# クラスター1,2重複
len(set(df_weather_class1).intersection(set(df_weather_class2)))
9

# クラスター2,0重複
len(set(df_weather_class2).intersection(set(df_weather_class0)))
2
```

先程と比較してだいぶクラスター間の重複を減らせたのではないかと思います。

# まとめ

- 降水量合計、日照時間、平均現地気圧、平均風速、平均湿度をもとに、天気概況を３分類程度に分けられそう。
- 他の地域でも試してみたい。（例えば、北海道など）