【问题标题】:Match people into groups according to food preference根据食物偏好将人们分组
【发布时间】:2021-11-21 23:42:34
【问题描述】:

我正在寻找一种算法,可以帮助我将人们分成 3 组(a、b、c)。一个群体中的人应该适合在一起,这意味着食物偏好应该以一种他们都同意同一种食物的方式相匹配。组内每个集群(子组)由 6 人组成。 假设有 4 种食物偏好:

  1. 人喜欢吃肉
  2. 人喜欢吃素
  3. 这个人喜欢吃素食
  4. 这个人没有食物偏好,这意味着这个人基本上什么都喜欢吃

我想将人们分成 3 个逻辑组:

  • a 组:肉类和 no_food_preference
  • b 组:素食主义者、素食主义者和 no_food_preference
  • c 组:素食和 no_food_preference

我使用 no_food_preference 的人来填充集群,以确保每个集群包含 6 人。

将所有人分组后,每组将由 6 人的倍数组成。

我的问题:我非常努力,但我找不到可以为我做这件事的算法。我发现很难处理这样一个事实,即算法应该处理任意数量的参与者。

示例:

import pandas as pd

df = pd.DataFrame(
 {
 "user_id": [i for i in range(1, 55)],    
 "Master_FoodPreference": ["meat", "vegetarian", "meat", "vegan", "meat", "vegetarian", "meat", "vegetarian", "no_food_preference", 
                           "meat",'no_food_preference', 'vegetarian',"meat", "meat",
                           "vegetarian", "vegetarian", "vegan", "vegetarian", "vegetarian", "no_food_preference", "vegan", 
                           "vegetarian", "vegetarian", "vegetarian", "vegetarian", "vegetarian", "vegetarian",
                           "meat", "vegetarian", "meat", "vegetarian", "no_food_preference", "vegetarian", "vegetarian", "vegetarian", 
                           "vegetarian", "vegetarian", "vegetarian", "vegetarian", "vegetarian", "no_food_preference", 
                           "no_food_preference", "no_food_preference", "meat", "no_food_preference", "meat", "meat", 
                           "vegan", "no_food_preference", "no_food_preference", "vegan" ,"no_food_preference" ,"vegan" ,"vegan" ]
 }
)

df.head() 
>>>>
  user_id   Master_FoodPreference
0   1       meat
1   2       vegetarian
2   3       meat
3   4       vegan
4   5       meat

您如何将这些人分为group_agroup_bgroup_c

编辑 - 组构成: 每个组 (a,b,c) 将获得一个特定的标签:

  • a 组:人们会用肉做饭
  • b 组:人们会做一顿纯素的饭菜
  • c 组:人们会做一顿素食餐

这意味着,我们应该尝试让大多数素食者加入group_c。如果group_c 完整,我们将它们放入group_b。注意:我们不能将纯素食者放入group_c,因为纯素食者不吃素食。

【问题讨论】:

  • 我知道这不是典型的 stackoverflow 问题,因为问题不是很具体。也许你知道我可以在哪里获得帮助解决我的问题的任何其他论坛?!
  • 你的最后一句话对素食者不是很清楚。优先考虑适合 B 组还是 C 组?
  • 非常抱歉给您带来的困惑!!!我再次编辑了我的笔记。我希望现在它对你有意义?!

标签: python pandas dataframe cluster-analysis


【解决方案1】:

似乎并不太难:将项目分组,然后使用“no_food_preference”中的项目以模 6 填充其他组 - 如果某些项目仍保留在“no_food_preference”中,则将它们移到另一个组中:

pref = ["meat", "vegetarian", "meat", "vegan", "meat", "vegetarian", "meat", "vegetarian", "no_food_preference", 
                           "meat",'no_food_preference', 'vegetarian',"meat", "meat",
                           "vegetarian", "vegetarian", "vegan", "vegetarian", "vegetarian", "no_food_preference", "vegan", 
                           "vegetarian", "vegetarian", "vegetarian", "vegetarian", "vegetarian", "vegetarian",
                           "meat", "vegetarian", "meat", "vegetarian", "no_food_preference", "vegetarian", "vegetarian", "vegetarian", 
                           "vegetarian", "vegetarian", "vegetarian", "vegetarian", "vegetarian", "no_food_preference", 
                           "no_food_preference", "no_food_preference", "meat", "no_food_preference", "meat", "meat", 
                           "vegan", "no_food_preference", "no_food_preference", "vegan" ,"no_food_preference" ,"vegan" ,"vegan" ]

