いものやま。

雑多な知識の寄せ集め

『Python実践データ分析100本ノック』を読んでみた。

f:id:yamaimo0625:20190203131318j:plain

Python実践データ分析100本ノック』がいい本だったので、紹介したい。

概要

この本は、実務で実際にありそうなシーンを対象として、どういったことを考え、そして分析していけばいいのかを取り扱ってる。

各項目は100本ノックという形で実際に手を動かしながら学べるようになっているので、テンポよく進められた。

具体的には、1章から10章まであり、各10本ずつノックが用意されている。
以下ではどんな内容があったのかを説明したい。

基礎編:データ加工

1、2章は基礎編で、データ加工について。

これらの章はけっこう重要で、実際にKaggleのコンペなんか見てみると分かるとおり、現実のデータってかなり汚い。
欠損値があったり、揺れがあったり。
あるいは、分析に適した形になっていなかったり。
そういう汚いデータを、実際に手を動かしながら整えていく経験をすることができる。

機械学習の入門書はいろいろ読んでるけど、概要の説明こそあれ、実例を使って試していくのはなかなか書かれていないので、勉強になった。

実践編1:機械学習

3〜5章では、機械学習を使って予測モデルを作っていく。

といっても、やるのは大部分がデータの整理。
結局、機械学習アルゴリズムを使う部分はライブラリを叩いてポンなんで、それまでが重要ということ。

第2部で紹介したケースはオーソドックスなものですが、思ったより機械学習モデルの構築までの道のりが長いと感じたのではないでしょうか。 実際の現場では、綺麗なデータが用意されていることはほとんどなく、多くのデータ加工とプレ分析が待ち構えています。
(『Python実践データ分析100本ノック』5章より引用)

実際に手を動かしてみると、このことを身をもって理解できると思う。

実践編2:最適化問題

この本は面白いことに、6〜8章では最適化問題を扱ってる。

部品工場から組み立て工場への輸送などをテーマとして考えていて、(グラフ理論の)グラフを描くためのライブラリであるNetworkXを使ったり、(線形計画問題になるので)線形計画問題を解くためのライブラリであるPuLPを使ったりする。
あと、口コミが広がっていく様子をシミュレーションしたりも。
機械学習の本では見ない内容だったので、面白かった。

ちょっとだけ難点を挙げておくと、6〜8章を書いた人はおそらくPythonに不慣れで、コードがかなりPythonっぽくない。
まぁ、それをPythonっぽいコードにするのも、いい練習になるかもしれないけど。

発展編:画像処理、言語処理

9、10章では、発展的な内容ということで、画像処理と言語処理を扱ってる。

これもけっこう他の本とは違って、画像処理は深層学習を使うとかではなく、OpenCVを使っている。
それと、言語処理は形態素解析のライブラリであるMeCabを扱ってる。
これらのライブラリも使ったことがなかったので、いい勉強になった。

PythonやNumPy、Pandasの入門書ではない

一つ気をつけたいこととして、この本はPythonやNumPy、Pandasの入門書ではないということ。

Pythonの文法も知らないような人がこの本を見ても、さっぱり分からないので、そういう人はPythonのチュートリアルや入門書を読んでからの方がいい。

また、NumPyやPandasをほとんど知らない状態でこの本を読むのも、なかなか大変だと思う。
もちろん、使われてる関数を逐次調べれば読めると思うけど、あまりオススメできない・・・

むしろ、NumPyやPandasを扱ってる入門書とかを読んでみて、ある程度理解しても、実際にそれでデータ分析してみようとすると、途端に困ってしまうことが多いので、そういうタイミングで読むととてもいいと思う。

NumPyやPandasを扱ってるもので、自分がかなりマシだと思った本は、『Python Data Science Handbook』(邦題『Pythonデータサイエンスハンドブック』)。
(正直「これだ!」という本に出会ってない・・・PythonやNumPy、Pandas、あるいはMatplotlibのデザインがゴミまみれなのが原因な気もする)

Python Data Science Handbook: Essential Tools for Working with Data

Python Data Science Handbook: Essential Tools for Working with Data

これは原稿がGitHubで公開されてるので、GitHubでそのまま読むこともできるし、cloneしてきてJupyter Notebookで読むこともできる。

コードの質

もう一つ気をつけたいこととして、マシな方だとは思うけど、コードの質はそこまで高くない。

groupby()の利用

これはこの本に限らないんだけど、まず思うのは、分かりにくいgroupby()を使うんじゃなくて、pivot_table()を使った方がいいということ。
ピボットテーブル作った方が、何をやってるのかが一発で分かる。

たとえば、ノック8でgroupby()を使った次のようなコードがあった:

join_data.groupby('payment_month').sum()['price']

意味は「payment_month列の各値ごとにテーブルを分割して、各テーブルの各列をsum()で合計してから(これで1行のレコードになる)分割したテーブルを再結合して、price列だけ抜き出す」というもの。
この「分割→処理→結合」という思考はややこしいし、コードもパッと見で何をやってるか分かりにくい。

ついでに言えば、

join_data.groupby('payment_month')['price'].sum()

とした方が余分な計算がされなくなるので早くなる。
(KaggleのNotebookで%timeを使って測ると、前者が約9ms、後者は約5msだった)

けど、そもそもこれはピボットテーブル使って

join_data.pivot_table(index='payment_month', values='price', aggfunc=sum)

