Пожалуйста, рассмотрите следующий фрейм данных:
df <- structure(list(oID = c(37751L, 30978L, 33498L),
peId = c(12L, 13L, 14L),
last_Name = c("ABC", "DEF", "EFG"),
first_Name = c("Z", "Y", "X"),
personnel_Number = list(structure(list(hId = c(1L, 4L, 5L),
hName = c("PS", "XY", "MN"),
personnel_Number = c("0123", "1234", "98")),
class = "data.frame",
row.names = c(NA, 3L)),
structure(list(hId = 1L, hName = "PS",
personnel_Number = "0987"),
class = "data.frame",
row.names = 1L),
structure(list(),
names = character(0),
row.names = integer(0),
class = "data.frame")),
ls_Role = list(structure(list(functionId = c(1L, 5L),
`function` = c("function A", "function B"),
function_Short = c("FA", "FB")),
class = "data.frame",
row.names = 1:2),
structure(list(functionId = 6L,
`function` = "function A",
function_Short = "FA"),
class = "data.frame",
row.names = 1L),
structure(list(functionId = 6L,
`function` = "function A",
function_Short = "FA"),
class = "data.frame",
row.names = 1L))),
row.names = c(1L, 2L, 3L),
class = "data.frame")
Как видите, фрейм данных содержит два столбца списка, каждый из которых содержит фреймы данных на основе строк. Я хочу отменить вложение каждого из этих столбцов списка таким образом, чтобы их «внутренние» фреймы данных были переведены в широкий формат, т. е. все столбцы повторяются для такого количества строк, сколько внутренние фреймы данных содержат в каждой строке.
Итак, для столбца «personnel_number» я ожидаю вернуть 9 столбцов (hId_1, _2, _3; hName_1, _2 и т. д.). И то же самое для столбца ls_Role.
Я знаю, как я могу сделать это вручную, открыв, а затем сильно изменив весь фрейм данных, но мне интересно, есть ли более краткий способ, который делает это более автоматически или с меньшим количеством вызовов функций, желательно в более аккуратной манере.
Вот мой текущий код (который также отражает ожидаемый результат):
library(tidyverse)
df |>
mutate(id = row_number()) |>
unnest_longer(col = personnel_Number, keep_empty = TRUE) |>
unpack(cols = personnel_Number) |>
mutate(id_inner = row_number(), .by = id) |>
pivot_wider(values_from = c(hId, hName, personnel_Number),
names_from = id_inner) |>
unnest_longer(col = ls_Role, keep_empty = TRUE) |>
unpack(cols = ls_Role) |>
mutate(id_inner = row_number(), .by = id) |>
pivot_wider(values_from = c(functionId, "function", function_Short),
names_from = id_inner)
Один из возможных способов решения вашей проблемы. Это решение основано на функциях из пакета data.table.
library(data.table)
fun = function(dfs) {
lst = vector("list", length(dfs))
for(i in seq_along(dfs)) {
d = setDT(copy(dfs[[i]]))
lst[[i]] = if (nrow(d)) dcast(d, . ~ seq_len(nrow(d)), value.var=names(d)) else d = data.table(. = ".")
}
rbindlist(lst, fill=T)[, . := NULL][]
}
df2 = do.call(cbind, list(df, fun(df$personnel_Number), fun(df$ls_Role)))
oID peId last_Name ... hId_1 hId_2 hId_3 hName_1 hName_2 hName_3 personnel_Number_1 personnel_Number_2 personnel_Number_3 functionId_1 functionId_2 function_1 function_2 function_Short_1 function_Short_2
1 37751 12 ABC ... 1 4 5 PS XY MN 0123 1234 98 1 5 function A function B FA FB
2 30978 13 DEF ... 1 NA NA PS <NA> <NA> 0987 <NA> <NA> 6 NA function A <NA> FA <NA>
3 33498 14 EFG ... NA NA NA <NA> <NA> <NA> <NA> <NA> <NA> 6 NA function A <NA> FA <NA>
Для данных вашего примера вы можете получить желаемый результат, дважды открыв более широкие все списки, например.
library(tidyr)
library(dplyr)
res <- df |>
mutate(id = row_number(), .after = first_Name) |>
unnest_wider(where(is.list)) |>
unnest_wider(where(is.list), names_sep = "_")
Где находится res
:
# A tibble: 3 × 20
oID peId last_Name first_Name id hId_1 hId_2 hId_3 hName_1 hName_2 hName_3
<int> <int> <chr> <chr> <int> <int> <int> <int> <chr> <chr> <chr>
1 37751 12 ABC Z 1 1 4 5 PS XY MN
2 30978 13 DEF Y 2 1 NA NA PS NA NA
3 33498 14 EFG X 3 NA NA NA NA NA NA
# ℹ 9 more variables: personnel_Number_1 <chr>, personnel_Number_2 <chr>,
# personnel_Number_3 <chr>, functionId_1 <int>, functionId_2 <int>,
# function_1 <chr>, function_2 <chr>, function_Short_1 <chr>,
# function_Short_2 <chr>
Убедитесь, что он соответствует желаемому результату:
identical(wanted, res)
[1] TRUE
wanted
данные:
wanted <- df |>
mutate(id = row_number()) |>
unnest_longer(col = personnel_Number, keep_empty = TRUE) |>
unpack(cols = personnel_Number) |>
mutate(id_inner = row_number(), .by = id) |>
pivot_wider(values_from = c(hId, hName, personnel_Number),
names_from = id_inner) |>
unnest_longer(col = ls_Role, keep_empty = TRUE) |>
unpack(cols = ls_Role) |>
mutate(id_inner = row_number(), .by = id) |>
pivot_wider(values_from = c(functionId, "function", function_Short),
names_from = id_inner)
О боже, это могло бы быть так просто. Единственное, что есть в моем реальном случае использования, это то, что два unnest_widers работают значительно медленнее (в 10-20 раз), чем unnest_longers.