へっぽこヘタレシステム管理者の管理人です。
pythonの勉強10日目です。
9日目から日数があり、かなり忘れていましたので思い出すのにかなり時間がかかりました。
さて、なんとかpythonと線形計画を使ってシフト表を作成するテストができたので、忘備録として掲載します。
pythonのコードの流れは、まとめるとざっくりとは次の流れです。
- エクセルから条件を取得
- pythonで計算
- エクセルに結果を出力
条件を抽出するエクセルは次の表です。
コードの全ては次のとおりです。
コードは2つにわけてあります。
前半がエクセルの表から必要なデータを取得するコードです。
各変数については、その都度 print() で内容を確認しています。
#前半・エクセルから必要なデータを抽出するまで
#ライブラリをインポート
import pandas
import openpyxl
import pulp
#ファイルを指定
df = pandas.read_excel('python_shift2.xlsx')
#勤務表を取得
df = df.iloc[6:, 0:]
df = df.reset_index(drop=True)
df.columns = [i for i in range(len(df.columns)) ]
#空白値を0
df=df.fillna(0)
#希望休を2に置換
df=df.replace('◎',1)
#表を表示させて確認
print('データフレーム')
print(df)
#変数設定
#勤務日を取得して確認
workdays = list(range(1,32))
print('勤務日')
print(workdays)
#従業員リストを取得して内容を確認
worker = df.iloc[0:10,0:1]
worker = worker.transpose() #行列を入れ替え
worker = worker.iloc[0,0:].values
print('従業員名')
print(worker)
#休日数を取得
holiday = df.iloc[0:10,32:33]
holiday = holiday.transpose() #行列を入れ替え
holiday = holiday.iloc[0,0:].values
print('従業員別休暇取得日数')
print(holiday)
#最低出勤人数を取得
syukkin = df.iloc[11,1:32].values
print('各勤務日の最低出勤人数')
print(syukkin)
#従業員の希望休を取得
#データ範囲を指定
df2 = df.iloc[0:10,0:32]
print('データフレームその2')
print(df2)
# 一致するインデックスと列を格納する空のリストを初期化する
hopeday = []
# テーブルの行を反復処理する
for row_index, row in df2.iterrows():
for cols in df2.columns:
if df2.loc[row_index,cols] == 1:
row_values = df2.loc[row_index, 0]
hopeday.append((row_values, cols))
# 結果を出力する
print('従業員別の希望休暇日')
print(hopeday)
#ここまでがエクセルの表からデータを抽出するまで
ここから後半です。
pulpの線形計画によりシフト表を組みます。
すべての定数・変数の無い様を確認するために
print()で出力しています。
出力内容をみることで、実際にどういう動きをしているかが分かるかと思います。
条件は次のとおりです。
- 1ヶ月の勤務日は31日
- 従業員はA~Jの10名
- 各従業員はそれぞれ1カ月中に取得しなければいけない休日数(契約休日数)の設定あり
- 各従業員は希望の休日を提出できる
- 各業務日には必要な出勤数が設定されている(全業務日7名)
- 各従業員に休暇日数分の休日を休ませる
- 各営業日における出勤者数を7名~8名とする
- 各従業員の希望休は絶対に反映させる
- 3連休を作らない
- 5連勤を作らない
- 飛び石連休を作らない(休み⇒出勤⇒休み)
です。
こちらも
#後半・pulpでの線形計画
#定数の定義
#最適休暇人数リスト 各勤務日で休暇を取得する人数
horiday_req = {d:10-d2 for d2 in syukkin for d in workdays}
print('各勤務日で休暇を取る人数')
print(horiday_req)
#必要休暇日数 各従業員の休暇取得日数計
worker_req = {e:hr for e,hr in zip(worker,holiday)}
print('各従業員別の休暇取得日の計')
print(worker_req)
#最適化モデルの定義
problem = pulp.LpProblem('Problem',pulp.LpMinimize)
#変数の定義 全ての変数(従業員,1~31の日付)
hensu = [(e,d) for e in worker for d in workdays]
hensu_x = pulp.LpVariable.dicts('hensu_x',hensu,cat='Binary')
print('従業員別の出勤日')
print(hensu_x)
#制約式の定義
#必要休暇日数を守る
print('各従業員の休暇日を守る制限')
for e in worker:
problem += pulp.lpSum([hensu_x[e,d] for d in workdays]) == worker_req[e]
print(pulp.lpSum([hensu_x[e,d] for d in workdays]) == worker_req[e])
#希望休を守る
print('希望休を守る')
for e,d in hopeday:
problem += hensu_x[e,d] == 1
print(hensu_x[e,d] == 1)
#1日当たりの休暇人数は2~3人
print('日当たりの休暇人数・2~3人')
for d in workdays:
problem += pulp.lpSum([hensu_x[e,d] for e in worker]) <= horiday_req[d]
problem += pulp.lpSum([hensu_x[e,d] for e in worker]) >= horiday_req[d]-1
print(pulp.lpSum([hensu_x[e,d] for e in worker]) <= horiday_req[d])
print(pulp.lpSum([hensu_x[e,d] for e in worker]) >= horiday_req[d]-1)
#3連休以上を作らない
print('3連休を作らない')
for e in worker:
for d in workdays[:-2]:
problem += hensu_x[e,d] + hensu_x[e,d+1] + hensu_x[e,d+2] <= 2
print(hensu_x[e,d] + hensu_x[e,d+1] + hensu_x[e,d+2] <= 2)
#5連勤以上を作らない
print('5連勤以上をつくらない')
for e in worker:
for d in workdays[:-4]:
problem += hensu_x[e,d] + hensu_x[e,d+1] + hensu_x[e,d+2] + hensu_x[e,d+3] + hensu_x[e,d+4] >= 1
print(hensu_x[e,d] + hensu_x[e,d+1] + hensu_x[e,d+2] + hensu_x[e,d+3] + hensu_x[e,d+4] >= 1)
#飛び石連休を作らない
print('飛び石連休を作らない')
for e in worker:
for d in workdays[:-2]:
problem += hensu_x[e,d] + hensu_x[e,d+2] <= 1
print(hensu_x[e,d] + hensu_x[e,d+2] <= 1)
#目的関数の定義
#'なし'
#答えを求める
result = problem.solve()
print('Status:', pulp.LpStatus[result])
#計算結果の表示
print(pandas.DataFrame([[hensu_x[e,d].value() for d in workdays] for e in worker]))
#エクセルに書き込み
result2 = pandas.DataFrame([[hensu_x[e,d].value() for d in workdays] for e in worker])
with pandas.ExcelWriter('python_shift2.xlsx', engine='openpyxl', mode='a', if_sheet_exists='overlay') as writer:
result2.to_excel(writer, sheet_name='Sheet1', startrow=26, startcol=1, header=False, index=False)
結果は次の出力となります。
各変数等の内容を print() で確認しながら進めば、
どのような動きをさせているのかが分かるかと思います。
最後に同じエクセルのB27セルから結果を出力しています。
従業員名・勤務日・曜日などのインデックスやカラムについては、あらかじめ枠をコピペしておいてください。
従業員ごとの休日数・各日にちの休暇取得人数・各日にちの出勤人数を計算すると、しっかりと条件に適合した数値が出力されていました。
つぎは、これを遺伝的アルゴリズムでやってみたいのですが・・・
モチベーションが続くかかなり怪しいです。
コメント