Эффективное преобразование вложенных списков различной длины в data.frame (с базой r)

Я надеюсь получить некоторую помощь в преобразовании вложенных списков во фрейм данных. Мое решение повторяется несколько раз, и мне было интересно, есть ли более эффективное решение.

Игрушечный пример вложенного списка:

nested_list <- list(list('date' = '2018-01-10', 'value1' = 1, 'value2' = 2), 
                    list('date' = '2018-01-09', 'value1' = 3, 'value2' = 4), 
                    list('date' = '2018-01 08', 'value1' = NULL, 'value2' = NULL), 
                    list('date' = '2018-01-07', 'value1' = NULL, 'value2' = NULL), 
                    list('date' = '2018-01-06', 'value1' = 5, 'value2' = 6))

Как видите, некоторые значения равны NULL. Я намерен полностью опустить эти данные.

В настоящее время я сначала маскирую все вложенные списки, длина которых больше единицы.

mask <- sapply((lapply(nested_list, unlist)), length) > 1 

Затем я применяю rbind через do.call и конвертирую в data.frame. В конце процесса мне нужно привести числовые значения, потому что все они преобразуются в символьные строки.

data.frame(do.call('rbind', lapply(nested_list[mask], unlist)), stringsAsFactors = FALSE)

Как видите, это выглядит беспорядочно, и мне было интересно, есть ли более эффективный способ выполнить преобразование во фрейм данных.

Спасибо

4
0
65
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Я знаю, что в вашем заголовке написано «base R», но вы также просите более эффективный способ. Итак, в качестве альтернативы есть решение tidyverse:

nested_list %>%
    map(unlist) %>%
    rbind_all() %>%
    filter(complete.cases(.)) %>%
    mutate_at(vars(contains("value")), as.numeric);
## A tibble: 3 x 3
#  date       value1 value2
#  <chr>       <dbl>  <dbl>
#1 2018-01-10     1.     2.
#2 2018-01-09     3.     4.
#3 2018-01-09     5.     6.

Или, как вариант (спасибо @arun):

nested_list %>% 
    transpose %>% 
    map_df(~ .x %>% replace(., lengths(.)==0, NA) %>% unlist) %>% 
    filter(!is.na(value1))  

Вам может потребоваться дополнительный шаг для преобразования столбцов "значения" в "числовые", как указано в сообщении OP.

akrun 11.04.2018 14:24

Я бы подумал, что вы можете использовать transpose, а также nested_list %>% transpose %>% map_df(~ .x %>% replace(., lengths(.)==0, NA) %>% unlist) %>% filter(!is.na(value1)), и это позволит избежать преобразования типа: =)

akrun 11.04.2018 14:27

@akrun Спасибо и обновлено. Надеюсь, вы не возражаете, что я также включил ваше решение.

Maurits Evers 11.04.2018 14:53

Большое спасибо за вашу помощь. Это похоже на хорошее решение, но в этом случае я стараюсь избегать зависимостей пакетов (за пределами httr). Я пытался проголосовать, но не смог.

user9630195 11.04.2018 14:54

@ user9630195 Добро пожаловать! Важно то, что вы получили именно то решение, которое искали. В этом прелесть R: всегда есть несколько решений ;-)

Maurits Evers 11.04.2018 15:14

Нисколько. Рад, что ты нашел это полезным

akrun 11.04.2018 18:40
Ответ принят как подходящий

Вот вариант base R, в котором мы перебираем элементы 'nested_list' и if, есть элемент any, имеющий length, равный 0, затем назначаем его NULL или возвращаем data.frame

res <- do.call(rbind, lapply(nested_list, function(x) 
      if(any(lengths(x) == 0)) NULL else data.frame(x, stringsAsFactors = FALSE)))
res
#         date value1 value2
#1 2018-01-10      1      2
#2 2018-01-09      3      4
#3 2018-01-06      5      6

Во входном наборе данных значения 'date' - character, лучше хранить как класс Date.

res$date <- as.Date(res$date)
str(res)
# 'data.frame':   3 obs. of  3 variables:
# $ date  : Date, format: "2018-01-10" "2018-01-09" "2018-01-06"
# $ value1: num  1 3 5
# $ value2: num  2 4 6

Спасибо, акрун, это выглядит элегантнее, чем мой подход. Я не упомянул ту часть, где обрабатываю даты, чтобы не загромождать пример. Я принял ответ, но не смог проголосовать за него из-за низкого рейтинга.

user9630195 11.04.2018 15:18

Другое решение:

> data.table::rbindlist(nested_list[sapply(nested_list, function(x) min(lengths(x))) > 0])
         date value1 value2
1: 2018-01-10      1      2
2: 2018-01-09      3      4
3: 2018-01-06      5      6

Огромное спасибо. К сожалению, я не использую пакет data.table. Это определенно в моем списке вещей, которым нужно научиться. Я попытался проголосовать за, но не смог, потому что я новичок на этом форуме, и моя репутация оценивается слишком низко (интересная функция).

user9630195 11.04.2018 14:53

Другие вопросы по теме