根据属性将两个人配对

6

我有一个包含不同人的数据框。每一行都包含描述个人特征的属性。基本上,我需要类似于过滤器或匹配算法的东西来加权特定属性。数据框看起来像这样:

df= pd.DataFrame({
'sex' : [m,f,m,f,m,f],
'food' : [0,0,1,3,4,3],
 'age': [young, young, young, old, young, young]
'kitchen': [0,1,2,0,1,2],
})

数据框 df 的外观如下:
    sex food  age     kitchen
0   m    0    young    0
1   f    0    young    1
2   m    1    young    2
3   f    3    old      0
4   m    4    young    1
5   f    3    young    2

我正在寻找一种算法,将数据框中的所有人分组成一对。我的计划是基于以下属性找到两个人的配对:
1. 一个人必须有厨房(kitchen=1) 至少有一个人拥有厨房非常重要。 kitchen=0 --> 没有厨房 kitchen=1 --> 有厨房 kitchen=2 --> 只有在紧急情况下才有厨房(当没有其他选择时)
2. 相同的饮食偏好 food=0 --> 肉食者 food=1 --> 不在意 food=2 --> 素食主义者 food=3 --> 素食者 肉食者(food=0)可以与不关心饮食偏好(food=1)的人匹配,但不能与素食主义者或素食者匹配。素食主义者(food=2)最适合与素食者(food=3)匹配,并且如果必要,可以选择食品=1。以此类推...
3. 年龄相似 有九个年龄段:10-18岁;18-22岁;22-26岁;26-29岁,29-34岁;34-40岁;40-45岁;45-55岁和55-75岁。同年龄段的人完全匹配。年轻年龄组与老年年龄组不太匹配。相似的年龄组稍微匹配一些。没有明确定义的条件。 "老"和"年轻"的含义是相对的。
性别并不重要。有许多可能的配对组合。因为我的实际数据框非常长(3000行),所以我需要找到一个自动化的解决方案。一个给我最好的配对的解决方案,可以是数据框或字典或其他什么东西。
我真的不知道如何解决这个问题。我在Stack Overflow上寻找类似的问题,但没有找到合适的东西。大多数情况下只是过于理论化了。而且我找不到任何真正适合我的问题的东西。
我的预期输出将是,例如一个字典(不确定如何)或一个数据框,按照每两行可以看作一对的方式进行排序。
背景:目标是为一些空闲时间活动做配对。因此,我认为,同龄人或相似年龄组的人分享相同的兴趣,因此我希望在我的代码中考虑这个事实。

@PParker,关于你的声明“如果可能,配对者应该处于同一年龄组。如果不可能,那么可能是在相似的年龄组。” 你的年龄列中是否有数字年龄,还是只有两个字符串值:“年轻”和“老”? - Anidhya Bhatnagar
1
@PParker 你可能想要查看以下资源:在距离约束下最大化配对花式算法带约束的匹配分配问题匈牙利算法 - a_guest
@AnidhyaBhatnagar 在我的简化示例中,我只有两个年龄组(年轻和老年)。然而,在真实的数据框中,我有数字年龄。我的计划是制作多个年龄组(例如“非常年轻”,“年轻”,“老年”,“非常老年”等)。 - PParker
3个回答

12

我通过将 'name' 作为标识人的关键字来进行了添加。

方法

我的方法是对值进行评分,然后根据给定条件过滤最终的成对结果。

厨房评分

我们使用以下评分标准:

  • 个人没有厨房:0
  • 个人有厨房:1
  • 个人有厨房但只在紧急情况下使用:0.5

厨房条件逻辑

我们检查[记录1的厨房评分] + [记录2的厨房评分]是否大于零。如下情况将出现:

  1. 两个成员均没有厨房(总和为0)[不符合 > 0 条件]
  2. 两个成员都有厨房(总和为2)
  3. 一个成员有厨房,另一个没有厨房(总和为1)
  4. 两个成员都有应急厨房(总和为1)
  5. 一个成员有应急厨房,另一个有厨房(总和为1.5)
  6. 一个成员有应急厨房,另一个没有厨房(总和为0.5)

食品评分

我们使用以下食品评分:

  • food = 0 --> 肉食者 : -1
  • food = 1 --> 不重要 : 0
  • food = 2 --> 素食主义者 : 1
  • food = 3 --> 素食者 : 1

