はじめに
2025年12月からPythonの学習を始めました。今回はアウトプットの一環で、PythonとPuLP、Pandasを使用してスケジューリングのできるツールを作成しました。
理由は、家内が小学校教員をしているのですが、3者面談の日程や席替え、クラス替え等の業務を全て人力で考えています。そのような業務負担を少しでも軽減できればという思いで、スケジューリングを対象にしました。応用すればシフトの自動作成や在庫管理、配送・物流最適化等も可能になるかと思います。
1.アウトプットの内容
今回は、3者面談のスケジューリングを対象に、以下の点についてアウトプットを実施しました。
- 保護者の面談希望日、不可日の記載されたExcelファイルを読み込み、データフレーム化
- PuLPを使用して面談日の自動振り分け
- 振り分けされたデータをExcelへ出力
2.前準備
2-1.環境構築
anacondaからJupyterLabをInstall→Launchを行い、JupyterLabで各ライブラリをインストール
!pip install pandas
!pip install pulp
2-2.全体の流れまとめ
- Excelファイルを読み込み、データフレーム化
- データフレームから保護者の情報を辞書型に変換(保護者データ作成)
- 面談枠を作成
- PuLPでモデル作成
- 各制約と目的関数を作成
- 結果を表示
- 新規Excelファイルへ保存
3.実装コード
PuLPとPandasをインポート
import pulp
import pandas as pd
Excelを読み込み、データフレーム化
df = pd.read_excel('3者面談日程希望.xlsx', sheet_name= '日程希望')
# 余計な情報が入らないように成型
df = df.iloc[0:25,0:6]
# '名前'をインデックスにして、辞書型に変換
parents_info = df.set_index('名前').to_dict(orient='index')
面談枠を作成(曜日と時間をリスト化)
days = ['月', '火','水','木','金']
times = ['14:00-14:30', '14:30-15:00', '15:00-15:30','15:30-16:00','16:00-16:30']
slots = []
for d in days:
i = 1 # その日の枠の順番
for t in times:
slots.append({
'day': d,
'time': t,
'index': len(slots), # 全体での通し番号
'order': i # その日の順番
})
i += 1
PuLPでモデル作成
model = pulp.LpProblem('Parent_Meeting_Schedule', pulp.LpMaximize)
x = {}
for parent in parents_info:
for slot in slots:
x[parent, slot['index']] = pulp.LpVariable(f'x_{parent}_{slot['index']}', cat='Binary')
制約1:1枠に1人まで
for slot in slots:
total = 0
for parent in parents_info:
total += x[parent,slot['index']]
model += total <= 1
制約2:保護者の必要枠
for parent, info in parents_info.items():
required_slots = info['枠数']
if required_slots == 1:
required_slots_total = 0
for slot in slots:
required_slots_total += x[parent, slot['index']]
model += required_slots_total == 1
continue
制約3:不可日を除外
for parent, info in parents_info.items():
for slot in slots:
if pd.notna(info['不可日']):
if slot['day'] in info['不可日']:
model += x[parent, slot['index']] == 0
目的関数:希望日を最大化
score_terms = []
# 優先度に対する重みを定義
priority_weights = {
'第1希望': 3,
'第2希望': 2,
'第3希望': 1
}
for parent, info in parents_info.items():
for slot in slots:
for priority, weight in priority_weights.items():
if pd.notna(info[priority]):
if slot['day'] in info[priority]:
score_terms.append(weight* x[parent, slot['index']])
model.objective = None
model += pulp.lpSum(score_terms)
解答して、リストへまとめる
model.solve()
for slot in slots:
for parent in parents_info:
if pulp.value(x[parent, slot['index']]) == 1:
schedule_results.append({
'名前':parent,
'日':slot['day'],
'時間':slot['time']
})
結果をデータフレーム化して、Excelへ保存
df_schedule= pd.DataFrame(schedule_results)
df_schedule.to_excel('3者面談スケジュール.xlsx', index=False)
以下が出力結果です。無事に各保護者の希望を割り当てることができました。

4.今後の課題
正直改良の余地は相当あると思っています。特に下記内容に関してまだまだアップグレードをしていきたいと考えています。
- 保護者の枠数が2以上になった時の処理
- 希望日に偏りがあり、最適化が困難な際のエラー表示
- 希望時間の考慮
- 特定の保護者と保護者は1枠以上を必ず空ける
- その他特定個人のみの条件の考慮
5.感想
ここまで記事をご覧いただきありがとうございました!
今回が初めてのアウトプットでしたが、ここまでの学習内容を振り返る良い時間になりました。しかし、今の自分の知識量と理解力では、クオリティの高いアウトプットをできなかったことが本当に悔しいです。
今後も継続的にアウトプットを行って、知識を定着させるためにQiitaへ投稿をしようと思います!
