Рваный список или фрейм данных в 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
)

ВНИМАНИЕ: Я не проверял это с другими структурами, поэтому возможно, что он попадет в угловые случаи, которые вам нужно будет отладить.

Другие вопросы по тегам