numpyのarrayを並び替えた結果をインデックスで取得する

データを大きい順や小さい順に並び替えることはよくある操作ですが、
その結果として、”n番目の値は何だったのか?”よりも、”何番目の要素がn番目だったのか?”を知りたいケースは地味にあります。
[241,631,362,222,2,44] のようなデータがあって、 最大値は631だってことよりも、
最大値のindexは1だ(0から始まることに注意)ってことを得たい場合ですね。

そのような時、ソートした結果をインデックスのリストで得られると便利です。
それは numpyのargsortで得られます。
numpy.argsort

通常の並べ替え結果を返してくれる sort とそれぞれ実行してみましょう。

それぞれドキュメントを見るといくつか引数が用意されていて、多次元の場合の挙動や、ソートアルゴリズムなどを指定できます。
しかし、自分にとってはその中に昇順/降順の指定が”ない”ことの方が重要です。
デフォルトで昇順にソートするので、降順がいい時はどは別途指定します。

それでは、sortとargsortを昇順降順で試します。


import numpy as np
data = [241, 631, 362, 222, 2, 44]

print(list(np.sort(data)))
# [2, 44, 222, 241, 362, 631]
print(list(np.argsort(data)))
# [4, 5, 3, 0, 2, 1]

# 降順にしたい時は[::-1]を使う
print(list(np.sort(data))[::-1])
# [631, 362, 241, 222, 44, 2]
print(list(np.argsort(data))[::-1])
# [1, 2, 0, 3, 5, 4]

うまく動いていますね。

このブログでもすでに次の記事のコード中で利用しています。
参考:pythonでトピックモデル(LDA)

mecabの新語辞書をインストールする

非常に便利な形態素解析ソフトMeCabですが、デフォルトの辞書は最近登場した単語に弱いと言う欠点があります。
自分で辞書を改善していけたらいいのですがそれも工数が大きくめんどくさい。

以上の状況において、定期的にメンテナンスされている非常にありがたい辞書があります。
mecab-ipadic-neologd

インストール手順はリポジトリの README.ja.mdにある通りです。


$ cd ~/Downloads
$ git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
$ cd mecab-ipadic-neologd
# -a をつけると全部入り
$ ./bin/install-mecab-ipadic-neologd -n -a

途中で
Do you want to install mecab-ipadic-NEologd? Type yes or no.
と聞かれるのでこれは yes.

ドキュメントにある通り、インストール先は次のコマンドで確認できます。
(出力は僕の環境の例)


$ echo `mecab-config --dicdir`"/mecab-ipadic-neologd"
/usr/local/lib/mecab/dic/mecab-ipadic-neologd

動作確認してみましょう。
まずデフォルト辞書の場合。


~$ mecab
志村五郎先生は偉大な数学者でした。
志村	名詞,固有名詞,人名,姓,*,*,志村,シムラ,シムラ
五郎	名詞,固有名詞,人名,名,*,*,五郎,ゴロウ,ゴロー
先生	名詞,一般,*,*,*,*,先生,センセイ,センセイ
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
偉大	名詞,形容動詞語幹,*,*,*,*,偉大,イダイ,イダイ
な	助動詞,*,*,*,特殊・ダ,体言接続,だ,ナ,ナ
数学	名詞,一般,*,*,*,*,数学,スウガク,スーガク
者	名詞,接尾,一般,*,*,*,者,シャ,シャ
でし	助動詞,*,*,*,特殊・デス,連用形,です,デシ,デシ
た	助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。	記号,句点,*,*,*,*,。,。,。
EOS

neologdの場合


~$ mecab -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd
志村五郎先生は偉大な数学者でした。
志村五郎	名詞,固有名詞,人名,一般,*,*,志村五郎,シムラゴロウ,シムラゴロー
先生	名詞,一般,*,*,*,*,先生,センセイ,センセイ
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
偉大	名詞,形容動詞語幹,*,*,*,*,偉大,イダイ,イダイ
な	助動詞,*,*,*,特殊・ダ,体言接続,だ,ナ,ナ
数学者	名詞,固有名詞,一般,*,*,*,数学者,スウガクシャ,スーガクシャ
でし	助動詞,*,*,*,特殊・デス,連用形,です,デシ,デシ
た	助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。	記号,句点,*,*,*,*,。,。,。
EOS

志村五郎先生の名前が固有名詞として認識されていますね。
数学者も。

cloneしてきた辞書ファイルの中を見るとこのように登録されていることがわかります。


mecab-ipadic-neologd$ grep 志村五郎 * -r
build/mecab-ipadic-2.7.0-20070801-neologd-20190610/mecab-user-dict-seed.20190610.csv:志村五郎,1289,1289,4757,名詞,固有名詞,人名,一般,*,*,志村五郎,シムラゴロウ,シムラゴロー

magic function 自体の説明を見る

jupyter notebook の便利な機能に magic functionがあります。
% や %% で始まるあれですね。
このblog記事中でも、処理時間を測ったり、トレジャーデータに接続したりと使ってます。

