なんでもかんでもリストで済ませようというのは止めよう。
numpy.ndarray を使うともっといいよ。
ということだが,場合によってはそれよりいいものもあるよという話。
いずれにせよ,過度にリストに頼るのは止めようよという話
その 1
1000万文字の大文字アルファベットが空白で区切られて 1 行に入力されている char.dat というファイルを対象にする。
C P U H I J R S W Q H W R ....
「それぞれのアルファベットが何個あるか集計せよ」というタスクを考えよう。
from time import time
以下のようにデータを入力する
f = open("char.dat")
a = f.read()
f.close()
b = a.split()
# ここまでは共通
まず,リストと辞書を使って書いてみる
start = time()
c = sorted(set(b))
tbl = {}
for i in c:
tbl.setdefault(i, b.count(i))
for i, j in zip(tbl.keys(), tbl.values()):
print(i, j)
print(time() - start) # 5.874 sec.
所要時間は 5.874 秒。簡単なものだ。
(ちなみに,コメントに示す awk プログラムでは 3.846 秒だから)
リストだけを使って書くと 16.096 秒 となり,かなり遅い。
start = time()
c = sorted(set(b))
count = []
for i in c:
count.append(b.count(i))
for i, j in zip(c, count):
print(i, j)
print(time() - start) # 16.096sec.
次は numpy.ndarray を使って書いてみる。
numpy を最初に import するのに必要な時間を除いて実行時間を計測する。
import numpy as np
start = time()
value, count = np.unique(b, return_counts=True)
for i, j in zip(value, count):
print(i, j)
print(time() - start) # 1.686 sec.
所要時間は 1.686 秒。辞書型を使うよりは速い。
しかも,集計部分は
value, count = np.unique(b, return_counts=True)
の一行だ。
その 2
もう少し大きい数値データで見てみよう。
1500 変数,100000 行の CSV ファイルで,1行目は変数名である。
2行目以降は平均値 50,標準偏差 10 の正規乱数が小数点以下 2 桁で記録されている。
今回は,変数の区別なく, np.floor() で整数化し,総計 1500 x 100000 個の変数の度数分布を見る。
ファイルからデータを読み,np.floor でリストにする
import re
start = time()
f = open("test.csv")
a = f.read()
f.close()
b = re.sub(r'[",\n]', ' ', a)
x = list(map(np.floor, map(float, b.split()[1500:]))) # list
print(time() - start) # 471.080 sec.
入力だけでも 471.080 秒もかかるのだけど,さらに...
辞書型を使って集計--とんでもなく遅い 624.511 秒
start = time()
c = sorted(set(x))
tbl = {}
for i in c:
tbl.setdefault(i, x.count(i))
print(time() - start) # 624.511 sec.
# (dict_keys([-7.0, -5.0, -4.0, -3.0, -2.0, -1.0, 0.0,
# dict_values([1, 2, 3, 6, 10, 21, 35,
np.unique() を使って集計--かなり速い 27.528 秒
start = time()
value, count = np.unique(x, return_counts=True)
print(time() - start) # 27.528 sec.
# (array([ -7., -5., -4., -3., -2., -1., 0.,
# array([ 1, 2, 3, 6, 10, 21, 35,
pandas でデータを入力し,np.ravel(), np.floor() で整数化して numpy.ndarray とする
import pandas as pd
start = time()
df = pd.read_csv('test.csv')
y = np.floor(np.ravel(df)) # numpy.ndarray
print(time() - start) # 33.201 sec.
start = time()
value, count = np.unique(y, return_counts=True)
print(time() - start) # 7.583 sec.
# (array([ -7., -5., -4., -3., -2., -1., 0.,
# array([ 1, 2, 3, 6, 10, 21, 35,
pandas の value_counts() を使うと,爆速 2.121 秒 一番速い
start = time()
ans = pd.value_counts(y)
ans2 = ans.sort_index()
print(time() - start) # 2.121 sec.
# -7.0 1
# -5.0 2
# -4.0 3
# -3.0 6
# -2.0 10
# ..
# 106.0 1
なお,R だと...遅い
> system.time({
+ df = read.csv("test.csv", colClasses="double")
+ x = as.integer(data.matrix(df))
+ tbl = table(x)
+ })
ユーザ システム 経過
50.547 5.324 55.960
getline a
n = split(a, arry, " ")
for (i = 1; i <= n; i++) {
freq[arry[i]] += 1
}
}
END {
for (a in freq) {
printf "'%s' %d\n", a, freq[a]
}
}
結果
$ time awk -f freq2.awk char.dat|sort
'A' 384835
'B' 385558
'C' 383821
'D' 384050
'E' 384445
'F' 385576
'G' 384354
'H' 384822
'I' 385866
'J' 385421
'K' 385149
'L' 383431
'M' 384126
'N' 384411
'O' 384677
'P' 384022
'Q' 383844
'R' 385406
'S' 384972
'T' 384658
'U' 385097
'V' 383946
'W' 384470
'X' 383768
'Y' 384658
'Z' 384617