1. 基本処理

mcmdを初めて利用するユーザにとって最も難しい点は、 80を超えるメソッドをどのように組み合わせれば、どのような処理を実現できるかを思い描けないことにあるであろう。 そこで、本チュートリアルでは、小さな課題別にその解決方法を示していく。 以下に示すいずれの例も、Pythonを起動してコピペすれば実行可能である。 なお、多くのサンプルコードには、わかりやすさのため、ポイントとなる箇所で途中経過をファイル(o="xx1"など)に出力しているが、 実際に動かす時は、この指定を外した方が処理速度は速くなる。

1.1. 来店回数(日数)を求める

 1from pprint import pprint
 2import nysol.mcmd as nm
 3
 4# どの顧客が来店した日付と時刻のデータ
 5dat=[
 6["customer","date","time"],
 7["A","20180101","101001"],
 8["B","20180101","123218"],
 9["A","20180101","141057"],
10["B","20180104","214014"],
11["B","20180106","200240"]
12]
13f=None
14# 同じ顧客が同じ日に複数回来店していれば来店は一回と数えることにする。
15# Aさんは2018/01/01に2回来店しているが、muniqすることで1回となる。
16f <<= nm.muniq(k="customer,date", i=dat)
17# customer項目が何行あるかカウントし、freqという項目で出力する。
18f <<= nm.mcount(k="customer", a="freq")
19# 必要な項目のみ選択する。
20f <<= nm.mcut(f="customer,freq")
21# ここで初めて実行し、結果はリストで変数resultに格納される。
22result=f.run()
23print(result)
24# 以下出力結果
25# [['A', '1'], ['B', '3']]

1.2. 平均来店間隔日数を求める

 1from pprint import pprint
 2import nysol.mcmd as nm
 3
 4# どの顧客が来店した日付と時刻のデータ(スペース節約のため顧客Aのみ)
 5dat=[
 6["customer","date","time"],
 7["A","20180101","101001"],
 8["A","20180101","123218"],
 9["A","20180112","141057"],
10["A","20180204","214014"],
11["A","20180226","200240"]
12]
13f=None
14# この処理は来店回数の処理の最初に同じ
15f <<= nm.mcut(f="customer,date", i=dat).muniq(k="customer,date")
16# customer項目を単位に、date昇順で並べ、date項目を一行上にずらしてnext項目として出力
17# すなわち、dateとnextに本日と次回来店日が出力される。
18f <<= nm.mslide(k="customer", s="date", f="date:next", o="xx1")
19# 途中経過xx1の内容
20# customer%0,date%1,next
21# A,20180101,20180112
22# A,20180112,20180204
23# A,20180204,20180226
24
25# 次回来店日−本日の日付計算をする。$d{項目名}にて日付型として識別される。
26# dを付けなければ数値として計算されるので注意。
27f <<= nm.mcal(c="$d{next}-$d{date}", a="days")
28# 求めた日数の顧客別平均を計算する。
29f <<= nm.mcut(f="customer,days")
30f <<= nm.mavg(k="customer",f="days")
31result=f.run()
32print(result)
33# 以下出力結果
34# [['A', '18.66666667']]

