PuLP を使ってみた
2018-10-29

遅ればせながら PuLP の使い方について勉強しました。

案件で使えるレベルとは程遠いですが、まぁ経験が大事ということで。

目次

基本 (Basis)

最初に PuLP の使い方を 勉強します。

大まかにいうと モデルを作って解決するだけです。 モデルには目的関数や制約条件関数を細かく設定できます。

LpVariable
一意な変数名 と 定義域、変数の型 を指定します
LpProblem

解決すべき問題の種類を指定することでモデルを定義します。

モデルの制約条件がある場合、条件式を加算代入で注ぎ足していきます。 条件式は前述した LpVariable で定義した変数を使って作成します。

問題の種類は LpMaximize (最大), LpMinimize (最小) のいずれかです。

最初は 適当な線形の目的関数に 適当な制約条件を追加し、 その前後で最大値、最小値 がどのように変化するか見ていきましょう。

In [21]:
%autosave 0
%matplotlib notebook
Autosave disabled
In [22]:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import pulp
In [23]:
# 変数の定義には 「名前(一意)」 「定義域(最小,最大)」 「型」 を指定する
# 連続した値(浮動小数点数) は 'Continuous' (default)
a = pulp.LpVariable('a', -3, 3, cat='Continuous')
# 整数は 'Integer'
b = pulp.LpVariable('b', -5, 5, cat='Integer')
# 真偽値 は 'Binary'
c = pulp.LpVariable('c', cat='Binary')
In [24]:
# モデルの初期化時に 「名前」 と 「最大か最小か(default:最小)」 を指定する
# model_max で最大値, model_min で最小値を 平行して求めていく
model_max = pulp.LpProblem('Maximizing', pulp.LpMaximize)
model_min = pulp.LpProblem('Maximizing', pulp.LpMinimize)
In [25]:
def objective_function(a, b):
    return -2 * a + 3 * b + 1
In [26]:
# モデルに目的関数と制約条件を追加していく
# 適当な目的関数
model_max += objective_function(a, b)
model_min += objective_function(a, b)
In [27]:
# model.solve() で 求解 (Optimal がでれば OK)
assert pulp.LpStatus[model_max.solve()] == 'Optimal'
solved_max1 = {'x': a.value(), 'y': b.value(), 'z': objective_function(a.value(), b.value())}

assert pulp.LpStatus[model_min.solve()] == 'Optimal'
solved_min1 = {'x': a.value(), 'y': b.value(), 'z': objective_function(a.value(), b.value())}

solved_max1, solved_min1
Out[27]:
({'x': -3.0, 'y': 5.0, 'z': 22.0}, {'x': 3.0, 'y': -5.0, 'z': -20.0})
In [28]:
# 適当な制約条件を追加
model_max += a >= 1
model_min += a >= 1

assert pulp.LpStatus[model_max.solve()] == 'Optimal'
solved_max2 = {'x': a.value(), 'y': b.value(), 'z': -2 * a.value() + 3 * b.value() + 1}

# 制約条件を加えても変わらないケースもある
assert pulp.LpStatus[model_min.solve()] == 'Optimal'
solved_min2 = {'x': a.value(), 'y': b.value(), 'z': -2 * a.value() + 3 * b.value() + 1}

solved_max2, solved_min2
Out[28]:
({'x': 1.0, 'y': 5.0, 'z': 14.0}, {'x': 3.0, 'y': -5.0, 'z': -20.0})
In [29]:
# 適当な制約条件を追加(2)
model_max += (a >= b) == c
model_min += (a >= b) == c

assert pulp.LpStatus[model_max.solve()] == 'Optimal'
solved_max3 = {'x': a.value(), 'y': b.value(), 'z': objective_function(a.value(), b.value())}

assert pulp.LpStatus[model_min.solve()] == 'Optimal'
solved_min3 = {'x': a.value(), 'y': b.value(), 'z': objective_function(a.value(), b.value())}

solved_max3, solved_min3
Out[29]:
({'x': 3.0, 'y': 3.0, 'z': 4.0}, {'x': 1.0, 'y': 0.0, 'z': -1.0})
In [32]:
# 座標をプロットしてみる
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
x = np.arange(a.lowBound, a.upBound + 1)
y = np.arange(b.lowBound, b.upBound + 1)
xs, ys = np.meshgrid(x, y)
zs = -2 * xs + 3 * ys + 1
ax.plot_wireframe(xs, ys, zs)
# 最大値(赤)
ax.plot(
    [solved_max1['x'], solved_max2['x'], solved_max3['x']], 
    [solved_max1['y'], solved_max2['y'], solved_max3['y']], 
    [solved_max1['z'], solved_max2['z'], solved_max3['z']]
, 'o', color='red')
# 最小値(緑)
ax.plot(
    [solved_min1['x'], solved_min2['x'], solved_min3['x']], 
    [solved_min1['y'], solved_min2['y'], solved_min3['y']], 
    [solved_min1['z'], solved_min2['z'], solved_min3['z']]
, 'o', color='green')
Out[32]:
[<mpl_toolkits.mplot3d.art3d.Line3D at 0x7f314d70e6d8>]
In [ ]:
 

