プログラム初心者がpythonを勉強する方法!その8・openpyxl・pulp・pandasでエクセルからデータフレームを抽出して線形計画を実行してみる!

スポンサーリンク
050-VBA

ヘタレシステム管理者の管理人です。

pythonの勉強8日目です。

本日は・・・

線形計画とエクセルの合わせ技だったので・・・

なかなかに学習時間がかかり・・・

満身創痍となりました。

やりたいことは・・・

そんなに難しいことではありません。

よくある線形計画で・・・

工場X・Yから各店舗A・B・Cに必要量を配送する際に最も安価なコストを求めるという問題です。

エクセルから必要な数値を取得してpythonで計算させる

普通なら・・・

pythonのプログラムにベタ打ちで目的関数と制約条件を入力するのですが・・・

おそらく今後使うのであれば、エクセルから数値を取得することになると考えたので・・・

なんとかして・・・

セクセルから数値を取得 → pythonで計算 → エクセルに結果を出力

やってみることにしました。

かなり嵌って、途中で投げ出しそうになりましたが・・・

取得するエクセルの表は数のとおりです。

コードは次のとおり

全体のコードは次のとおりです。

間違っていた部分は敢えてコメントアウトで残しています。

また、逐次printで取得している値を確認しながら実行しています。

#ライブラリをインポート
import pandas
import openpyxl
import pulp

problem = pulp.LpProblem('test',pulp.LpMinimize)

#ファイルを指定
df = pandas.read_excel('pandas_test.xlsx', sheet_name=0,header=None,index_col=None)

#リストを設定
#shop_list = df.iloc[0:1,1:4].values
#cost_x = df.iloc[1:2,1:4].values
#cost_y = df.iloc[2:3,1:4].values
#jyuyou = df.iloc[3:4,1:4].values
#kyoukyu = df.iloc[1:3,4:5].values

shop_list = df.iloc[0,1:4].values
cost_x = df.iloc[1,1:4].values
cost_y = df.iloc[2,1:4].values
jyuyou = df.iloc[3,1:4].values
kyoukyu = df.iloc[1:3,4].values


#表示
print(df)
print(shop_list)
print(cost_x)
print(cost_y)
print(jyuyou)
print(kyoukyu)

#工場Xの供給量
fact_x = [pulp.LpVariable(f'fact_x_{i}', 
    lowBound = 0, # 下限値。デフォルト値はNone
    upBound=None, # 上限値。デフォルト値はNone
    cat=pulp.LpInteger
        # 変数の種類。デフォルトは"Continuous"(連続値)
        # 他には pulp.LpInteger 整数、pulp.LpBinary バイナリ値
    ) for i in shop_list]

#工場Yの供給量
fact_y = [pulp.LpVariable(f'fact_y_{i}', 
    lowBound = 0, # 下限値。デフォルト値はNone
    upBound=None, # 上限値。デフォルト値はNone
    cat=pulp.LpInteger
    ) for i in shop_list]

#表示
print(fact_x)
print(fact_y)

#目的関数
problem += pulp.lpSum(fact_x * cost_x) + pulp.lpSum(fact_y * cost_y)

#表示
print(pulp.lpSum(fact_x * cost_x) + pulp.lpSum(fact_y * cost_y))

#制約条件
#需要量
for i in range(3):
    problem += fact_x[i] + fact_y[i] >= jyuyou[i]

#表示
    print(fact_x[i] + fact_y[i] >= jyuyou[i])

#供給量
problem += pulp.lpSum(fact_x) <= kyoukyu[0]
problem += pulp.lpSum(fact_y) <= kyoukyu[1]

#表示
print(pulp.lpSum(fact_x) <= kyoukyu[0])
print(pulp.lpSum(fact_y) <= kyoukyu[1])

#実行
status = problem.solve(pulp.PULP_CBC_CMD(msg=0))
print("Status", pulp.LpStatus[status])

#結果表示
result = pandas.DataFrame(
    [
        [fact_x[i].value() for i in range(3)],
        [fact_y[i].value() for i in range(3)],
    ],
    columns=["店a", "店b", "店c"],
    index=["工場x", "工場y"]
)

print(result)
print('最小のコスト =', problem.objective.value())

#エクセルに書き込み
#result.to_excel('pandas_test2.xlsx', sheet_name='new_sheet',header=False, index=False)

