Вернуть вложенный список с вложенным уровнем и значением

Я хотел бы визуализировать некоторые глубоко вложенные данные с помощью networkD3. Я не могу понять, как преобразовать данные в правильный формат перед отправкой в ​​radialNetwork.

Вот несколько примеров данных:

level <- c(1, 2, 3, 4, 4, 3, 4, 4, 1, 2, 3)
value <- letters[1:11]

где level указывает уровень гнезда, а value - имя узла. Используя эти два вектора, мне нужно получить данные в следующем формате:

my_list <- list(
  name = "root",
  children = list(
    list(
      name = value[1], ## a
      children = list(list(
        name = value[2], ## b
        children = list(list(
          name = value[3], ## c
          children = list(
            list(name = value[4]), ## d
            list(name = value[5]) ## e
          )
        ),
        list(
          name = value[6], ## f
          children = list(
            list(name = value[7]), ## g
            list(name = value[8]) ## h
          )
        ))
      ))
    ),
    list(
      name = value[9], ## i
      children = list(list(
        name = value[10], ## j
        children = list(list(
          name = value[11] ## k
        ))
      ))
    )
  )
)

Вот ушедший объект:

> dput(my_list)
# structure(list(name = "root",
#                children = list(
#                  structure(list(
#                    name = "a",
#                    children = list(structure(
#                      list(name = "b",
#                           children = list(
#                             structure(list(
#                               name = "c", children = list(
#                                 structure(list(name = "d"), .Names = "name"),
#                                 structure(list(name = "e"), .Names = "name")
#                               )
#                             ), .Names = c("name",
#                                           "children")), structure(list(
#                                             name = "f", children = list(
#                                               structure(list(name = "g"), .Names = "name"),
#                                               structure(list(name = "h"), .Names = "name")
#                                             )
#                                           ), .Names = c("name",
#                                                         "children"))
#                           )), .Names = c("name", "children")
#                    ))
#                  ), .Names = c("name",
#                                "children")), structure(list(
#                                  name = "i", children = list(structure(
#                                    list(name = "j", children = list(structure(
#                                      list(name = "k"), .Names = "name"
#                                    ))), .Names = c("name",
#                                                    "children")
#                                  ))
#                                ), .Names = c("name", "children"))
#                )),
#           .Names = c("name",
#                      "children"))

Затем я могу передать его последней функции построения графика:

library(networkD3)
radialNetwork(List = my_list)

Результат будет выглядеть примерно так:

введите здесь описание изображения


Вопрос: Как создать вложенный список?

Примечание. Как указано в @ zx8754, в этом SO post, но для этого требуется data.frame в качестве ввода. Из-за непоследовательности моего level я не вижу простого способа превратить его в data.frame.


person Boxuan    schedule 15.12.2016    source источник
comment
@ zx8754 Добавлен dput(my_list). Вдобавок входные данные не data.frame, и превратить их в data.frame непросто, ИМО, потому что уровни не согласованы. Вот почему я пометил recursion и подумал, что это может быть направление. Однако поправьте меня, если я ошибаюсь.   -  person Boxuan    schedule 15.12.2016
comment
Нам нужна рекурсивная функция, которая будет брать фрейм данных и разбивать на минимальное значение, извините, сейчас нет времени на код. Что-то вроде: df1 <- data.frame(level, value, stringsAsFactors = FALSE); split(df1, cumsum(df1$level == 1)) затем удалите минимальные значения и разделите на следующее минимальное значение и т. Д.   -  person zx8754    schedule 15.12.2016
comment
Я тоже думал об этом, но не знал, как привязать каждого ребенка к правильным родителям. Другими словами, как мы могли бы предотвратить привязку значения 2-го уровня 2 к 1-му родительскому элементу.   -  person Boxuan    schedule 16.12.2016


Ответы (2)


Использование слияния в стиле data.table:

library(data.table)
dt = data.table(idx=1:length(value), level, parent=value)

