pythonで計算！【台風偏差値】


![large.jpg](https://qiita-image-store.s3.amazonaws.com/0/221435/7f2beae7-bd6d-0c80-64bd-99be307af4d3.jpeg)
# 台風偏差値？



 本日(2018年9月4日)は台風21号の影響により外に出ることができませんでした。

 ニュースでは毎回毎回「今回の台風は非常に強い」という口ぶりですので、しっかりと過去のデータに基づき、今回の台風がいかに強いのかを計算したいと思います。

# 強さとは?
台風の強さを示す指標として、今回は気圧を採用しました。

低気圧であればあるほど力強く気体が流れ込むため
### **気圧が低い台風を強い**
と定義いたします。


# データ集め
さて、偏差値を計算するためにはできるだけ多くの過去のデータが必要になります。色々と調べてみたのですが、最終的にWikipediaからスクレイピングすることになりました。

> スクレイピング・・・Webサイトから情報を抽出すること

下の画像のようにWikiには各年の台風情報がまとめられています。

<img width="686" alt="スクリーンショット 2018-09-04 13.49.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/221435/01952ab3-d012-d5cf-4c5b-4faaa8d95f9c.png">

規則的に書かれていないとプログラムで情報を抽出しにくいので、ドキドキしながら見ていきますと1999年からは規則的に書かれていました。

なので今回は1999年~2018年の20年間の台風データを元に偏差値を計算することにしました。

## ① URLを取得
幸運にもWikiのURLはとても規則的でした。

例えば2018年の台風は
`https://ja.wikipedia.org/wiki/2018%E5%B9%B4%E3%81%AE%E5%8F%B0%E9%A2%A8`
というURLなのですが、2018のところを2017に変えると2017年の台風のページのURLになっていました。

なので、プログラム全体で

```python
for year in range(1999, 2019):
    url = "https://ja.wikipedia.org/wiki/" + str(year) + "%E5%B9%B4%E3%81%AE%E5%8F%B0%E9%A2%A8"
```

みたいなfor文をぶん回せば良いってことですね！

## ② pythonスクレイピングの基本
pythonでスクレイピングしたければ、**beautiful soup**というライブラリが良いそうです。

pipなどお好きな方法で使える状態にしてください。

Beautiful Soupは下のように**soup**というオブジェクトを生成します。

こいつが良い感じに抽出してきてくれるそうです。

```python
import urllib
from bs4 import BeautifulSoup

for year in range(1999, 2019):
    url = "https://ja.wikipedia.org/wiki/" + str(year) + "%E5%B9%B4%E3%81%AE%E5%8F%B0%E9%A2%A8"

    # ----- 以下、新しいところ -----
    html = urllib.request.urlopen(url)
    soup = BeautifulSoup(html, "html.parser")
```

例えば、指定したURL先のページの中の`<table>`タグに囲われた文字列を取得したければ、

```python
soup.find_all("table")
```

とすると、htmlファイル内にある全てのtableタグ内の文字列を返してくれます。(`<table>`という文字も含む)

どうやら、この**find_allという関数が肝になってきそうです。**

それでは、wikiのhtmlをしっかり観察し、欲しい情報にロックオンしてfind_allで抽出してもらいましょう。

## ③ html観察
chromeにはデベロッパーツールという便利機能があります。macであれば`command`+`alt`+`i`を押すと、下のようにwebページのhtmlを見ることができます。(Windowsは知らん！)

下の画像のように、table(表)の中に気圧情報は格納されているみたいですね。


<img width="1240" alt="スクリーンショット 2018-09-04 14.55.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/221435/99edb3c5-b099-2d52-fb2a-f6acee5e47f9.png">

便利すぎて困っちゃうくらい便利で、見ての通り気圧データが格納されているtableを一瞬で発見することができます。

▶︎の部分をぽちぽちしていくとさらに細かくコードを表示することができます。

下の画像より、どうやらtableの中の`<tr style="border-bottom: 1px solid #aaa">`みたいなところに気圧データがあるみたいですね。

<img width="1240" alt="スクリーンショット 2018-09-04 15.04.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/221435/949bec90-2af0-889a-e598-b997fe75c960.png">

今回のお目当の気圧は1002ですね。


## ④スクレイピング開始！

```python
import urllib
from bs4 import BeautifulSoup

for year in range(1999, 2019):
    url = "https://ja.wikipedia.org/wiki/" + str(year) + "%E5%B9%B4%E3%81%AE%E5%8F%B0%E9%A2%A8"

    html = urllib.request.urlopen(url)
    soup = BeautifulSoup(html, "html.parser")

    # ----- 以下、新しいところ -----
    table_bottom_list = soup.find_all("tr", style="border-bottom: 1px solid #aaa")
```

上のコードのように、`find_all`を使って`<tr style="border-bottom: 1px solid #aaa">`に対応する部分を抽出してきます。


```html
<tr style="border-bottom: 1px solid #aaa">
    <th style="white-space:nowrap">ピーク時の強さ
    </th>
    <td>
        35&nbsp;
        <a href="/wiki/%E3%83%8E%E3%83%83%E3%83%88" title="ノット">kt</a>
        <small>(10分間平均)</small>
        &nbsp;
        <br>
        1002&nbsp;
        <a href="/wiki/%E3%83%98%E3%82%AF%E3%83%88%E3%83%91%E3%82%B9%E3%82%AB%E3%83%AB" title="ヘクトパスカル">hPa</a>
    </td>
</tr>
```

すると、上の文字列が`table_bottom_list`の0番目の要素に格納されることになります。他の台風に関しても1番目、2番目と順次格納されていくことになります。

正直、ここからどうすればいいかよくわからなかったので、泥臭く文字列操作を行いました。(正しい方法を知っている方はコメントなどで教えてください！！！)

```python
import csv
import urllib
from bs4 import BeautifulSoup

data_list = []

for year in range(1999, 2019):
    url = "https://ja.wikipedia.org/wiki/" + str(year) + "%E5%B9%B4%E3%81%AE%E5%8F%B0%E9%A2%A8"

    html = urllib.request.urlopen(url)
    soup = BeautifulSoup(html, "html.parser")

    table_bottom_list = soup.find_all("tr", style="border-bottom: 1px solid #aaa")

    # ----- 以下、新しいところ -----
    for table_bottom in table_bottom_list:
        table_bottom = str(table_bottom)
        try:
            index = table_bottom.index("</small>")
            if table_bottom[index+16] == '1':
                data_list.append(table_bottom[index+16 : index+20])
            else:
                data_list.append(table_bottom[index+16 : index+19])
        except:
            print("Not Found hPa")
```
はい、ドーン！

こんな感じです。

先ほど取得した`table_bottom_list`からfor文で一つ一つを`table_bottom`に格納します。

> `table_bottom_list`が複数の台風の表の文字列
> `table_bottom`が一つの台風の表の文字列

`table_bottom`を`str`で文字列に変換します。

文字列に変換したことで、`table_bottom`は**文字のリスト**として扱えます。どういうことかと言いますと、例えば`table_bottom[0]`とすると、`<`が出てくることになります。これで一気に扱いやすくなりました。

`index`という関数で`</small>`という文字列がある位置を調べます。だいたい`270`だよみたいな数字が返ってくるということです。そして、`</small>`からお目当の数字である気圧は16文字後にありますので、

```python
data_list.append(table_bottom[index+16 : index+20])
```
とすることで今回のお目当の数字である1002が抽出されるのです！

※`try`とかしてるのは、そもそも文字列が存在しているかどうかの例外処理です。
※`if`で場合分けしているのは、気圧が4桁の場合と3桁の場合で分けています。

## ⑤ CSVファイルに書き込み
　偏差値を計算するたびにスクレイピングを行うのは非常に非効率ですので、一度抽出したデータはcsvデータとして保存しておきましょう。

　こんなのはpython君の標準ライブラリでチョチョイのチョイです。


```python
import csv
import urllib
from bs4 import BeautifulSoup

data_list = []

for year in range(1999, 2019):
    url = "https://ja.wikipedia.org/wiki/" + str(year) + "%E5%B9%B4%E3%81%AE%E5%8F%B0%E9%A2%A8"

    html = urllib.request.urlopen(url)
    soup = BeautifulSoup(html, "html.parser")

    table_bottom_list = soup.find_all("tr", style="border-bottom: 1px solid #aaa")

    for table_bottom in table_bottom_list:
        table_bottom = str(table_bottom)
        try:
            index = table_bottom.index("</small>")
            if table_bottom[index+16] == '1':
                data_list.append(table_bottom[index+16 : index+20])
            else:
                data_list.append(table_bottom[index+16 : index+19])
        except:
            print("Not Found hPa")

# ----- 以下、新しいところ -----
f = open('data.csv', 'w')

writer = csv.writer(f, lineterminator='\n')
writer.writerow(data_list)

f.close()
```

以上でスクレイピングは終了です！

さて、これらは`scraping.py`という一つのファイルとして保存しておきましょう。`data.csv`というファイルができればお役御免なのですが、まぁ記念に残しておきましょう。

それでは、いよいよ偏差値の計算です。。。

# 偏差値の計算
先ほど作った`data.csv`を`main.py`という新しいファイルから呼び出しましょう。

```python
import csv

if __name__ == "__main__":
    f = open('./data/data.csv', 'r')
    reader = csv.reader(f)
```

`if __name__ == "__main__"`の部分がわからない人は気にしないでください。C言語のmain関数と同じです。(知らんけど)

このままでは計算しにくいので、少し前処理を施します。

```python
import csv
import numpy as np

if __name__ == "__main__":

    f = open('./data/data.csv', 'r')
    reader = csv.reader(f)
    data = list(reader)[0]
    data = list(map(float, data))
    data = np.array(data)
```

最終的に`numpy`のリスト型にしています。

`numpy`とは最強の計算ライブラリです。
`numpy`のリスト型にしておくことで、下のように平均値や標準偏差など1行で計算できるのです。

```python
mean = np.mean(np_list) # 平均
std = np.std(np_list)   # 標準偏差
```

さて、準備は完全に整いました！

計算しましょう！

偏差値Tiは以下の式で定義されています。
<img width="200" alt="スクリーンショット 2018-09-04 15.33.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/221435/91f63b99-5c70-dfd0-a8a6-d787bbc6e80d.png">

xi ： 偏差値が知りたいデータの値
μx : 母集合の平均
σx : 母集合の標準偏差

しかし、今回では気圧は低ければ低いほど強い台風であるので、`50`から`10(xi-μx)/σx`を引くことにします。

```python

def deviation(x, np_list):

    mean = np.mean(np_list)
    std = np.std(np_list)

    answer = 50 - ((x - mean)*10/std)
    answer = round(answer, 2)

    return answer
```

ということで、一つのデータ(`x`)とnumpyのリスト(`np_list`)を受け取り、`np_list`の中で`x`という一つのデータの偏差値を出力する関数`deviation`が完成しました！

※最後に`round`としているのは、有効数字2桁に丸めているという意味です。


# 結果



<img width="782" alt="スクリーンショット 2018-09-04 19.24.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/221435/7cad0ec1-d575-0c5f-eeed-79daf0f8921a.png">


台風21号の気圧は915hPaでした。

したがって、`915`という数字と先ほどの`data`(numpyのリスト)を`deviation`関数にぶん投げて、`print`してあげると、、、

```python
import csv
import numpy as np

def deviation(x, np_list):

    mean = np.mean(np_list)
    std = np.std(np_list)

    answer = 50 - ((x - mean)*10/std)
    answer = round(answer, 2)

    return answer


if __name__ == "__main__":

    f = open('./data/data.csv', 'r')
    reader = csv.reader(f)
    data = list(reader)[0]
    data = list(map(float, data))
    data = np.array(data)

    print(deviation(915, data))
```

<img width="668" alt="スクリーンショット 2018-09-04 19.20.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/221435/67c24202-cd35-0300-2678-3de33ece13f2.png">



偏差値`67.63`！！！

今回の台風、ヤバイ！！！！！

# その他データ

|||
|:--:|:--:|
|最大値|1010|
|最小値|885|
|平均値|967.44|
|最頻値|985|
|標準偏差|29.74|

ソースコードは[こちら](https://github.com/wataoka)

# 参考
- スクレイピング
 - [Python Webスクレイピング 実践入門](https://qiita.com/Azunyan1111/items/9b3d16428d2bcc7c9406)
 - [PythonとBeautiful Soupでスクレイピング](https://qiita.com/itkr/items/513318a9b5b92bd56185)

 - [Pythonの「文字列に○○が含まれているか」の判定について気になったこと](http://d.hatena.ne.jp/laco0416/20121022/1350907756)
- numpy
 - [データの最頻値をNumPyで求める](http://www.irohabook.com/numpy-statistics-mode)