python , pulpを用いて、グループ分け(最適化問題)
解決したいこと
python , pulpを用いて、グループ分けを行いたい。
手元に51人の名前、役職、所属部署(チーム)、前回の班、前々回の班を記したcsvデータがあり、
それを基に適切なグループ分けをしたいです。
条件は下記の通り。
・人数:51人
・13のグループに分けたい。
・1グループ4人または3人。
・前回、前々回同じ班になった人とは一緒のグループにならない
・役職がリーダーの人が、各グループに最低1人は存在する(リーダーは全部で14人)
・部署はできるだけ分ける
・役職はできるだけ分ける
発生している問題・エラー
自身でコードを調べながら書いてますが、複数人がグループに含まれていなかったり同じ人が複数回登場したりします。
例)
# データは読み込み済
import pandas as pd
import itertools
import math
import matplotlib.pyplot as plt
!pip install pulp
import pulp
# グループ数や人数を設定
num_groups = 13
group_size_options = [4, 3]
# 問題の定義
model = pulp.LpProblem("Grouping Problem", pulp.LpMinimize)
# メンバーの情報をリストに格納
members = []
for row in df.iterrows():
member = row[1]
members.append({
"id": member["社員番号"],
"name": member["名前"],
"position": member["役職"],
"team": member["チーム"],
"prev_group": member["前回の班"],
"prev_prev_group": member["前々回の班"]
})
# メンバー数と班数
num_members = len(members)
num_group_sizes = len(group_size_options)
# グループごとの人数を定義する変数
group_size_vars = pulp.LpVariable.dicts("GroupSize", (range(num_groups), range(num_group_sizes)), cat="Binary")
# メンバーをグループに割り当てる変数
group_vars = pulp.LpVariable.dicts("Group", (range(num_groups), range(num_members)), cat="Binary")
# 目的関数:役職がリーダーのメンバーを各グループに最低1人配置する
for member in members:
if member["position"] == "リーダー":
model += sum(group_vars[group][index] for group in range(num_groups) for index in range(num_members) if members[index]["id"] == member["id"]) >= 1
# 目的関数:グループ内の人数のバランスを最小化する
total_group_size_deviation = pulp.LpVariable("TotalGroupSizeDeviation", lowBound=0, cat="Continuous")
model += total_group_size_deviation
for group in range(num_groups):
for size_index, size_option in enumerate(group_size_options):
model += group_size_vars[group][size_index] * size_option == sum(group_vars[group][index] for index in range(num_members))
# 補助変数を導入してグループ内の人数のバランスを最小化する制約を表現
group_size = pulp.lpSum(group_vars[group][index] for index in range(num_members))
model += group_size - size_option <= total_group_size_deviation
model += group_size - size_option >= -total_group_size_deviation
# 制約条件:1班の人数は4人ないし3人
for group in range(num_groups):
model += pulp.lpSum(group_vars[group][index] for index in range(num_members)) >= 3
model += pulp.lpSum(group_vars[group][index] for index in range(num_members)) <= 4
# 制約条件:部署はできるだけ分ける
for group in range(num_groups):
department_count = {}
for index in range(num_members):
department = members[index]["team"]
if department not in department_count:
department_count[department] = 0
department_count[department] += group_vars[group][index]
for count in department_count.values():
model += count <= 1
# 制約条件:役職はできるだけ分ける
for group in range(num_groups):
position_count = {}
for index in range(num_members):
position = members[index]["position"]
if position not in position_count:
position_count[position] = 0
position_count[position] += group_vars[group][index]
for count in position_count.values():
model += count <= 1
# ソルバーを使用して最適化
model.solve()
# 結果の表示
grouped_members = [[] for _ in range(num_groups)]
for group in range(num_groups):
for index in range(num_members):
if pulp.value(group_vars[group][index]) == 1:
grouped_members[group].append(members[index]["name"])
# 班ごとに社員の名前を表示
for group in range(num_groups):
print(f"Group {group + 1}: {', '.join(grouped_members[group])}")