У меня есть фрейм данных, внутри которого содержится список.
df <- data.frame(
id=c(1:4),
a=I(list(c(1,"a1"),2,c("a31","a32","a33"),"a4")),
b=I(list(2,c("b1","b2",3),c("b3","b4"),4))
); print(df)
id a b
1 1 1, a1 2
2 2 2 b1, b2, 3
3 3 a31, a32.... b3, b4
4 4 a4 4
Теперь мне нужно unnest список, чтобы получить такой фрейм данных:
df2 <- data.frame(
id=c(1,1,2,2,2,3,3,3,3,3,3,4),
a=c(1,"a1",2,2,2,"a31","a31","a32","a32","a33","a33","a4"),
b=c(2,2,"b1","b2",3,"b3","b3","b3","b4","b4","b4",4)
) ; print(df2)
id a b
1 1 1 2
2 1 a1 2
3 2 2 b1
4 2 2 b2
5 2 2 3
6 3 a31 b3
7 3 a31 b3
8 3 a32 b3
9 3 a32 b4
10 3 a33 b4
11 3 a33 b4
12 4 a4 4
Раньше я использовал unnest() для тех, которые содержат одинаковое количество элементов списка в некоторых строках/столбцах, но текущий фрейм данных содержит разное количество элементов в некоторых строках и столбцах. В настоящее время я столкнулся со следующей ошибкой.
> target <- c("id","a","b")
> df %>% unnest(cols=target)
Error in `unnest()`:
! In row 3, can't recycle input of size 3 to size 2.
Run `rlang::last_trace()` to see where the error occurred.
Из-за непредсказуемости того, где это происходит (по строкам/столбцам) и сколько элементов оно будет содержать, я не могу найти подходящий подход для решения этой проблемы. Количество столбцов и их названия не могут быть определены заранее.
Я ценю ваше предложение, особенно простые, которые можно интегрировать в текущую работу трубопровода в dplyr. Base R и другие подходы также приветствуются.
====
Невоспроизводимые данные примера
Позвольте мне поделиться воспроизводимым примером реального фрейма данных, над которым я работаю. С решениями от @ThomasIsCoding это не сработало. Я подозреваю, что это вызвано NULL, но это не было причиной. Пустое/Нулевое значение не обязательно реплицируется.
structure(list(cluster = c("1", "2", "3", "4", "5", "6"), st_sub_main_th = list(
"hira", NULL, "tsuma", "tsuma", NULL, c("other", "hira")),
roo_main = list("2", "4", "3", "2", c("1", "3"), c("6", "7",
"2", "1")), st_con_rt = list("sub-room", "main-room", "sub-room",
"sub-room", "main-room", "sub-room"), st_con_tr = list(
"terrace", c("terrace", "direct"), "terrace", "terrace",
"terrace", "direct"), st_adsb = list("add", "add", "add",
"sub", "add", "sub"), st_th = list(NULL, "tsuma", NULL,
NULL, "hira", NULL), st_sub2_main_th = list(NULL, NULL,
NULL, "hira", "hira", "tsuma"), isstilt = list(NULL,
NULL, NULL, NULL, NULL, "0")), class = "data.frame", row.names = c(NA,
-6L))





