【问题标题】:Find all sequences with the same column value查找具有相同列值的所有序列
【发布时间】:2016-04-24 18:45:08
【问题描述】:

我有以下数据框:

╔══════╦═════════╗
║ Code ║ Airline ║
╠══════╬═════════╣
║    1 ║ AF      ║
║    1 ║ KL      ║
║    8 ║ AR      ║
║    8 ║ AZ      ║
║    8 ║ DL      ║
╚══════╩═════════╝

dat <- structure(list(Code = c(1L, 1L, 8L, 8L, 8L), Airline = structure(c(1L, 
5L, 2L, 3L, 4L), .Label = c("AF  ", "AR  ", "AZ  ", "DL", "KL  "
), class = "factor")), .Names = c("Code", "Airline"), class = "data.frame", row.names = c(NA, 
-5L))

我的目标是让每家航空公司找到所有共享代码,即一个或多个其他航空公司使用的代码。 所以输出将是

+--------------------+
| Airline SharedWith |
+--------------------+
| AF      "KL"       |
| KL      "AF"       |
| AR      "AZ","DL"  |
+--------------------+

伪代码是任何命令式语言都可以

for each code
  lookup all rows in the table where the value = code

由于 R 不是那么面向列表,那么实现预期输出的最佳方法是什么?

【问题讨论】:

  • 最好以可以提供给 R 的形式提供示例输入。让想要帮助您的人更容易。
  • 使用dput将数据输出成易于导入的表格
  • 没有那么多面向列表?大声笑,它只是R中的主要数据结构。
  • 我也是。我们可以达成一致。
  • 其实……数据框是一个列表:)

标签: r


【解决方案1】:

使用data.table 包的几个选项:

1) 使用strsplitpaste & 按行操作:

library(data.table)
setDT(dat)[, Airline := trimws(Airline)  # this step is needed to remove the leading and trailing whitespaces
           ][, sharedwith := paste(Airline, collapse = ','), Code
            ][, sharedwith := paste(unlist(strsplit(sharedwith,','))[!unlist(strsplit(sharedwith,',')) %in% Airline], 
                                    collapse = ','), 1:nrow(dat)]

给出:

> dat
   Code Airline sharedwith
1:    1      AF         KL
2:    1      KL         AF
3:    8      AR      AZ,DL
4:    8      AZ      AR,DL
5:    8      DL      AR,AZ

2)strsplitpastemapply 一起使用,而不是by = 1:nrow(dat)

setDT(dat)[, Airline := trimws(Airline)
           ][, sharedwith := paste(Airline, collapse = ','), Code
             ][, sharedwith := mapply(function(s,a) paste(unlist(strsplit(s,','))[!unlist(strsplit(s,',')) %in% a], 
                                                          collapse = ','),
                                      sharedwith, Airline)][]

这会给你同样的结果。

3) 或者将CJ 函数与paste 一起使用(灵感来自@zx8754 的expand.grid 解决方案):

library(data.table)
setDT(dat)[, Airline := trimws(Airline)
           ][, CJ(air=Airline, Airline,  unique=TRUE)[air!=V2][, .(shared=paste(V2,collapse=',')), air],
             Code]

给出:

   Code air shared
1:    1  AF     KL
2:    1  KL     AF
3:    8  AR  AZ,DL
4:    8  AZ  AR,DL
5:    8  DL  AR,AZ

使用dplyrtidyr 获得所需解决方案的解决方案(受@jaimedash 启发):

library(dplyr)
library(tidyr)

dat <- dat %>% mutate(Airline = trimws(as.character(Airline)))

dat %>%
  mutate(SharedWith = Airline) %>% 
  group_by(Code) %>%
  nest(-Code, -Airline, .key = SharedWith) %>%
  left_join(dat, ., by = 'Code') %>%
  unnest() %>%
  filter(Airline != SharedWith) %>%
  group_by(Code, Airline) %>%
  summarise(SharedWith = toString(SharedWith))

给出:

   Code Airline SharedWith
  (int)   (chr)      (chr)
1     1      AF         KL
2     1      KL         AF
3     8      AR     AZ, DL
4     8      AZ     AR, DL
5     8      DL     AR, AZ