食品条件逻辑

我们检查 *[记录1的食品评分] x [记录2的食品评分]* 是否大于或等于 。如下情况将出现:

  1. 两个成员都是肉食者:-1 x -1 = 1 [包含]
  2. 其中一位成员是肉食者,另一位是素食主义者或素食者:-1 x 1 = -1 [不包含]
  3. 其中一位成员是肉食者,另一位不在意:-1 x 0 = 0 [包含]
  4. 其中一位成员是素食主义者或素食者,另一位不在意:1 x 0 = 0 [包含]
  5. 两个成员均为素食主义者或素食者:1 x 1 = 1 [包含]

年龄组评分

为了对年龄组进行评分,我们为不同的组分配了一些值:

  • 10-18 :1
  • 18-22 :2
  • 22-26 :3
  • 26-29 :4
  • 29-34 :5
  • 34-40 :6
  • 40-45 :7
  • 45-55 :8
  • 55-75 :9

年龄分数计算方法

计算年龄分数采用以下公式:age_score = round((1 - (abs(个人1所在年龄组值 - 个人2所在年龄组值) / 10)), 2)

以上公式的计算步骤如下:

  1. 首先我们计算两个人年龄组的值之间的差的绝对值。
  2. 然后我们将其除以10进行归一化处理。
  3. 接着从1中减去这个值来反转距离,因此在此步骤之后,相似或更接近年龄组的人具有较高的值,而不同或更远的年龄组的人的值较低。

情况如下:

  1. 18-22和18-22:round(1 - (abs(2 - 2) / 10), 2) = 1.0
  2. 45-55和45-55:round(1 - (abs(8 - 8) / 10), 2) = 1.0
  3. 18-22和45-55:round(1 - (abs(2 - 8) / 10), 2) = 0.4
  4. 10-18和55-75:round(1 - (abs(1 - 9) / 10), 2) = 0.2

最终分数计算方法

为了计算最终分数,我们使用了以下公式:

Final Score = Food Score + Kitchen Score + Age Score

然后,我们按照最终分数对数据进行排序,以获取最佳配对。

解决方案代码

import pandas as pd
import numpy as np

# Creating the DataFrame, here I have added the attribute 'name' for identifying the record.
df = pd.DataFrame({
    'name' : ['jacob', 'mary', 'rick', 'emily', 'sabastein', 'anna', 
              'christina', 'allen', 'jolly', 'rock', 'smith', 'waterman', 
              'mimi', 'katie', 'john', 'rose', 'leonardo', 'cinthy', 'jim', 
              'paul'],
    'sex' : ['m', 'f', 'm', 'f', 'm', 'f', 'f', 'm', 'f', 'm', 'm', 'm', 'f', 
             'f', 'm', 'f', 'm', 'f', 'm', 'm'],
    'food' : [0, 0, 1, 3, 2, 3, 1, 0, 0, 3, 3, 2, 1, 2, 1, 0, 1, 0, 3, 1],
    'age' : ['10-18', '22-26', '29-34', '40-45', '18-22', '34-40', '55-75',
             '45-55', '26-29', '26-29', '18-22', '55-75', '22-26', '45-55', 
             '10-18', '22-26', '40-45', '45-55', '10-18', '29-34'],
    'kitchen' : [0, 1, 2, 0, 1, 2, 2, 1, 0, 0, 1, 0, 1, 1, 1, 0, 2, 0, 2, 1],
})

# Adding a normalized field 'k_scr' for kitchen
df['k_scr'] = np.where((df['kitchen'] == 2), 0.5, df['kitchen'])

# Adding a normalized field 'f_scr' for food
df['f_scr'] = np.where((df['food'] == 1), 0, df['food'])
df['f_scr'] = np.where((df['food'] == 0), -1, df['f_scr'])
df['f_scr'] = np.where((df['food'] == 2), 1, df['f_scr'])
df['f_scr'] = np.where((df['food'] == 3), 1, df['f_scr'])

