加入数据框并在存在多个匹配项时选择随机行

时间:2019-06-12 10:06:18

标签: r join dplyr data.table

我有一个参考数据框(df1),其中包含三列“特性”(性别,年份,代码)和两列“值”(金额,状态)。看起来像这样,但是有很多行:

gender    year    code    amount   status
     M    2011       A        15      EMX
     M    2011       A       123      NOX
     F    2015       B         0      MIX
     F    2018       A        12      NOX
     F    2015       B        11      NOX

我还有另一个数据框(df2),其中只有三个“特性”列。例如:

gender    year   code
     M    2011      A
     M    2011      A
     F    2018      A
     F    2015      B

对于df2中的每一行,我想根据df1的“特征”中的匹配项分配“值”。如果有多个匹配项,我想随机选择成对的“值”。因此,当df2中存在重复的“特征”时,它们可能会以不同的“值”对结束,但是所有这些变量在df1中将具有完全匹配的结果。本质上,对于特征的每种组合,我希望值的分布在两个表之间匹配。

例如,“ df2”中的最后一行(性别= F,年份= 2015,代码= B)与“ df1”中的两行匹配:第三行(amont = 0,状态= MIX)和第五行(金额= 11,状态= NOX)。然后,应随机选择这些匹配行之一。对于基于性别,年份和代码在“ df2”和“ df1”之间多次匹配的所有此类情况,应选择一个随机行。


到目前为止,我的方法是开始使用dplyr在两个数据帧之间执行left_join。但是,这为df2中的每一行提供了所有可能的“值”,而不是随机选择一个。因此,我必须按特征分组并选择一个。这样会产生一个很大的中间表,而且效率不高。

我想知道是否有人建议使用更有效的方法?以前,我发现加入data.table软件包的速度更快,但实际上对软件包没有很好的了解。我还想知道我是否应该进行联接,还是应该只使用sample函数?

非常感谢任何帮助。

4 个答案:

答案 0 :(得分:4)

根据'gender','year','code'(d1[d2, on = .(gender, year, code), ...])中的匹配项,使用'd2'查找'd1'中的行。对于每个匹配项(by = .EACHI,请采样一行(sample(.N, 1L))。用它来索引“数量”和“状态”。

d1[d2, on = .(gender, year, code),
  {ri <- sample(.N, 1L)
  .(amount = amount[ri], status = status[ri])}, by = .EACHI]

# sample based on set.seed(1)
#    gender year code amount status
# 1:      M 2011    A     15    EMX
# 2:      M 2011    A     15    EMX
# 3:      F 2018    A     12    NOX
# 4:      F 2015    B     11    NOX

请注意,Enhanced functionality of mult argument上存在一个未解决的问题,即x中的多行与i中的行匹配时如何处理情况。当前,有效选项为"all"(默认),"first""last"。但是,如果/在实施问题时,可以使用mult = "random"sample(.N, size = 1L))在匹配项中选择随机行(行)。

答案 1 :(得分:1)

我的data.table游戏非常弱,但是这是一种使用与上述类似的方法的潜在解决方案。首先,我定义数据帧。

# Define data frames
df1 <- read.table(text= "gender    year    code    amount   status
M    2011       A        15      EMX
M    2011       A       123      NOX
F    2015       B         0      MIX
F    2018       A        12      NOX
F    2015       B        11      NOX", header = TRUE)

df2 <- read.table(text = "gender    year   code
     M    2011      A
     M    2011      A
     F    2018      A
     F    2015      B", header = TRUE)

然后,为重现性设置随机数生成器种子并加载库。

# Set RNG seed
set.seed(4)

# Load library
library(data.table)

接下来,我将数据帧转换为数据表。

# Convert to data tables
dt1 <- data.table(df1) 
dt2 <- data.table(df2) 

在这里,我进行实际的联接,等等。我已经循环执行了5次以显示结果的随机性。

for(i in c(1:5)){
  # Add row numbers
  dt3 <- dt2[, rn :=.I
             ][dt1,on = .(gender, year, code)
               ][, .SD[sample(.N)[1]], .(gender, year, code, rn)
                 ][, rn := NULL]

  # Check results
  print(dt3)
}
#>    gender year code amount status
#> 1:      M 2011    A    123    NOX
#> 2:      M 2011    A     15    EMX
#> 3:      F 2015    B      0    MIX
#> 4:      F 2018    A     12    NOX
#>    gender year code amount status
#> 1:      M 2011    A    123    NOX
#> 2:      M 2011    A    123    NOX
#> 3:      F 2015    B     11    NOX
#> 4:      F 2018    A     12    NOX
#>    gender year code amount status
#> 1:      M 2011    A    123    NOX
#> 2:      M 2011    A    123    NOX
#> 3:      F 2015    B     11    NOX
#> 4:      F 2018    A     12    NOX
#>    gender year code amount status
#> 1:      M 2011    A     15    EMX
#> 2:      M 2011    A     15    EMX
#> 3:      F 2015    B     11    NOX
#> 4:      F 2018    A     12    NOX
#>    gender year code amount status
#> 1:      M 2011    A    123    NOX
#> 2:      M 2011    A     15    EMX
#> 3:      F 2015    B      0    MIX
#> 4:      F 2018    A     12    NOX

reprex package(v0.3.0)于2019-06-12创建

我实际上所做的是在数据表中添加行号,这将帮助我缩减最终的数据表。我联接数据表,然后将源自dt2中单个行的所有行分组,并使用sample随机提取其中的一行。 (这段代码是从@akrun here借来的。)最后,我删除了行号列。

答案 2 :(得分:1)

df2 %>%
  mutate(
    amount = pmap_chr(
      .l = df2,
      .f = ~ df1 %>%
        filter(gender == ..1, year == ..2, code == ..3) %>%
        select(amount) %>%
        sample_n(1) %>%
        pull(amount)
    ),
    status = pmap_chr(
      .l = df2,
      .f = ~ df1 %>%
        filter(gender == ..1, year == ..2, code == ..3) %>%
        select(status) %>%
        sample_n(1) %>%
        pull(status)
    )
  )

这很慢,我个人会避免这样做,但这是一种方法。

答案 3 :(得分:1)

我希望这样会很有效:

df1[, row := .I]
keys <- c("year", "gender", "code")
setkeyv(df1, keys)
setkeyv(df2, keys)

for (rowdf2 in seq_len(nrow(df2))) {
  set(df2, i = rowdf2, j = "rowindf1", value = df1[df2[rowdf2], x.row[sample(.N, 1)]])
}

setkeyv(df1, "row")
df1[df2[, .(rowindf1)]]

示例输出:

#    gender year code amount status row
# 1:      M 2011    A    123    NOX   2
# 2:      M 2011    A     15    EMX   1
# 3:      F 2015    B     11    NOX   5
# 4:      F 2018    A     12    NOX   4