Построить фрейм данных из попарных комбинаций элементов списка - PullRequest
2 голосов
/ 11 января 2020

У меня есть список list. Первые 5 элементов этого списка:

[[1]]
[1] "#solarpanels" "#solar"      

[[2]]
[1] "#Nuclear" "#Wind"    "#solar"  

[[3]]
[1] "#solar"

[[4]]
[1] "#steel"           "#windenergy"      "#solarenergy"     "#carbonfootprint"

[[5]]
[1] "#solar" "#wind"

Я хотел бы удалить такие элементы, как [[3]], поскольку содержит только один элемент. Более того, я хотел бы построить фрейм данных, содержащий все возможные комбинации для каждой строки списка. Например, фрейм данных с двумя столбцами (например, первый с именем A, второй B), например:

A                  B
"#solarpanels"     "#solar"
"#Nuclear"         "#Wind"  
"#Nuclear"         "#solar"
"#steel"           "#windenergy"
"#steel"           "#solarenergy"
"#steel"           "#carbonfootprint"
"#windenergy"      "#carbonfootprint"
"#windenergy"      "#solarenergy"
"#solarenergy"     "#carbonfootprint"
"#solar"           "#wind"

Я пытался (только для одного элемента)

for (i in 1:(length(list[[4]])-1)) {
  df$from = rep(list[[4]][i],length(list[[4]])-i)
  df$to = list[[4]][(i+1):length(list[[4]])]
}

где

df=data.frame(A=character(), 
                    B=character(),
                    stringsAsFactors=FALSE) 

но я получил

data.frame`(`*tmp*`, A, value = c("#steel", "#steel",  : 
 replacement has 3 rows, data has 0

для i=1.

1 Ответ

8 голосов
/ 11 января 2020

Сначала ваши данные:

l = list(
  c("#solarpanels", "#solar"),
  c("#Nuclear", "#Wind", "#solar"),
  "#solar",
  c("#steel", "#windenergy", "#solarenergy", "#carbonfootprint"),
  c("#solar", "#wind")
)

Вот двухстрочная версия:

l = l[lengths(l) > 1L]
data.frame(do.call(rbind, unlist(lapply(l, combn, 2L, simplify = FALSE), recursive = FALSE)))
#              X1               X2
# 1  #solarpanels           #solar
# 2      #Nuclear            #Wind
# 3      #Nuclear           #solar
# 4         #Wind           #solar
# 5        #steel      #windenergy
# 6        #steel     #solarenergy
# 7        #steel #carbonfootprint
# 8   #windenergy     #solarenergy
# 9   #windenergy #carbonfootprint
# 10 #solarenergy #carbonfootprint
# 11       #solar            #wind

Медленнее, для ясности:

combn(x, k) возвращает все возможные (неупорядоченное) подмножество размера k из x; то, что вы ищете, это пары из каждого элемента списка. По умолчанию он возвращает это как matrix со столбцами p = choose(length(x), k), но это не очень полезный формат для вашего случая использования; simplify = FALSE возвращает каждое подмножество как новый элемент list.

Так что lapply(l, combn, 2L, simplify = FALSE) будет выглядеть примерно так:

# [[1]]
# [[1]][[1]]
# [1] "#solarpanels" "#solar"      
# 
# 
# [[2]]
# [[2]][[1]]
# [1] "#Nuclear" "#Wind"   
# 
# [[2]][[2]]
# [1] "#Nuclear" "#solar"  

(мы должны отфильтровать length-1 сначала элементы l, так как ошибка запрашивать 2 элементов у объекта длины-1, отсюда и первая строка)

Бит lapply(.) - суть вашей проблемы; остальное - просто добавление вывода (в котором уже есть все правильные данные) в формат data.frame.

Во-первых, вывод lapply является вложенным - это list из list s. , Более равномерно иметь list векторов длины-2; unlist(., recusive=FALSE) выполняет это путем удаления вложенных списков первого уровня (с recursive=TRUE мы получим большой длинный вектор и потеряем парную структуру; мы могли бы работать с этим, но я думаю, что это может быть немного неестественно) .

Далее мы превращаем список векторов длины 2 в матрицу (с учетом конечной цели - матрицу из 2 столбцов очень легко преобразовать в data.frame); list -> matrix выполняется в base с do.call(rbind, .).

Наконец мы передаем это data.frame, et voila !

В data.table я бы сделал это немного чище и в одной команде:

setDT(transpose(
  unlist(lapply(l[lengths(l) > 1L], combn, 2L, simplify = FALSE), recursive = FALSE)
))[]

Учитывая, что вы, вероятно, не очень заботитесь о промежуточном выводе, это было бы также неплохо использовать magrittr:

library(magrittr)
l[lengths(l) > 1L] %>%
  lapply(combn, 2L, simplify = FALSE) %>% 
  unlist(recursive = FALSE) %>%
  do.call(rbind, . ) %>%
  data.frame

Это более читабельно, но в этом случае было бы неплохо увидеть, что data.frame является конечной целью заранее, так как в противном случае намерение шагов unlist & do.call может быть неясным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...