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)
несколько заметок...
- я добавил
colClasses = "character"
на вашread.table
команды, чтобы номера ID не приводились к целым числам без ведущих нулей и чтобы строки не преобразовывались в множители. - Я завернул все в
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"
}
]
}