と書いた方が分かりやすい。
意味は「payment_month列の各値ごとにprice列の値の合計の表を作る」と明快。
%timeで時間を測ると、約11msでgroupby()より少し遅いという問題はあった)

あるいは、ノック25だと、顧客と月ごとの利用回数をgroupby()で次のように集計している:

uselog_months = uselog.groupby(["年月","customer_id"],as_index=False).count()
uselog_months.rename(columns={"log_id":"count"}, inplace=True)
del uselog_months["usedate"]

いや、パッと見でイミフでしょ。

pivot_table()を使えばもっとシンプルになる:

uselog_months = uselog.pivot_table(index='customer_id', columns='年月', aggfunc='size', fill_value=0)

なお、groupby()とは違う表になるけど、分析にはピボットテーブルの表の方が圧倒的に見やすいと思う。
もしgroupby()と同じ表にしたいなら、melt()を使うといい。

ちなみに、このあと顧客ごとの利用回数に関する集計をするんだけど、本だと

uselog_customer = uselog_months.groupby("customer_id").agg(["mean", "median", "max", "min" ])["count"]
uselog_customer = uselog_customer.reset_index(drop=False)
uselog_customer.head()

とまたgroupby()を使ってるのに対し、ピボットテーブルにした自分のコードなら、

uselog_customer = pd.DataFrame({
    'mean': uselog_months.mean(axis=1),
    'median': uselog_months.median(axis=1),
    'max': uselog_months.max(axis=1),
    'min': uselog_months.min(axis=1),
}).reset_index() # customer_idを列に戻しておく

と、各行について集計をすればいいだけなので、分かりやすい。

他にも、けっこうドッタンバッタンしてるコードがいるので、そういう部分はリファクタリングを考えながら進めるといいかも。

SettingWithCopyWarningが出る

コードを試していると、SettingWithCopyWarningという警告が出ることがあった。
けど、本では言及なし・・・(警告を抑制してる? それとも単に無視してる?)

これは何かというと、Pandasは列や行を抽出したときに、実体を返すかビューを返すかが曖昧なときがあり、そういった実体かビューかが曖昧なオブジェクトに対して実体を更新する処理をすると出る警告っぽい。
大抵は問題ないっぽいけど、どの実体が更新されるのか曖昧で、ちょっと危ないところがある。

実際に警告が出た一例は、ノック32。

# クラスタリングで得られたラベルの列を追加
customer_clustering['cluster'] = clusters.labels_ # ここで警告が出た

パッと見だとなんでこれでSettingWithCopyWarningが出るのか分からないんだけど、原因はずっと前にあった:

features = ['mean', 'median', 'max', 'min', 'membership_period']
customer_clustering = customer[features] # ここで列を抽出してる!

...

customer_clustering['cluster'] = clusters.labels_ # ここで警告が出た

原因がずっと前の方にあるので、気付きにくい。。。
これは明示的にコピーしておくと警告が出なくなった:

customer_clustering = customer[features].copy() # 実体が返されるので曖昧でなくなる

最適化問題を扱ってるコード

あと、前述の通り、最適化問題を扱ってるところのコードはけっこうアレなので、頑張って直した方がいい。

Kaggleで試すときの注意事項

実際に手を動かしながら読んだ方がいいので、KaggleでNotebookを作って試すのがオススメ。

ただし、9章、10章に関しては、ちょっと注意が必要だった。

9章の注意点

まず、9章では画像を表示するのにcv2.imshow()を使っているんだけど、これをKaggleのNotebooで実行すると、Notebookがリスタートする(^^;

この問題を解決するには、cv2.imshow()の代わりにMatplotlibのimshow()を使ってあげるといい:
(このとき、色をBGRからRGBに変換する必要がある)

img = cv2.imread('img01.jpg')

# 以下、エラーになる
# cv2.imshow('img', img)

# 代わりにplt.imshowで表示
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
fig, ax = plt.subplots(figsize=(15, 15))
ax.imshow(img_rgb);

あと、動画のキャプチャで、本ではcap.isOpened()で終了判定をしてるけど、これ、動画の最後に来てもFalseを返さないっぽい。
このせいで、動画の最後に来てるのにret, frame = cap.read()が呼ばれ、これが無限待ちになって処理が終わらなくなる。。。

これは代わりに総フレーム数を使うようにするといい:

cap = cv2.VideoCapture('mov01.avi')
count = cap.get(cv2.CAP_PROP_FRAME_COUNT)

# 以下、無限ループで終わらなくなる
# while cap.isOpened():
#     ...

# 総フレーム数を使う
for i in range(int(count)):
    ...

10章の注意点

10章の方は、MeCabのインストールがそれなりに大変。

以下のNotebookを参考にさせてもらった:

""" # インストールするときはこの行をコメントアウト
!mkdir mecab
%cd mecab
!apt install curl mecab libmecab-dev mecab-ipadic-utf8 file -y
!git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
%cd mecab-ipadic-neologd
!bin/install-mecab-ipadic-neologd -n -a -y --prefix /var/lib/mecab/dic/mecab-ipadic-neologd
!sed -i -e 's@^dicdir.*$@dicdir = /var/lib/mecab/dic/mecab-ipadic-neologd@' /etc/mecabrc
!pip install mecab-python3
%cd ../..
"""; # インストールするときはこの行をコメントアウト

これでインストールできるはず。


そんな感じで、問題がまったくないわけではないけど、むしろその問題の解決も含めていい勉強になると思うので、オススメ。

今日はここまで!