1.3. 累積計算

 1from pprint import pprint
 2import nysol.mcmd as nm
 3
 4# 顧客が月別に何回来店したかのデータ
 5dat=[
 6["customer","month","freq"],
 7["A","201801",5],
 8["B","201801",2],
 9["A","201802",4],
10["B","201802",14],
11["B","201803",8]
12]
13# もちろんmaccumメソッドを使えば簡単に実現できる。
14# 顧客別に月の順に来店回数を足しこんだ結果がaccum項目に出力される。
15result=nm.maccum(k="customer",s="month",f="freq:accum",i=dat).run()
16pprint(result)
17# 以下出力結果
18# [['A', '201801', '5', '5'],
19#  ['A', '201802', '4', '9'],
20#  ['B', '201801', '2', '2'],
21#  ['B', '201802', '14', '16'],
22#  ['B', '201803', '8', '24']]
23
24# mkeybreakを用いれば、より汎用的に累積計算が書ける。
25f=None
26# customer+monthで並べ替え、同じcustomerの先頭行のtop項目に1が出力される。
27f <<= nm.mkeybreak(k="customer",s="month", a="top,bottom", i=dat, o="xx1")
28# 途中経過xx1の内容
29# customer%0,month%1,freq,top,bottom
30# A,201801,5,1,
31# A,201802,4,,1
32# B,201801,2,1,
33# B,201802,14,,
34# B,201803,8,,1
35
36# nullを0に置換しているのは、次のmcalのif関数がnullに対する処理はnullになるので、それを避けるため。
37f <<= nm.mnullto(f="top,bottom",v="0")
38# キーの最初の行はfreqをそのまま出力し、その他の行では、前行の結果(#{})にfreqを足し込む。
39f <<= nm.mcal(c="if(${top}==1,${freq},#{}+${freq})",a="accum")
40# 分かりやすさのために項目名ヘッダーも出力しておく。
41f <<= nm.writelist(header=True)
42result=f.run()
43pprint(result)
44# 以下出力結果
45# [['customer', 'month', 'freq', 'top', 'bottom', 'accum'],
46#  ['A', '201801', '5', '1', '0', '5'],
47#  ['A', '201802', '4', '0', '1', '9'],
48#  ['B', '201801', '2', '1', '0', '2'],
49#  ['B', '201802', '14', '0', '0', '16'],
50#  ['B', '201803', '8', '0', '1', '24']]

1.4. ある条件を満たした行以降にフラグを立てる

 1from pprint import pprint
 2import nysol.mcmd as nm
 3
 4# 顧客の来店日と購入数量(qtty)データを想定する。
 5# 顧客別に購入数量が10を超えた日付以降にフラグ1を立てたい。
 6dat=[
 7["customer","date","qtty"],
 8["A","20180101","2"],
 9["A","20180101","5"],
10["A","20180112","11"],
11["A","20180204","7"],
12["A","20180226","18"],
13["B","20180110","1"],
14["B","20180113","6"]
15]
16f=None
17# customer項目のキーブレイク行を識別する項目(top,bot)を追加する。
18# キー内の先頭行に1をtop項目に、最終行をbot項目に出力する。それ以外はnullが出力されるので、nullは0に変換しておく。
19f <<= nm.mkeybreak(k="customer", s="date", i=dat)
20f <<= nm.mnullto(f="top,bot",v="0",o="xx1")
21# 途中経過xx1の内容
22# customer%0,date%1,qtty,top,bot
23# A,20180101,2,1,0
24# A,20180101,5,0,0
25# A,20180112,11,0,0
26# A,20180204,7,0,0
27# A,20180226,18,0,1
28# B,20180110,1,1,0
29# B,20180113,6,0,1
30
31# 顧客の先頭行であれば、数量が10以上なら1、未満なら0を出力しておき、
32# 先頭行以外では、前の行の結果が1であれば1を、数量が10以上なら1を出力し、それ以外は0を出力する。
33f <<= nm.mcal(c='if(${top}==1, if(${qtty}>=10,1,0), if(#{}==1,1,if(${qtty}>=10,1,0)))', a="flag")
34# 分かりやすさのために項目名ヘッダーも出力しておく。
35f <<= nm.writelist(header=True)
36result=f.run()
37pprint(result)
38# 以下出力結果
39# 顧客Aは2018/1/12に初めて10を超えるので、それ以降のflag項目は1になっている。
40# 顧客Bは10を超えることはなかった。
41# [['customer', 'date', 'qtty', 'top', 'bot', 'flag'],
42# ['A', '20180101', '2', '1', '0', '0'],
43# ['A', '20180101', '5', '0', '0', '0'],
44# ['A', '20180112', '11', '0', '0', '1'],
45# ['A', '20180204', '7', '0', '0', '1'],
46# ['A', '20180226', '18', '0', '1', '1'],
47# ['B', '20180110', '1', '1', '0', '0'],
48# ['B', '20180113', '6', '0', '1', '0']]
49
50# mnulltoを使えば裏技的に同等の処理が可能。
51f=None
52# qttyが10以上の行を1にして、それ以外はnullにしておく。
53f <<= nm.mcal(c="if(${qtty}>=10,1,nulln())",a="flag", i=dat,o="xx2")
54# 途中経過xx2の内容
55# customer,date,qtty,flag
56# A,20180101,2,
57# A,20180101,5,
58# A,20180112,11,1
59# A,20180204,7,
60# A,20180226,18,1
61# B,20180110,1,
62# B,20180113,6,
63
64# mnulltoで、p=Trueを指定すると、null値を前行の値で置換する。
65f <<= nm.mnullto(k="customer", s="date", f="flag", p=True,o="xx3")
66# 途中経過xx3の内容
67# customer%0,date%1,qtty,flag
68# A,20180101,2,
69# A,20180101,5,
70# A,20180112,11,1
71# A,20180204,7,1
72# A,20180226,18,1
73# B,20180110,1,
74# B,20180113,6,
75
76# このmnulltoは一般的なの使い方で、flag項目のnullを0に変換する。
77f <<= nm.mnullto(f="flag", v="0")
78result=f.run()
79pprint(result)
80# [['A', '20180101', '2', '0'],
81# ['A', '20180101', '5', '0'],
82# ['A', '20180112', '11', '1'],
83# ['A', '20180204', '7', '1'],
84# ['A', '20180226', '18', '1'],
85# ['B', '20180110', '1', '0'],
86# ['B', '20180113', '6', '0']]