座標プロットのところはちょっとわかりにくかったですかね?

  • objective_function は x, y 座標を受け取って係数をかけて返却する単純な 目的関数です
  • 格子状の平面が 目的関数です
  • 最初は プロットが 変数自体の定義域の制限により格子上下端に配置されていて、 制約条件を 与えると動ける範囲で 最大、あるいは最小の点に移動します。
    • 意味のない制約条件を与えると、座標が変わらないこともあります。
      • 最小値はの点は1つカブらせたので2つしかないです

線形計画法 (Linear programming)

続いて、応用としてつとむさんが作成した以下の問題を解いてみます。斎藤さんだぞ。 (最適化におけるPython)

材料AとBから合成できる化学製品XとYをたくさん作成したい。

Xを1kg作るのに、Aが1kg、Bが3kg必要である。

Yを1kg作るのに、Aが2kg、Bが1kg必要である。

また、XもYも1kg当りの価格は100円である。

材料Aは16kg、Bは18kgしかないときに、XとYの価格の合計が最大になるようにするには、 XとYをどれだけ作成すればよいか求めよ。

PuLP を使って解いてみます。

In [1]:
%autosave 0
%matplotlib inline
Autosave disabled
In [2]:
import pulp
In [3]:
# 金額が最大になるようにしたいので LpMaximize
model = pulp.LpProblem('Maximizing', pulp.LpMaximize)
x = pulp.LpVariable('x', lowBound=0, cat='Integer')
y = pulp.LpVariable('y', lowBound=0, cat='Integer')
In [4]:
# 目的関数
model += 100 * x + 100 * y
# Aの制約条件: Xなら1kg, Yなら2kg を必要とし 16kg まで利用可能
model += x + 2 * y <= 16
# Bの制約条件: Xなら3kg, Yなら1kg を必要とし 18kg まで利用可能
model += 3 * x + y <= 18
In [5]:
assert pulp.LpStatus[model.solve()] == 'Optimal'
x.value(), y.value()
Out[5]:
(4.0, 6.0)
In [ ]:
 

とても簡単ですね。

PuLP を使わずに解くとこんな感じ

In [1]:
%matplotlib notebook
In [2]:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
from sympy import symbols, Eq, Le, init_printing, solve

init_printing()
In [3]:
a, b, x, y, k = symbols('a b x y k')

変数の定義

  • a: A の使用量合計
  • b: B の使用量合計
  • x: X の作成量
  • y: Y の作成量
  • k: 価格合計
In [4]:
# x, y を使って 目的関数 k を定義
Eq(k, 100 * x + 100 * y)
Out[4]:
$$k = 100 x + 100 y$$
In [5]:
# x, y を使って a を定義
Eq(a, x + 2 * y)
Out[5]:
$$a = x + 2 y$$
In [6]:
# x, y を使って b を定義
Eq(b, 3 * x + y)
Out[6]:
$$b = 3 x + y$$
In [7]:
# a の制約条件(a <= 16) に代入して
 x + 2 * y <= 16
Out[7]:
$$x + 2 y \leq 16$$
In [8]:
# b の制約条件(b <= 18) に代入して
3 * x + y <= 18
Out[8]:
$$3 x + y \leq 18$$
In [9]:
# 交点の座標
solve([x + 2 * y - 16, 3 * x + y - 18])
Out[9]:
$$\left \{ x : 4, \quad y : 6\right \}$$

だいぶ情報は集まってきましたが、この時点では効率の良い(材料をちょうど使い切る)組み合わせを求めただけで金額が最大になるとは言えませんね。

ここで 目的関数 k を使います。しかし k は 2変数関数なので 普通に考えると 3次元のグラフになってしまいそうです。

そこで k を定数として y の式に変形することで y = -x + k/100 のように 傾き:-1, 切片:k/100 の 1次関数とみなすことができます。

制約条件のグラフを使って 逆像法的に 以下の条件を満たす x, y を探してみます。

  • k/100 (切片) が最大になる
  • 制約条件と 共有点 を持つ
In [10]:
# 制約条件を図示してみる (塗りつぶしが取りうる範囲)
XLIM = YLIM = 10

