StatsBeginner: 初学者の統計学習ノート

初学者が統計学、機械学習、R、Pythonの勉強の過程をメモっていくノート。

Pythonメモ: Pandasで文字列検索をループするとかなり遅かった

 前回のエントリで、Pandasのデータフレームを文字列で検索すると遅かったと書いたんですが、実際に検索の方法を変えるとどれぐらい違うのかを計測してみました。
 結論から言えば、辞書型に変換してから検索したらだいぶ速くなったのですが、それが凄いというよりは、Pandasの基本がまだ分かってないということかなと思いました。こうやると速いとか遅いとかいう記事はいろいろあるみたいなのでまた勉強したいと思います。


 一応、前回のエントリでやった方法を確認のため比較することとして、PN Tableに載っている単語をランダムに1万個選んだ上で、1個1個についてPN値を選択して取ってくるという処理の時間を測ります。
 Pandas内での工夫についてはよくしらないので、Pandasではない形で検索した場合と比べました。
 時間を図る方法は単純に、処理の直前と直後に時刻を取得して引き算するだけにしました。


 まずパターン1では、単純にPN Tableを読み込んだPandasのデータフレームで、単語の列を検索してヒットした行のPN値を取ってきます。
 次にパターン2では、単語の列とPN値の列をそれぞれリストにしておいて、先に単語のリストを検索してインデックス番号を取得し、そのインデックスでPN値のリストから値を取得します。
 最後にパターン3では、単語の列をリストにしたものと、PN値の列をリストにしたものを結合した辞書(dict型)を作っておき、この辞書をキーで検索してPN値を取得します。
 なお、PN Tableの行数は55125行です。

import pandas as pd
import random
import time

# PN Tableを読み込み(パスは適当に)
pn_df = pd.read_csv('dictionary/PN_Table/pn_ja.dic.txt',\
                    sep=':',
                    encoding='utf-8',
                    names=('Word','Reading','POS', 'PN')
                   )

# PN Tableに載っている単語を10000個抽出
word_list = list(pn_df['Word'])               # 語のリスト
searchlist = random.sample(word_list, 10000)  # 探す単語をランダムに決める


# --- パターン1:pandasで純粋に検索 --- #
start_time = time.time()          ### ▼時間計測▼ ###
pns1 = []
for w in searchlist:
    pn = pn_df.loc[pn_df.Word == w, 'PN']
    if len(pn) == 1:
        pns1.append(float(pn))
    else:
        pns1.append(0.0)
print(time.time() - start_time)   ### ▲時間計測▲ ###

# --- パターン2:リストにしてインデックスで検索 --- #
start_time = time.time()          ### ▼時間計測▼ ###
word_list = list(pn_df['Word'])               # 語のリスト
pn_list = list(pn_df['PN'])                   # PN値のリスト
pns2 = []
for w in searchlist:
    if w in word_list:
        ix = word_list.index(w)  # 先にインデックスを取得
        pn = float(pn_list[ix])  # 同じインデックスでPNリストにアクセス
    else:
        pn = 0.0
    pns2.append(pn)
print(time.time() - start_time)   ### ▲時間計測▲ ###

# --- パターン3:辞書にしてキーで検索 --- #
start_time = time.time()          ### ▼時間計測▼ ###
pn_list = list(pn_df['PN'])                   # PN値のリスト
pn_dict = dict(zip(word_list, pn_list))  # さっきのリストと合体して辞書に
pn3 =[]
for w in searchlist:
    if w in pn_dict:
        pn3.append(float(pn_dict[w]))
    else:
        pn3.append(0.0)
print(time.time() - start_time)   ### ▲時間計測▲ ###


 それぞれの時間をみてみると、

# パターン1
>>> 38.16759991645813

# パターン2
>>> 15.531062126159668

# パターン3
>>> 1.524580955505371


 となりました。何回かやってみたけどだいたい似たようなもんでした。
 辞書型に変えてから検索すると、このテストの処理で25倍速ということになりました。
 これが、昨日のエントリで行ったツイートの解析処理だと、色々ループしていることもあって、1万3000ツイートの解析を行った時間でいうと200倍速ぐらいになったわけです。


 そもそもPandasではない形にしてしまっているので無理矢理感があり、もっと勉強しないとなと思います。そもそもデータフレームに対してループで処理するのって適切なんですかね。whereなどSQLライクな関数で取ってくるべき?
 ただまぁ、とりあえず昨日やった処理のどの部分で時間がかかっているかを特定し、そこだけ方法を変えることで全体の処理が圧倒的に早まるという、目先の目的は達成されたのでよかったです。