【问题标题】:R - comparing data between 2 data frames in RR - 比较 R 中 2 个数据帧之间的数据
【发布时间】:2018-09-27 17:29:15
【问题描述】:

假设我们在 R 中有 2 个数据帧。

a = data.frame(col1 = round(runif(6,1,20)),col2 = c("a b c","b e z","a c q","a b","w","u o p l"), stringsAsFactors = F)
b = data.frame(col1 = 1:10, col2 = round(runif(10,1,10)), col3 = round(runif(10,10,20)), col4 = c(paste(letters[1:15], collapse=" "),paste(letters[10:25], collapse=" "),paste(letters[1:15], collapse=" "),paste(letters[1:19], collapse=" "),paste(letters[10:15], collapse=" "),paste(letters[1:15], collapse=" "),paste(letters[20:25], collapse=" "),paste(letters[1:15], collapse=" "),paste(letters[3:26], collapse=" "),paste(letters[1:2], collapse=" ")),stringsAsFactors = F)

数据集是:

a
 col1    col2
   15   a b c
    8   b e z
   11   a c q
   15     a b
    5       w
   12 u o p l

b
col1 col2  col3                                           col4
1     1     10                   a b c d e f g h i j k l m n o
2     2     12                 j k l m n o p q r s t u v w x y
3     4     12                   a b c d e f g h i j k l m n o
4     4     16           a b c d e f g h i j k l m n o p q r s
5     2     13                                     j k l m n o
6     3     15                   a b c d e f g h i j k l m n o
7     1     12                                     t u v w x y
8     2     18                   a b c d e f g h i j k l m n o
9     4     16 c d e f g h i j k l m n o p q r s t u v w x y z
10    3     12                                             a b

我想根据以下几点在数据框“a”中创建第三列 col3:

  1. a$col3 将包含来自数据帧“b”的 col1 的值的序列号列表。
  2. 将选择 b$col1 的值,其中 a$col1 位于每行的 b$col2 和 b$col3 值之间。
  3. 另外,a$col2 中的字母应该出现在 b$col4 中。 (不需要排序。例如,“a s”等价于“s a”。)

最终需要的数据集。

a
 col1    col2     col3
   15   a b c    4 6 8
    8   b e z   
   11   a c q      4 9
   15     a b    4 6 8
    5       w    2 7 9
   12 u o p l      2 9

请注意—— 由于我使用的数据框很大,因此 For-loops 解决方案将不起作用。 (有数百万行)。任何其他方法将不胜感激。

提前致谢。

【问题讨论】:

  • 您的观点 1 中有一个令人困惑的错字。您所说的“slno.s”是什么意思?
  • @ngm 抱歉,我的意思是序列号。改变了。

标签: r loops dataframe dplyr compare


【解决方案1】:

使用tidyversedplyrstringrpurrr)你可以做这样的事情......

 a2 <- b %>% mutate(col5=map2(col2,col3,~seq(.x,.y,1))) %>% #expand b to include all values between col2 and col3
  unnest() %>% 
  inner_join(a,by=c("col5"="col1")) %>% #match these against a col1
  filter(map2_lgl(col2.y,col4,~all(str_detect(.y,unlist(strsplit(.x," ")))))) %>% #filter by string matches
  group_by(col5,col2.y) %>% #group by original a columns
  summarize(col3=paste(sort(col1),collapse=" ")) %>% #collapse matching b col1 values
  right_join(a,by=c("col5"="col1","col2.y"="col2")) %>% #merge back into a
  rename(col1=col5,col2=col2.y) #restore column names

由于您的随机化过程,我得到了不同的数据帧(顺便说一句,sample() 可能比round(runif()) 更好),但这是我最终得到的......

> a
  col1    col2
1    7   a b c
2    5   b e z
3   10   a c q
4   14     a b
5    4       w
6    2 u o p l

> b
   col1 col2 col3                                            col4
1     1    4   11                   a b c d e f g h i j k l m n o
2     2   10   15                 j k l m n o p q r s t u v w x y
3     3    4   19                   a b c d e f g h i j k l m n o
4     4    8   13           a b c d e f g h i j k l m n o p q r s
5     5    7   13                                     j k l m n o
6     6    2   14                   a b c d e f g h i j k l m n o
7     7    8   11                                     t u v w x y
8     8    8   19                   a b c d e f g h i j k l m n o
9     9   10   19 c d e f g h i j k l m n o p q r s t u v w x y z
10   10    8   16                                             a b

> a2
# A tibble: 6 x 3
# Groups:   col1 [6]
   col1 col2    col3    
  <dbl> <chr>   <chr>   
1    7. a b c   1 3 6   
2    5. b e z   NA      
3   10. a c q   4       
4   14. a b     3 6 8 10
5    4. w       NA      
6    2. u o p l NA

【讨论】:

  • a2$col3 的第二个元素不应该是NA 吗?没有 b 的行,因此 5 介于 b$col2b$col3 之间,并且所有 b e z 都在 b$col4 中。
  • @ngm 确实是 - 使用矢量化的stringr::str_detect 而不是grepl 的上述更正版本,这是这些警告的来源。感谢您发现它!
【解决方案2】:

这是一个可能的解决方案。对我来说,运行代码生成a和b后,数据集如下。

一个

col1 col2
5    a b c
4    b e z
2    a c q
17   a b
8    w
17   u o p l

b