fig = plt.figure()
ax = fig.add_subplot(111)
ax.grid(color='gray', linestyle='-')
ax.set_xlabel('X')
ax.set_ylabel('Y')

xs = np.arange(0, XLIM + 1, 1)
ys1 = xs * -1/2 + 8
ys2 = xs * -3 + 18
p1 = ax.plot(xs, ys1, color='red')
p2 = ax.plot(xs, ys2, color='blue')

ax.fill_between(xs, ys1, where=xs <= 4, color="orange")
ax.fill_between(xs, ys2, where= xs >= 4, color="orange")

ax.set_xlim([0, XLIM])
ax.set_ylim([0, YLIM])

ims = [
    ax.plot(xs, -1 * xs + seg, 'g')
    for seg in range(11)
]

ax.legend(['[a] constraint cond boundary', '[b] constraint cond boundary', 'objective function'])
ani = animation.ArtistAnimation(fig, ims)
plt.show()

k/100 が 制約条件を満たす範囲(塗りつぶし領域と共有点を持ち)、切片が最大となる点は グラフを見て分かる通り 10 で、その共有点は、やはり (4, 6) でした。

今回は最大になる組み合わせを聞かれているだけなので重要なのは価格の比率だけで、実際にいくらになるかを求める必要はありませんが、 k=10 を 100倍して 1000円 が最大価格と言えそうです。 目的関数に代入してもわかります。

In [ ]:
 

本当はアニメーションで 目的関数が上下移動するようにしたんですが、 静止画でしか保存できないようなので 制約条件下で切片が最大になったときの目的関数を表示しています。

魔方陣 (Magic square)

最後に魔方陣を解きます。 魔方陣のルールは Wikipedia を参照。

魔方陣(まほうじん、英:Magic square)とは、n×n 個の正方形の方陣に数字を配置し、縦・横・対角線のいずれの列についても、その列の数字の合計が同じになるもののことである。 特に1から方陣のマスの総数 n2 までの数字を1つずつ過不足なく使ったものを言う。

魔方陣を通して組合せ最適化を学ぶ を参考にしました。 斉藤さんだぞ。(毎回言うやつ)

重要なのは 必要な条件を漏れなく制約条件として追加してあげることです。 一つでも抜けると正しい解は得られません。

備考

今回はセルの抽出を用途として pandas も使っています。

In [1]:
%autosave 0
%matplotlib inline
Autosave disabled
In [2]:
import pandas as pd
import pulp
In [3]:
df = pd.DataFrame([
    (i, j , k , pulp.LpVariable(f'Var_{i}_{j}_{k}', cat=pulp.LpBinary))
    for i in range(3)
    for j in range(3)
    for k in range(1, 10)
], columns=['row', 'col', 'num', 'Var'])
# 各セル(座標)に 1 ~ 9 までの数字を割り当て、適用可否を示すフラグを変数として持つ
df
Out[3]:
row col num Var
0 0 0 1 Var_0_0_1
1 0 0 2 Var_0_0_2
2 0 0 3 Var_0_0_3
3 0 0 4 Var_0_0_4
4 0 0 5 Var_0_0_5
5 0 0 6 Var_0_0_6
6 0 0 7 Var_0_0_7
7 0 0 8 Var_0_0_8
8 0 0 9 Var_0_0_9
9 0 1 1 Var_0_1_1
10 0 1 2 Var_0_1_2
11 0 1 3 Var_0_1_3
12 0 1 4 Var_0_1_4
13 0 1 5 Var_0_1_5
14 0 1 6 Var_0_1_6
15 0 1 7 Var_0_1_7
16 0 1 8 Var_0_1_8
17 0 1 9 Var_0_1_9
18 0 2 1 Var_0_2_1
19 0 2 2 Var_0_2_2
20 0 2 3 Var_0_2_3
21 0 2 4 Var_0_2_4
22 0 2 5 Var_0_2_5
23 0 2 6 Var_0_2_6
24 0 2 7 Var_0_2_7
25 0 2 8 Var_0_2_8
26 0 2 9 Var_0_2_9
27 1 0 1 Var_1_0_1
28 1 0 2 Var_1_0_2
29 1 0 3 Var_1_0_3
... ... ... ... ...
51 1 2 7 Var_1_2_7
52 1 2 8 Var_1_2_8
53 1 2 9 Var_1_2_9
54 2 0 1 Var_2_0_1
55 2 0 2 Var_2_0_2
56 2 0 3 Var_2_0_3
57 2 0 4 Var_2_0_4
58 2 0 5 Var_2_0_5
59 2 0 6 Var_2_0_6
60 2 0 7 Var_2_0_7
61 2 0 8 Var_2_0_8
62 2 0 9 Var_2_0_9
63 2 1 1 Var_2_1_1
64 2 1 2 Var_2_1_2
65 2 1 3 Var_2_1_3
66 2 1 4 Var_2_1_4
67 2 1 5 Var_2_1_5
68 2 1 6 Var_2_1_6
69 2 1 7 Var_2_1_7
70 2 1 8 Var_2_1_8
71 2 1 9 Var_2_1_9
72 2 2 1 Var_2_2_1
73 2 2 2 Var_2_2_2
74 2 2 3 Var_2_2_3
75 2 2 4 Var_2_2_4
76 2 2 5 Var_2_2_5
77 2 2 6 Var_2_2_6
78 2 2 7 Var_2_2_7
79 2 2 8 Var_2_2_8
80 2 2 9 Var_2_2_9

