静かなる名辞

pythonとプログラミングのこと

sklearnのLabelEncoderとOneHotEncoderの使い方

はじめに

 sklearnのLabelEncoderとOneHotEncoderは、カテゴリデータを取り扱うときに大活躍します。シチュエーションとしては、

  • なんかぐちゃぐちゃとカテゴリデータがある特徴量をとにかくなんとかしてしまいたい
  • 教師ラベルがカテゴリデータなので数値ラベルにしたい

 こんなとき使えます。

 使い方は簡単なのですが、備忘録としてまとめておきます。

LabelEncoderの使い方

 厳密にはsklearn.preprocessing.LabelEncoderですね。

sklearn.preprocessing.LabelEncoder — scikit-learn 0.19.1 documentation

 必要なことは公式サンプルにぜんぶ書いてあるのですが、自分でも使ってみましょう。

>>> from sklearn.preprocessing import LabelEncoder
>>> week_breakfast = ["パン","ご飯","なし","パン","シリアル","なし","なし"]
>>> le = LabelEncoder()
>>> labels = le.fit_transform(week_breakfast)
>>> labels
array([3, 0, 1, 3, 2, 1, 1])

 このように変換できます。

 ラベルから元のカテゴリに変換するには?

>>> le.classes_  # indexとカテゴリが対応したnumpy配列になっていることを確認
array(['ご飯', 'なし', 'シリアル', 'パン'], dtype='<U4')
>>> [le.classes_[x] for x in labels]    # リスト内包表記を活用する(最速かどうかはよくわからない)
['パン', 'ご飯', 'なし', 'パン', 'シリアル', 'なし', 'なし']
>>> week_breakfast  # 確認のため再掲(私の食生活とかではありません)
['パン', 'ご飯', 'なし', 'パン', 'シリアル', 'なし', 'なし']

 普通にできますね。

 実際にはこういうことはしないと思います。その代わり、le.classes_をいろいろなものに渡して使うことができます。

 たとえば、分類をしてsklearn.metrics.classification_reportで見てみたいと思ったときに、

>>> from sklearn.metrics import classification_report
>>> print(classification_report(labels, labels))  # とりあえず両方同じlabelsを渡している
             precision    recall  f1-score   support

          0       1.00      1.00      1.00         1
          1       1.00      1.00      1.00         3
          2       1.00      1.00      1.00         1
          3       1.00      1.00      1.00         2

avg / total       1.00      1.00      1.00         7

 0,1,2,3だとわかりづらいですね。でもclassification_reportにはtarget_namesという引数があり、

>>> print(classification_report(labels, labels, target_names=le.classes_))
             precision    recall  f1-score   support

         ご飯       1.00      1.00      1.00         1
         なし       1.00      1.00      1.00         3
       シリアル       1.00      1.00      1.00         1
         パン       1.00      1.00      1.00         2

avg / total       1.00      1.00      1.00         7

 こうしてやることができる訳です。なので、LabelEncoderのインスタンスは、大切に(プログラムのスコープ上とかに)取っておきましょう。

 参考:
sklearn.metrics.classification_report — scikit-learn 0.19.1 documentation
sklearnのclassification_reportで多クラス分類の結果を簡単に見る - 静かなる名辞
 (二番目は自分の記事)

OneHotEncoderの使い方

 これはいわゆるOne-hot表現を得るものです。いろいろな機械学習フレームワークに類似の機能があると思いますが、sklearnではsklearn.preprocessing.OneHotEncoderが対応します。

 使い方は以下の通りです。

>>> import numpy as np
>>> data = np.arange(9).reshape(9,1)
>>> data
array([[0],
       [1],
       [2],
       [3],
       [4],
       [5],
       [6],
       [7],
       [8]])
>>> from sklearn.preprocessing import OneHotEncoder
>>> ohe = OneHotEncoder()
>>> ohe.fit_transform(data).A  # sparse matrixを返しやがるのでdenseにして見ている
array([[1., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 1., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 1., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 1., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 1., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 1., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 1.]])

 注意点として、ndim=1の配列を渡すとエラーになるので、ベクトル風の表現に直して渡してやる必要があります。

>>> ohe.fit_transform(np.arange(10))
# 中略
ValueError: Expected 2D array, got 1D array instead:
array=[0. 1. 2. 3. 4. 5. 6. 7. 8. 9.].
Reshape your data either using array.reshape(-1, 1) if your data has a single feature or array.reshape(1, -1) if it contains a single sample.
>>> ohe.fit_transform(np.arange(10).reshape(-1, 1)).A
array([[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 1., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 1., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 1., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 1., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 1., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 1., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 1.]])

 ところで、ベクトル風にして渡すということは、こういうことになります。

>>> np.arange(10).reshape(5,2)
array([[0, 1],
       [2, 3],
       [4, 5],
       [6, 7],
       [8, 9]])