# Adding a normalized field 'a_scr' for age
df['a_scr'] = np.where((df['age'] == '10-18'), 1, df['age'])
df['a_scr'] = np.where((df['age'] == '18-22'), 2, df['a_scr'])
df['a_scr'] = np.where((df['age'] == '22-26'), 3, df['a_scr'])
df['a_scr'] = np.where((df['age'] == '26-29'), 4, df['a_scr'])
df['a_scr'] = np.where((df['age'] == '29-34'), 5, df['a_scr'])
df['a_scr'] = np.where((df['age'] == '34-40'), 6, df['a_scr'])
df['a_scr'] = np.where((df['age'] == '40-45'), 7, df['a_scr'])
df['a_scr'] = np.where((df['age'] == '45-55'), 8, df['a_scr'])
df['a_scr'] = np.where((df['age'] == '55-75'), 9, df['a_scr'])

# Printing DataFrame after adding normalized score values
print(df)

commonarr = [] # Empty array for our output
dfarr = np.array(df) # Converting DataFrame to Numpy Array
for i in range(len(dfarr) - 1): # Iterating the Array row
    for j in range(i + 1, len(dfarr)): # Iterating the Array row + 1
        # Check for Food Condition to include relevant records
        if dfarr[i][6] * dfarr[j][6] >= 0: 
            # Check for Kitchen Condition to include relevant records
            if dfarr[i][5] + dfarr[j][5] > 0:
                row = []
                # Appending the names
                row.append(dfarr[i][0])
                row.append(dfarr[j][0])
                # Appending the final score
                row.append((dfarr[i][6] * dfarr[j][6]) +
                           (dfarr[i][5] + dfarr[j][5]) +
                           (round((1 - (abs(dfarr[i][7] -
                                            dfarr[j][7]) / 10)), 2)))

                # Appending the row to the Final Array
                commonarr.append(row)

# Converting Array to DataFrame
ndf = pd.DataFrame(commonarr)

# Sorting the DataFrame on Final Score
ndf = ndf.sort_values(by=[2], ascending=False)
print(ndf)

包含分数的输入/中间数据框

         name sex  food    age  kitchen  k_scr  f_scr a_scr
0       jacob   m     0  10-18        0    0.0     -1     1
1        mary   f     0  22-26        1    1.0     -1     3
2        rick   m     1  29-34        2    0.5      0     5
3       emily   f     3  40-45        0    0.0      1     7
4   sabastein   m     2  18-22        1    1.0      1     2
5        anna   f     3  34-40        2    0.5      1     6
6   christina   f     1  55-75        2    0.5      0     9
7       allen   m     0  45-55        1    1.0     -1     8
8       jolly   f     0  26-29        0    0.0     -1     4
9        rock   m     3  26-29        0    0.0      1     4
10      smith   m     3  18-22        1    1.0      1     2
11   waterman   m     2  55-75        0    0.0      1     9
12       mimi   f     1  22-26        1    1.0      0     3
13      katie   f     2  45-55        1    1.0      1     8
14       john   m     1  10-18        1    1.0      0     1
15       rose   f     0  22-26        0    0.0     -1     3
16   leonardo   m     1  40-45        2    0.5      0     7
17     cinthy   f     0  45-55        0    0.0     -1     8
18        jim   m     3  10-18        2    0.5      1     1
19       paul   m     1  29-34        1    1.0      0     5

输出

             0          1    2
48   sabastein      smith  4.0
10        mary      allen  3.5
51   sabastein      katie  3.4
102      smith        jim  3.4
54   sabastein        jim  3.4
99       smith      katie  3.4
61        anna      katie  3.3
45   sabastein       anna  3.1
58        anna      smith  3.1
14        mary       rose  3.0
12        mary       mimi  3.0
84       allen     cinthy  3.0
98       smith       mimi  2.9
105   waterman      katie  2.9
11        mary      jolly  2.9
50   sabastein       mimi  2.9
40       emily      katie  2.9
52   sabastein       john  2.9
100      smith       john  2.9
90        rock      smith  2.8
47   sabastein       rock  2.8
0        jacob       mary  2.8
17        mary       paul  2.8
13        mary       john  2.8
119      katie        jim  2.8
116       mimi       paul  2.8
111       mimi       john  2.8
103      smith       paul  2.7
85       allen       paul  2.7
120      katie       paul  2.7
..         ...        ...  ...

该解决方案还有进一步优化的空间。


