3.8.6. pandas を用いた結果表の出力

割当問題などでは最適化の結果を表形式で見たいケースが多くあります. 本項では,データ解析分野で表操作によく使われる pandas モジュールの DataFrame を用いて結果を加工します.

まずは簡単な割当問題を見てみましょう.:

i = Element(value=range(4))  # 仕事
j = Element(value='ABCD')    # 人
x = BinaryVariable(index=(i,j))
p = Problem()
p += Sum(x[i,j], i) == 2, '各人は 2 つの仕事をする'
p += Sum(x[i,j], j) == 2, '各仕事は 2 人で分担する'
p.solve(silent=True)

一般に最適化の出力は整然データ(tidy data) の形式になります. 整然データを表形式に加工するには pivot 関数を使うことでできます.:

import pandas as pd
output = pd.DataFrame((x[i,j].val>0).set, columns=['i','j'])
output['val'] = 1
print(output.pivot(*output.columns).fillna(0).astype(int))

出力:

j  A  B  C  D
i
0  0  0  1  1
1  1  0  1  0
2  0  1  0  1
3  1  1  0  0

データ加工の二行目ではまず,x[i,j].val>0 で正の値が入っている部分からなる(二次元の)添字を抽出しています. 添字自体はイテレートできないので,set 属性により長さ 2 のタプル列を DataFrame に渡します. 三行目では値列を追加し,四行目でピボットテーブルに加工します. pivot 関数は index, columns, values 引数を取りますが,それぞれが output の i 列,j 列,val 列に対応するため, output.columns を渡すことで簡略化しています. 次のように,最初から三つ組を DataFrame に与えることもできます.:

val = [(*ij, v) for ij, v in x.val.items()]
output = pd.DataFrame(val, columns=['i','j', 'val'])
output.pivot(*output.columns).astype(int)

次は,別の例としてシフト表を作ってみましょう. 5 人の一週間のシフトとして日勤(Day)・夜勤(Ngt)・休み(Off)を決めます.:

m = Element(value='ABCDE')     # Man
d = Element(value=range(1,8))  # Day
s = Element(value=['Day','Ngt','Off'])  # Shift
prb = Problem()
x = BinaryVariable(index=(m,d,s))
prb += Sum(x[m,d,s], s) == 1, '一日に勤務は一つ'
prb += Sum(x[m,d,'Day'], m) == 2, '日勤は二人ちょうど'
prb += Sum(x[m,d,'Ngt'], m) >= 1, '夜勤は一人以上'
prb += Sum(x[m,d,'Off'], d) >= 1, '一回以上は休み'
prb += Sum(x[m,d,'Ngt'], d) <= 2, '夜勤は二回まで'
d1 = d != 7
prb += x[m,d1+1,'Off'] >= x[m,d1,'Ngt'], '夜勤の次の日は代休'
prb.solve(silent=True)

今回は,DataFrame への値の与え方が少し異なり,添字自体が三次元です.:

output = pd.DataFrame((x[m,d,s].val>0).set, columns=['Man','Day','Shift'])
piv = output.pivot(*output.columns)
print(piv)

出力:

Day    1    2    3    4    5    6    7
Man
A    Day  Off  Day  Ngt  Off  Day  Ngt
B    Off  Day  Off  Day  Day  Off  Ngt
C    Off  Ngt  Off  Day  Ngt  Off  Day
D    Ngt  Off  Ngt  Off  Off  Day  Day
E    Day  Day  Day  Off  Day  Ngt  Off

制約を満たしているか確認のため集計列を集計行を付け加えてみましょう.:

piv.loc['DayCount'] = (piv == 'Day').sum(axis=0)
piv.loc['NgtCount'] = (piv == 'Ngt').sum(axis=0)
piv = piv.assign(OffCount=(piv == 'Off').sum(axis=1),
                 NgtCount=(piv == 'Ngt').sum(axis=1))
print(piv)

出力:

Day         1    2    3    4    5    6    7  OffCount  NgtCount
Man
A         Day  Off  Day  Ngt  Off  Day  Ngt         2         2
B         Off  Day  Off  Day  Day  Off  Ngt         3         1
C         Off  Ngt  Off  Day  Ngt  Off  Day         3         2
D         Ngt  Off  Ngt  Off  Off  Day  Day         3         2
E         Day  Day  Day  Off  Day  Ngt  Off         2         1
DayCount    2    2    2    2    2    2    2         0         0
NgtCount    1    1    1    1    1    1    2         0         0