Найти все последовательности с одинаковым значением столбца

У меня есть следующий фрейм данных:

╔══════╦═════════╗
║ 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 не столько ориентирован на список, каков был бы лучший способ добиться ожидаемого результата?

Ответ 1

Несколько вариантов с использованием пакета data.table:

1) Используя strsplit, paste и работайте по строке:

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) Используя strsplit и paste с mapply вместо 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 (вдохновленной решением expand.grid @zx8754):

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

Решение с dplyr и tidyr, чтобы получить желаемое решение (вдохновленное @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

Ответ 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 и aggregate:

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=",")
))
)

Ответ 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

ummm... извините, я не понимаю, как этот psedudocode связан с вашим желаемым выходом

+--------------------+
| 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

Вы можете сделать это быстро с помощью tidyr nest (хотя, если вы вначале не перевели Авиакомпанию как фактор, чтобы она была менее быстрой) и 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"

чтобы вы могли легко (albiet не красиво) индексировать векторы общих значений, например:

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

вместо того, чтобы использовать strsplit или аналогичный