with pandas.ExcelWriter('pandas_test2.xlsx',engine='openpyxl',mode='a',if_sheet_exists='new') as xlswriter:

    result.to_excel(xlswriter, sheet_name='new_sheet',header=False, index=False)

wb = openpyxl.load_workbook('pandas_test2.xlsx')
ws = wb.sheetnames
max_sheet_name = max(ws)
ws = wb[max_sheet_name]

ws['A10'].value = problem.objective.value()

wb.save('pandas_test2.xlsx')

出力される内容は次のとおりです。

      0    1    2    3    4
0   NaN  店 a  店 b  店 c  供給量
1  工場 x   10    6   16    8
2  工場 y    8    8    4   16
3   需要量   12    4    8  NaN
['店 a' '店 b' '店 c']
[10 6 16]
[8 8 4]
[12 4 8]
[8 16]
[fact_x_店_a, fact_x_店_b, fact_x_店_c]
[fact_y_店_a, fact_y_店_b, fact_y_店_c]
10*fact_x_店_a + 6*fact_x_店_b + 16*fact_x_店_c + 8*fact_y_店_a + 8*fact_y_店_b + 4*fact_y_店_c
fact_x_店_a + fact_y_店_a >= 12
fact_x_店_b + fact_y_店_b >= 4
fact_x_店_c + fact_y_店_c >= 8

fact_x_店_a + fact_x_店_b + fact_x_店_c <= 8
fact_y_店_a + fact_y_店_b + fact_y_店_c <= 16
Status Optimal
      店a   店b   店c
工場x  4.0  4.0  0.0
工場y  8.0  0.0  8.0
最小のコスト = 160.0

最終的に出力されるエクセルは次のとおりです。

コードの細かい内容は次のとおりです。

ライブラリのインポート

#ライブラリをインポート
import pandas
import openpyxl
import pulp

必要なライブラリをインストールします。

pulpを定義

problem = pulp.LpProblem('test',pulp.LpMinimize)

pulpを定義します。今回は最小の値を求めます。

エクセルファイルを開く

#ファイルを指定
df = pandas.read_excel('pandas_test.xlsx', sheet_name=0,header=None,index_col=None)

エクセルファイルを開きます。相対参照または絶対参照どちらでもOKです。

スポンサーリンク

リストの取得

#リストを設定
#shop_list = df.iloc[0:1,1:4].values
#cost_x = df.iloc[1:2,1:4].values
#cost_y = df.iloc[2:3,1:4].values
#jyuyou = df.iloc[3:4,1:4].values
#kyoukyu = df.iloc[1:3,4:5].values

shop_list = df.iloc[0,1:4].values
cost_x = df.iloc[1,1:4].values
cost_y = df.iloc[2,1:4].values
jyuyou = df.iloc[3,1:4].values
kyoukyu = df.iloc[1:3,4].values

#表示
print(df)
print(shop_list)
print(cost_x)
print(cost_y)
print(jyuyou)
print(kyoukyu)
      0    1    2    3    4
0   NaN  店 a  店 b  店 c  供給量
1  工場 x   10    6   16    8
2  工場 y    8    8    4   16
3   需要量   12    4    8  NaN
['店 a' '店 b' '店 c']
[10 6 16]
[8 8 4]
[12 4 8]
[8 16]

【pandas】の【iloc】を使ってリストを作成します。

例えば・・・工場Xのコストのリストを作成する場合ですが・・・

行の範囲指定のやり方で結果が変わりました。

参照する部分は同じなのですが・・・

行を範囲で指定すると余計な[]が付きます。

列も同じです。

スポンサーリンク

特定の行や列を指定する場合は範囲指定【:】を使わずにダイレクトに数値を記入します。

cost_x = df.iloc[1:2,1:4].values → [[10 6 16]]
cost_x = df.iloc[1,1:4].values → [10 6 10]

変数と目的関数の設定

#工場Xの供給量
fact_x = [pulp.LpVariable(f'fact_x_{i}', 
    lowBound = 0, # 下限値。デフォルト値はNone
    upBound=None, # 上限値。デフォルト値はNone
    cat=pulp.LpInteger
        # 変数の種類。デフォルトは"Continuous"(連続値)
        # 他には pulp.LpInteger 整数、pulp.LpBinary バイナリ値
    ) for i in shop_list]

#工場Yの供給量
fact_y = [pulp.LpVariable(f'fact_y_{i}', 
    lowBound = 0, # 下限値。デフォルト値はNone
    upBound=None, # 上限値。デフォルト値はNone
    cat=pulp.LpInteger
    ) for i in shop_list]

