R для иерархического JSON с использованием JSONLITE?

Моя конечная игра - создать древовидную визуализацию из иерархического файла JSON, используя D3js.

Иерархия, которую я должен представить, - это диаграмма, где у A есть дочерние элементы B,C,D; B имеет детей E,F,G; У детей есть дети, я; и у D нет детей. Узлы будут иметь несколько пар ключ: значение. Я перечислил только 3 для простоты.

                             -- name:E
                            |   type:dkBlue
                            |   id: 005
                            |
                            |-- name:F
            -- name:B ------|   type:medBlue 
            |  type:blue    |   id: 006
            |  id:002       |
            |               |-- name:G
            |                   type:ltBlue
 name:A ----|                   id:007     
 type:colors|
 id:001     |-- name:C  ----|-- name:H
            |   type:red    |   type:dkRed         
            |   id:003      |    id:008
            |               |  
            |               |
            |               |-- name:I
            |                   type:medRed
            |                   id:009
            |-- name:D
                type:green
                id: 004

Мои исходные данные в R выглядят так:

nodes <-read.table(header = TRUE, text = "
ID name type
001 A   colors
002 B   blue
003 C   red
004 D   green
005 E   dkBlue
006 F   medBlue
007 G   ltBlue
008 H   dkRed
009 I   medRed
")

links <- read.table(header = TRUE, text = "
startID  relation endID    
001      hasSubCat 002
001      hasSubCat 003
001      hasSubCat 004
002      hasSubCat 005
002      hasSubCat 006
002      hasSubCat 007
003      hasSubCat 008
003      hasSubCat 009
")

Я должен преобразовать его в следующий JSON:

{"name": "A",
 "type": "colors",
 "id" : "001",
 "children": [
    {"name": "B",
      "type": "blue",
      "id"  : "002", 
      "children": [
          {"name": "E",
           "type": "dkBlue",
           "id"  : "003"},
          {"name": "F", 
           "type": "medBlue",
           "id": "004"},
          {"name": "G", 
           "type": "ltBlue",
           "id": "005"}
    ]},
    {"name": "C",
      "type": "red",
      "id"  : "006", 
      "children": [
          {"name": "H",
           "type": "dkRed",
           "id"  : "007"},
          {"name": "I", 
           "type": "dkBlue",
           "id": "008"}
    ]},
    {"name": "D",
      "type": "green",
      "id"  : "009"}
]}  

Буду признателен за любую помощь, которую вы можете предложить!

[ОБНОВЛЕНИЕ 2017-04-18]

Основываясь на ссылках Яна, я посмотрел на data.tree в R. Я могу восстановить свою иерархию, если я реструктурирую свои данные, как показано ниже. Обратите внимание, что я потерял тип связи (hasSubcat) между каждым узлом, значение которого может варьироваться для каждой ссылки / ребра в реальной жизни. Я готов отпустить это (пока), если смогу получить работоспособную иерархию. Пересмотренные данные для data.tree:

df <-read.table(header = TRUE, text = "
paths  type     id 
A      colors   001
A/B    blue     002
A/B/E  dkBlue   005
A/B/F  medBlue  006
A/B/G  ltBlue   007
A/C    red      003
A/C/H  dkRed    008
A/C/I  medRed   009
A/D    green    004
")

myPaths <- as.Node(df, pathName = "paths")
myPaths$leafCount / (myPaths$totalCount - myPaths$leafCount)
print(myPaths, "type", "id", limit = 25)

Отпечаток отображает иерархию, которую я набросал в исходном сообщении, и даже содержит ключ: значения для каждого узла. Ницца!

  levelName    type id
1 A          colors  1
2  ¦--B        blue  2
3  ¦   ¦--E  dkBlue  5
4  ¦   ¦--F medBlue  6
5  ¦   °--G  ltBlue  7
6  ¦--C         red  3
7  ¦   ¦--H   dkRed  8
8  ¦   °--I  medRed  9
9  °--D       green  4

Еще раз я в растерянности из-за того, как перевести это из дерева во вложенный JSON. Пример здесь https://ipub.com/data-tree-to-networkd3/, как и большинство примеров, предполагает наличие ключа: пары значений только на конечных узлах, а не на узлах ветвления. Я думаю, что ответ заключается в создании вложенного списка для подачи в JSONIO или JSONLITE, и я понятия не имею, как это сделать.

3 ответа

data.tree Это очень полезно и, вероятно, лучший способ для достижения вашей цели. Ради интереса, я представлю более окольный способ для достижения вашего вложенного JSON с помощью igraph а также d3r,

nodes <-read.table(header = TRUE, text = "
ID name type
001 A   colors
002 B   blue
003 C   red
004 D   green
005 E   dkBlue
006 F   medBlue
007 G   ltBlue
008 H   dkRed
009 I   medRed
")

links <- read.table(header = TRUE, text = "
startID  relation endID    
001      hasSubCat 002
001      hasSubCat 003
001      hasSubCat 004
002      hasSubCat 005
002      hasSubCat 006
002      hasSubCat 007
003      hasSubCat 008
003      hasSubCat 009
")

library(d3r)
library(dplyr)
library(igraph)

# make it an igraph
gf <- graph_from_data_frame(links[,c(1,3,2)],vertices = nodes)

# if we know that this is a tree with root as "A"
#  we can do something like this
df_tree <- dplyr::bind_rows(
  lapply(
    all_shortest_paths(gf,from="A")$res,
    function(x){data.frame(t(names(unclass(x))), stringsAsFactors=FALSE)}
  )
)

# we can discard the first column
df_tree <- df_tree[,-1]
# then make df_tree[1,1] as 1 (A)
df_tree[1,1] <- "A"

# now add node attributes to our data.frame
df_tree <- df_tree %>%
  # let's get the last non-NA in each row so we can join with nodes
  mutate(
    last_non_na = apply(df_tree, MARGIN=1, function(x){tail(na.exclude(x),1)})
  ) %>%
  # now join with nodes
  left_join(
    nodes,
    by = c("last_non_na" = "name")
  ) %>%
  # now remove last_non_na column
  select(-last_non_na)

# use d3r to nest as we would like
nested <- df_tree %>%
  d3_nest(value_cols = c("ID", "type"))

Хороший, если немного трудно обернуть голову, способ сделать это с помощью самореферентной функции, как показано ниже...

nodes <- read.table(header = TRUE, colClasses = "character", text = "
ID name type
001 A   colors
002 B   blue
003 C   red
004 D   green
005 E   dkBlue
006 F   medBlue
007 G   ltBlue
008 H   dkRed
009 I   medRed
")

links <- read.table(header = TRUE, colClasses = "character", text = "
startID  relation endID    
001      hasSubCat 002
001      hasSubCat 003
001      hasSubCat 004
002      hasSubCat 005
002      hasSubCat 006
002      hasSubCat 007
003      hasSubCat 008
003      hasSubCat 009
")

convert_hier <- function(linksDf, nodesDf, sourceId = "startID", 
                         targetId = "endID", nodesID = "ID") {
  makelist <- function(nodeid) {
    child_ids <- linksDf[[targetId]][which(linksDf[[sourceId]] == nodeid)]

    if (length(child_ids) == 0) 
      return(as.list(nodesDf[nodesDf[[nodesID]] == nodeid, ]))

    c(as.list(nodesDf[nodesDf[[nodesID]] == nodeid, ]), 
      children = list(lapply(child_ids, makelist)))
  }

  ids <- unique(c(linksDf[[sourceId]], linksDf[[targetId]]))
  rootid <- ids[! ids %in% linksDf[[targetId]]]
  jsonlite::toJSON(makelist(rootid), pretty = T, auto_unbox = T)
}

convert_hier(links, nodes)

несколько заметок...

  1. я добавил colClasses = "character" на ваш read.table команды, чтобы номера ID не приводились к целым числам без ведущих нулей и чтобы строки не преобразовывались в множители.
  2. Я завернул все в convert_hier функция, чтобы облегчить адаптацию к другим сценариям, но настоящая магия в makelist функция.

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

library(jsonlite)
...
df2list <- function(i) as.vector(nodes[nodes$name == i,])

# GRANDPARENT LEVEL
jsonlist <- as.list(nodes[nodes$name=='A',])
# PARENT LEVEL       
jsonlist$children <- lapply(c('B','C','D'), function(i) as.list(nodes[nodes$name == i,]))
# CHILDREN LEVEL
jsonlist$children[[1]]$children <- lapply(c('E','F','G'), df2list)
jsonlist$children[[2]]$children <- lapply(c('H','I'), df2list)

toJSON(jsonlist, pretty=TRUE)

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

Следовательно, рассмотрим очистку дополнительных скобок с помощью вложенных gsub который все еще делает действительным JSON:

output <- toJSON(jsonlist, pretty=TRUE)

gsub('"\\]\n', '"\n', gsub('"\\],\n', '",\n', gsub('": \\["', '": "', output)))

Окончательный вывод

{
  "ID": "001",
  "name": "A",
  "type": "colors",
  "children": [
    {
      "ID": "002",
      "name": "B",
      "type": "blue",
      "children": [
        {
          "ID": "005",
          "name": "E",
          "type": "dkBlue"
        },
        {
          "ID": "006",
          "name": "F",
          "type": "medBlue"
        },
        {
          "ID": "007",
          "name": "G",
          "type": "ltBlue"
        }
      ]
    },
    {
      "ID": "003",
      "name": "C",
      "type": "red",
      "children": [
        {
          "ID": "008",
          "name": "H",
          "type": "dkRed"
        },
        {
          "ID": "009",
          "name": "I",
          "type": "medRed"
        }
      ]
    },
    {
      "ID": "004",
      "name": "D",
      "type": "green"
    }
  ]
} 
Другие вопросы по тегам