def assign_groups(pref):
    groups={}
    for i,p in enumerate(pref):
        if p in groups:
            groups[p].append(i)
        else:
            groups[p] = [i]
        for p in ['meat','vegetarian','vegan']:
            need = len(groups[p]) % 6
            if need:
                for i in range(6-need):
                    groups[p].append(groups["no_food_preference"].pop())
        if len(groups["no_food_preference"]):
            groups["meat"] += groups["no_food_preference"]
            del groups["no_food_preference"]
        return groups

assign_groups(pref)       
{'meat': [0, 2, 4, 6, 9, 12, 13, 27, 29, 43, 45, 46, 8, 10, 19, 31, 40, 41], 'vegetarian': [1, 5, 7, 11, 14, 15, 17, 18, 21, 22, 23, 24, 25, 26, 28, 30, 32, 33, 34, 35, 36, 37, 38, 39], 'vegan': [3, 16, 20, 47, 50, 52, 53, 51, 49, 48, 44, 42]}

当然,如果项目总数是 6 的倍数,这将起作用。

编辑

我更新了代码以更符合原始请求,并处理一些特殊情况。一些观察:

  • 我们需要总人数为 6 的倍数(或我们为“集群”大小选择的值)
  • 如果我们想确保处理所有可能性,我们需要假设肉食者也可以吃蔬菜 - 即它们可以用来填充蔬菜甚至素食集群。否则有些情况是无法解决的,例如,如果集群大小为 6,则 7 x 肉类、7 x 蔬菜、7 x 素食、3 x no-pref 没有解决方案
  • 所以我们首先处理素食者群体,用无偏好填充它,然后如果需要素食者,然后如果仍然需要肉食者;然后与剩下的素食者打交道,让他们的团队充满不喜欢的人,然后是肉食者;最后是肉组,只能用无首选项填充;最后,如果还有一些无偏好的集群,我们将它们添加到一组(肉类)

修改后的代码如下所示(我添加了一个帮助函数将人们从一个组转移到另一个组):

pref = ["meat", "vegetarian", "meat", "vegan", "meat", "vegetarian", "meat", "vegetarian", "no_food_preference", "meat",
        'no_food_preference', 'vegetarian',"meat", "meat","vegetarian", "vegetarian", "vegan", "vegetarian", "vegetarian",
        "no_food_preference", "vegan", "vegetarian", "vegetarian", "vegetarian", "vegetarian", "vegetarian", "vegetarian",
        "meat", "vegetarian", "meat", "vegetarian", "no_food_preference", "vegetarian", "vegetarian", "vegetarian",
        "vegetarian", "vegetarian", "vegetarian", "vegetarian", "vegetarian", "no_food_preference",
        "no_food_preference", "no_food_preference", "meat", "no_food_preference", "meat", "meat", "vegan",
        "no_food_preference", "no_food_preference", "vegan" ,"no_food_preference" ,"vegan" ,"vegan" ]

groups = {}

def assign_groups(pref, pergroup):
    global groups, pref
    groups = {'meat':[], 'vegetarian':[], 'vegan':[], 'no_food_preference':[]}
    fillers = {'meat':['no_food_preference'],
               'vegetarian':['no_food_preference', 'meat'],
               'vegan':['no_food_preference', 'vegetarian', 'meat']}
    for i,p in enumerate(pref):
        groups[p].append(i)
    for p in ['vegan','vegetarian','meat']:
        need = len(groups[p]) % pergroup
        if need:
            fill_idx = 0
            need = pergroup - need
            while need:
                f = fillers[p][fill_idx]
                avail = len(groups[f])
                if need > avail:
                    from_to(p, f, avail)
                    need -= avail
                    fill_idx += 1
                else:
                    from_to(p, f, need)
                    need = 0
    
    if len(groups["no_food_preference"]):
        from_to("meat", "no_food_preference", len(groups["no_food_preference"]))
    return groups

def from_to(p,f,n):
    global groups
    for i in range(n):
        groups[p].append(groups[f].pop())