1
非常感谢您提供的这种解决问题的方法,对于我的迟回复我很抱歉。我还有一个问题想要问:1)假设我有多个年龄组。在我的情况下,我有9个年龄组。我认为我需要改变方程式。但是,我不确定应该给每个年龄组什么分数。也许您可以在这里帮助我! - PParker
1
谢谢你的帮助!我有九个年龄组:10-18岁;18-22岁;22-26岁;26-29岁;29-34岁;34-40岁;40-45岁;45-55岁和55-75岁。同一年龄组的人完全匹配。年轻年龄组与较老年龄组不太匹配。相似年龄组稍微匹配一些。没有明确定义的条件。"老"和"年轻"的含义是相对的。背景:目标是为一些空闲时间活动做配对。因此,我认为,同龄或类似年龄组的人分享相同的兴趣,因此我想在我的代码中考虑这个事实。 - PParker
1
@PParker,我已经根据您提供的年龄组信息更新了问题,并更新了答案及其解释。现在程序考虑两个人年龄组之间的距离,然后根据他们的距离评分。希望这可以帮到您。 - Anidhya Bhatnagar
1
@PParker 1.) 是的,根据人们和他们的特点,不是每个人都能找到伴侣是正确的说法。2.) 不要混淆,Sabastein与Smith得分最高,而Smith与Sabastein也得分最高。因为如果你匹配Sabastein->Smith或者Smith->Sabastein,得分将会是相同的4.0。所以当你选择一对比如(Sabastein和Smith),然后忽略所有后续有Sabastein或Smith的配对。这样你就可以得到唯一的配对。 - Anidhya Bhatnagar
1
非常感谢您的回答。我刚刚意识到了一些新的东西:我们现在有一个非常长的数据框,其中包含不同的配对组合和分数。事实证明,一个人可以与多个人最匹配。例如,安娜与凯蒂(3.3)的工作效果最佳,但克里斯蒂娜也与凯蒂(2.4)最匹配。有很多类似的例子。因此,我认为最好的方法是编写一个函数,从数据框中选择最佳的配对。目标是尽可能找到更多好的配对。也许您可以对此发表评论。您通常会如何处理这个问题? - PParker
显示剩余4条评论

1

这对我来说似乎是一个非常有趣的问题。解决这个问题有几种方法。我将给你介绍其中一种,但我会链接到另一个解决方案,我认为它与此相关。

可能的方法之一是在数据框中创建一个附加列,包括引用给定属性的'code'。例如:

    sex  food  age      kitchen   code
0   m    0     young    0         0y0
1   f    0     young    1         0y1
2   m    1     young    2         1y2
3   f    3     old      0         3o0
4   m    4     young    1         4y1
5   f    3     young    2         3y2

这个“代码”由你的属性简称组成。由于性别无关紧要,代码中的第一个符号代表“食物”,第二个符号代表“年龄”,第三个符号代表“厨房”。
4y1 = food 4, age young, kitchen 1.

根据这些代码,您可以得出一个模式。我建议您使用正则表达式来进行此操作。然后,您可以编写类似于以下内容的代码:

import re
haskitchen = r'(\S\S1)
hasnokitchen = r'(\S\S0)
df_dict = df.to_dict

match_kitchen = re.findall(haskitchen, df_dict)
match_nokitchen = re.dinfall(hasnokitchen, df_dict)

kitchendict["Has kitchen"] = [match_kitchen]
kitchendict["Has no kitchen"] = [match_notkitchen]

基于这个,你可以循环遍历条目并按照自己的方式组合它们。可能有更简单的解决方案,我没有证明代码,但这只是我的想法。有一件事是肯定的:使用正则表达式进行匹配。

1

好的,让我们来测试一下厨房。

for I in(kitchen):
    if (I != 0):
        print("Kitchen Found)
    else:
        print("No kitchen")

现在我们已经在有厨房的人家中找到了一个厨房,让我们找一些没有厨房但食物口味相似的人。让我们创建一个变量来告诉我们有多少人拥有厨房(x)。同时,让我们也创建一个人的变量来计算人数。

people = 0
x = 0
for I in(kitchen):
    x = x + 1
    for A in (food):
            if (I != 0):
                x = x + 1
                print("Kitchen Found)
            else:
                print("No kitchen")
                for J in(food):
                    if(i == J):
                        print("food match found")
                    elif(A == 0):
                        if(J == 1):
                            print("food match found for person" + x)
                    elif(A == 2 or A == 3):
                        if(J == 2 or J == 3 or J == 1):
                            print("food match found for person" + x)

我目前正在修改年龄部分的一些内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接