81 rows × 4 columns

In [4]:
# 3(行) * 3(列) * 9(1~9) = 81
len(df)
Out[4]:
81
In [5]:
model = pulp.LpProblem()
# 各セルの値は必ず1回だけ使う
for key, group in df.groupby(['row', 'col']):
    model += pulp.lpSum(group.Var) == 1
# 数字は必ず一回だけ使う
for key, group in df.groupby('num'):
    model += pulp.lpSum(group.Var) == 1
# 行方向の合計が 15 になるような組み合わせ
for key, group in df.groupby('row'):
    model += pulp.lpDot(group.num, group.Var) == 15
# 列方向の合計が 15 になるような組み合わせ
for key, group in df.groupby('col'):
    model += pulp.lpDot(group.num, group.Var) == 15
# 対角線上の合計値 が 15 になるような組み合わせ
model += pulp.lpDot(*df.query('row==col')[['num','Var']].T.values) == 15
# 対角線(逆向き)上の合計値 が 15 になるような組み合わせ
model += pulp.lpDot(*df.query('row + col == 2')[['num','Var']].T.values) == 15

assert model.solve() == 1

上記は Var0 or 1 の 2値をとるという性質を活用して制約条件を組んでいる

  • 一回だけ使う というのは 合計値が 1
  • 組み合わせの抽出は 使う(1), 使わない(0) との内積
In [6]:
df['Val'] = df.Var.apply(pulp.value)
pd.DataFrame(df[df.Val>0.5].num.values.reshape(3, 3))
Out[6]:
0 1 2
0 4 3 8
1 9 5 1
2 2 7 6
In [7]:
# 関数化してみる
def solve_square(num):
    df = pd.DataFrame([
        (i, j , k , pulp.LpVariable(f'Var_{i}_{j}_{k}', cat=pulp.LpBinary))
        for i in range(num)
        for j in range(num)
        for k in range(1, num ** 2 + 1)
    ], columns=['row', 'col', 'num', 'Var'])
    model = pulp.LpProblem()
    expected_sum = sum(range(1, num ** 2 + 1)) / num

    # 行, 列 につき一つの値しかとらない
    for key, group in df.groupby(['row', 'col']):
        model += pulp.lpSum(group.Var) == 1
    
    # 値は 1 回ずつしか使わない
    for key, group in df.groupby('num'):
        model += pulp.lpSum(group.Var) == 1
    
    # 行方向の内積合計値
    for key, group in df.groupby('row'):
        model += pulp.lpDot(group.num, group.Var) == expected_sum
    
    # 列方向の内積合計値
    for key, group in df.groupby('col'):
        model += pulp.lpDot(group.num, group.Var) == expected_sum
    
    # 対角線上の内積合計値
    model += pulp.lpDot(*df.query('row == col')[['num','Var']].T.values) == expected_sum
    
    # 対角線(逆向き)上の内積合計値
    model += pulp.lpDot(*df.query('row + col == {}'.format(num-1))[['num','Var']].T.values) == expected_sum
    
    assert pulp.LpStatus[model.solve()] == 'Optimal'
    df['Val'] = df.Var.apply(pulp.value)
    return pd.DataFrame(df[df.Val > 0.5].num.values.reshape(num, num))
In [12]:
solve_square(3)
Out[12]:
0 1 2
0 4 3 8
1 9 5 1
2 2 7 6
In [13]:
solve_square(4)
Out[13]:
0 1 2 3
0 2 3 14 15
1 7 13 4 10
2 16 6 11 1
3 9 12 5 8
In [14]:
solve_square(5)
Out[14]:
0 1 2 3 4
0 17 6 9 23 10
1 21 4 24 2 14
2 7 8 13 19 18
3 5 25 3 20 12
4 15 22 16 1 11
In [15]:
solve_square(6)
Out[15]:
0 1 2 3 4 5
0 9 36 20 27 13 6
1 34 12 30 8 5 22
2 32 2 17 11 23 26
3 1 10 25 21 35 19
4 4 33 3 29 28 14
5 31 18 16 15 7 24