assign_groups(pref, 6)
{'meat': [0, 2, 4, 6, 9, 12, 13, 27, 29, 43, 45, 46, 41, 40, 31, 19, 10, 8], 'vegetarian': [1, 5, 7, 11, 14, 15, 17, 18, 21, 22, 23, 24, 25, 26, 28, 30, 32, 33, 34, 35, 36, 37, 38, 39], 'vegan': [3, 16, 20, 47, 50, 52, 53, 51, 49, 48, 44, 42], 'no_food_preference': []}

【讨论】:

  • 我运行它,它对我有用。似乎没有“no_food_preference”的人,并且至少有一个组需要添加某人才能达到 6 的倍数。您是否尝试过您的原始示例?
  • 我刚刚意识到我没有看到你想把素食者和素食者放在 B 组。我想这意味着素食者可以无差别地放在 B 组或 C 组?
  • 抱歉,我没有说我只是添加了该变量,以便有可能尝试不同的集群大小。对于您的示例,只需通过 6。我将编辑添加函数调用的答案
  • 是的,pergroup 是唯一需要更改的参数。而且我希望 3x 肉、2x 素食和 1x 素食应该导致 1 个素食群体(所以孤独的素食主义者会毁掉其他人的晚餐:D)。我稍后会测试它
  • 是的,代码中有一个错误和一个不准确之处:我忘记更新fill_idx,所以当您需要从多个组中提取人员时,代码将永远循环; groups 在函数外部初始化,所以如果你想调用它两次,你会得到奇怪的结果。编辑答案,现在应该可以了
【解决方案2】:

创建 4 个数据框:3 个用于您的组(dfA、dfB、dfC),1 个用于没有食物偏好的组 (dfX),然后在需要时用组 X 填充每个组 A、B、C:

dfX = df[df['Master_FoodPreference'].eq('no_food_preference')]

dfA = df[df['Master_FoodPreference'].eq('meat')]
dfA = dfA.append(dfX.sample(len(dfA) % 6))

dfB = df[df['Master_FoodPreference'].eq('vegan')
         | df['Master_FoodPreference'].eq('vegetarian')]
dfB = dfB.append(dfX.sample(len(dfB) % 6))

dfC = df[df['Master_FoodPreference'].eq('vegetarian')]
dfC = dfC.append(dfX.sample(len(dfC) % 6))

输出:

>>> dfB
    user_id Master_FoodPreference
1         2            vegetarian
3         4                 vegan
5         6            vegetarian
7         8            vegetarian
11       12            vegetarian
14       15            vegetarian
15       16            vegetarian
16       17                 vegan
17       18            vegetarian
18       19            vegetarian
20       21                 vegan
21       22            vegetarian
22       23            vegetarian
23       24            vegetarian
24       25            vegetarian
25       26            vegetarian
26       27            vegetarian
28       29            vegetarian
30       31            vegetarian
32       33            vegetarian
33       34            vegetarian
34       35            vegetarian
35       36            vegetarian
36       37            vegetarian
37       38            vegetarian
38       39            vegetarian
39       40            vegetarian
47       48                 vegan
50       51                 vegan
52       53                 vegan
53       54                 vegan
49       50    no_food_preference

将所有人分组后,每组将由 6 人的倍数组成。

你的样品可以做到:

# Before append
>>> len(dfA), len(dfB), len(dfC), len(dfX)
(12, 31, 24, 11)

【讨论】:

  • 哇,这是一个非常简短的解决方案。我喜欢。第一个问题:dfX 是什么?您能否指定这一点,以便我可以自己运行代码?非常感谢您的帮助!!!
  • 抱歉,复制/粘贴错误,我忘记了dfX。我更新了我的答案。
  • 我喜欢您的解决方案,但我有一些 cmets:1) 我认为我不清楚组的组成。我将在主帖中添加注释! 2) 最后,我需要 3 组,也许还有第 4 组,供所有没有找到组的人使用。因此,我们可以将dfX 的人员分配给其他组吗?
  • 我在主帖中添加了一些 cmets。我认为您示例中的分布可以根据我的评论进行优化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-14
  • 1970-01-01
  • 2012-04-10
相关资源
最近更新 更多