pandas x スクレイピングで時系列データ分析を試す



## 今年の夏熱くない？
いつも私はエアコンなし・扇風機一本で夏を乗り切る男なのですが、
今年は暑さに耐え切れずエアコンを使用しています。
今年の夏の暑さが異常なのか、年を取って体力が落ちたのか、調査をしたいと思います。

## 調査方法
7月から8月までの平均気温と平均湿度を年ごとに折れ線グラフでプロットします。
データは気象庁のデータをスクレイピングすることで取得します。

## モジュールインポート
~~~python
import datetime
# データ分析
import pandas as pd
import numpy as np
# スクレイピング
import requests
from bs4 import BeautifulSoup
# 可視化
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import seaborn as sns
~~~

## 分析用dataframeの作成
### 分析対象日のdatetimeindexを作成する
~~~python
index = pd.date_range(start="2022-07-01", end="2022-08-31")
for i in range(2):
    # 一年分ずらす
    y_delta = index - pd.DateOffset(years=1)
    index   = index.union(y_delta)
index
~~~

結果

![output_1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269297/a5e88e9e-4919-9154-8bf8-2e1bafe2dfba.png)


indexの結合にはunionメソッドを使うらしい。また新しいものに対して古いものを結合したのに、
結合後は自動的に時系列に並び変えてくれた。すごい。

### 作成したindexを基にdataframeを作成する
~~~python
df = pd.DataFrame(index=index)

df["temp."] = np.nan
df["hum"]   = np.nan
df
~~~

結果

![output_2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269297/e82f22cb-e53d-087b-085e-6f18febef9b9.png)


## 気象庁のデータをスクレイピングする
### スクレイピング元
以下のようなページからデータを取得する。
年と月を基準にurlを更新すれば、その年月について日毎のデータが表に表示される。

![気象庁.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269297/d708dbd3-ca41-f956-efc9-ff64f508bb7b.png)


### 年と月を基準にグループ化する
後述のスクレイピングのループのiteratorとするために、
年月単位でグルーピングしたindexを取得する。
mean処理は特に意味はないが、何らかの処理を行わないとdataframeになってくれないので、やむを得ず実行。

~~~python
hoge = df.groupby(by=[df.index.year, df.index.month]).mean()
hoge.index
~~~

結果

![output_3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269297/c4cf7340-5e24-b2a0-4bfc-a6304ba1f59d.png)

### 気象庁のデータをスクレイピングする

url更新→テーブルデータ取得→所望の行と列からデータを抜いてdfに格納

~~~python
for Y, m in hoge.index:
    # urlを更新
    url = f"https://www.data.jma.go.jp/obd/stats/etrn/view/daily_s1.php?prec_no=44&block_no=47662&year={Y}&month={m}&day=&view="
    # htmlのtable要素を取得
    r     = requests.get(url)
    soup  = BeautifulSoup(r.text)
    table = soup.find("table", { "class" : "data2_s" })
    # 
    for iter, tr in enumerate(table.findAll("tr")):

        # 最初の四行は処理を行わない
        if iter <= 3:
            continue
        # 1列目と7列目と10列目の値を取得
        tds   = tr.findAll("td")
        d     = int(tds[0].string)
        try:
            _temp = float(tds[6].string)
            hum   = float(tds[9].string)
        except:
            _temp, hum = [np.nan, np.nan]
        # dfに格納
        df.loc[pd.to_datetime(datetime.datetime(Y, m, d)), "temp.":"hum"] = [_temp, hum]
df
~~~

結果

![output_4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269297/40668a17-9f5f-1fc9-871e-07dbab5f9cac.png)


## データを可視化する
### 分析用の列を追加。
seabornで可視化を行う。
今回は月日毎の温度と気温の変化を年ごとに比べたいので、以下の様に列を追加。
~~~python
df["m/d"] = [timestamp.strftime(r"%m/%d") for timestamp in df.index]
df["Y"]   = df.index.year
df
~~~

結果

![output_5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269297/658414f5-b2e5-9e80-8729-d24a8b625a0a.png)

### グラフで可視化
平均気温について
~~~python
sns.set_style("whitegrid")
# figure定義
fig, ax = plt.subplots(figsize=(12.8, 7.2), dpi=100)
# plot
sns.lineplot(data=df, x="m/d", y="temp.", hue="Y")
# x軸を30度回転
fig.autofmt_xdate()
# x軸の表示間隔を5個ごとに変更
ax.xaxis.set_major_locator(ticker.MultipleLocator(5))
~~~

結果

![output_6.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269297/f9a55cda-9f54-8d4b-6507-9432c78d50bc.png)


全体的に今年の気温は例年よりも高い。

湿度について
~~~python
fig, ax = plt.subplots(figsize=(12.8, 7.2), dpi=100)
sns.lineplot(data=df, x="m/d", y="hum", hue="Y")

fig.autofmt_xdate()
ax.xaxis.set_major_locator(ticker.MultipleLocator(5))
~~~

結果

![output_7.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269297/c6454b7c-b7bc-6437-b659-7271da548167.png)


意外なことに湿度は例年よりも低い時が多い。

## まとめ

湿度についてはともかく、温度については例年よりも高いことが分かりました。
夏バテの影響は加齢のせいだけでないことがわかり安心しました。

また、pandasのtimestampやdatetimeについて、
少し理解が深まりまったこともよかったなと思います。


## 参考

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DatetimeIndex.html

https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#dateoffset-objects

https://stackoverflow.com/questions/55695924/how-to-concatenate-datetimeindex-objects-in-pandas

https://stackoverflow.com/questions/22003302/beautiful-soup-just-get-the-value-inside-the-tag

https://stackoverflow.com/questions/24082784/pandas-dataframe-groupby-datetime-month

https://www.gis-py.com/entry/scraping-weather-data

https://www.yutaka-note.com/entry/matplotlib_axis

https://www.web-dev-qa-db-ja.com/ja/python/matplotlib%E3%81%AEx%E8%BB%B8%E3%81%BE%E3%81%9F%E3%81%AFy%E8%BB%B8%E3%81%AE%E3%80%8C%E7%9B%AE%E7%9B%9B%E3%82%8A%E3%81%AE%E9%A0%BB%E5%BA%A6%E3%80%8D%E3%82%92%E5%A4%89%E6%9B%B4%E3%81%99%E3%82%8B/1069360408/