ただの写経では面白くないので、応用として 違う大きさの 魔方陣も作っています。

6x6 の魔方陣は 多分処理に数時間かかってます。放置してたので正確な時間はわかりませんが。

やっぱり魔法ではないんですね〜

DataFrameの値がどのように抽出されるか見たい方はこちら

In [1]:
import pandas as pd
import pulp
In [2]:
df = pd.DataFrame([
    (i, j , k , pulp.LpVariable(f'Var_{i}_{j}_{k}', cat=pulp.LpBinary))
    for i in range(3)
    for j in range(3)
    for k in range(1, 10)
], columns=['row', 'col', 'num', 'Var'])
In [3]:
for key, group in df.groupby(['row', 'col']):
    print(f'---------- {key} ----------')
    print(group)
---------- (0, 0) ----------
   row  col  num        Var
0    0    0    1  Var_0_0_1
1    0    0    2  Var_0_0_2
2    0    0    3  Var_0_0_3
3    0    0    4  Var_0_0_4
4    0    0    5  Var_0_0_5
5    0    0    6  Var_0_0_6
6    0    0    7  Var_0_0_7
7    0    0    8  Var_0_0_8
8    0    0    9  Var_0_0_9
---------- (0, 1) ----------
    row  col  num        Var
9     0    1    1  Var_0_1_1
10    0    1    2  Var_0_1_2
11    0    1    3  Var_0_1_3
12    0    1    4  Var_0_1_4
13    0    1    5  Var_0_1_5
14    0    1    6  Var_0_1_6
15    0    1    7  Var_0_1_7
16    0    1    8  Var_0_1_8
17    0    1    9  Var_0_1_9
---------- (0, 2) ----------
    row  col  num        Var
18    0    2    1  Var_0_2_1
19    0    2    2  Var_0_2_2
20    0    2    3  Var_0_2_3
21    0    2    4  Var_0_2_4
22    0    2    5  Var_0_2_5
23    0    2    6  Var_0_2_6
24    0    2    7  Var_0_2_7
25    0    2    8  Var_0_2_8
26    0    2    9  Var_0_2_9
---------- (1, 0) ----------
    row  col  num        Var
27    1    0    1  Var_1_0_1
28    1    0    2  Var_1_0_2
29    1    0    3  Var_1_0_3
30    1    0    4  Var_1_0_4
31    1    0    5  Var_1_0_5
32    1    0    6  Var_1_0_6
33    1    0    7  Var_1_0_7
34    1    0    8  Var_1_0_8
35    1    0    9  Var_1_0_9
---------- (1, 1) ----------
    row  col  num        Var
36    1    1    1  Var_1_1_1
37    1    1    2  Var_1_1_2
38    1    1    3  Var_1_1_3
39    1    1    4  Var_1_1_4
40    1    1    5  Var_1_1_5
41    1    1    6  Var_1_1_6
42    1    1    7  Var_1_1_7
43    1    1    8  Var_1_1_8
44    1    1    9  Var_1_1_9
---------- (1, 2) ----------
    row  col  num        Var
45    1    2    1  Var_1_2_1
46    1    2    2  Var_1_2_2
47    1    2    3  Var_1_2_3
48    1    2    4  Var_1_2_4
49    1    2    5  Var_1_2_5
50    1    2    6  Var_1_2_6
51    1    2    7  Var_1_2_7
52    1    2    8  Var_1_2_8
53    1    2    9  Var_1_2_9
---------- (2, 0) ----------
    row  col  num        Var
54    2    0    1  Var_2_0_1
55    2    0    2  Var_2_0_2
56    2    0    3  Var_2_0_3
57    2    0    4  Var_2_0_4
58    2    0    5  Var_2_0_5
59    2    0    6  Var_2_0_6
60    2    0    7  Var_2_0_7
61    2    0    8  Var_2_0_8
62    2    0    9  Var_2_0_9
---------- (2, 1) ----------
    row  col  num        Var
63    2    1    1  Var_2_1_1
64    2    1    2  Var_2_1_2
65    2    1    3  Var_2_1_3
66    2    1    4  Var_2_1_4
67    2    1    5  Var_2_1_5
68    2    1    6  Var_2_1_6
69    2    1    7  Var_2_1_7
70    2    1    8  Var_2_1_8
71    2    1    9  Var_2_1_9
---------- (2, 2) ----------
    row  col  num        Var
