Рваный список или фрейм данных в JSON
Я пытаюсь создать рваный список в R, который соответствует древовидной структуре D3 flare.json. Мои данные находятся в data.frame:
path <- data.frame(P1=c("direct","direct","organic","direct"),
P2=c("direct","direct","end","end"),
P3=c("direct","organic","",""),
P4=c("end","end","",""), size=c(5,12,23,45))
path
P1 P2 P3 P4 size
1 direct direct direct end 5
2 direct direct organic end 12
3 organic end 23
4 direct end 45
но это также может быть список или изменено при необходимости:
path <- list()
path[[1]] <- list(name=c("direct","direct","direct","end"),size=5)
path[[2]] <- list(name=c("direct","direct","organic","end"), size=12)
path[[3]] <- list(name=c("organic", "end"), size=23)
path[[4]] <- list(name=c("direct", "end"), size=45)
Желаемый результат:
rl <- list()
rl <- list(name="root", children=list())
rl$children[1] <- list(list(name="direct", children=list()))
rl$children[[1]]$children[1] <- list(list(name="direct", children=list()))
rl$children[[1]]$children[[1]]$children[1] <- list(list(name="direct", children=list()))
rl$children[[1]]$children[[1]]$children[[1]]$children[1] <- list(list(name="end", size=5))
rl$children[[1]]$children[[1]]$children[2] <- list(list(name="organic", children=list()))
rl$children[[1]]$children[[1]]$children[[2]]$children[1] <- list(list(name="end", size=12))
rl$children[[1]]$children[2] <- list(list(name="end", size=23))
rl$children[2] = list(list(name="organic", children=list()))
rl$children[[2]]$children[1] <- list(list(name="end", size=45))
Поэтому, когда я печатаю в json, это:
require(RJSONIO)
cat(toJSON(rl, pretty=T))
{
"name" : "root",
"children" : [
{
"name" : "direct",
"children" : [
{
"name" : "direct",
"children" : [
{
"name" : "direct",
"children" : [
{
"name" : "end",
"size" : 5
}
]
},
{
"name" : "organic",
"children" : [
{
"name" : "end",
"size" : 12
}
]
}
]
},
{
"name" : "end",
"size" : 23
}
]
},
{
"name" : "organic",
"children" : [
{
"name" : "end",
"size" : 45
}
]
}
]
}
Я с трудом нахожу в голове рекурсивные шаги, необходимые для создания этой структуры списка в R. В JS я могу довольно легко перемещаться по узлам и на каждом узле определять, добавлять ли новый узел или продолжать двигаться вниз. дерево, используя push при необходимости, например: new = {"name": node, "children": []};
или же new = {"name": node, "size": size};
как в этом примере. Я пытался split
data.frame как в этом примере:
makeList<-function(x){
if(ncol(x)>2){
listSplit<-split(x,x[1],drop=T)
lapply(names(listSplit),function(y){list(name=y,children=makeList(listSplit[[y]]))})
} else {
lapply(seq(nrow(x[1])),function(y){list(name=x[,1][y],size=x[,2][y])})
}
}
jsonOut<-toJSON(list(name="root",children=makeList(path)))
но это дает мне ошибку
Error: evaluation nested too deeply: infinite recursion / options(expressions=)?
Error during wrapup: evaluation nested too deeply: infinite recursion / options(expressions=)?
2 ответа
Функция, указанная в связанных вопросах и ответах, по сути, то, что вам нужно, однако она не сработала в вашем наборе данных из-за нулевых значений для некоторых строк в последующих столбцах. Вместо того, чтобы просто слепо повторять рекурсию до тех пор, пока у вас не закончатся столбцы, вам нужно проверить свое "конечное" значение и использовать его для перехода к созданию листьев:
makeList<-function(x){
listSplit<-split(x[-1],x[1], drop=TRUE);
lapply(names(listSplit),function(y){
if (y == "end") {
l <- list();
rows = listSplit[[y]];
for(i in 1:nrow(rows) ) {
l <- c(l, list(name=y, size=rows[i,"size"] ) );
}
l;
}
else {
list(name=y,children=makeList(listSplit[[y]]))
}
});
}
Я считаю, что это делает то, что вы хотите, хотя у него есть некоторые ограничения. В частности, предполагается, что каждая ветвь в вашей сети уникальна (т.е. в вашем фрейме данных не может быть двух строк, которые равны для каждого столбца, кроме размера):
df.split <- function(p.df) {
p.lst.tmp <- unname(split(p.df, p.df[, 1]))
p.lst <- lapply(
p.lst.tmp,
function(x) {
if(ncol(x) == 2L && nrow(x) == 1L) {
return(list(name=x[1, 1], size=unname(x[, 2])))
} else if (isTRUE(is.na(unname(x[ ,2])))) {
return(list(name=x[1, 1], size=unname(x[, ncol(x)])))
}
list(name=x[1, 1], children=df.split(x[, -1, drop=F]))
}
)
p.lst
}
all.equal(rl, df.split(path)[[1]])
# [1] TRUE
Обратите внимание, что вы изменили органический размер, поэтому мне пришлось исправить ваши rl
чтобы получить этот результат (rl
имеет 45, но ваш path
как 23). Кроме того, я изменил ваш path
немного data.frame:
path <- data.frame(
root=rep("root", 4),
P1=c("direct","direct","organic","direct"),
P2=c("direct","direct","end","end"),
P3=c("direct","organic",NA,NA),
P4=c("end","end",NA,NA),
size=c(5,12,23,45),
stringsAsFactors=F
)
ВНИМАНИЕ: Я не проверял это с другими структурами, поэтому возможно, что он попадет в угловые случаи, которые вам нужно будет отладить.