>>> ohe.fit_transform(np.arange(10).reshape(5,2)).A
array([[1., 0., 0., 0., 0., 1., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0., 0., 1., 0., 0., 0.],
       [0., 0., 1., 0., 0., 0., 0., 1., 0., 0.],
       [0., 0., 0., 1., 0., 0., 0., 0., 1., 0.],
       [0., 0., 0., 0., 1., 0., 0., 0., 0., 1.]])

 なるほど、こうなるのか。

 文字列でもいけるかな?

>>> week_breakfast = ["パン","ご飯","なし","パン","シリアル","なし","なし"]
>>> ohe.fit_transform(np.array(week_breakfast).reshape(-1, 1))
# 中略
ValueError: could not convert string to float: 'パン'

 ダメでした。LabelEncoderと併用して数値ラベルにしておく必要があるということだと思います(ドキュメントにもそんな感じのことが書いてある)。

おまけ:CategoricalEncoder

 これは開発中のscikit-learn 0.20の機能です。なので、まだ使えません。リリース待ちです(2018年6月現在)。

sklearn.preprocessing.CategoricalEncoder — scikit-learn 0.20.dev0 documentation

 いろいろと柔軟に使えるような機能が追加されているようです。リリースされたら、こっちも使ってみましょう(つっても、まだまだ時間かかりそうよねぇ)。

【python】反転させて先頭n個取るスライス

 タイトルの通りのものが必要になりました。一体どう書くのでしょう?

とりあえず反転させる