dt = dt[dt[, .(i=idx, level=level-1, child=parent)], on=.(level, idx < i), mult='last']

dt[is.na(parent), parent:= 'root'][, c('idx','level'):= NULL]

> dt
#     parent child
#  1:   root     a
#  2:      a     b
#  3:      b     c
#  4:      c     d
#  5:      c     e
#  6:      b     f
#  7:      f     g
#  8:      f     h
#  9:   root     i
# 10:      i     j
# 11:      j     k

Теперь мы можем использовать решение из другого сообщение:

x = maketreelist(as.data.frame(dt))

> identical(x, my_list)
# [1] TRUE
person sirallen    schedule 18.12.2016
comment
Это круто. Спасибо! Я пытаюсь понять код, поэтому быстрый вопрос к вам: ваша вторая строка похожа на перекрестное соединение и фильтруется по последней строке уровня? - person Boxuan; 19.12.2016
comment
Np. Вторая строка - это неэквивалентное соединение, отфильтрованное по последнему совпадению. См. channel9.msdn.com/Events/useR-international-R-User-conference/ - person sirallen; 19.12.2016

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

Вот base-й способ преобразования ваших данных в фрейм данных с двумя столбцами, parent и child, а затем передачу его в data.tree функции, которые можно легко преобразовать в нужный вам формат JSON ... и затем передать его radialNetwork .. .

level <- c(1, 2, 3, 4, 4, 3, 4, 4, 1, 2, 3)
value <- letters[1:11]

library(data.tree)
library(networkD3)

parent_idx <- sapply(1:length(level), function(n) rev(which(level[1:n] < level[n]))[1])
df <- data.frame(parent = value[parent_idx], child = value, stringsAsFactors = F)
df$parent[is.na(df$parent)] <- ""

list <- ToListExplicit(FromDataFrameNetwork(df), unname = T)
radialNetwork(list)

Вот способ tidyverse достижения того же ...

level <- c(1, 2, 3, 4, 4, 3, 4, 4, 1, 2, 3)
value <- letters[1:11]

library(tidyverse)
library(data.tree)
library(networkD3)

data.frame(level, value, stringsAsFactors = F) %>%
  mutate(row = row_number()) %>%
  mutate(level2 = level, value2 = value) %>%
  spread(level2, value2) %>%
  mutate(`0` = "") %>%
  arrange(row) %>%
  fill(-level, -value, -row) %>%
  gather(parent_level, parent, -level, -value, -row) %>%
  filter(parent_level == level - 1) %>%
  arrange(row) %>%
  select(parent, child = value) %>%
  data.tree::FromDataFrameNetwork() %>%
  data.tree::ToListExplicit(unname = TRUE) %>%
  radialNetwork()

а в качестве бонуса текущая версия networkD3 (v0.4.9000) для разработчиков имеет новую функцию treeNetwork, которая принимает фрейм данных с nodeId и parentId столбцами / переменными, что устраняет необходимость data.tree функций для преобразования в JSON, так что что-то вроде это работает...

level <- c(1, 2, 3, 4, 4, 3, 4, 4, 1, 2, 3)
value <- letters[1:11]

library(tidyverse)
library(networkD3)

data.frame(level, value, stringsAsFactors = F) %>%
  mutate(row = row_number()) %>%
  mutate(level2 = level, value2 = value) %>%
  spread(level2, value2) %>%
  mutate(`0` = "root") %>%
  arrange(row) %>%
  fill(-level, -value, -row) %>%
  gather(parent_level, parent, -level, -value, -row) %>%
  filter(parent_level == level - 1) %>%
  arrange(row) %>%
  select(nodeId = value, parentId = parent) %>%
  rbind(data.frame(nodeId = "root", parentId = NA)) %>% 
  mutate(name = nodeId) %>% 
  treeNetwork(direction = "radial")
person CJ Yetman    schedule 25.12.2017