【讨论】:

    【解决方案2】:

    igraph 方法

    library(igraph)
    
    g <- graph_from_data_frame(dat)
    
    # Find neighbours for select nodes
    ne <- setNames(ego(g,2, nodes=as.character(dat$Airline), mindist=2), dat$Airline)
    ne
    #$`AF  `
    #+ 1/7 vertex, named:
    #[1] KL  
    
    #$`KL  `
    #+ 1/7 vertex, named:
    #[1] AF  
    ---
    ---
    
    # Get final format
    data.frame(Airline=names(ne), 
               Shared=sapply(ne, function(x)
                                          paste(V(g)$name[x], collapse=",")))
    #   Airline Shared
    # 1      AF     KL
    # 2      KL     AF
    # 3      AR  AZ,DL
    # 4      AZ  AR,DL
    # 5      DL  AR,AZ
    

    【讨论】:

      【解决方案3】:

      我认为您只需要table

      dat <- structure(list(Code = c(1L, 1L, 8L, 8L, 8L),Airline = structure(c(1L, 5L, 2L, 3L, 4L),.Label = c("AF", "AR", "AZ", "DL", "KL"),class = "factor")),.Names = c("Code", "Airline"),class = "data.frame", row.names = c(NA, -5L))
      
      tbl <- crossprod(table(dat))
      diag(tbl) <- 0
      
      #        Airline
      # Airline AF AR AZ DL KL
      #      AF  0  0  0  0  1
      #      AR  0  0  1  1  0
      #      AZ  0  1  0  1  0
      #      DL  0  1  1  0  0
      #      KL  1  0  0  0  0
      
      dd <- data.frame(Airline = colnames(tbl),
                       shared = apply(tbl, 1, function(x)
                         paste(names(x)[x > 0], collapse = ', ')))
      
      merge(dat, dd)
      #   Airline Code shared
      # 1      AF    1     KL
      # 2      AR    8 AZ, DL
      # 3      AZ    8 AR, DL
      # 4      DL    8 AR, AZ
      # 5      KL    1     AF
      

      【讨论】:

      • 大家欢呼table。很好用
      【解决方案4】:

      可能有一条更有效的路线,但这应该会飞:

      # example data
      d <- data.frame(code = c(1,1,8,8,8),
           airline = c("AF","KL","AR","AZ","DL"),
           stringsAsFactors = FALSE)
      
      # merge d to itself on the code column.  This isn't necessarily efficient
      d2 <- merge(d, d, by = "code")
      
      # prune d2 to remove occasions where
      # airline.x and airline.y (from the merge) are equal
      d2 <- d2[d2[["airline.x"]] != d2[["airline.y"]], ]
      # construct the combinations for each airline using a split, apply, combine
      # then, use stack to get a nice structure for merging
      d2 <- stack(
            lapply(split(d2, d2[["airline.x"]]),
              function(ii) paste0(ii$airline.y, collapse = ",")))
      
      # merge d and d2.  "ind" is a column produced by stack
      merge(d, d2, by.x = "airline", by.y = "ind")
      #  airline code values
      #1      AF    1     KL
      #2      AR    8  AZ,DL
      #3      AZ    8  AR,DL
      #4      DL    8  AR,AZ
      #5      KL    1     AF
      

      【讨论】:

      • “但这应该会飞” - 很好的双关语。
      【解决方案5】:

      使用 expand.grid 和聚合:

      do.call(rbind,
              lapply(split(dat, dat$Code), function(i){
                x <- expand.grid(i$Airline, i$Airline)
                x <- x[ x$Var1 != x$Var2, ]
                x <- aggregate(x$Var2, list(x$Var1), paste, collapse = ",")
                colnames(x) <- c("Airline", "SharedWith")
                cbind(Code = i$Code, x)
              }))
      
      # output
      #     Code Airline SharedWith
      # 1.1    1      AF         KL
      # 1.2    1      KL         AF
      # 8.1    8      AR      AZ,DL
      # 8.2    8      AZ      AR,DL
      # 8.3    8      DL      AR,AZ
      

      【讨论】:

        【解决方案6】:

        split 有帮助。这是一个完全可重现的 EDIT,无需任何附加包即可工作。与 OPs data.frame 一起使用 - 在 OP 添加可重现的数据集后对其进行了更改。

        # strip white space in Airline names:
        dat$Airline <- gsub(" ","",dat$Airline)
        li <- split(dat,factor(dat$Code))
        do.call("rbind",lapply(li,function(x) 
        data.frame(Airline = x[1,2],
                 SharedWith = paste(x$Airline[-1]
                                    ,collapse=",")
        ))
        )
        

        【讨论】:

        • 我设法通过 dplyr 的 group_by 获得了类似的结果。但后来我被困在寻找每列的所有排列。
        • 尝试:df %&gt;% group_by(code) %&gt;% mutate(SharedWith = paste(sort(Airline), collapse = ', ')) 这也将航空公司保留在同一列中。
        • 太棒了!这好多了!但我需要排除同一家航空公司,因为现在它也与自己共享。
        • @AndreiVaranovich 一个dplyr 以 R 为基础的免费解决方案,它应该给出你上面指定的输出......但是 - 我不知道你到底想要做什么。但我有一种预感,这种做法有点不成熟。
        • @AndreiVaranovich 这能解决重复问题吗?此解决方案的优点是您不需要其他软件包。话虽如此,我喜欢@Procrastinatus Maximus,因为data.table 真的很酷,而且可能是计算时间方面最快的解决方案。
        【解决方案7】:

        你可以在dplyr尝试这样的事情

        library(dplyr)
        df %>% group_by(code) %>% mutate(SharedWith = paste(sort(Airline), collapse = ', ')) %>% ungroup() %>% select(Airline, SharedWith)
        

        【讨论】:

          【解决方案8】:

          将以下内容作为评论作为答案发布,因为这样可以更方便地格式化。

          for each code
            lookup all rows in the table where the value = code
          

          嗯...抱歉,我不明白这个伪代码与您想要的输出有何关系

          +--------------------+
          | Airline SharedWith |
          +--------------------+
          | AF      "KL"       |
          | KL      "AF"       |
          | AR      "AZ","DL"  |
          +--------------------+
          

          这个伪代码的结果应该是:

          +---------------------+
          + Code  +  Airlines   +
          +---------------------+
          +  1    +  AF, KL     +
          +  2    +  AR, AZ, DL +
          +---------------------+
          

          也就是说,

          codes <- unique(dat$Code)
          data.frame(Code=codes, Airlines = sapply(codes, function(x) paste(subset(dat, Code %in% x)$Airline, collapse=",")))
          

          【讨论】:

            【解决方案9】:

            您可以使用tidyrnest 快速完成此操作(尽管除非您首先将航空公司作为因素转换为字符,否则速度会较慢)和merge

             library(tidyr)
             dat$Airline <- as.character(dat$Airline)
             new_dat <- merge(dat, dat %>% nest(-Code, .key= SharedWith), by="Code")
            

            > new_dat
              Code Airline SharedWith
            1    1      AF     AF, KL
            2    1      KL     AF, KL
            3    8      AR AR, AZ, DL
            4    8      AZ AR, AZ, DL
            5    8      DL AR, AZ, DL
            

            与其他一些解决方案相比,此解决方案的优势SharedWith 成为 data.frame 的列表列,而不是说一个字符

            > str(new_dat$SharedWith)
            List of 5
             $ :'data.frame':   2 obs. of  1 variable:
              ..$ Airline: chr [1:2] "AF" "KL"
             $ :'data.frame':   2 obs. of  1 variable:
              ..$ Airline: chr [1:2] "AF" "KL"
             $ :'data.frame':   3 obs. of  1 variable:
              ..$ Airline: chr [1:3] "AR" "AZ" "DL"
             $ :'data.frame':   3 obs. of  1 variable:
              ..$ Airline: chr [1:3] "AR" "AZ" "DL"
             $ :'data.frame':   3 obs. of  1 variable:
              ..$ Airline: chr [1:3] "AR" "AZ" "DL"
            

            这样您就可以轻松(虽然不是很漂亮)索引共享值的向量,例如:

            > new_dat$SharedWith[[1]]$Airline
            [1] "AF" "KL"
            

            而不必使用strsplit 或类似的

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-01-27
              • 2012-06-18
              • 1970-01-01
              相关资源
              最近更新 更多