mcmdのベンチマークテスト(FIT2018)¶
FIT2018 に発表した原稿 1 に掲載されたスクリプトについて説明する。 そこでは2つのスクリプトが掲載されており、1つはmcmdとpandasのベンチマークテストに関するもので、 他方は、相関ルール生成のスクリプトである。 いずれもデータとして株価4本値データを利用している。
準備¶
本節で扱っているPythonスクリプトの全ては github よりダウンロードできる ( リスト 19 )。 ダウンロードしたディレクトリ下 bench/fit2018/nysol_mcmd に 以下の3つのスクリプトがある。
1$ git clone https://github.com/nysol/bench.git
2$ cd bench/fit2018/nysol_mcmd
mcmdベンチマークテスト(vs. pandas)¶
nysol.mcmdの処理速度を評価する時に、Pythonにおける表構造データの加工によく利用される Pandas をベンチマークとする速度比較テストを実施した。 Pandasは、表データをメモリ上で加工するための多種のメソッドを提供しており、 mcmdと同様にデータの前処理によく利用されるモジュールである。 内部では Numpy のコードも利用しており高速に動作する。 mcmdとの大きな違いは、Pandasはメモリ内に収まるデータを対象としているのに対して、 mcmdはファイル処理が基本のため、メモリサイズの制約を受けない。
利用データ¶
利用したデータは、「 乱数に基づいた株価データ 」節で解説されている人工的に生成した株価4本値データであり、
上述のgitからダウンロードしたスクリプト mkdata.py
を実行すれば( リスト 20 )、
DATAディレクトリの下にデータが生成される。20〜30分ほど時間を要する。
1$ ./mkdata.py 2id 1000 period 3902 3id 1001 period 6941 4id 1002 period 5896 5 : 6id 6999 period 6877
データは 表 13 に例示されるように、
銘柄ID( id
)、日付( date
)、 始値( o
)、 高値( h
)、 安値( l
)、 終値( c
)
の6項目から構成される株価4本値データである。
id |
date |
o |
h |
l |
c |
---|---|---|---|---|---|
1000 |
20180628 |
63427 |
63492 |
58474 |
61979 |
1000 |
20180627 |
61341 |
65684 |
61341 |
63604 |
1000 |
20180626 |
64200 |
66672 |
62960 |
64267 |
1000 |
20180625 |
65361 |
66354 |
63189 |
65268 |
: |
: |
: |
: |
: |
: |
このデータからサイズ違いの3種類のデータ(large,middle,small)を用意した。 それぞれのデータ行数とサイズは、 表 14 に示される通りである。 名称=multiは、並列処理用にlargeデータを日別に分割したものである。 株価生成スクリプトでは固定の乱数シードを用いているので、データの内容も同一であるはずであるが、 乱数システムの違いから異なる可能性もあり、サイズを確認されたい。
名称 |
ファイル名 |
行数(ヘッダ含む) |
サイズ(byte) |
内容 |
---|---|---|---|---|
large |
price_large.csv |
27,707,820 |
1,032,785,399 |
個別銘柄別4本値データ |
middle |
price_middle.csv |
1,116,001 |
42,182,505 |
largeから2017/12/25以降を選択 |
small |
price_small.csv |
114,001 |
4,297,064 |
largeから2018/6/10以降を選択 |
multi |
sep |
11,546(ファイル数) |
largeを日別ファイルにしたもの |
処理内容¶
処理内容は、平均計算(task="avg")、移動平均の計算(task="win")、ループ処理(task="for")の3つのである。
それぞれについて、mcmdとpandasの最速と思われる実装によりコーディングしている。
時間計測も含めたベンチマークテストのスクリプトは リスト 21 に示すとおりである。
平均値の計算においては、largeデータを日別に分割しておき、
それぞれのファイルで平均値を並列計算させる処理を参考までに実施した( 関数 nm1a
)。
また、mcmdでの移動平均の結果出力において、Pythonのリストに変換せず、ファイルにそのまま出力する実験も追加している( 関数 nm2a
)。
ループ処理については、pandasの提供するインデックス参照 iloc
が低速であったために、
内部的にnumpyを用いた values
を用いた処理(関数 pd3a
)も行った。
計測結果は、 OUTPUT/bench/price_5.txt
に出力される。
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-/
3import os
4import sys
5import time
6from pprint import pprint
7from datetime import datetime
8from glob import glob
9
10import pandas as pd
11import math
12import nysol.mcmd as nm
13
14loop=5
15
16iPath=root="./DATA"
17oPath=root="./OUTPUT/bench"
18os.system("mkdir -p %s"%(oPath))
19oFile="%s/price_%d.txt"%(oPath,loop)
20
21# CSVテキストをPython内部のデータ型に変換するための項目名-データ型対応表
22# pd2,nm2,pd3,pd3aで利用している。
23t={'id':'str','date':'int','o':'float','h':'float','l':'float','c':'float'}
24
25# pandasによる平均計算
26def pd1(iFile):
27 df = pd.read_csv(iFile)
28 dfg=df.groupby("date")
29 r = dfg.mean(numeric_only = True)
30
31# mcmdによる平均計算
32def nm1(iFile):
33 r = nm.mhashavg(k="date",f="o,h,l,c",i=iFile).run()
34
35# mcmdによる平均計算(並列処理)
36def nm1a(iPath):
37 fs=[]
38 for iFile in glob("%s/*"%iPath):
39 fs.append(nm.mhashavg(f="o,h,l,c",i=iFile))
40 r=nm.runs(fs)
41
42# pandasによる移動平均
43def pd2(iFile):
44 df=pd.read_csv(iFile,dtype=t,usecols=['id','date','c'])
45 df_id=df.groupby("id", sort=False).apply(lambda x: x.sort_values(["date"])).reset_index(drop=True)
46 r=df_id.groupby("id", sort=False).rolling(on="date",window=3, min_periods=3).mean()
47
48# mcmdによる移動平均(リスト出力)
49def nm2(iFile):
50 f=None
51 f <<= nm.mcut(f="id,date,c",i=iFile)
52 f <<= nm.mwindow(k="id",wk="date:win",t=3)
53 f <<= nm.mavg(k="id,win",f="c")
54 f <<= nm.writelist(dtype="win:int,date:int,c:float")
55 r=f.run()
56
57# mcmdによる移動平均(file出力)
58def nm2a(iFile):
59 f=None
60 f <<= nm.mcut(f="id,date,c",i=iFile)
61 f <<= nm.mwindow(k="id",wk="date:win",t=3)
62 f <<= nm.mavg(k="id,win",f="c",o="%s/output_nm2.csv"%oPath)
63 r=f.run()
64
65# pandasによる行単位のloop処理
66def pd3(iFile):
67 df = pd.read_csv(iFile,dtype=t)
68 dfo = df.o.iloc; dfh = df.h.iloc
69 dfl = df.l.iloc; dfc = df.c.iloc
70 r=[0.0,0.0,0.0,0.0]
71 for idx in range(df.shape[0]):
72 r[0]+=dfo[idx] if not math.isnan(dfo[idx]) else 0.0
73 r[1]+=dfh[idx] if not math.isnan(dfh[idx]) else 0.0
74 r[2]+=dfl[idx] if not math.isnan(dfl[idx]) else 0.0
75 r[3]+=dfc[idx] if not math.isnan(dfc[idx]) else 0.0
76
77# pandasによる行単位のloop処理(numpyのvaluesを利用)
78def pd3a(iFile):
79 df = pd.read_csv(iFile,dtype=t)
80 dfo = df.o.values; dfh = df.h.values
81 dfl = df.l.values; dfc = df.c.values
82 r=[0.0,0.0,0.0,0.0]
83 for idx in range(df.shape[0]):
84 r[0]+=dfo[idx] if not math.isnan(dfo[idx]) else 0.0
85 r[1]+=dfh[idx] if not math.isnan(dfh[idx]) else 0.0
86 r[2]+=dfl[idx] if not math.isnan(dfl[idx]) else 0.0
87 r[3]+=dfc[idx] if not math.isnan(dfc[idx]) else 0.0
88
89# mcmdによる行単位のloop処理
90def nm3(iFile):
91 r=[0.0,0.0,0.0,0.0]
92 for line in nm.mnullto(f="*",v=0,i=iFile).convtype(t):
93 r[0]+= line[2];r[1]+= line[3]
94 r[2]+= line[4];r[3]+= line[5]
95
96# entry point
97sec={}
98mean={}
99params=[]
100small ="%s/price_small.csv"%iPath
101middle="%s/price_middle.csv"%iPath
102large ="%s/price_large.csv"%iPath
103
104params.append(["pd1" ,small])
105params.append(["nm1" ,small])
106params.append(["pd2" ,small])
107params.append(["nm2" ,small])
108params.append(["nm2a",small])
109params.append(["pd3" ,small])
110params.append(["pd3a",small])
111params.append(["nm3" ,small])
112params.append(["pd1" ,middle])
113params.append(["nm1" ,middle])
114params.append(["pd2" ,middle])
115params.append(["nm2" ,middle])
116params.append(["nm2a",middle])
117params.append(["pd3" ,middle])
118params.append(["pd3a",middle])
119params.append(["nm3" ,middle])
120
121params.append(["pd1" ,large])
122params.append(["nm1" ,large])
123params.append(["nm1a","%s/sep"%iPath])
124params.append(["pd2" ,large])
125params.append(["nm2" ,large])
126params.append(["nm2a",large])
127params.append(["pd3" ,large])
128params.append(["pd3a",large])
129params.append(["nm3" ,large])
130
131for param in params:
132 func=param[0]
133 iFile=param[1]
134 name="%s_%s"%(func,iFile)
135 print("START:",name)
136 sec[name]=[]
137 for i in range(loop):
138 st=time.time()
139 eval(func+'("%s")'%iFile)
140 sec[name].append(time.time()-st)
141 mean[name]=0
142 for i in range(loop):
143 mean[name]+=sec[name][i]
144 mean[name]/=loop
145
146print("write to: ",oFile)
147with open(oFile, "w") as file:
148 pprint(sys.argv[0], stream=file)
149 pprint(loop, stream=file)
150 pprint(sec, stream=file)
151 pprint(mean, stream=file)
結果¶
出力結果をまとめたものを 表 15 に示している。 defは リスト 21 の関数名を表す。 small,mid,largeは 表 14 に示したサイズ別データセットの名称である。 また、fit2018での報告日に実験をやり直した結果は 表 16 に示している。
task |
program |
def |
small |
mid |
large |
---|---|---|---|---|---|
avg |
pandas |
pd1 |
0.130 |
1.28 |
29.18 |
avg |
mcmd |
nm1 |
0.036 |
0.33 |
7.39 |
avg |
mcmd(multi) |
nm1a |
5.38 |
||
win |
pandas |
pd2 |
16.91 |
19.22 |
74.88 |
win |
mcmd |
nm2 |
0.27 |
2.54 |
63.94 |
win |
mcmd(file) |
nm2a |
0.19 |
1.63 |
41.87 |
for |
pandas |
pd3 |
18.72 |
174.54 |
|
for |
pandas(values) |
pd3a |
0.35 |
3.10 |
73.42 |
for |
mcmd |
nm3 |
0.27 |
2.73 |
60.43 |
task |
program |
def |
small |
mid |
large |
---|---|---|---|---|---|
avg |
pandas |
pd1 |
0.103 |
0.96 |
24.10 |
avg |
mcmd |
nm1 |
0.031 |
0.29 |
7.32 |
avg |
mcmd(multi) |
nm1a |
7.85 |
||
win |
pandas |
pd2 |
26.21 |
27.77 |
66.96 |
win |
mcmd |
nm2 |
0.23 |
2.29 |
58.70 |
win |
mcmd(file) |
nm2a |
0.15 |
1.39 |
38.03 |
for |
pandas |
pd3 |
16.92 |
165.96 |
|
for |
pandas(values) |
pd3a |
0.30 |
2.92 |
71.29 |
for |
mcmd |
nm3 |
0.28 |
2.65 |
65.43 |
ベンチマークテストを実施した計算環境は以下の通りである。
PC: MacPro(2013)
CPU: 2.7GHz 12-Core Intel Xeon E5
memory: 64GB
hdd: USB3 HDD
注釈
ここ以降の内容は、近い将来「 チュートリアル 」の節に移動します。
高収益率による銘柄の共起分析¶
リスト 22 は原稿の図1に掲載したスクリプトである (原稿にはタイポがいくつかあり修正している)。 このスクリプトは、日をトランザクションにして、収益率が高くなる時に共起しやすい銘柄について 2アイテムの相関ルール分析を行ったものである。 内容の詳細については、原稿を参考にしてもらいたい。
1import nysol.mcmd as nm
2tra=None
3tra <<= nm.mcut(f="id,date,c",i=iFile)
4tra <<= nm.mjoin(k="date",m=topix,f="i")
5tra <<= nm.mslide(k="id",s="date",f="date:date2,c:c2,i:i2")
6tra <<= nm.mcal(c="${c2}/${c}-${i2}/${i}",a="ret")
7tra <<= nm.mselnum(f="ret",c="[0.05,0.1]")
8tra <<= nm.mcut(f="id,date2:date,ret")
9freq=None
10freq <<= nm.mcut(f="id",i=tra)
11freq <<= nm.mcount(k="id",a="freq")
12freq <<= nm.mselnum(f="freq",c="[5,]")
13total=None
14total <<= nm.mcut(f="date",i=tra)
15total <<= nm.muniq(k="date")
16total <<= nm.mcount(a="total")
17coFreq=None
18coFreq <<= nm.mcut(f="date,id",i=tra)
19coFreq <<= nm.mcommon(k="id",m=freq)
20coFreq <<= nm.mcombi(k="date",n=2,f="id",a="id1,id2")
21coFreq <<= nm.mcut(f="id1,id2")
22coFreq <<= nm.mfsort(f="id1,id2")
23coFreq <<= nm.mcount(k="id1,id2",a="coFreq")
24coFreq <<= nm.mjoin(k="id1",m=freq,K="id",f="freq:freq1")
25coFreq <<= nm.mjoin(k="id2",m=freq,K="id",f="freq:freq2")
26coFreq <<= nm.mproduct(m=total,f="total")
27coFreq <<= nm.mcal(c="(${coFreq}*${total})/(${freq1}*${freq2})",a="lift")
28coFreq <<= nm.msel(c="${lift}>10 && ${coFreq}>6",o="result.csv")
29coFreq.run(msg="on")
30# result.csvの内容
31# id1,id2%0,coFreq,freq1,freq2,total,lift
32# 1075,3669,7,126,123,9127,4.122402891
33# 3519,3669,8,96,123,9127,6.183604336
34# 1921,3669,7,94,123,9127,5.525774088
35# : , : ,:, :, :, : , :
36# 4729,6609,8,112,129,9127,5.053709856
Footnotes
- 1
中元政一,羽室行信,「NYSOL: Pythonにおける大規模データ前処理支援ツール」FIT2018:第17回情報科学技術フォーラム,2018/9/20,福岡工業大学.