根据该数据帧和另一个数据帧B中的多个匹配标准获得数据帧A的行索引

时间:2016-11-10 12:32:28

标签: r matching

假设我们在R中有两个数据框,df.Adf.B,由此定义:

bin_name <- c('bin_1','bin_2','bin_3','bin_4','bin_5')
bin_min <- c(0,2,4,6,8)
bin_max <- c(2,4,6,8,10)
df.A <- data.frame(bin_name, bin_min, bin_max, stringsAsFactors = FALSE)

obs_ID <- c('obs_1','obs_2','obs_3','obs_4','obs_5','obs_6','obs_7','obs_8','obs_9','obs_10')
obs_min <- c(6.5,0,8,2,1,7,5,6,8,3)
obs_max <- c(7,3,10,3,9,8,5.5,8,10,4)
df.B <- data.frame(obs_ID, obs_min, obs_max, stringsAsFactors = FALSE)

df.A定义了区间范围,而df.B由观察行组成,其最小值和最大值可能完全或可能不完全落在df.A中定义的区间内。

我们希望生成一个长度为nrow(df.B)的新向量,其中包含df.A的行索引,对应于每个观察完全落入的bin。如果观察跨越了一个箱子落在其上或部分在它之外,则它不能被分配到一个箱子并且应该返回NA(或类似的东西)。

在上面的例子中,正确的输出向量是:

bin_rows <- c(4, NA, 5, 2, NA, 4, 3, 4, 5, 2)

我使用sapply

提出了一个冗长的解决方案
bin_assignments <- sapply(1:nrow(df.B), function(i) which(df.A$bin_max >= df.B$obs_max[i] & df.A$bin_min <= df.B$obs_min[i])) #get bin assignments for every observation
bin_assignments[bin_assignments == "integer(0)"] <- NA #replace "integer(0)" entries with NA
bin_assignments <- do.call("c", bin_assignments) #concatenate the output of the sapply call

几个月前,我发现了一个简单的单行解决方案,没有使用apply函数。但是,我忘记了我是如何做到的,而且我无法重新发现它!解决方案可能涉及match()which()。有什么想法吗?

1 个答案:

答案 0 :(得分:1)

1)使用SQL可以在一个语句中轻松完成:

library(sqldf)

sqldf('select a.rowid
       from "df.B" b 
       left join "df.A" a on obs_min >= bin_min and obs_max <= bin_max')

   rowid
1      4
2     NA
3      5
4      2
5     NA
6      4
7      3
8      4
9      5
10     2

2)合并/ 我们可以使用mergeby在两个语句中执行此操作。没有使用包裹。

这确实有一个缺点,即它实现了SQL解决方案不需要做的大型连接。

请注意,问题中定义的df.B obs_10是第二级而不是第10级。如果obs_10是第10级,则by的第二个参数可能只是m$obs_ID,因此首先修复输入可以简化它。

m <- merge(df.B, df.A)
stack(by(m, as.numeric(sub(".*_", "", m$obs_ID)), 
      with, c(which(obs_min >= bin_min & obs_max <= bin_max), NA)[1]))

,并提供:

   values ind
1       4   1
2      NA   2
3       5   3
4       2   4
5      NA   5
6       4   6
7       3   7
8       4   8
9       5   9
10      2  10

3)sapply 请注意,使用(2)中的c(..., NA)[1]技巧,我们可以将问题中的sapply解决方案简化为一个语句:

sapply(1:nrow(df.B), function(i)
  c(which(df.A$bin_max >= df.B$obs_max[i] & df.A$bin_min <= df.B$obs_min[i]), NA)[1]) 

,并提供:

[1]  4 NA  5  2 NA  4  3  4  5  2

3a)mapply 使用mapply的更好的变体(@ 3)由@Ronak Shah`在评论中给出:

mapply(function(x, y) c(which(x >= df.A$bin_min & y <= df.A$bin_max), NA)[1], 
       df.B$obs_min, 
       df.B$obs_max)

4)外部这是另一个不使用包的语句解决方案。

seq_len(nrow(df.A)) %*% 
  (outer(df.A$bin_max, df.B$obs_max, ">=") & outer(df.A$bin_min, df.B$obs_min, "<="))

,并提供:

     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
[1,]    4    0    5    2    0    4    3    4    5     2