整洁的嵌套json树

时间:2016-09-18 02:05:50

标签: json r

在处理API时出现了很多。

大多数时候,为了进行真正的分析,我想让我的数据集整洁,但通常,这需要为每种类型的树提供解决方案,而不是更通用的方法。

我认为有一个生成整洁数据的函数会很好(尽管在深度嵌套的树中有很多不同的NA,但有许多不同的因子水平。

我有一个hackish解决方案,使用unlist(..., recursive = FALSE) +一个命名约定,

但我想看看这里有人可能有更好的解决方案来整理这些列表结构。

#####################
# Some Test Data
aNestedTree = 
  list(a = 1, 
       b = 2, 
       c = list(
         a = list(1:5), 
         b = 2, 
         c = list(
           a = 1, 
           d = 3,
           e = list())),
       d = list(
         y = 3,
         z = 2
       ))

############################################################
# Run through the list and rename all list elements,
# We unlist once at  time, adding "__" at each unlist step
# until the object is no longer a list

renameVars <- function(lst, sep = '__') {
  if(is.list(lst)) {
    names(lst) <- paste0(names(lst),sep)
    renameVars(unlist(lst, recursive = FALSE),sep = sep)
  } else {
    lst
  }
}

res <- renameVars(aNestedTree)

我们可以检查输出,看看我们有一个奇怪命名的对象, 但是有一种解决这种疯狂的方法。

> res
    a________     b________  c__.a____1__  c__.a____2__  c__.a____3__ 
            1             2             1             2             3 
 c__.a____4__  c__.a____5__   c__.b______ c__.c__.a____ c__.c__.d____ 
            4             5             2             1             3 
  d__.y______   d__.z______ 
            3             2 

现在我把它放在data.table中,所以我可以塑造它。

library(data.table)
dt <- data.table(values = res, name = names(res))

# Use some regex to split that name up, along with data.table's tstrsplit
# function to separate them into as many columns as there are nests

> dt[,paste0('V',seq_along(s <- tstrsplit(dt$name,'[__]+(\\.|)'))) := s]
> dt
    values          name V1 V2 V3
 1:      1     a________  a NA NA
 2:      2     b________  b NA NA
 3:      1  c__.a____1__  c  a  1
 4:      2  c__.a____2__  c  a  2
 5:      3  c__.a____3__  c  a  3
 6:      4  c__.a____4__  c  a  4
 7:      5  c__.a____5__  c  a  5
 8:      2   c__.b______  c  b NA
 9:      1 c__.c__.a____  c  c  a
10:      3 c__.c__.d____  c  c  d
11:      3   d__.y______  d  y NA
12:      2   d__.z______  d  z NA

然后我可以过滤我想要的因子组合(或dcast / spread)。 (虽然如果它们存在,我实际上会拆分最低级别的表格)

我考虑过通过bind.c并提取do_unlist以通过Rcpp创建一个具有灵活命名约定的函数,但是我的C ++生锈了,所以我想在我做任何事之前都会在这里发布激烈。

3 个答案:

答案 0 :(得分:0)

我在类似情况下挣扎,但tidyjson软件包在处理嵌套JSON时一次又一次地保护我。需要大量的输入,但tidyjson函数返回一个整洁的对象。这里的文档:https://github.com/sailthru/tidyjson

答案 1 :(得分:0)

正如dracodoc指出的那样,data.tree可能有所帮助。例如。像这样:

library(data.tree)
aNestedTree = 
  list(a = 1, 
       b = 2, 
       c = list(
         a = list(1:5), 
         b = 2, 
         c = list(
           a = 1, 
           d = 3,
           e = list())),
       d = list(
         y = 3,
         z = 2
       ))

tree <- FromListSimple(aNestedTree)
print(tree)

这将给出:

      levelName  z
1 Root          NA
2  ¦--c         NA
3  ¦   ¦--a     NA
4  ¦   °--c     NA
5  ¦       °--e NA
6  °--d          2

tree$fieldsAll
[1] "a" "b" "1" "d" "y" "z"

旁注:通常,您可以这样做:

do.call("print", c(tree, tree$fieldsAll))

但是,这里不起作用,因为某些节点名称与字段名称相同。我认为这是一个错误,很快就会修复它。

答案 2 :(得分:0)

我倾向于倾向于tidyjson。在tidyverse中,您要查找的行为似乎属于gather系列。

我认为gather中的tidyjson函数系列可以做一些改进,使这些助手不必要。现在,他们非常敏感&#34;和错误或抛出不匹配的类型。无论如何,解决方案并不是太具有挑战性,尽管它绝对缺乏优雅。请注意,bind_rows变体目前来自我的开发版本,并不是主流。但希望这说明了这个想法。

关于方法的说明:

  • 所有值都是数字(我之后将它们转换为字符)
  • 帮助者收集不同类型的元素,bind_rows将数据集堆叠在一起。
  • 级别按递归级别跟踪

首先定义帮助者:

recurse_gather <- function(.x,.level) {
  .x <- tidyjson::bind_rows(
    gobj(.x,.level)
    , garr(.x,.level)
    , gpersist(.x,.level)
  )

  if (any(as.character(json_types(.x,'type')$type) %in% c('object','array'))) {
    .x <- recurse_gather(.x,.level+1)
  }

  return(.x)
}
gobj <- function(.x,.level) {
  .x %>% json_types('type') %>%
    filter(type=='object') %>%
    gather_object(paste0('v',.level)) %>%
    select(-type)
}

gpersist <- function(.x,.level) {
  .x %>% json_types('type') %>%
    filter(! type %in% c('object','array')) %>%
    mutate_(.dots=setNames(
      paste0('as.character(NA)')
      ,paste0('v',.level)
    )) %>%
    select(-type)
}

garr <- function(.x,.level) {
  .x %>% json_types('type') %>%
    filter(type=='array') %>%
    gather_array('arridx') %>%
    append_values_number(paste0('v',.level)) %>%
    mutate_(.dots=setNames(
      paste0('as.character(v',.level,')')
      ,paste0('v',.level)
    )) %>%
    select(-arridx,-type)
}

然后使用助手非常简单。

library(dplyr)
library(tidyjson)

j <- "{\"a\":[1],\"b\":[2],\"c\":{\"a\":[1,2,3,4,5],\"b\":[2],\"c\":{\"a\":[1],\"d\":[3],\"e\":[]}},\"d\":{\"y\":[3],\"z\":[2]}}"
recurse_gather(j, 1) %>% arrange(v1, v2, v3, v4) %>% tbl_df()
#> # A tibble: 12 x 5
#>    document.id    v1    v2    v3    v4
#>  *       <int> <chr> <chr> <chr> <chr>
#>  1           1     a     1  <NA>  <NA>
#>  2           1     b     2  <NA>  <NA>
#>  3           1     c     a     1  <NA>
#>  4           1     c     a     2  <NA>
#>  5           1     c     a     3  <NA>
#>  6           1     c     a     4  <NA>
#>  7           1     c     a     5  <NA>
#>  8           1     c     b     2  <NA>
#>  9           1     c     c     a     1
#> 10           1     c     c     d     3
#> 11           1     d     y     3  <NA>
#> 12           1     d     z     2  <NA>

希望tidyjson包的未来发展能够解决这个问题更容易解决的问题!