読者です 読者をやめる 読者になる 読者になる

Pythonで統計処理した結果を表示する場合の便利関数を書いた

  • 全てのキーの組み合わせが無い場合でも、defaultdictの初期値を用いて表を完成させるように修正。
  • pandasのMultiIndexを使って表示する処理を追記。

統計処理ではネストされた辞書(dict)が多用することで、比較的簡単に所望の複合キーに対応する値を取得しておくことができる。例えば、population[prefecture][gender][age]のようなものだ。これを例えば、横軸prefecture、縦軸gender x ageという具合に、三次元のグラフが出来る表として出力するとき、第一次辞書のキー(この場合はprefecture)を横軸に取れば良いのだが、横軸方向に値を表示していくことを考えると、少し面倒な処理が必要になる。このような表は良く使うので、そのための関数を持つライブラリを書いて使ってみた。もし、横軸にageを取りたければ、population[age][prefecture][gender]というネストされた辞書を作って集計しておけば良い。

実質的にネストの次元はおそらく際限なく増やすことが可能だが、今回は5次元までとしている。もう少し機能を追加したら、PYPIしてみるのもいいかも。月や曜日でもうまくソートできるようにしたい。また、第一次辞書のキーだけではなく任意の辞書のキーを横軸に取れるようにする、など。表示形式としてはセパレータを指定できるので、TSVやCSVにも対応できる。

なお、defaultdictは、キーに対するデフォルトの値を指定することができる(実際には値ではなく、新しいオブジェクトを返すファクトリ関数を指定する)。キーの存在を確認する必要なく、そのキーに対するデフォルトの値を仮定して処理が書けるのでキーの存在確認を行う必要がなく、複雑な辞書を扱う際にシンプルに書ける。

gist.github.com

$ cat a.py 
from collections import defaultdict
from chart import chart

adata = defaultdict(lambda:defaultdict(int))

n = 0
for a in ["1","2","3"]:
    for b in ["xxx","yyy","zzz"]:
        n += 1
        adata[a][b] = n

chart(adata)
print()

bdata = defaultdict(lambda:defaultdict(lambda:defaultdict(int)))

n = 0
for a in ["A","B","C","D","E","F","G"]:
    for b in ["xxx","yyy","zzz"]:
        for c in ["hhh","iii","jjj"]:
            n += 1
            bdata[a][b][c] = n
chart(bdata,"\t")
print()


cdata = defaultdict(lambda:defaultdict(lambda:defaultdict(int)))

cdata["A"]["xxx"]["hhh"] = 1
cdata["B"]["yyy"]["iii"] = 2
cdata["C"]["zzz"]["jjj"] = 3
cdata["D"]["zzz"]["iii"] = 4
cdata["E"]["yyy"]["hhh"] = 5
cdata["F"]["xxx"]["iii"] = 6
cdata["G"]["xxx"]["jjj"] = 7

chart(cdata,"\t")
print()
chart(cdata)
$ python a.py 
,1,2,3
xxx,1,4,7,
yyy,2,5,8,
zzz,3,6,9,

        A   B   C   D   E   F   G
xxx hhh 1   10  19  28  37  46  55  
xxx iii 2   11  20  29  38  47  56  
xxx jjj 3   12  21  30  39  48  57  
yyy hhh 4   13  22  31  40  49  58  
yyy iii 5   14  23  32  41  50  59  
yyy jjj 6   15  24  33  42  51  60  
zzz hhh 7   16  25  34  43  52  61  
zzz iii 8   17  26  35  44  53  62  
zzz jjj 9   18  27  36  45  54  63  

        A   B   C   D   E   F   G
xxx hhh 1   0   0   0   0   0   0   
xxx iii 0   0   0   0   0   6   0   
xxx jjj 0   0   0   0   0   0   7   
yyy hhh 0   0   0   0   5   0   0   
yyy iii 0   2   0   0   0   0   0   
yyy jjj 0   0   0   0   0   0   0   
zzz hhh 0   0   0   0   0   0   0   
zzz iii 0   0   0   4   0   0   0   
zzz jjj 0   0   3   0   0   0   0   

,,A,B,C,D,E,F,G
xxx,hhh,1,0,0,0,0,0,0,
xxx,iii,0,0,0,0,0,6,0,
xxx,jjj,0,0,0,0,0,0,7,
yyy,hhh,0,0,0,0,5,0,0,
yyy,iii,0,2,0,0,0,0,0,
yyy,jjj,0,0,0,0,0,0,0,
zzz,hhh,0,0,0,0,0,0,0,
zzz,iii,0,0,0,4,0,0,0,
zzz,jjj,0,0,3,0,0,0,0,

pandasのMultiIndexを利用した場合

データ分析で良く使うpandasのMultiIndexを用いた場合、stack() unstack()を使えば、同様の処理がより柔軟に行えるようだ。ただし、ネストされたdictionaryをそのままDataFrameに入れてMultiIndexとして使うことはできず、以下のようなリフォーム処理が必要になる。つまり、キーをタプルとしてdictionaryを作成する。

reformed_bdata = { (key1,key3,key2) : [val3] for key1, val1 in bdata.items() for key2, val2 in val1.items() for key3, val3 in val2.items() }
bdf = pd.DataFrame(reformed_bdata)
print(bdf.stack().stack())

reformed_cdata = { (key1,key3,key2) : [val3] for key1, val1 in cdata.items() for key2, val2 in val1.items() for key3, val3 in val2.items() }
cdf = pd.DataFrame(reformed_cdata)
print(cdf.stack().stack())
           A   B   C   D   E   F   G
0 xxx hhh  1  10  19  28  37  46  55
      iii  2  11  20  29  38  47  56
      jjj  3  12  21  30  39  48  57
  yyy hhh  4  13  22  31  40  49  58
      iii  5  14  23  32  41  50  59
      jjj  6  15  24  33  42  51  60
  zzz hhh  7  16  25  34  43  52  61
      iii  8  17  26  35  44  53  62
      jjj  9  18  27  36  45  54  63

           A  B  C  D  E  F  G
0 xxx hhh  1  0  0  0  0  0  0
      iii  0  0  0  0  0  6  0
      jjj  0  0  0  0  0  0  7
  yyy hhh  0  0  0  0  5  0  0
      iii  0  2  0  0  0  0  0
      jjj  0  0  0  0  0  0  0
  zzz hhh  0  0  0  0  0  0  0
      iii  0  0  0  4  0  0  0
      jjj  0  0  3  0  0  0  0

setdefault

collectionsモジュールをインポートしなくても、setdefaultを使えば、キーの確認をせずにキーが存在するものとして値を扱える。

$ cat setdefault_test.py
a = {}  
a.setdefault("aaa",0)
a["aaa"] += 1
print(a)
           
b = {}  
b.setdefault("aaa",[])
b["aaa"].append(10)
print(b) 

$ python setdefault_test.py
{'aaa': 1}
{'aaa': [10]}