col1 col2 col3 col4
1    5    13   a b c d e f g h i j k l m n o
2    6    20   j k l m n o p q r s t u v w x y
3    8    17   a b c d e f g h i j k l m n o
4    3    17   a b c d e f g h i j k l m n o p q r s
5    7    12   j k l m n o
6    4    13   a b c d e f g h i j k l m n o
7    2    18   t u v w x y
8    7    14   a b c d e f g h i j k l m n o
9    4    18   c d e f g h i j k l m n o p q r s t u v w x y z
10   8    18   a b

首先我们使用 fuzzjoin 包来确保 a$col1 的值在 b$col2 和 b$col3 之间(包括)。

library(fuzzyjoin)
c <- fuzzy_inner_join(a, b,
                      by = c("col1" = "col2", "col1" = "col3"),
                      match_fun = list(`>=`, `<=`))

接下来感谢this的回答,我们使用

compare <- function(s1, s2) {
  c1 <- unique(strsplit(s1, "")[[1]])
  c2 <- unique(strsplit(s2, "")[[1]])
  length(intersect(c1,c2))/length(c1)
}

vcomp <- Vectorize(compare)
c <- transform(c, comp = vcomp(col2.x, col4))

我们估计了 a$col2 中出现在 b$col4 中的字符的百分比。

最后,我们限制了 100% 字符匹配的记录,并将 b$col1 折叠成一个由空格分隔的字符串。这是使用 dplyr 包完成的。

library(dplyr)
d <- c %>%
  filter(comp >= 1) %>%
  select(col1.x, col2.x, col1.y) %>%
  group_by(col1.x, col2.x) %>%
  summarise(col3 = paste(col1.y, collapse = " "))
colnames(d) <- c("col1", "col2", "col3")

最终结果见表d。

col1 col2    col3
5    a b c   1 4 6
8    w       2 7 9
17   a b     3 4 10
17   u o p l 2 9

【讨论】:

    【解决方案3】:

    示例数据具有随机性 - 使用 set.seed 始终是可重复性的好主意。所以这是另一个数据集:

    set.seed(1)
    a = data.frame(col1 = round(runif(6,1,20)),col2 = c("a b c","b e z","a c q","a b","w","u o p l"), stringsAsFactors = F)
    b = data.frame(col1 = 1:10, col2 = round(runif(10,1,10)), col3 = round(runif(10,10,20)), col4 = c(paste(letters[1:15], collapse=" "),paste(letters[10:25], collapse=" "),paste(letters[1:15], collapse=" "),paste(letters[1:19], collapse=" "),paste(letters[10:15], collapse=" "),paste(letters[1:15], collapse=" "),paste(letters[20:25], collapse=" "),paste(letters[1:15], collapse=" "),paste(letters[3:26], collapse=" "),paste(letters[1:2], collapse=" ")),stringsAsFactors = F)
    
    > a
      col1    col2
    1    6   a b c
    2    8   b e z
    3   12   a c q
    4   18     a b
    5    5       w
    6   18 u o p l
    
    > b
       col1 col2 col3                                            col4
    1     1   10   17                   a b c d e f g h i j k l m n o
    2     2    7   20                 j k l m n o p q r s t u v w x y
    3     3    7   14                   a b c d e f g h i j k l m n o
    4     4    2   18           a b c d e f g h i j k l m n o p q r s
    5     5    3   19                                     j k l m n o
    6     6    3   12                   a b c d e f g h i j k l m n o
    7     7    7   17                                     t u v w x y
    8     8    4   11                   a b c d e f g h i j k l m n o
    9     9    8   13 c d e f g h i j k l m n o p q r s t u v w x y z
    10   10    5   14                                             a b
    

    首先将字符串转换成向量:

    a$col2_vec <- strsplit(a$col2, " ")
    b$col4_vec <- strsplit(b$col4, " ")
    

    查找所有满足“a$col1b$col2b$col3 之间”的行。

    btwn <- lapply(a$col1, function(x) which(b$col2 <= x & x <= b$col3))
    

    找出所有满足“a$col2的字母在b$col4”的行

    ltr_in <- lapply(a$col2_vec, 
                     function(y) which(sapply(b$col4_vec, 
                                              function(x) all(y %in% x))
                                      )
                    )  
    

    找到行的交叉点并将它们粘贴到一个字符串中。

    a$col3 <- sapply(lapply(seq_along(btwn), 
                            function(i) intersect(btwn[[i]], ltr_in[[i]])), 
                     paste0, collapse=" ")
    

    结果:

    a$col2_vec <- NULL
    > a
      col1    col2  col3
    1    6   a b c 4 6 8
    2    8   b e z      
    3   12   a c q     4
    4   18     a b     4
    5    5       w      
    6   18 u o p l     2
    

    如果一个人关心一个非常大的数据集,这似乎比另一个答案更快,这对于学习purrr 的东西仍然非常好。 (编辑:添加了第三个答案。)

    Unit: milliseconds
               expr       min        lq      mean   median       uq       max neval
               @ngm  1.300393  1.412308  1.625972  1.45799  1.49936  14.94079   100
     @Andrew Gustar 18.630475 19.208137 19.825766 19.47883 20.09018  23.84303   100
          @radmuzon 57.647023 58.555243 64.455069 60.30342 62.77680 286.40073   100
    

    【讨论】:

    • 非常感谢您的回答。只是一个额外的问题。如果有 2 列之间要测试怎么办。例如。一个纬度和经度。如何在测试之间容纳 2?
    猜你喜欢
    • 2018-01-26
    • 1970-01-01
    • 2017-04-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多