72    2    2    1  Var_2_2_1
73    2    2    2  Var_2_2_2
74    2    2    3  Var_2_2_3
75    2    2    4  Var_2_2_4
76    2    2    5  Var_2_2_5
77    2    2    6  Var_2_2_6
78    2    2    7  Var_2_2_7
79    2    2    8  Var_2_2_8
80    2    2    9  Var_2_2_9
In [4]:
for key, group in df.groupby('num'):
    print(f'---------- {key} ----------')
    print(group)
---------- 1 ----------
    row  col  num        Var
0     0    0    1  Var_0_0_1
9     0    1    1  Var_0_1_1
18    0    2    1  Var_0_2_1
27    1    0    1  Var_1_0_1
36    1    1    1  Var_1_1_1
45    1    2    1  Var_1_2_1
54    2    0    1  Var_2_0_1
63    2    1    1  Var_2_1_1
72    2    2    1  Var_2_2_1
---------- 2 ----------
    row  col  num        Var
1     0    0    2  Var_0_0_2
10    0    1    2  Var_0_1_2
19    0    2    2  Var_0_2_2
28    1    0    2  Var_1_0_2
37    1    1    2  Var_1_1_2
46    1    2    2  Var_1_2_2
55    2    0    2  Var_2_0_2
64    2    1    2  Var_2_1_2
73    2    2    2  Var_2_2_2
---------- 3 ----------
    row  col  num        Var
2     0    0    3  Var_0_0_3
11    0    1    3  Var_0_1_3
20    0    2    3  Var_0_2_3
29    1    0    3  Var_1_0_3
38    1    1    3  Var_1_1_3
47    1    2    3  Var_1_2_3
56    2    0    3  Var_2_0_3
65    2    1    3  Var_2_1_3
74    2    2    3  Var_2_2_3
---------- 4 ----------
    row  col  num        Var
3     0    0    4  Var_0_0_4
12    0    1    4  Var_0_1_4
21    0    2    4  Var_0_2_4
30    1    0    4  Var_1_0_4
39    1    1    4  Var_1_1_4
48    1    2    4  Var_1_2_4
57    2    0    4  Var_2_0_4
66    2    1    4  Var_2_1_4
75    2    2    4  Var_2_2_4
---------- 5 ----------
    row  col  num        Var
4     0    0    5  Var_0_0_5
13    0    1    5  Var_0_1_5
22    0    2    5  Var_0_2_5
31    1    0    5  Var_1_0_5
40    1    1    5  Var_1_1_5
49    1    2    5  Var_1_2_5
58    2    0    5  Var_2_0_5
67    2    1    5  Var_2_1_5
76    2    2    5  Var_2_2_5
---------- 6 ----------
    row  col  num        Var
5     0    0    6  Var_0_0_6
14    0    1    6  Var_0_1_6
23    0    2    6  Var_0_2_6
32    1    0    6  Var_1_0_6
41    1    1    6  Var_1_1_6
50    1    2    6  Var_1_2_6
59    2    0    6  Var_2_0_6
68    2    1    6  Var_2_1_6
77    2    2    6  Var_2_2_6
---------- 7 ----------
    row  col  num        Var
6     0    0    7  Var_0_0_7
15    0    1    7  Var_0_1_7
24    0    2    7  Var_0_2_7
33    1    0    7  Var_1_0_7
42    1    1    7  Var_1_1_7
51    1    2    7  Var_1_2_7
60    2    0    7  Var_2_0_7
69    2    1    7  Var_2_1_7
78    2    2    7  Var_2_2_7
---------- 8 ----------
    row  col  num        Var
7     0    0    8  Var_0_0_8
16    0    1    8  Var_0_1_8
25    0    2    8  Var_0_2_8
34    1    0    8  Var_1_0_8
43    1    1    8  Var_1_1_8
52    1    2    8  Var_1_2_8
61    2    0    8  Var_2_0_8
70    2    1    8  Var_2_1_8
79    2    2    8  Var_2_2_8
---------- 9 ----------
    row  col  num        Var
8     0    0    9  Var_0_0_9
17    0    1    9  Var_0_1_9
26    0    2    9  Var_0_2_9
35    1    0    9  Var_1_0_9
44    1    1    9  Var_1_1_9
53    1    2    9  Var_1_2_9
62    2    0    9  Var_2_0_9
71    2    1    9  Var_2_1_9
80    2    2    9  Var_2_2_9
In [5]:
for key, group in df.groupby('row'):
    print(f'---------- {key} ----------')
    print(group)
---------- 0 ----------
    row  col  num        Var
