LoginSignup
xsmmy929
@xsmmy929

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

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])}")
0

1Answer

6名から2名の選択とします

now =[1,2,3,4,5,6]
tab =[
   [0,1,1,1,1,1],
    [1,0,1,1,1,1],
    [1,1,0,1,1,1],
    [1,1,1,0,1,1],
    [1,1,1,1,0,1],
    [1,1,1,1,1,0]
]

既存グループ者を記録するか?未組み合わせ者の候補から選択する。ロジックが思い浮かびます。
tab行列が1のみを計算し、その中から2名をチョイスし、nowから2名を減算できるなら、選択グループとします。
その組み合わせの行列を2箇所0にします。

端数者のロジックは51+1 % 13 = 0
つまり 、1名架空の候補者を用意し52名から4名を選択します。

リーダー選抜ロジックは14名から13名選択しnowから減算しtab行からメンバーを減算する?

・部署はできるだけ分ける
・役職はできるだけ分ける
のロジックは上記のプロセスをtblが空になるまで繰り返し、全パターンを重み付けして得点の低い順に選択する。

0

Comments

  1. @xsmmy929

    Questioner

    回答ありがとうございます!
    「1名架空の候補者を用意し52名から4名を選択」、なるほど、その発想はありませんでした。

    他の部分について、書いていただいている内容が正確に理解できているか分かりません。
    もしよければコード例を教えていただけませんか?

  2. pandas,numpyにて行列演算で一発で答えが出そうなのですが、思い浮かびません。

     リーダー選出(14名から13名)後に、リーダーがメンバーを選出するロジックにすると良いかも。溢れたリーダーはメンバーとなり

    39名から3名を選択しする全パターン(13パターンしかない?)を求め、重み付けでチョイスしたほうが良いかもしれません。

    部署、役職を分けた時に偏りを分散するロジックは?

    私はコードの前でモヤモヤ気分です。何かスッキリするロジックがありそうですが?

Your answer might help someone💌