1.5. webページAから2-hops以内で到達できるページ

 1from pprint import pprint
 2import nysol.mcmd as nm
 3
 4# あるwebサイトで、page1がpage2へのリンクを持っていることを表したデータ
 5dat=[
 6["page1","page2"],
 7["A","C"],
 8["C","A"],
 9["C","E"],
10["A","E"],
11["C","D"],
12["D","B"],
13["E","F"],
14["F","B"],
15["B","C"],
16]
17### Aから1-hopで到達できるページの選択
18f1=None
19f1 <<= nm.mselstr(f="page1", v="A", i=dat)
20f1 <<= nm.mcut(f="page2:page").muniq(k="page")
21### Aから2-hopsで到達できるページの選択
22f2=None
23# datを自己joinする。2つの同じデータdatがあると考えればよい。
24# 1つ目のdatのpage2と2つ目のdatのpage1で突き合わせてpage2を結合すれば、それが2-hopsで到達できるページとなる。
25# このとき、page1とpage2は同じページが複数存在する(例えばC)のでmnjoinを使う。
26f2 <<= nm.mnjoin(k="page2", K="page1", m=dat, f="page2:page3", i=dat, o="xx1")
27# 途中経過xx1の内容
28# page1,page2%0,page3
29# C,A,C
30# C,A,E
31# D,B,C
32# F,B,C
33# A,C,A
34# A,C,E
35# A,C,D
36# B,C,A
37# B,C,E
38# B,C,D
39# C,D,B
40# C,E,F
41# A,E,F
42# E,F,B
43
44# Aから始まる経路を選択
45f2 <<= nm.mselstr(f="page1", v="A")
46# page3で同じページに到達するルートもありえるので、単一化し、必要な最終到達ページ項目のみ選択する。
47# 項目名をpageに変更しているのは、次にm2catするため。
48f2 <<= nm.mcut(f="page3:page").muniq(k="page")
49## f1,f2の結果を結合し、resultに格納する。
50result=nm.m2cat(i=[f1,f2]).muniq(k="page").run()
51print(result)
52# 以下出力結果
53# [['A'], ['C'], ['D'], ['E'], ['F']]