0     0    0    1  Var_0_0_1
1     0    0    2  Var_0_0_2
2     0    0    3  Var_0_0_3
3     0    0    4  Var_0_0_4
4     0    0    5  Var_0_0_5
5     0    0    6  Var_0_0_6
6     0    0    7  Var_0_0_7
7     0    0    8  Var_0_0_8
8     0    0    9  Var_0_0_9
9     0    1    1  Var_0_1_1
10    0    1    2  Var_0_1_2
11    0    1    3  Var_0_1_3
12    0    1    4  Var_0_1_4
13    0    1    5  Var_0_1_5
14    0    1    6  Var_0_1_6
15    0    1    7  Var_0_1_7
16    0    1    8  Var_0_1_8
17    0    1    9  Var_0_1_9
18    0    2    1  Var_0_2_1
19    0    2    2  Var_0_2_2
20    0    2    3  Var_0_2_3
21    0    2    4  Var_0_2_4
22    0    2    5  Var_0_2_5
23    0    2    6  Var_0_2_6
24    0    2    7  Var_0_2_7
25    0    2    8  Var_0_2_8
26    0    2    9  Var_0_2_9
---------- 1 ----------
    row  col  num        Var
27    1    0    1  Var_1_0_1
28    1    0    2  Var_1_0_2
29    1    0    3  Var_1_0_3
30    1    0    4  Var_1_0_4
31    1    0    5  Var_1_0_5
32    1    0    6  Var_1_0_6
33    1    0    7  Var_1_0_7
34    1    0    8  Var_1_0_8
35    1    0    9  Var_1_0_9
36    1    1    1  Var_1_1_1
37    1    1    2  Var_1_1_2
38    1    1    3  Var_1_1_3
39    1    1    4  Var_1_1_4
40    1    1    5  Var_1_1_5
41    1    1    6  Var_1_1_6
42    1    1    7  Var_1_1_7
43    1    1    8  Var_1_1_8
44    1    1    9  Var_1_1_9
45    1    2    1  Var_1_2_1
46    1    2    2  Var_1_2_2
47    1    2    3  Var_1_2_3
48    1    2    4  Var_1_2_4
49    1    2    5  Var_1_2_5
50    1    2    6  Var_1_2_6
51    1    2    7  Var_1_2_7
52    1    2    8  Var_1_2_8
53    1    2    9  Var_1_2_9
---------- 2 ----------
    row  col  num        Var
54    2    0    1  Var_2_0_1
55    2    0    2  Var_2_0_2
56    2    0    3  Var_2_0_3
57    2    0    4  Var_2_0_4
58    2    0    5  Var_2_0_5
59    2    0    6  Var_2_0_6
60    2    0    7  Var_2_0_7
61    2    0    8  Var_2_0_8
62    2    0    9  Var_2_0_9
63    2    1    1  Var_2_1_1
64    2    1    2  Var_2_1_2
65    2    1    3  Var_2_1_3
66    2    1    4  Var_2_1_4
67    2    1    5  Var_2_1_5
68    2    1    6  Var_2_1_6
69    2    1    7  Var_2_1_7
70    2    1    8  Var_2_1_8
71    2    1    9  Var_2_1_9
72    2    2    1  Var_2_2_1
73    2    2    2  Var_2_2_2
74    2    2    3  Var_2_2_3
75    2    2    4  Var_2_2_4
76    2    2    5  Var_2_2_5
77    2    2    6  Var_2_2_6
78    2    2    7  Var_2_2_7
79    2    2    8  Var_2_2_8
80    2    2    9  Var_2_2_9
In [6]:
for key, group in df.groupby('col'):
    print(f'---------- {key} ----------')
    print(group)
---------- 0 ----------
    row  col  num        Var
0     0    0    1  Var_0_0_1
1     0    0    2  Var_0_0_2
2     0    0    3  Var_0_0_3
3     0    0    4  Var_0_0_4
4     0    0    5  Var_0_0_5
5     0    0    6  Var_0_0_6
6     0    0    7  Var_0_0_7
7     0    0    8  Var_0_0_8
8     0    0    9  Var_0_0_9
27    1    0    1  Var_1_0_1
28    1    0    2  Var_1_0_2
29    1    0    3  Var_1_0_3
30    1    0    4  Var_1_0_4
31    1    0    5  Var_1_0_5
32    1    0    6  Var_1_0_6
33    1    0    7  Var_1_0_7
34    1    0    8  Var_1_0_8
35    1    0    9  Var_1_0_9
54    2    0    1  Var_2_0_1
55    2    0    2  Var_2_0_2
56    2    0    3  Var_2_0_3
57    2    0    4  Var_2_0_4
58    2    0    5  Var_2_0_5
59    2    0    6  Var_2_0_6
60    2    0    7  Var_2_0_7
61    2    0    8  Var_2_0_8
62    2    0    9  Var_2_0_9
---------- 1 ----------
    row  col  num        Var
