【问题标题】:Tidy nested json tree整洁的嵌套 json 树
【发布时间】:2016-09-18 02:05:50
【问题描述】:

这在处理 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++ 生锈了,所以我想在我做任何激烈的事情之前我会在这里发帖。

【问题讨论】:

标签: json r


【解决方案1】:

我也倾向于tidyjson。在tidyverse 中,您要查找的行为似乎在gather 家族中。

我认为tidyjson 中的gather 系列函数可以做一些改进,使这些助手变得不必要。现在,它们非常“类型敏感”并且错误或抛出不匹配的类型。在任何情况下,解决方法都不是太具有挑战性,尽管它肯定缺乏优雅。请注意,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 包的未来开发将使这个问题更容易解决!

【讨论】:

    【解决方案2】:

    我曾在类似的情况下苦苦挣扎,但在处理嵌套 JSON 时,tidyjson 包一次又一次地帮助我摆脱困境。需要大量输入,但tidyjson 函数返回一个整洁的对象。此处的文档:https://github.com/sailthru/tidyjson

    【讨论】:

    • 你能给我看一个与树无关的 tidyjson 示例吗? (即你不需要知道深度嵌套级别的名称?我以前玩过它,但我认为它通常需要我想通过制作获得的关于树结构的知识它更整洁。
    • 嗯,很抱歉,但我不确定这样的例子是否存在。得到 JSON 后,我调用 jsonlite::prettify() 来查看整个结构,然后开始解析 tidyjson::enter_object()tidyjson::spread_values() 等。
    • 由于我正在使用的 json 的大小,这并不总是一个简单的选择。我正在寻找一种功能,就像我在上面发布的那样,可以在任何嵌套树上工作,并将其移动到一个整洁的结构中。
    【解决方案3】:

    正如 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))
    

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

    【讨论】:

      猜你喜欢
      • 2023-04-08
      • 2013-12-04
      • 2018-01-13
      • 1970-01-01
      • 1970-01-01
      • 2021-12-22
      • 1970-01-01
      • 2013-10-03
      • 1970-01-01
      相关资源
      最近更新 更多