Pythonによる白魔術(テクニック)の紹介


# Pythonによる白魔術(便利な使い方)の紹介

この記事は [琉大情報工学科（知能情報コース) Advent Calendar 2018](https://adventar.org/calendars/3436) の15日目の記事になります。

## みんなもPythonマスターになろう

Pythonが弊大学の授業で取り入れられて2年、後輩たちが当たり前のようにPythonを使うようになってきましたね。非常に嬉しい限りです。Pythonを採用してくれた某先生に感謝感激雨あられですね。

そんなPythonマスター[^1]も諸事情あって、大学院進学ではなく就職を選ぶことになり、あと数ヶ月もすれば卒業してしまいます。卒業する前に後輩たちにPythonマスターが使っている白魔術を伝えていきたい。


というわけで今回はPythonで白魔術を使う方法を伝授して卒業していきたいと思います。

### 諸注意

- 紹介するテクニックの中には一部中毒性があり、用法用量を守らなかった場合、めまいや吐き気を催すほか、可読性が低くなり人間関係の悪化を招く可能性がございますのでご注意ください。
- 今回紹介するのは「白魔術」がメインです。私が使う魔術とは反対に位置する「黒魔術」というものも存在し、そちらの紹介は今回無いです。

### モジュールのインポート


```python
from bs4 import BeautifulSoup
from urllib.request import urlopen, urljoin
from urllib.parse import unquote
import os
import numpy as np
import pandas as pd
```

## 内包表記
任意のリストを1行で作成してしまうものですね。


```python
# 内包表記例
[_ for _ in range(10)]
```




    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]



#### 内包表記でfizzbuzz
この内包表記とif文を組み合わせることで簡単に複雑なelif処理を1行にまとめることができます


```python
# 内包表記でfizzbuzz
["fizzbuzz" if _%15==0 else "fizz" if _%3==0 else "buzz" if _%5==0 else _ for _ in range(1, 100)][:15]
```




    [1,
     2,
     'fizz',
     4,
     'buzz',
     'fizz',
     7,
     8,
     'fizz',
     'buzz',
     11,
     'fizz',
     13,
     14,
     'fizzbuzz']



#### 内包表記でスクレイピング

まずはスクレイピングの材料になる弊大学のホームページをクローリングします。

```python
# うちの学科のホームページをクローリング
homepage = urlopen("https://ie.u-ryukyu.ac.jp").read()
soup = BeautifulSoup(homepage, "lxml")
```


```python
# htmlデータからテキストデータをすべて取り出します
soup.find_all("a")[:5]
```




    [<a class="skip-link screen-reader-text" href="#content">
     			Skip to content		</a>,
     <a href="https://ie.u-ryukyu.ac.jp/%e5%ad%a6%e5%a4%96%e5%90%91%e3%81%91/%e5%8f%97%e9%a8%93%e7%94%9f%e3%81%ae%e6%96%b9%e3%81%b8/">受験生の方へ</a>,
     <a href="https://ie.u-ryukyu.ac.jp/%e5%ad%a6%e5%a4%96%e5%90%91%e3%81%91/%e5%8f%97%e9%a8%93%e7%94%9f%e3%81%ae%e6%96%b9%e3%81%b8/%e3%82%b3%e3%83%bc%e3%82%b9%e9%95%b7%e6%8c%a8%e6%8b%b6/">コース長挨拶</a>,
     <a href="https://ie.u-ryukyu.ac.jp/%e5%ad%a6%e5%a4%96%e5%90%91%e3%81%91/%e5%8f%97%e9%a8%93%e7%94%9f%e3%81%ae%e6%96%b9%e3%81%b8/%e5%ad%a6%e7%bf%92%e6%95%99%e8%82%b2%e7%9b%ae%e6%a8%99/">学習教育目標</a>,
     <a href="https://ie.u-ryukyu.ac.jp/%e5%ad%a6%e5%a4%96%e5%90%91%e3%81%91/%e5%8f%97%e9%a8%93%e7%94%9f%e3%81%ae%e6%96%b9%e3%81%b8/course-info/">知能情報コース紹介</a>]