1.6. 顧客の会員期間(開始-終了)を行に展開して集計

 1from pprint import pprint
 2import nysol.mcmd as nm
 3
 4# 顧客Aは2018/1/10〜2018/1/15まで会員だった。
 5dat=[
 6["customer","frDate","toDate"],
 7["A","20180110","20180115"],
 8["B","20180108","20180113"],
 9["C","20180112","20180116"],
10]
11
12# 会員別開始日付と終了日付を切り出して併合する
13f1 = nm.mcut(f="customer,frDate:date", i=dat)
14f2 = nm.mcut(f="customer,toDate:date", i=dat)
15f=None
16f <<= nm.m2cat(i=[f1,f2],o="xx1")
17# 途中経過xx1の内容
18# date項目には、顧客別に開始日付と終了日付の2行ある。
19# customer,date
20# A,20180110
21# B,20180108
22# C,20180112
23# A,20180115
24# B,20180113
25# C,20180116
26
27# 顧客別に、開始日付と終了日付が飛んでいる期間を連続した日付で埋める
28# Aならば2018/1/10〜2018/1/15の間の日付が追加される。
29f <<= nm.mpadding(k="customer",f="date%d",o="xx2")
30# 途中経過xx2の内容
31# customer%0,date%1
32# A,20180110
33# A,20180111
34# A,20180112
35# A,20180113
36# A,20180114
37# A,20180115
38# B,20180108
39# B,20180109
40# B,20180110
41# B,20180111
42# B,20180112
43# B,20180113
44# C,20180112
45# C,20180113
46# C,20180114
47# C,20180115
48# C,20180116
49
50# 日別に何人が会員であったかをカウント集計する。
51f <<= nm.mcut(f="date")
52f <<= nm.mcount(k="date",a="freq")
53result=f.run()
54pprint(result)
55# 以下出力結果
56# [['20180108', '1'],
57# ['20180109', '1'],
58# ['20180110', '2'],
59# ['20180111', '2'],
60# ['20180112', '3'],
61# ['20180113', '3'],
62# ['20180114', '2'],
63# ['20180115', '2'],
64# ['20180116', '1']]

1.7. ニュースのキーワードの出現回数を過去3日のスライド窓毎に求める。

 1from pprint import pprint
 2import nysol.mcmd as nm
 3
 4# 2018/1/1にAというキーワードがニュースに10回登場した。簡単のためキーワードはAのみ。
 5dat=[
 6["keyword","date","freq"],
 7["A","20180101",10],
 8["A","20180103",2],
 9["A","20180104",3],
10["A","20180106",3],
11]
12f=None
13# 飛んでいる日付を埋める。k=,f=の項目、すなわちfreq項目は0で埋められる(v=0)。
14f <<= nm.mpadding(k="keyword",f="date%d",v=0,i=dat,o="xx1")
15# 途中経過xx1の内容
16# keyword%0,date%1,freq
17# A,20180101,10
18# A,20180102,0
19# A,20180103,2
20# A,20180104,3
21# A,20180105,0
22# A,20180106,3
23
24# dateで3行(日)のスライド窓項目winを作る
25f <<= nm.mwindow(k="keyword",wk="date:win",t=3,o="xx2")
26# 途中経過xx2の内容
27# win%1,keyword%0,date,freq
28# 20180103,A,20180101,10
29# 20180103,A,20180102,0
30# 20180103,A,20180103,2
31# 20180104,A,20180102,0
32# 20180104,A,20180103,2
33# 20180104,A,20180104,3
34# 20180105,A,20180103,2
35# 20180105,A,20180104,3
36# 20180105,A,20180105,0
37# 20180106,A,20180104,3
38# 20180106,A,20180105,0
39# 20180106,A,20180106,3
40
41# スライド窓別に頻度を合計して出来上がり。
42f <<= nm.mcut(f="keyword,win,freq")
43f <<= nm.msum(k="keyword,win", f="freq")
44result=f.run()
45pprint(result)
46# 以下出力結果: キーワードAは2018/1/3の過去3日間(1/3,1/2,1/1)のニュースに12回出現したということ。
47# [['A', '20180103', '12'],
48#  ['A', '20180104', '5'],
49#  ['A', '20180105', '5'],
50#  ['A', '20180106', '6']]

つづく。。