9     0    1    1  Var_0_1_1
10    0    1    2  Var_0_1_2
11    0    1    3  Var_0_1_3
12    0    1    4  Var_0_1_4
13    0    1    5  Var_0_1_5
14    0    1    6  Var_0_1_6
15    0    1    7  Var_0_1_7
16    0    1    8  Var_0_1_8
17    0    1    9  Var_0_1_9
36    1    1    1  Var_1_1_1
37    1    1    2  Var_1_1_2
38    1    1    3  Var_1_1_3
39    1    1    4  Var_1_1_4
40    1    1    5  Var_1_1_5
41    1    1    6  Var_1_1_6
42    1    1    7  Var_1_1_7
43    1    1    8  Var_1_1_8
44    1    1    9  Var_1_1_9
63    2    1    1  Var_2_1_1
64    2    1    2  Var_2_1_2
65    2    1    3  Var_2_1_3
66    2    1    4  Var_2_1_4
67    2    1    5  Var_2_1_5
68    2    1    6  Var_2_1_6
69    2    1    7  Var_2_1_7
70    2    1    8  Var_2_1_8
71    2    1    9  Var_2_1_9
---------- 2 ----------
    row  col  num        Var
18    0    2    1  Var_0_2_1
19    0    2    2  Var_0_2_2
20    0    2    3  Var_0_2_3
21    0    2    4  Var_0_2_4
22    0    2    5  Var_0_2_5
23    0    2    6  Var_0_2_6
24    0    2    7  Var_0_2_7
25    0    2    8  Var_0_2_8
26    0    2    9  Var_0_2_9
45    1    2    1  Var_1_2_1
46    1    2    2  Var_1_2_2
47    1    2    3  Var_1_2_3
48    1    2    4  Var_1_2_4
49    1    2    5  Var_1_2_5
50    1    2    6  Var_1_2_6
51    1    2    7  Var_1_2_7
52    1    2    8  Var_1_2_8
53    1    2    9  Var_1_2_9
72    2    2    1  Var_2_2_1
73    2    2    2  Var_2_2_2
74    2    2    3  Var_2_2_3
75    2    2    4  Var_2_2_4
76    2    2    5  Var_2_2_5
77    2    2    6  Var_2_2_6
78    2    2    7  Var_2_2_7
79    2    2    8  Var_2_2_8
80    2    2    9  Var_2_2_9
In [7]:
# 対角セル (横長だと見にくいので転地した)
pd.DataFrame(df.query('row == col')[['num','Var']].T.values.T)
Out[7]:
0 1
0 1 Var_0_0_1
1 2 Var_0_0_2
2 3 Var_0_0_3
3 4 Var_0_0_4
4 5 Var_0_0_5
5 6 Var_0_0_6
6 7 Var_0_0_7
7 8 Var_0_0_8
8 9 Var_0_0_9
9 1 Var_1_1_1
10 2 Var_1_1_2
11 3 Var_1_1_3
12 4 Var_1_1_4
13 5 Var_1_1_5
14 6 Var_1_1_6
15 7 Var_1_1_7
16 8 Var_1_1_8
17 9 Var_1_1_9
18 1 Var_2_2_1
19 2 Var_2_2_2
20 3 Var_2_2_3
21 4 Var_2_2_4
22 5 Var_2_2_5
23 6 Var_2_2_6
24 7 Var_2_2_7
25 8 Var_2_2_8
26 9 Var_2_2_9
In [8]:
# 反対角セル (横長だと見にくいので転地した)
pd.DataFrame(df.query('row + col == 2')[['num','Var']].T.values.T)
Out[8]:
0 1
0 1 Var_0_2_1
1 2 Var_0_2_2
2 3 Var_0_2_3
3 4 Var_0_2_4
4 5 Var_0_2_5
5 6 Var_0_2_6
6 7 Var_0_2_7
7 8 Var_0_2_8
8 9 Var_0_2_9
9 1 Var_1_1_1
10 2 Var_1_1_2
11 3 Var_1_1_3
12 4 Var_1_1_4
13 5 Var_1_1_5
14 6 Var_1_1_6
15 7 Var_1_1_7
16 8 Var_1_1_8
17 9 Var_1_1_9
18 1 Var_2_0_1
19 2 Var_2_0_2
20 3 Var_2_0_3
21 4 Var_2_0_4
22 5 Var_2_0_5
23 6 Var_2_0_6
24 7 Var_2_0_7
25 8 Var_2_0_8
26 9 Var_2_0_9
In [ ]:
 
参考