上記のテキストデータからハイパーリンクのみを抽出し、必要があれば日本語にデコードするコードを内包表記でやっていきます。


```python
# 内包表記で前処理
urls = [unquote(url) for url in [text.attrs["href"] for text in soup.find_all("a") if text.attrs["href"][:5] == "https"]]
```


```python
urls[:5]
```




    ['https://ie.u-ryukyu.ac.jp/学外向け/受験生の方へ/',
     'https://ie.u-ryukyu.ac.jp/学外向け/受験生の方へ/コース長挨拶/',
     'https://ie.u-ryukyu.ac.jp/学外向け/受験生の方へ/学習教育目標/',
     'https://ie.u-ryukyu.ac.jp/学外向け/受験生の方へ/course-info/',
     'https://ie.u-ryukyu.ac.jp/学外向け/受験生の方へ/特色ある授業/']



## 文字列チェック
`in` 演算子を使うことで、簡素な文字列を含んでいるかチェックすることができます。ただし、in演算子で比較するリストはPurePythonのリストでないといけないので注意が必要です(pd.Seriesとかダメ)。

```python
# 学外向け以外のリンクだけ抽出したい
[url for url in urls if "学外向け" not in url]
```




    ['https://ie.u-ryukyu.ac.jp/inside-top/',
     'https://ie.u-ryukyu.ac.jp/sitemap/',
     'https://ie.u-ryukyu.ac.jp/',
     'https://ie.u-ryukyu.ac.jp/inside-top/',
     'https://ie.u-ryukyu.ac.jp/sitemap/',
     'https://ie.u-ryukyu.ac.jp/files/2018/07/pamph2018_0711s.pdf',
     'https://ie.u-ryukyu.ac.jp/news-ie/2018/12/13/実験２：1月実施分について/',
     'https://ie.u-ryukyu.ac.jp/news-ie/2018/12/11/短期アルバイト募集のお知らせ/',
     'https://ie.u-ryukyu.ac.jp/news-ie/2018/12/10/osの11-1の課題について/',
     'https://ie.u-ryukyu.ac.jp/news-ie/2018/12/08/yapctokyo-2019に参加しませんか/',
     'https://ie.u-ryukyu.ac.jp/news-ie/2018/12/06/pycon-kyushu-in-okinawa-スタッフ募集のお知らせ/',
     'https://ie.u-ryukyu.ac.jp/news-ie/2018/12/06/it業界研究座談会（12-10mon-1810-at-1-321）-2/',
     'https://ie.u-ryukyu.ac.jp/news-ie/2018/12/04/知能情報実験２の休講のお知らせ/',
     'https://ie.u-ryukyu.ac.jp/news-ie/2018/12/03/工学部後援会キャリア形成支援セミナー（12-5wed-12-12wed/',
     'https://ie.u-ryukyu.ac.jp/news-ie/category/ura.ie.announce/',
     'https://ie.u-ryukyu.ac.jp/交通アクセス/',
     'https://ie.u-ryukyu.ac.jp/sitemap',
     'https://ie.u-ryukyu.ac.jp/wp-admin',
     'https://ie.u-ryukyu.ac.jp/author/admin/',
     'https://ie.u-ryukyu.ac.jp/author/admin/',
     'https://ie.u-ryukyu.ac.jp/2018/07/26/',
     'https://ie.u-ryukyu.ac.jp/#respond',
     'https://ie.u-ryukyu.ac.jp/sitemap',
     'https://ie.u-ryukyu.ac.jp/wp-admin']



## ファイルパスを再帰的に探索する
- ソートを挟むことでファイル名を順番になめすことができるのでsortedを入れることをおすすめします。

(どうやら最新のPythonにはos.walkよりもっと便利な関数があるらしいので調べて追記します。)


