денормализовать/принудить список (с вложенными векторами) к data.frame в R

Я читаю файл yaml, например

- person_id: 111
  person_name: Russell
  time:
  - 1
  - 2
  - 3
  value:
  - a
  - b
  - c
- person_id: 222
  person_name: Steven
  time:
  - 1
  - 2
  value:
  - d
  - e

что я хочу денормализовать:

  person_id person_name time value
1       111     Russell    1     a
2       111     Russell    2     b
3       111     Russell    3     c
4       222      Steven    1     d
5       222      Steven    2     e

У меня есть решение, но я надеялся, что есть что-то более краткое. Вот вложенный список:

l <- list(
  list( 
    person_id   = 111L,
    person_name = "Russell", 
    time        = 1:3, 
    value       = letters[1:3]
  ),
  list( 
    person_id   = 222L,
    person_name = "Steven", 
    time        = 1:2, 
    value       = letters[4:5]
  )
)   

Что касается возможных дубликатов, этот вопрос аналогичен (1) Как денормализовать вложенный список в R?, но структура отличается ( Структура round/diff/saldo перенесена по сравнению с time/value здесь) и (2) Разделить столбец, разделенный запятыми, на отдельные строки, но time – это вектор, а не элемент, разделенный запятыми, как director. Я надеюсь, что эта другая структура поможет.


person wibeasley    schedule 11.11.2017    source источник
comment
Вот простая база R с одним вкладышем: do.call(rbind, lapply(l, data.frame)).   -  person lmo    schedule 12.11.2017
comment
@lmo, это круто. Мне нравится, как lapply() копирует родительские переменные person_id и person_name. Если вы опубликуете это как ответ, я бы с удовольствием проголосовал за него.   -  person wibeasley    schedule 12.11.2017


Ответы (3)


Простой базовый метод R состоит в том, чтобы использовать lapply и data.frame для возврата списка data.frames, а затем использовать do.call с rbind для объединения data.frames в один объект data.frame.

do.call(rbind, lapply(l, data.frame))

который возвращает

  person_id person_name time value
1       111     Russell    1     a
2       111     Russell    2     b
3       111     Russell    3     c
4       222      Steven    1     d
5       222      Steven    2     e

Обратите внимание, что person_name и value будут факторными векторами, работать с которыми может быть неудобно. При желании вы можете преобразовать их в векторы символов, используя аргумент stringsAsFactors.

do.call(rbind, lapply(l, data.frame, stringsAsFactors=FALSE))

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

person Community    schedule 11.11.2017

Чтобы дополнить идеи/подходы @lmo и @submartingale, вот версия purrr/tidyverse, которая преобразует каждый вложенный список в data.frame/tibble (путем репликации родительских элементов name и id), а затем складывает их в один tibble. .

l %>% 
  purrr::map_df(tibble::as_tibble)

Спасибо, ребята, за предложение чего-то такого краткого и обобщающего.

person wibeasley    schedule 11.11.2017

Это работает, но далеко не идеально, потому что (а) необходимо обрабатывать каждый вектор в новом data.frame и (б) тип каждого вектора является явным (например,, purrr:map_chr против purrr:map_int)

# Step 1: Determine how many time the 'parent' rows need to be replicated.
values_per_person <- l %>% 
  purrr::modify_depth(2, length) %>% 
  purrr::map_int("value")

# Step 2: Pull out the parent rows and replicate the elements to match `time`.
id_replicated <- l %>% 
  purrr::map_int("person_id") %>% 
  rep(times=values_per_person)    
name_replicated <- l %>%
  purrr::map_chr("person_name") %>% 
  rep(times=values_per_person)

# Step 3: Pull out the nested/child rows.
time <- l %>%
  purrr::modify_depth(1, "time") %>% 
  purrr::flatten_int()
value <- l %>%
  purrr::modify_depth(1, "value") %>% 
  purrr::flatten_chr()

# Step 4: Combine the vectors in a data frame.
data.frame(
  person_id   = id_replicated,
  person_name = name_replicated,
  time        = time,
  value       = value
)
person wibeasley    schedule 11.11.2017