ただ、個々の関数の使い方の説明はわかっても、そもそも magic functionとは何者か、
という点の理解をこれまでおろそかにしてました。

それで、何を読めばいいのかちょっと調べていたのですが、
jupyter notebookで、%magicを実行すると、magic functionの説明が出てくるようです。

結構な長文が表示されたので、この記事中に貼り付けるようなことはしないのですが、
ザーッと眺めた限りでも自分が知らなかった情報が多く含まれているようです。

よく使うものについてはこのブログでも今後取り上げていきたいですが、
取り急ぎ、%magicを実行して出てきた文章を読んでいただくと色々参考になるのではないでしょうか。

matplotlibのhist()の戻り値

matplotlibでヒストグラムを書く時、次のように、hist()を使います。(リンク先は公式ドキュメント)


import matplotlib.pyplot as plt
import numpy as np
data = np.random.randn(100) * 10
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.hist(data)
plt.show()

ついこの間まで知らなかったのですが、ドキュメントによると、ax.hist(data) は戻り値を返しています。
戻されるのは、各区間の度数と、区間の区切り位置、描写に使われているmatplotlibのオブジェクトの3つです。
これらのうち、度数と区切り位置を取れるのは可視化とその他の集計を整合性を保ちながら行うのに便利そうです。

とりあえず、戻り値を受け取れるようにしてもう一回やってみましょう。
(図は今回省略します。)


import matplotlib.pyplot as plt
import numpy as np
data = np.random.randn(100) * 10
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
n, bins, patches = ax.hist(data)
plt.show()
print(n)
print(bins)
print(patches)

# 以下出力 (図は略)
[ 1.  5.  6. 18. 14. 16. 21. 13.  4.  2.]
[-29.53118806 -24.17059351 -18.80999896 -13.44940441  -8.08880985
  -2.7282153    2.63237925   7.9929738   13.35356835  18.7141629
  24.07475745]

ビンの数は今回何も指定していないので、デフォルトの10個です。
そのため、度数の配列nには10個の要素が含まれ、
区切り位置binsは右端左端が両方入っているので、11この要素を持つ配列になっています。

pythonの関数中でグローバル変数に代入する

前の記事のフィボナッチ数列の実装の中で、関数が呼び出された回数を数えるために少し仕掛けをいれました。
参考:pythonの関数をメモ化する

で、その中で globalと言うのを使っているのでその解説です。

ドキュメントはこちら。
global文

pythonでは、関数の中でグローバル変数を”参照”することができます。
しかし、そのままではグローバル変数に”代入”はできません。

やってみましょう。
まずは、”参照”ができることの確認。


a = 1


def sample1():
    # グローバル変数aの値を参照できるためaの値1を戻す。
    return a


sample1()  # 1

次に、代入を試してみます。ちょっとわかりにくいですが、グローバル変数bを用意し、
関数中でbに代入していますがこのbが実はローカルの変数で、
関数の外でもともと宣言されていたbに影響していないことがわかります。


b = 2


def sample2():
    # グローバル変数bへの代入はできないので、このbはローカル変数
    b = 3
    print(b)


sample2()  # 3
# 変数bは変更されていない
print(b)  # 2

これが、global文を使うと、グローバル変数への代入が可能になります。


c = 10


def sample3():
    global c
    c = 20
    print(c)


sample3()  # 20
# 関数中の変更が反映されている
print(c)  # 20

pythonの関数をメモ化する

プログラムを書いていると再帰呼び出しする関数や整数値を引数にとる関数など、同じ値を渡して何度も実行される関数があります。
そのような場合、メモリーが十分あるのであれば毎回毎回処理を実行するより結果を保存しておく方が効率的です。

それを、メモ化(Memoization)と言うそうです。
メモ化 – Wikipedia

自分で実装するのも難しくないですが、pythonでメモ化を行うには、
@functools.lru_cache
と言う便利なデコレーターが用意されています。

ちなみに、lruは、Least Recently Usedの略です。
キャッシュアルゴリズム – Wikipedia

フィボナッチ数列で実際に使ってみましょう。
また、実際に効率化できている(関数中の処理の実行回数が減っている)ことを確認するために、
その関数が何回呼び出されたのかを記録し、表示します。
また、実行時間も測りましょう。

まず、メモ化をしない例。(jupyter notebookで動作させることを前提としたコードです。)


%%time
fib_count = 0  # 関数が呼び出された回数記録用


def fib(n):
    global fib_count
    fib_count = fib_count + 1
    if n <= 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)


print("F(100)=", fib(30))
print("実行回数:", fib_count)

# 以下出力
F(100)= 832040
実行回数: 2692537
CPU times: user 449 ms, sys: 3.35 ms, total: 453 ms
Wall time: 456 ms

fibが何度も繰り返し実行され、非常に無駄の多い実装になっていることがわかります。

続いて、lru_cacheでメモ化した例です。


%%time
from functools import lru_cache
fib_memo_count = 0  # 関数が呼び出された回数記録用