#表示
print(fact_x)
print(fact_y)

#目的関数
problem += pulp.lpSum(fact_x * cost_x) + pulp.lpSum(fact_y * cost_y)
[fact_x_店_a, fact_x_店_b, fact_x_店_c]
[fact_y_店_a, fact_y_店_b, fact_y_店_c]
10*fact_x_店_a + 6*fact_x_店_b + 16*fact_x_店_c + 8*fact_y_店_a + 8*fact_y_店_b + 4*fact_y_店_c

次に目的関数の設定です。

求めたい目的関数は・・・

16 * XA + 6 * XB + 16 * XC + 8 * YA + 8 * YB + 4 * YC

です。

ブログランキングにご協力ください!
ブログランキング・にほんブログ村へ

これを【f-stiring】と【リスト内包表記】を使って記述します。

まず・・・

変数(XA・XB・XC・YA・YB・YC)を設定します。

余計な記述を消せば下のとおりとなります。

これでリスト【XA XB XC YA YB YC】となります。

#工場Xの供給量
fact_x = [pulp.LpVariable(f'fact_x_{i}',lowBound = 0,upBound=None,cat=pulp.LpInteger) for i in shop_list]

#工場Yの供給量
fact_y = [pulp.LpVariable(f'fact_y_{i}',lowBound = 0,upBound=None,cat=pulp.LpInteger) for i in shop_list]

これから【pulp.lpsum】を使って・・・

16 * XA + 6 * XB + 16 * XC + 8 * YA + 8 * YB + 4 * YC

を目的関数とします。

#目的関数
problem += pulp.lpSum(fact_x * cost_x) + pulp.lpSum(fact_y * cost_y)

制約条件

#制約条件
#需要量
for i in range(3):
    problem += fact_x[i] + fact_y[i] >= jyuyou[i]

#表示
    print(fact_x[i] + fact_y[i] >= jyuyou[i])

#供給量
problem += pulp.lpSum(fact_x) <= kyoukyu[0]
problem += pulp.lpSum(fact_y) <= kyoukyu[1]

#表示
#表示
print(pulp.lpSum(fact_x) <= kyoukyu[0])
print(pulp.lpSum(fact_y) <= kyoukyu[1])

fact_x_店_a + fact_y_店_a >= 12
fact_x_店_b + fact_y_店_b >= 4
fact_x_店_c + fact_y_店_c >= 8

fact_x_店_a + fact_x_店_b + fact_x_店_c <= 8
fact_y_店_a + fact_y_店_b + fact_y_店_c <= 16

制約条件(需要量と供給量)設定します。

実行と結果表示

#実行
status = problem.solve(pulp.PULP_CBC_CMD(msg=0))
print("Status", pulp.LpStatus[status])

#結果表示
result = pandas.DataFrame(
    [
        [fact_x[i].value() for i in range(3)],
        [fact_y[i].value() for i in range(3)],
    ],
    columns=["店a", "店b", "店c"],
    index=["工場x", "工場y"]
)

print(result)
print('最小のコスト =', problem.objective.value())
Status Optimal
      店a   店b   店c
工場x  4.0  4.0  0.0
工場y  8.0  0.0  8.0
最小のコスト = 160.0

結果をデータフレームで表示します。

エクセルに書き込み

#エクセルに書き込み
#result.to_excel('pandas_test2.xlsx', sheet_name='new_sheet',header=False, index=False)

with pandas.ExcelWriter('pandas_test2.xlsx',engine='openpyxl',mode='a',if_sheet_exists='new') as xlswriter:

    result.to_excel(xlswriter, sheet_name='new_sheet',header=False, index=False)

wb = openpyxl.load_workbook('pandas_test2.xlsx')
ws = wb.sheetnames
max_sheet_name = max(ws)
ws = wb[max_sheet_name]

ws['A10'].value = problem.objective.value()

wb.save('pandas_test2.xlsx')

各工場から各店舗への配送数量をデータフレームとしてエクセル(新規シート)に書き込みます。

また計算結果(最小コスト)を(新規シートのA10セル)に書き込みます。

本日はここまで・・・

本日のお勉強は半日以上かかりました。

もう嫌です。

ブログ開設に必要なドメイン取得、サーバーレンタル、ASPの登録等は、こちらのサイトから!

コメント

タイトルとURLをコピーしました