NULLsПоскольку у вас могут быть NULL, вы можете проделать следующий трюк с новыми данными в вашем вопросе.
d <- list2DF(lapply(df, \(u) replace(u, lengths(u) == 0, "")))
do.call(
rbind,
do.call(Map, c(expand.grid, d, stringsAsFactors = FALSE))
)
который дает
cluster st_sub_main_th roo_main st_con_rt st_con_tr st_adsb st_th
1 1 hira 2 sub-room terrace add
2.1 2 4 main-room terrace add tsuma
2.2 2 4 main-room direct add tsuma
3 3 tsuma 3 sub-room terrace add
4 4 tsuma 2 sub-room terrace sub
5.1 5 1 main-room terrace add hira
5.2 5 3 main-room terrace add hira
6.1 6 other 6 sub-room direct sub
6.2 6 hira 6 sub-room direct sub
6.3 6 other 7 sub-room direct sub
6.4 6 hira 7 sub-room direct sub
6.5 6 other 2 sub-room direct sub
6.6 6 hira 2 sub-room direct sub
6.7 6 other 1 sub-room direct sub
6.8 6 hira 1 sub-room direct sub
st_sub2_main_th isstilt
1
2.1
2.2
3
4 hira
5.1 hira
5.2 hira
6.1 tsuma 0
6.2 tsuma 0
6.3 tsuma 0
6.4 tsuma 0
6.5 tsuma 0
6.6 tsuma 0
6.7 tsuma 0
6.8 tsuma 0
NULLВы можете попробовать expand.grid с Map, как показано ниже.
do.call(
rbind,
do.call(Map, c(expand.grid, df, stringsAsFactors = FALSE))
)
который дает
id a b
1 1 1 2
2 1 a1 2
3 2 2 b1
4 2 2 b2
5 2 2 3
6 3 a31 b3
7 3 a32 b3
8 3 a33 b3
9 3 a31 b4
10 3 a32 b4
11 3 a33 b4
12 4 a4 4
Спасибо. В реальном фрейме данных столбцы не могут быть указаны динамически. Я заменил Map(expand.grid, id, a, b, stringsAsFactors = FALSE) на Map(expand.grid, !!sym(target), stringsAsFactors = FALSE), но столкнулся с ошибкой Can't convert a character vector to a symbol. Я тоже просто ставлю target без !!sym(), но оно возвращается 'names' attribute [3] must be the same length as the vector [1]. Ничего плохого?
@HSJ Вы можете попробовать мое обновленное решение
Это хорошо сработало для тестовых данных в посте, но я получил пустые данные, когда попробовал это с моим реальным фреймом данных. Я подозревал, что это было вызвано значениями NULL, но проблема была не в этом. Я ценю, если это произойдет и с вами..
@HSJ да, NULL вызывает проблему, но вы можете попробовать заменить ее на "" или NA, прежде чем продолжить expand.grid. Смотрите мое обновление
Вы можете сделать это, unnestпросматривая каждый столбец за раз. Однако сначала вам нужно преобразовать элементы в character, иначе отмена вложения не сработает, поскольку вы будете пытаться создать вектор/столбец как с numeric, так и с character элементами.
library(purrr) # For map()
library(tidyr) # For unnest()
library(dplyr) # For mutate()
df %>%
mutate(a = map(a, as.character),
b = map(b, as.character)) %>%
unnest(cols = a) %>%
unnest(cols = b)
В вашем примере данных вам также необходимо заменить NULL на что-то другое, иначе они исчезнут при промежуточном разложении:
df2 <- structure(list(
cluster = c("1", "2", "3", "4", "5", "6"),
st_sub_main_th = list("hira", NULL, "tsuma", "tsuma", NULL, c("other", "hira")),
roo_main = list("2", "4", "3", "2", c("1", "3"), c("6", "7", "2", "1")),
st_con_rt = list("sub-room", "main-room", "sub-room", "sub-room", "main-room", "sub-room"),
st_con_tr = list("terrace", c("terrace", "direct"), "terrace", "terrace", "terrace", "direct"),
st_adsb = list("add", "add", "add", "sub", "add", "sub"),
st_th = list(NULL, "tsuma", NULL, NULL, "hira", NULL),
st_sub2_main_th = list(NULL, NULL, NULL, "hira", "hira", "tsuma"),
isstilt = list(NULL, NULL, NULL, NULL, NULL, "0")),
class = "data.frame", row.names = c(NA, -6L))
df2 %>%
mutate_if (is.list, map, coalesce, NA_character_) %>%
mutate_if (is.list, map, as.character) %>%
unnest(st_sub_main_th) %>%
unnest(roo_main) %>%
unnest(st_con_rt) %>%
unnest(st_con_tr) %>%
unnest(st_adsb) %>%
unnest(st_th) %>%
unnest(st_sub2_main_th) %>%
unnest(isstilt)
Спасибо, одна проблема в том, что я не могу заранее исправить количество и имя столбцов...
Возможно, используйте ... %>% Reduce(unnest, which(sapply(., is.list)), init = .), чтобы не нужно было указывать каждое невложение отдельно.
Используйте tidyverse:
library(tidyverse)
exec(pmap_df, df, expand_grid)
# A tibble: 15 × 9
cluster st_sub_main_th roo_main st_con_rt st_con_tr st_adsb st_th st_sub2_main_th isstilt
<chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
1 1 hira 2 sub-room terrace add NA NA NA
2 2 NA 4 main-room terrace add tsuma NA NA
3 2 NA 4 main-room direct add tsuma NA NA
4 3 tsuma 3 sub-room terrace add NA NA NA
5 4 tsuma 2 sub-room terrace sub NA hira NA
6 5 NA 1 main-room terrace add hira hira NA
7 5 NA 3 main-room terrace add hira hira NA
8 6 other 6 sub-room direct sub NA tsuma 0
9 6 other 7 sub-room direct sub NA tsuma 0
10 6 other 2 sub-room direct sub NA tsuma 0
11 6 other 1 sub-room direct sub NA tsuma 0
12 6 hira 6 sub-room direct sub NA tsuma 0
13 6 hira 7 sub-room direct sub NA tsuma 0
14 6 hira 2 sub-room direct sub NA tsuma 0
15 6 hira 1 sub-room direct sub NA tsuma 0
Обратите внимание, что приведенное выше эквивалентно:
bind_rows(exec(pmap, df, expand_grid))
Если у вас смешанные типы данных, учтите следующее:
exec(rbind, !!!exec(pmap, df, expand_grid))
Это очень удобно выполнять в рамках операции с каналом. Спасибо!
У вас больше проблем, чем размер элементов в списках: в этих списках смешаны элементы
numericиcharacter, их нелегко «преобразовать» в простой вектор/столбец.