```python
for root, dirs, fnames in os.walk("/Users/intel0tw5727/Develop/PyData.Okinawa/meetup032/"):
    for fname in sorted(fnames):
        print(os.path.join(root, fname))
```

    /Users/intel0tw5727/Develop/PyData.Okinawa/meetup032/Edward_model_example.ipynb
    /Users/intel0tw5727/Develop/PyData.Okinawa/meetup032/beta_bernoulli_model.png
    /Users/intel0tw5727/Develop/PyData.Okinawa/meetup032/beta_bernoulli_model_w_inference_distribution.png
    /Users/intel0tw5727/Develop/PyData.Okinawa/meetup032/coinflip_mcmc.ipynb
    /Users/intel0tw5727/Develop/PyData.Okinawa/meetup032/coinflip_variational_inference.ipynb
    /Users/intel0tw5727/Develop/PyData.Okinawa/meetup032/pydataokinawa_meetup032.ipynb

## PurePython, Numpy, Pandas 行ったり来たり

- numpy 配列にするなら np.array 関数で
- pandas 配列にするなら pd.Series(pd.DataFrame) 関数で


```python
# PurePython to Numpy, Pandas
l1 = [1,2,3,4,5]
n1 = np.array(l1)
p1 = pd.Series(l1)
#p1 = pd.DataFrame(l1)
```

### 行ったり来たりを有効活用

本当は各リストごとにもっとメリットは有るのですが簡単にひとつだけ挙げると、

- PurePython強み: リストへの追加や削除が速い
- Numpy: 行列計算(画像などの2次元データへの処理)が速い
- pandas: 列計算が速い

があります。

### リストに値を追加しまくる
計測してみると

#### PurePython

```python
%%time
pp = []
for i in range(10000):
    pp.append(i)
```

    CPU times: user 1.55 ms, sys: 326 µs, total: 1.88 ms
    Wall time: 1.9 ms


#### Numpy

```python
%%time
pp = np.empty(1)
for i in range(10000):
    np.append(pp, i)
```

    CPU times: user 79.9 ms, sys: 3.05 ms, total: 82.9 ms
    Wall time: 83.8 ms



#### Pandas


```python
%%time
pp = pd.Series()
for i in range(10000):
    pp.append(pd.Series([i]))
```

    CPU times: user 5.79 s, sys: 53 ms, total: 5.84 s
    Wall time: 6.03 s


処理時間はPurePython <<< Numpy <<< Pandasな形になりました。

単純に行追加は遅いので列追加して転置するほうがもっと早そう。

### 演算した結果を四捨五入(小数第二位)して絶対値を取る
事前にデータを作っておきます。

```python
np.random.seed(98)
pp_a = [np.random.random() for _ in range(1,1001)]
pp_b = [np.random.random() for _ in range(1001, 2001)]
```

#### PurePython
```python
%%time
[np.abs(np.round((a1 - b1), 2)) for a1, b1 in zip(pp_a, pp_b)][:10]
```

    CPU times: user 11.1 ms, sys: 553 µs, total: 11.6 ms
    Wall time: 13.1 ms





    [0.29, 0.32, 0.01, 0.28, 0.39, 0.01, 0.13, 0.52, 0.21, 0.8]



#### Numpy
```python
%%time
np.abs(np.round((np_a - np_b), 2))[:10]
```

    CPU times: user 305 µs, sys: 246 µs, total: 551 µs
    Wall time: 335 µs





    array([0.29, 0.32, 0.01, 0.28, 0.39, 0.01, 0.13, 0.52, 0.21, 0.8 ])



#### Pandas
```python
%%time
(pd_a - pd_b).round(2).abs().head()
```

    CPU times: user 2.08 ms, sys: 9.54 ms, total: 11.6 ms
    Wall time: 994 µs





    0    0.29
    1    0.32
    2    0.01
    3    0.28
    4    0.39
    dtype: float64




処理時間は Numpy <<< Pandas <<< PurePython になりました。やはり数値計算はモジュールのほうが早そうですね[^2]。

## まとめ

- Pure Pythonを駆使するだけでも便利にいろんな操作ができる。
- モジュールごとに得意な分野はそこに任せるとなお良い
- Enjoy Python Life！！

明日は 八雲アナグラさん(@AnaTofuZ)のカレンダーです！

[^1]: 自称(Pythonできるとは言ってない)(脚注つかってみたかった)(コーナーで差をつけろ)
[^2]: 計測方法としてはあまり良くないので複数回の平均を取りましょうね。