>>> lst = list(range(20))
>>> lst[::-1]
[19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

 ま、これは常識(python廃人の皆さんには)。

n個取ってみる

>>> lst[::-1][:10]
[19, 18, 17, 16, 15, 14, 13, 12, 11, 10]

 こうするとリストオブジェクトの生成が二回繰り返されるので遅いはずです。できればスライス一発で済ませたい。

やってみる

>>> lst[:9:-1]
[19, 18, 17, 16, 15, 14, 13, 12, 11, 10]

 なんとなくできたような気になりますが、

>>> lst[:3:-1]
[19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4]

 逆向きなのでした。スライスも左から順に評価される(切り出してから反転)のでしょうか。

正しい(?)やりかた

>>> lst[:-4:-1]
[19, 18, 17]

 微妙な違和感が・・・

先頭n個取って反転

 これもやってみよう。考え方は同じです。

>>> lst[2::-1]
[2, 1, 0]

測ってみた

>>> import timeit
>>> timeit.timeit(lambda : lst[::-1][:3])
0.35431641300010597
>>> timeit.timeit(lambda : lst[:-4:-1])
0.20682314900022902

 計測誤差ではないようです。かくして約43%の高速化が実現されました。

追記:書き上げて投稿してから思いついた別解

>>> lst[:-4:-1]
[19, 18, 17]
>>> lst[-3:][::-1]
[19, 18, 17]

 結果は同じ。これだと反転するリストが小さいので速いのでは? と思いました。

 効果を見やすくするために、長さ1000のリストで試してみます。

>>> timeit.timeit(lambda : lst[:-4:-1])
0.21827364299997498
>>> timeit.timeit(lambda : lst[::-1][:3])
2.8037329549997594
>>> timeit.timeit(lambda : lst[-3:][::-1])
0.33725221500026237

 早い順に、

  1. 1つのスライスで反転と切り出しをやる
  2. 後ろ3つを取ってから反転
  3. ぜんぶ反転させてから先頭3つを取る

 となりました。

 「ぜんぶ反転させてから~」が遅いのはまあ、予想通りですが、「後ろ3つを取ってから~」は1つのスライスでやる方法に勝てておらず、リストが小さいときの「ぜんぶ反転させてから~」と同程度。つまりオーバーヘッド分が同程度あるということです。また、built-inのスライスはそうとう気が効いてるみたいです(恐らく、lenを見てどの順番で切っていくか決めているのでは? 確認していませんが・・・)。

 結論としては、とにかくできるだけ1つのスライスで書いちゃおう、ということになると思います。ただし、実際には大した時間じゃないので、可読性重視で分けるのも不可ではない、という程度です。

【python】べき乗とべき根の計算

 べき乗はx^n、べき根は\sqrt[n]{x}です。2乗とかsqrtくらいはわかっても、n乗根あたりになるとすぐ出てこないという人も多いのでは?

 目次

組み込み関数powを使う方法

 powという組み込み関数があります。まあ、要は達します。

>>> pow(2, 10)
1024
>>> pow(0.5, 10)
0.0009765625
>>> pow(1.5, 3.5)
4.133513940946613

 見たところ浮動小数点でも大丈夫。

 ドキュメントによると、第三引数を指定することで「x の y 乗に対する z の剰余」を計算できるそうですが、何に使えるのでしょうかね・・・。

2. 組み込み関数 — Python 3.6.5 ドキュメント

べき乗演算子を使う方法

 まったく同じことが演算子でもできます。

>>> 2**10
1024
>>> 0.5**10
0.0009765625
>>> 1.5**3.5
4.133513940946613

 powは必要なのでしょうか。powを多用すると式がごちゃごちゃするので、こちらを使いたいです。

 ただ、罠があるようです。

Python のべき乗演算子に潜む罠 | CUBE SUGAR STORAGE

 思い通りの結果を得たければカッコを多用すべきであり、そうするとなんかpowでも良いような気もしてきます。

numpyに頼る方法

 np.powerがあります。

>>> np.power(2, 10)
1024
>>> np.power(0.5, 10)
0.0009765625
>>> np.power(1.5, 3.5)
4.133513940946613

 見たところ、型の取扱も含めてほぼ同じ。まあ、これを単体で使うメリットは特に感じません。numpy配列を相手にするなら、ありかも(ただしnumpy配列にべき乗演算子を使うという選択肢もある)。なお、片方が配列で片方がスカラー、とか配列同士、というケースでは、

>>> import numpy as np
>>> np.power([1,2,3], 2)
array([1, 4, 9])
>>> np.power(2, [1,2,3])
array([2, 4, 8])
>>> np.power([1,2,3], [4,5,6])
array([  1,  32, 729])

 こんな扱いになります。特に難しいことはないです。

n乗根について

 n乗根(べき根)がわからなかった人は、\sqrt[2]{x} = x^\frac{1}{2}を思い出しましょう。

>>> 2**10  # べき乗(10乗)
1024
>>> 1024**(1/10)  # べき根(10乗根)
2.0

 小数のべき乗が計算できれば、べき根も計算できるということです。

どれが速いの?

 timeitで簡単に比較してみます。私はIPython使いではないので、モジュールimportで使います。

>>> import numpy as np
>>> import timeit
>>> timeit.timeit(lambda : [[x**y for y in range(30)] for x in range(30)], number=1000)
0.37399847699998645
>>> timeit.timeit(lambda : [[pow(x,y) for y in range(30)] for x in range(30)], number=1000)
0.4485901359998934
>>> timeit.timeit(lambda : [[np.power(x,y) for y in range(30)] for x in range(30)], number=1000)
1.3186961119999978

 スカラーに対してはべき乗演算子一択。

 numpy配列だと、べき乗演算子とnp.powerはどちらが上でしょうか。

>>> timeit.timeit(lambda : np.arange(1000)**np.arange(1000), number=100000)
1.4120757860000595
>>> timeit.timeit(lambda : np.power(np.arange(1000), np.arange(1000)), number=100000)
1.402805570000055

 そもそも冗談みたいに速いんですが(numberに注目)、速度差自体は計測誤差レベル。一番コアな部分は同じ処理なのでしょう。

まとめ

 ま、べき乗演算子ですべてまかなえるので、罠に気をつけて使えば良いと思いました。

 あと、numpyすごいなぁ・・・

python環境構築まとめ

はじめに

 pythonは最近よく流行っているスクリプト言語ですが、残念ながら環境構築のとっつきづらさは他の言語の比ではないと思います*1。初心者が変な環境を作ってトラブルの元になる・・・というのもよくあることなので、この際まとめておこうと思いました。

 さて、まず大前提として、メインで使うpythonはpython3系にします。今からpython2系を学習する意味はほとんどないからです。

 また、この記事では具体的な環境構築の操作手順については述べません。それぞれの構築方法についてご自身で検索していただければ良いと思います。日本語Web圏におおむね十分な情報があります*2

pythonをやるのに向いたOS

 ご存知の通り、pythonマルチプラットフォームスクリプト言語です。しかしどうしても、OSによって向き不向きがあるようです。

windows

 おすすめ度:☆2
 率直に言って、おすすめできません。windowsだと余計な苦労が増えます。上級者は普通に使いこなせると思いますが、初心者向けではありません。

 Bash on Ubuntu on Windowsは使ったことがないのでわかりません。ごめんなさい。

Mac OS

 おすすめ度:☆3
 これも、あまりおすすめしません。使い勝手自体はwindowsよりはマシですが、Web情報はwindowsより少なかったりします。Mac特有のハマり方があるので*3、少ない英語情報を必死に探って自己解決できる人でないとしんどいと思います。

 初心者は避けた方が無難です。「pythonでプログラミングを勉強したい!」と思ったとき、いきなりMacを買う理由はないということです。

linux

 おすすめ度:☆5
 デファクトスタンダードです。初心者はlinux系を使えば良いと思います(pythonをやるついでにlinuxの勉強も出来て一石二鳥です)。windowsマシンの上に仮想マシン(後で述べるpython仮想環境とは別物です)で入れれば、環境ぶっ壊しちゃっても作り直せば良いだけで、とても気楽です。

 linux系にはディストリビューションがいろいろありますが、デファクトスタンダードubuntuです。このことは、「ubuntu python 環境構築」とか検索すれば幾らでも記事が出てくる、ということを意味します。他のディストリビューションはそこまで強くないので、手を出すのは中級者以上になってからで良いと思います。

その他のOS

 そんなの使いたがる人はこの記事を読んでいないと思いますが・・・。

 FreeBSD使いの人はいるかもしれませんね。頑張って茨の道を歩んでください。応援しています。

 AndroidiPhoneなどのスマホタブレットに入れたがる人も時々います。対応した実装もあるといえばあるのですが、あまり使いやすいものではありませんから、素直にノートパソコンを買った方が有利です。

Web上実行環境

 OSではありませんが、実はWeb上でpython(に限らずいろいろな言語)を実行できるサービスがあります。たとえばWandboxなんてどうでしょう。

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

 こういった環境は、勉強とか、ちょっとした動作チェックには重宝します。それ以上でもそれ以下でもないのですが、とりあえず取っ掛かりとしてはありです。簡単に試してみたい、というときはここに入門書の内容などを打ち込んでいくと良いと思います。

結論

 今使ってるwindowsマシンにVMWare PlayerかVirtualBoxを落としてきて、ubuntuを入れましょう。

pythonを使う(直接インストールしてそのまま使う)

 さて、ubuntuを入れた方はターミナルで「python」と打つといきなりpythonが立ち上がると思います。今どきのUNIX系OS(事実上linux+Mac)であれば最初からpythonが入っているので、それが使える訳です。

 これをそのまま使う、というのは選択肢の一つです。ただ、2018年6月現在では、まだシステムデフォルトのpythonがpython2系のシステムがほとんどだと思います。できればpython3系を使いたいので、これはボツです。もしデフォルトがpython3系のシステムにあたったら、しばらくはそれを使って基本的な構文や機能の勉強をやれば良いと思います。

 システムのpythonがpython2系だけどpython3系を使いたい、という場合、システムにpython3をインストールすることができます。この場合、python2を消したり、置き換えたりはせず、python2とpython3を共存させるようにするのが無難です。python2はシステムの中で使っているプログラムがいろいろあるので、消してしまうとOSがまともに動かなくなります。

 また、python2系がデフォルトだけど、python3系も一緒に入っている、というシステムもあります(最近のubuntuはそうだったはずです)。これはしめたもので、そのまま使えばインストールの手間が省けますし、インストールで事故る確率も減らせます。ただし、pipなどは自分で入れてやる必要があったりするようです*4

 おそらく、python2とpython3が共存する環境では、「python」とターミナルに打ち込むと平然とpython2が立ち上がってくるはずです。そういうときは、慌てずに「python3」と打ち込みます。そうすると(ちゃんとインストールされていれば)python3が立ち上がるので、使うことができます。

 また、パッケージマネージャのpipも、(インストールされていれば)「pip」と打つとpython2に対応するpipが立ち上がってくるはずです。同様にpip3と打てばpip3が立ち上がるので、こちらを使います。パッケージマネージャについては次章で詳しく述べます。

パッケージマネージャについて

 パッケージマネージャは、主に外部ライブラリなどを管理するためのツールです。

 2018年6月現在では、間違いなくpipがスタンダードです。大抵のものがpipで入ります。というか、pipで入らないものは他のパッケージマネージャでも扱えないので、pip以外を使う意味がまったくありません(というか、そもそも存在しているの? レベル)。

 例外として、anaconda(あとで述べます)を使う人はcondaというanaconda独自のツールを使う必要があります。また、pipで扱えない、ソースコードで配布されているだけのパッケージも存在します。そういうものはsetup.pyなど*5を使って環境に入れます。大抵はパッケージのreadmeとかで親切に説明してくれているので、その通りにやれば良いです。

 さて、pipと一口に言っても、実はコマンドの打ち方はいろいろあります。

 もしインストール手順などで「pip install ○○」と指示されていたら、それは「自分の環境に合った方法でpipを使って入れろ」ってことです。この通りコマンドを打っても、ほぼ入りません。

 以下に大まかな傾向を書いておきますが、あくまでも参考情報です。極論すれば、pip自体のありかがわかって、権限の問題が解決できれば、コマンドの細かい違いはどうでも良いことです。でも、使い分ける上ではこれらは重要です。

  • sudo pip

 これは生pythonにインストールする場合です(そういう認識で構いません。普通にaptとかでインストールするとsudoが必要な場所に入ると思います)。sudoはunix系のコマンドであり、windowsだとsudoはないので、なにか違う方法で同様のことを実現する必要があります*6。また、python2とpython3を使い分けていれば「sudo pip3」というのもありえます。

  • pip

 ただのpip。仮想環境を使うと、これで済むと思います。逆に、仮想環境していないとただのpipで済む場面はあまりないとも言えます。

  • pip2、pip3

 システムに異なるバリエーションのpythonを複数インストールして、仮想環境等を使わずに使い分ける場合、区別のために数字付きのpipコマンドを使うことになります。インストール先にもよりますが、sudoを付ける必要があるかもしれません。また、python3.5とpython3.6を使い分けたい、という場合、pip3.5とpip3.6を使い分けます。面倒くさいですね。

 自分が使っているpythonに入れるにはどれを使えば良いのか、把握しておきましょう。

「pipでインストールしたのに使えません!」
「おまえ、違うpythonに入れてるじゃねえか!」

 というやり取り、割と頻繁にあるようです。

仮想環境編

 この章はあんまり書きたくはないのですが・・・。

 生python(インストールしたままのpython)を使うと、ミスって環境をぶっ壊しちゃうリスクがあります。管理もいろいろ面倒です。そこで、仮想環境を使うと良いですよ、ということがいろいろなところで言われています*7

 ぶっちゃけた話、仮想環境を作るのは初心者にはコストが高いです。まあ、ネットで調べてその通り打ち込むだけといえば、その通りなのですが、それでもけっこうハマりどころがあります。なので、入門書程度の内容をやっている間は、仮想環境は要らないと思います。pipでライブラリをがつがつ入れたくなったときに、検討してみてください。

 また、げんなりすることに、仮想環境は何種類もあります。互換性もないし、使い方も違うしで、どれを選べば良いのかよくわかりませんよね。安心してください。ある程度定石があります。

定石

 venvかvirtualenvのどちらかを使う。

 これで良いと思います。それぞれについては追って説明します。

venv

 おすすめ度:☆5 
 最近のスタンダードです。が、実は次節のvirtualenvがpython3.3で標準モジュールとして取り入れられたものと解釈して、ほとんど間違いありません。

 venvはシステムにインストールされているpython3.3以上に依存します。というか、python3.3以上のpythonが環境に入っていれば、自動的に使えます(よほど変な環境構築にしていない限り)。なので、最近のubuntuならこれを使えば良いですし、自分でpython3を入れてvenv、というのもありです。

 特に使いづらいとかもなく、普通に使えます。

virtualenv

 おすすめ度:☆4 
 一昔前のスタンダードでした。venvがpython3.3に入って以降は存在意義が薄くなっていますが、システムにpython2系しかなく、というかそもそも使うのがpython2系だけです、みたいな状況で使うのは今でもありです。

 これは外部ライブラリなので、pipでインストールする必要があります。

pyenv

 おすすめ度:☆3 
 一昔前にちょっと流行っていました。未だに記事をたくさん見かけますが、今から使いたいかと言われると、正直イヤです。venvでいいと思います。

 長所はpythonに依存しないことです。そのかわり、シェルスクリプトに依存します。当然windowsでは動きません。これを欠点と呼ぶかは悩みますが(記事の最初でwindowsは斬って捨ててる訳だし)、とにかくそういう特徴があります。

anaconda

 おすすめ度:☆3
 anacondaは有名ですね。環境構築の手間が省けて、統計や機械学習用のライブラリまで丸ごと入れられる、という奴です。しかし、個人的には☆3を付けます。

 anacondaの最大の欠点は、パッケージ管理周りがpipとはまったく別物なので、anaconda独自のハマりどころがたくさんある、ということです。ハマると面倒ですし、ちょっとマイナーな話題だとWebに情報があるかも怪しい、githubにissue上がっててもcloseしないまま何ヶ月も放置されてる、といった具合です。それを使いこなすくらいなら、最初からpip使ってほしいパッケージをしこしこ入れた方がマシです。

 それでも、環境構築のことは何も知らない人が、手軽に統計とか機械学習を試すためにあると思えは、anacondaは弁護できます。実際、numpyとかscipyとかcythonとかmatplotlibとかsklearnとかを自分でインストールしようとすると、それはそれで一筋縄ではいかないので、「その辺のライブラリをさっさと使いたいんです、授業が/研究が/研修が済んだらもうpythonなんか触らないんです」という状況であれば、anacondaはありです。逆に、長くpythonを使い続ける、いろいろなライブラリを入れたりして愛用していく、のであれば、やめた方が良いと個人的には思います。多少手間がかかっても、自分で環境構築した方が良いです。

 申し訳ありませんが、そういう認識です。

その他の仮想環境

 まだまだあるよ! といってもwrapper系や別名などです。採点はしません。

virtualenvwrapper

 名前の通り、virtualenvのラッパーです。使い勝手が改善されているらしいですが、使ったことがないのでわかりません。ごめんなさい。

 生virtualenvを使いづらいと感じたことは特にないので、これを使うならvirtualenvで良いと思います。まあ、最終的には好みの問題ですが、多少コマンドが打ちやすくなるとかのために不安要素を増やすかというと、Noです。

pyenv-virtualenv

 pyenvでvirtualenvwrapperに相当するものです。これも恐らく要らないと思います。

pyvenv

 venvの別名。あまりにpyenvと紛らわしいため(だと思います)、非推奨にされたそうです。

28.3. venv — 仮想環境の作成 — Python 3.6.5 ドキュメント

conda

 これはanacondaの管理ツールです。「conad install ○○」とかやって使うそうです。見た目はpipっぽいですが、中身はまったくの別物。

結論

 venvかvirtualenvのどちらかを使う。

 最初に言いたいことはまとめたので、特に追加で言うことはないです。

まとめ

 この記事に書いてあることをどこまで信頼するかは、あなた自身で決めてください。私は何も保証しません。それはあなたの責任です。

 自分の認識としては、この記事の内容自体はそんなに外していないとは思います。しかし、人によって考え方の違いもありますし、環境構築に正解なんてありません。動けば良いのです。せいぜい一つの指針にしてください。

 あと、間違いにお気づきになられた識者の方は、ご遠慮無くご指摘いただけると大変助かります。

*1:もちろん、探せばもっとひどい言語はあるだろうけど。スクリプト言語の平均よりは明らかに悪いという程度の意味

*2:信用に足る記事があるかはまた別ですが・・・新し目でまともそうなものを選ぼう、としか言えません

*3:というかライブラリとかがMacまでしっかり考えて作られていない・・・Macのパッケージマネージャもいまいちpythonに優しくない・・・

*4:参考:Ubuntu環境のPython - python.jp

*5:distutilsなどという。参考:28.1. distutils — Python モジュールの構築とインストール — Python 3.6.5 ドキュメント

*6:もしかしたらただのpipかもしれないし、管理者権限でやる必要があるかもしれない。インストール先等で変わってきます

*7:仮想環境とは何ぞや、という基本的なことについてはこちらを参照:仮想環境 - python.jp

【python】listをforループで回してremoveしたら思い通りにならない


 pythonプログラミングを始めたばかりの人がよくハマるネタです。日本語Web圏にはイマイチよくまとまった記事がないようなので、まとめておきます。

問題の概要

 たとえば、0から9のリストから偶数だけ取り出そうとして、こんなコードを書いてみます。

>>> lst = [0,1,2,3,4,5,6,7,8,9]
>>> for x in lst:
...     if x%2 != 0:
...         lst.remove(x)
... 
>>> lst
[0, 2, 4, 6, 8]

 一見すると上手く動いているようです。調子に乗って、今度は3の倍数を取り出そうとしてみます。

>>> lst = [0,1,2,3,4,5,6,7,8,9]
>>> for x in lst:
...     if x%3 != 0:
...         lst.remove(x)
... 
>>> lst
[0, 2, 3, 5, 6, 8, 9]

 おかしくなった。なぜでしょう? forがちゃんと動いていない? という感じで、ハマります。

原因

 こういうときはforのループごとにxに代入されている値をprintしてみると、どんなことになっているのかよくわかります。

>>> lst = [0,1,2,3,4,5,6,7,8,9]
>>> for x in lst:
...     print(x)
...     if x%2 != 0:
...         lst.remove(x)
... 
0
1
3
5
7
9
>>> lst
[0, 2, 4, 6, 8]
>>> lst = [0,1,2,3,4,5,6,7,8,9]
>>> for x in lst:
...     print(x)
...     if x%3 != 0:
...         lst.remove(x)
... 
0
1
3
4
6
7
9
>>> lst
[0, 2, 3, 5, 6, 8, 9]

 なんてことでしょう、ちゃんと動いていない!

 ・・・これはドキュメントにも書いてある、pythonのれっきとした仕様です。

注釈 ループ中でのシーケンスの変更には微妙な問題があります (これはミュータブルなシーケンス、すなわちリストなどでのみ起こります)。どの要素が次に使われるかを追跡するために、内部的なカウンタが使われており、このカウンタは反復のたびに加算されます。このカウンタがシーケンスの長さに達すると、ループは終了します。このことから、スイート中でシーケンスから現在の (または以前の) 要素を除去すると、(次の要素のインデクスは、すでに取り扱った現在の要素のインデクスになるために) 次の要素が飛ばされることになります。(※筆者強調) 同様に、スイート中でシーケンス中の現在の要素以前に要素を挿入すると、現在の要素がループの次の週で再度扱われることになります。こうした仕様は、厄介なバグにつながります。

8. 複合文 (compound statement) — Python 3.6.5 ドキュメント

 インタプリタの中では、カウンタで管理しているんですね。それが原因です。

回避策

 とりあえず、公式ドキュメントにはこのような方法が記載されています。

for x in a[:]:
    if x < 0: a.remove(x)

 ここで[:]というのは範囲指定なしのスライスです。これはこのように機能します。

>>> lst = [0,1,2,3,4,5,6,7,8,9]
>>> lst[:]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

 まったく無意味な気がしますが、実は両者は別のオブジェクトになっています。id()関数で確認してみます。

>>> id(lst)
140603063055816
>>> id(lst[:])
140603063055048

 つまり、[:]は中身の同じコピーを作ることができます。こうすればループの対象のリストは変更されないので、問題なくループを回せるという訳ですね。

 でもこういうコードはちょっとかっこ悪いので、内包表記を使った方が基本的にはベターです。

>>> lst = [0,1,2,3,4,5,6,7,8,9]
>>> [x for x in lst if x%3 == 0]  # 条件の反転に注意(残すものの条件を指定する)
[0, 3, 6, 9]
>>> lst  # 上のコードは新しいリストを作る。元のリストは変わらない
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> lst = [x for x in lst if x%3 == 0]  # 再代入するとlstの値が変わる(ただし別のオブジェクトになる)
>>> lst
[0, 3, 6, 9]

 どうしても同じオブジェクトをいじらないといけない、というシチュエーションも往々にしてありますが、そうでなければ内包表記などを使って新しくリストを作る、という発想で書いた方が簡単ですし、へんなバグも生みません。

 余談ですが、pythonではlist.remove()はあまり使わないメソッドです。他にもlist.pop()やlist.insert()などリストを操作するメソッドはたくさんありますが、これらをforループと組み合わせて書くような操作は、大抵の場合は内包表記などで代用できます。そして、その方が元のリストを壊さないので、バグが発生する余地が減ります*1*2

 なので、初心者の方はあまりこういったものに頼らず、まずは内包表記から覚えるか、内包表記がとっつきづらければ空listにappendしていく方法を使うのが良いと思います。

>>> lst = [0,1,2,3,4,5,6,7,8,9]
>>> result = []
>>> for x in lst:
...     if x%3 == 0:
...         result.append(x)
... 
>>> result
[0, 3, 6, 9]

 これはappendで書く場合の例です。実はリスト内包表記とほとんど同じようなことをやっているのですが、最初はこちらの方が読みやすいかもしれません。

まとめ

 pythonってけっこう直感的じゃない仕様があるので、「なんで!?」と思うこともままありますね。でも、どうせ慣れれば、そういう仕様は使わないで済ませられるようになってくるので、大丈夫です*3

 ある程度慣れるまでは、「listをforループで回すときは、回しているlist自体はいじらないで処理する」ことを心がけると気が楽だと思います。

*1:この考え方はけっこう重要です。こういうオブジェクトの状態を変更する操作を破壊的操作といいますが、これはよく把握していないとわかりづらいバグを生みやすいです

*2:他にも、特にlist.remove()はけっこうコストが高い(該当する要素が見つかるまで線形探索する)という理由があり、嫌われがちなメソッドです

*3:むしろプログラムの難読化に応用する人がいそうな気もする

【python】pandasのDataFrameをLaTeX出力

 そんな機能があるらしい。DataFrame.to_latex()です。

pandas.DataFrame.to_latex — pandas 0.21.1 documentation

 これが使えると何かの役に立つかもしれないので、使い物になるかどうか確認してみる。

お試し

 とりあえず、てきとーにdfを作ってみる。中身に意味はないけど、意味のないdfをできるだけ手っ取り早く作りたかったのでnumpy配列から作っている。*1

>>> import numpy as np
>>> import pandas as pd
>>> df = pd.DataFrame(np.arange(32).reshape(8,4), columns=list("abcd"))
>>> df
    a   b   c   d
0   0   1   2   3
1   4   5   6   7
2   8   9  10  11
3  12  13  14  15
4  16  17  18  19
5  20  21  22  23
6  24  25  26  27
7  28  29  30  31

 そのまま何も考えず、to_latex()を呼ぶ。strで返っても都合が悪いのでprintしてみる。

>>> print(df.to_latex())
\begin{tabular}{lrrrr}
\toprule
{} &   a &   b &   c &   d \\
\midrule
0 &   0 &   1 &   2 &   3 \\
1 &   4 &   5 &   6 &   7 \\
2 &   8 &   9 &  10 &  11 \\
3 &  12 &  13 &  14 &  15 \\
4 &  16 &  17 &  18 &  19 \\
5 &  20 &  21 &  22 &  23 \\
6 &  24 &  25 &  26 &  27 \\
7 &  28 &  29 &  30 &  31 \\
\bottomrule
\end{tabular}

 そしたらこれを別途作ったTeXのソースに貼る。ドキュメント曰く、

Render an object to a tabular environment table. You can splice this into a LaTeX document. Requires \usepackage{booktabs}.

 (強調は僕が勝手に付けたもの)

 ということらしい。とにかく次のようなTeXファイルを作ってみた。

\documentclass{jsarticle}
\usepackage{booktabs}

\begin{document}

\begin{table}[h]
\begin{tabular}{lrrrr}
\toprule
{} &   a &   b &   c &   d \\
\midrule
0 &   0 &   1 &   2 &   3 \\
1 &   4 &   5 &   6 &   7 \\
2 &   8 &   9 &  10 &  11 \\
3 &  12 &  13 &  14 &  15 \\
4 &  16 &  17 &  18 &  19 \\
5 &  20 &  21 &  22 &  23 \\
6 &  24 &  25 &  26 &  27 \\
7 &  28 &  29 &  30 &  31 \\
\bottomrule
\end{tabular}
\end{table}

\end{document}

 TeXとかよくわからないけど、これでコンパイルできてこんな結果が得られた。

できた表

 なるほど、できてますね。論文でよく見かける罫線の少ない表です。カッコいい気もするし、罫線多めのちょいダサな表の方が安心感があって良いような気もするという、人によって好みの分かれる奴です。

カスタマイズしてみよう

 たかがto_latex()なのに、なんかいろいろ引数があります。公式をまとめておきます。

  • bold_rows : boolean, default False

 インデックス列の文字がboldになる

  • column_format : str, default None

 \begin{tabular}{}の{}の中に入る列の書式を文字列で渡す

  • longtable : boolean, default will be read from the pandas config module Default: False

 TeXのlongtableだって。参考(外部サイト):[LaTeX]長い表を表示する

  • escape : boolean, default will be read from the pandas config module Default: True.

 エスケープがうまく効くかどうかにかかってくるんだと思う

  • encoding : str, default None

 何も指定しないとpython2はascii, python3はutf-8になるらしい。

  • decimal : string, default ‘.’

 Character recognized as decimal separator, e.g. ‘,’ in Europe.
 (説明を読んでもよくわからん)

  • multicolumn : boolean, default True

Use multicolumn to enhance MultiIndex columns. The default will be read from the config module.

  • multicolumn_format : str, default ‘l’

The alignment for multicolumns, similar to column_format The default will be read from the config module.

  • multirow : boolean, default False

Use multirow to enhance MultiIndex rows. Requires adding a \usepackage{multirow} to your LaTeX preamble. Will print centered labels (instead of top-aligned) across the contained rows, separating groups via clines. The default will be read from the pandas config module.

 上の3つは使い方がよくわからない。まあ、たぶん使えば使えるんだろう。

 せっかくなので、インデックス列bold、罫線多めな表を作ってみようと思う。見た目がダサくなるはずだ。

>>> print(df.to_latex(bold_rows=True, column_format="|l|l|l|l|"))
\begin{tabular}{|l|l|l|l|}
\toprule
{} &   a &   b &   c &   d \\
\midrule
\textbf{0} &   0 &   1 &   2 &   3 \\
\textbf{1} &   4 &   5 &   6 &   7 \\
\textbf{2} &   8 &   9 &  10 &  11 \\
\textbf{3} &  12 &  13 &  14 &  15 \\
\textbf{4} &  16 &  17 &  18 &  19 \\
\textbf{5} &  20 &  21 &  22 &  23 \\
\textbf{6} &  24 &  25 &  26 &  27 \\
\textbf{7} &  28 &  29 &  30 &  31 \\
\bottomrule
\end{tabular}
\documentclass{jsarticle}
\usepackage{booktabs}

\begin{document}

\begin{table}[h]
\begin{tabular}{|l|l|l|l|l|}
\toprule
{} &   a &   b &   c &   d \\
\midrule
\textbf{0} &   0 &   1 &   2 &   3 \\
\textbf{1} &   4 &   5 &   6 &   7 \\
\textbf{2} &   8 &   9 &  10 &  11 \\
\textbf{3} &  12 &  13 &  14 &  15 \\
\textbf{4} &  16 &  17 &  18 &  19 \\
\textbf{5} &  20 &  21 &  22 &  23 \\
\textbf{6} &  24 &  25 &  26 &  27 \\
\textbf{7} &  28 &  29 &  30 &  31 \\
\bottomrule
\end{tabular}
\end{table}

\end{document}

 結果は、

f:id:hayataka2049:20180531015049p:plain

 なんか思ってたのと違う・・・\*rule系と縦罫線の相性が悪いので、\hlineに変えてみる(TeXソースを直接いじって)。

\documentclass{jsarticle}
\usepackage{booktabs}

\begin{document}

\begin{table}[h]
\begin{tabular}{|l|l|l|l|l|}
\hline
{} &   a &   b &   c &   d \\
\hline
\textbf{0} &   0 &   1 &   2 &   3 \\
\textbf{1} &   4 &   5 &   6 &   7 \\
\textbf{2} &   8 &   9 &  10 &  11 \\
\textbf{3} &  12 &  13 &  14 &  15 \\
\textbf{4} &  16 &  17 &  18 &  19 \\
\textbf{5} &  20 &  21 &  22 &  23 \\
\textbf{6} &  24 &  25 &  26 &  27 \\
\textbf{7} &  28 &  29 &  30 &  31 \\
\hline
\end{tabular}
\end{table}

\end{document}

f:id:hayataka2049:20180531015313p:plain

 これは期待通りの結果だが、わざわざpandasが出力されるものをいじってこうしたいか? と考えると、デフォルトで吐き出されたものをそのまま使った方が潔いかもしれない。

まとめ

 使えるか? というと、とても微妙な機能ですが、考えようによっては、データをDataFrameに入れさえすれば、TeXの表組みと格闘する必要が一切なくなります。
(デフォルトで出てきた表の見た目に満足できれば)

 なので、それなりにおすすめです。

*1:pandasの機能を試すときって、試すためのdf作るのがそもそも面倒くさいということが往々にしてある。みんなはどうやってるんだろうか

VMware Playerでキャッシュを削除して仮想ディスクの容量を空ける(linux)

 VMware Playerはホストとゲスト間で、ドラッグ・アンド・ドロップやコピ・アンド・ペーストによってファイルを移動できる。

 便利な機能なので頻繁に使ってしまうが、これは腹立たしいことにゲストの仮想ディスク上にキャッシュを生成する。そしてこのキャッシュはなぜか勝手に消えてくれないので、気がつくとかなりディスク容量を圧迫してたりする。

 その消し方を備忘録としてメモ。

  1. ~/.cache/vmware/drag_and_drop/をまるごと消す

 以上。他にやることは特にない。

 僕が使っているのはubuntuだけど、たぶんlinux系なら何でも同じ場所にあるんだと思う(未確認)。もしかしたらVMware Playerのバージョンによっては場所が違うとかあるかもしれないけど、そのときはvmwareって名前の付いたディレクトリを検索すれば出てくると思う。

 放って置くとどんどん大きくなるので、たまに消してあげよう。これは仮想ディスクがいっぱいになっちゃった! というとき、とりあえず容量を空ける方法としても役に立つ。