@lru_cache(maxsize=None)
def fib_memo(n):
    global fib_memo_count
    fib_memo_count = fib_memo_count + 1
    if n <= 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib_memo(n-1) + fib_memo(n-2)


print("F(100)=", fib_memo(30))
print("実行回数:", fib_memo_count)

# 以下出力
F(100)= 832040
実行回数: 31
CPU times: user 229 µs, sys: 120 µs, total: 349 µs
Wall time: 311 µs

こんどのfib_memoは31回しか実行されませんでした。
n=0,...,30 でそれぞれ1回ずつですね。
処理時間も桁違いに早くなっています。

チェビシェフの不等式

たまには何か統計学の話題をだそう、と言うことでチェビシェフの不等式を紹介します。

確率変数$X$に対して、$\mu=E(X)$, $\sigma^2=V(X)$とすると、次の不等式が成立します。
$$
P(|X-\mu|\geq k\sigma) \leq \frac{1}{k^2}
$$

この式の便利さはどんな確率変数についても成立する点にあります。

もっとも、確率変数が従う分布がわかっていれば、これよりはるかに正確に見積もることができます。
ただ、実際の運用上、これは正規分布だとかこれは一様分布だとか何かしら保証されていることは稀で、
正確な分布はわからないけど、標本平均と標本分散だけは取れてて、
平均から大きく外れる確率をざっくり見積もりたいと言う場面は実際にあるのでそのような時に使えます。
(分布の大まかな形から正規分布だとか仮定してしまうことも多いのですが。)

参考文献 : 基礎統計学Ⅰ 統計学入門 (東京大学出版会) P104 5.4 チェビシェフの不等式

ディレクトリやファイルの権限を一括で修正する

職場で使っている端末の調子が悪く、新端末に交換することになったので大量のファイルを移行しました。
その際、一度NASにファイルを移して持って行ったのですが、
なぜかファイルやディレクトリの権限が全部 777 になってしまうと言う面倒な事態になりました。

chmod で一つ一つ直すのが面倒だったので一括で修正するコマンドがわかったので紹介します。

前提として、カレントディレクトリ”.”配下のすべてのファイルとディレクトリを対象に、
ファイルは644、ディレクトリは755に設定するには次のコマンドを実行します。
カレント以外を対象にする場合は、”.” の代わりにそのPathを入力したらできます。


find . -type d -exec chmod 755 {} +
find . -type f -exec chmod 644 {} +

一応解説。
find . -type d
と、
find . -type f
はそれぞれ、カレントディレクトリ配下の ディレクトリとファイルの一覧を取得します。
そして、 -exec はその後ろに書かれたコマンドを{}を find で見つけたpathに置換して実行します。
+ をつけておくと、各ファイル/ディレクトリに対してコマンドを個別に実行するのではなく、
まとめて実行してくれます。

詳細は man find で、マニュアルを読むのが確実です。

jupyter notebookからトレジャーデータに接続する

最近まで pandas-td を使っていましたが、 トレジャーデータのかたから pytd というライブラリを教えていただきましたのでその紹介です。
pytdのリポジトリ

インストールは pipできます。


pip install pytd

また、準備として環境変数の TD_API_KEY に APIキーをセットしておきましょう。
(write onlyではなく マスターKeyを。)

README.md には connect を使って接続して、queryを発行するサンプルコードが乗っていますが、
個人的にはマジックファンクションを使う方が断然お勧めです。


%load_ext pytd.pandas_td.ipython

とすると、 pandas_td と互換のマジックファンクションが使えるようになります。
(README に %%load_ext pytd.pandas_td.ipython って乗ってますが、これだと%が多くてエラーになります)


%%td_presto {データベース名} -o {結果の格納先変数名}
{ここにSQLを書く}

と言う構文で、jupyterでSQLを実行でき、しかも結果が pandas のデータフレームに格納されるので非常にスムーズに
以降の分析に入ることができます。

pandas-td から移行したばかりなのですが、心なしかpandas-tdより動作が速いのもいいですね。

pythonで集合(set)の包含関係を判定する

実は最近まで知らなかったのですが、pythonの集合(set)同士の包含関係を不等号で確認することができます。

ドキュメントはこちら
組み込み型 set(集合)型

念のため軽く用語の説明をしておくと、
集合Aと集合Bに対して、
Aの任意の要素xがBの要素でもある時、AはBの部分集合(subset)であるといいます。
それを記号で、 $A\subseteq B$ と書きます。
左右反転して、 $B\supseteq A$ と書くこともできます。
また、さらに集合Aと集合Bが等しくない時は、真部分集合(もしくは狭義の部分集合)といい、
$A\subset B$ もしくは $B\supset A$と書きます。

これをpythonではそれぞれ、 <=, >=, <, >, で計算できます。
戻り値はTrueかFalseです。
いくつかやってみましょう。


{1, 3, 5} <= {1, 2, 3, 4, 5}
# True

{1, 3, 5, 7} <= {1, 2, 3, 4, 5}
# False

{1, 3, 5} >= {1, 3, 5}
# True

{1, 3, 5} > {1, 3, 5}
# Flase