Python/R 中两个不同大小的数据帧中的 1 到 2 匹配

1 to 2 matching in two dataframes with different sizes in Python/R

请帮我解决这个问题,我整天都在苦苦挣扎,哈哈,Python 或 R 中的解决方案都可以! 请帮助我真的卡住了!!!

我有两个数据框——df1 有 44 行,df2 有 100 行,它们都有这些列: ID、状态 (0,1)、年龄、性别、种族、民族、身高、体重

对于 df1 中的每一行,我需要在 df2 中找到 age 匹配项:

  1. 它可以是精确的年龄匹配,但应该使用的标准是 - df2[age]-5 <= df1[age]<= df2[age]+5
  2. 我需要一个 list/dictionary 来存储哪些是 df1 的年龄匹配,以及他们的 ID
  3. 然后我需要从df2中随机select 2个ID作为df1 age
  4. 的最终匹配
  5. 我还需要确保 2 个 df2 匹配项与 df1
  6. 具有相同的性别和种族

我试过 R 和 Python,都卡在嵌套循环部分。 我不确定如何遍历 df1 和 df2 的每条记录,将 df1 age 与 df2 age-5 和 df2 age+5 进行比较,并存储匹配项

以下是 df1 和 df2 的示例数据格式: |编号 |性别 |年龄 |比赛 | | ------ | -------------- |--------|--------| | 284336 |女性 | 42.8 | 2 | | 294123 |男性 | 48.5 | 1 |

这是我在 R 中的尝试:

id_match <- NULL
for (i in 1:nrow(gwi_case)){
  age <- gwi_case$age[i]
  gender <- gwi_case$gender[i]
  ethnicity <- gwi_case$hispanic_non[i]
  race <- gwi_case$race[i]
  
  x <- which(gwi_control$gender==gender & gwi_control$age>=age-5 & gwi_control$age<=age+5 & gwi_control$hispanic_non==ethnicity & gwi_control$race==race)
  
  y <- sample(x, min(2, length(x)))
  
  id_match <- c(id_match, y)
}

id_match <- id_match[!duplicated(id_match)]
length(id_match)

不确定我是否完全理解要求,但是...在 python 中,您可以使用应用于数据框和 lambda 函数来执行一些时髦的事情

df1['age_matched_ids'] = df1.apply(lambda x: list(df2.loc[df2['Age'] >= x['Age'] - 5 & df2['Age'] <= x['Age'] + 5, 'ID']), axis=1)

这将在 'age_matched_ids' 列中存储 df2 中年龄在 +/- 5 岁之间的 ID 列表。您可以从这里执行#2 和#3。

问题是这样问的:

  • 对于 df1 中的每一行,找到 df2 中的年龄匹配,使得 df2[age] - 5 <= df1[age] <= df2[age] + 5
  • 创建一个 list/dictionary 来保存 df1
  • 的年龄匹配和 ID
  • 随机select来自df2的2个ID作为df1年龄的最终匹配

这是一些 Python 代码:

  • 使用条件填充列表列表 ageMatches,其中包含与每个唯一 df1 年龄
  • 匹配的唯一 df2 年龄列表
  • df1 中的每个年龄在 df2 上调用 DataFrame.query() 以填充 idMatches 与年龄匹配每个唯一 df1 年龄
  • 使用唯一的 df1 年龄键填充 age1ToID2 并使用 2 个(或更少,如果可用数量 < 2)列表的值随机 selected df2 ID匹配年龄
  • df1 添加一列,其中包含一对 selected df2 ID 对应于每一行的年龄(即 age1ToID2 中的值)
import pandas as pd
import numpy as np
df1 = pd.DataFrame({'ID':list(range(101,145)), 'Age':[v % 11 + 21 for v in range(44)], 'Height':[67]*44})
df2 = pd.DataFrame({'ID':list(range(1,101)), 'Age':[v % 10 + 14 for v in range(50)] + [v % 20 + 25 for v in range(0,100,2)], 'Height':[67]*100})

ages1 = np.sort(df1['Age'].unique())
ages2 = np.sort(df2['Age'].unique())
ageMatches = [[] for _ in ages1]
j1, j2 = 0, 0
for i, age1 in enumerate(ages1):
    while j1 < len(ages2) and ages2[j1] < age1 - 5:
        j1 += 1
    if j2 <= j1:
        j2 = j1 + 1
    while j2 < len(ages2) and ages2[j2] <= age1 + 5:
        j2 += 1
    ageMatches[i] += list(ages2[j1:j2])
idMatches = [df2.query('Age in @m')['ID'].to_list() for i, m in enumerate(ageMatches)]

# select random pair of df2 IDs for each unique df1 age and put them into a new df1 column
from random import sample
age1ToID2 = {ages1[i]:m if len(m) < 2 else sample(m, 2) for i, m in enumerate(idMatches)}
df1['df2_matches'] = df1['Age'].apply(lambda x: age1ToID2[x])
print(df1)

输出:

     ID  Age  Height df2_matches
0   101   21      67    [24, 30]
1   102   22      67    [50, 72]
2   103   23      67    [10, 37]
3   104   24      67    [63, 83]
4   105   25      67    [83, 49]
5   106   26      67    [20, 52]
6   107   27      67    [49, 84]
7   108   28      67    [54, 55]
8   109   29      67    [91, 55]
9   110   30      67    [65, 51]
10  111   31      67    [75, 72]
11  112   21      67    [24, 30]
...
42  143   30      67    [65, 51]
43  144   31      67    [75, 72]

这有望提供 OP 要求的结果和中间集合,或者足够接近以获得所需结果的东西。

或者,要让 df1 中每一行的随机 selection 不同,我们可以这样做:

# select random pair of df2 IDs for each df1 row and put them into a new df1 column
from random import sample
age1ToID2 = {ages1[i]:m for i, m in enumerate(idMatches)}
def foo(x):
    m = age1ToID2[x]
    return m if len(m) < 2 else sample(m, 2)
df1['df2_matches'] = df1['Age'].apply(foo)
print(df1)

输出:

     ID  Age  Height df2_matches
0   101   21      67    [71, 38]
1   102   22      67     [71, 5]
2   103   23      67     [9, 38]
3   104   24      67    [49, 61]
4   105   25      67    [27, 93]
5   106   26      67    [40, 20]
6   107   27      67     [9, 19]
7   108   28      67    [53, 72]
8   109   29      67    [82, 53]
9   110   30      67    [74, 62]
10  111   31      67    [52, 62]
11  112   21      67    [71, 39]
...
42  143   30      67    [96, 66]
43  144   31      67    [63, 83]