【问题标题】:Parse dictionary-like structure in which key and values are numeric values using R使用 R 解析其中键和值是数值的类字典结构
【发布时间】:2020-04-20 03:38:50
【问题描述】:

我正在尝试解析我在旅行中遇到的一些奇怪的数据结构。本质上它们类似于 python 或 javascript 字典,但键和值都是数字:

weird <- "{47=4578.0005, 181=23456.7831, 216=7548.2367}"

为了将其转换为表格,我尝试将其转换为更典型的字典格式并使用 jsonlite 解析它:

library(tidyverse)
library(jsonlite)
weird <- parse_json(str_replace(weird, "=", ":"))
#> Error: parse error: invalid object key (must be a string)

parse_json 函数正确地抱怨密钥不是字符串。我当然可以使用str_split 剖析所有这些,但我希望善良的灵魂可能对更优雅/紧凑的解决方案(希望完全避免使用正则表达式)有一些见解,可以将其解析成这样的表格:

tibble::tribble(
  ~ key,    ~ value,
  47,  4578.0005,
  181, 23456.7831,
  216,  7548.2367 
)
#> # A tibble: 3 x 2
#>     key  value
#>   <dbl>  <dbl>
#> 1    47  4578.
#> 2   181 23457.
#> 3   216  7548.

谢谢!

【问题讨论】:

    标签: r dictionary jsonlite


    【解决方案1】:

    虽然您可能想要一个紧凑的非正则表达式版本,但对我来说,这似乎是最容易理解和易于理解的:

    library(stringr)
    library(dplyr)
    
    weird <- "{47=4578.0005, 181=23456.7831, 216=7548.2367}"
    
    str_remove_all(weird, "[{}]") %>% 
      str_split(", ", simplify = TRUE) %>% 
      str_split("=", simplify = TRUE) %>% 
      as_tibble() %>% 
      mutate_all(as.numeric)
    
    #> # A tibble: 3 x 2
    #>      V1     V2
    #>   <dbl>  <dbl>
    #> 1    47  4578.
    #> 2   181 23457.
    #> 3   216  7548.
    

    由reprex package (v0.3.0) 于 2020-04-20 创建

    我不介意对数据进行三遍,每一步只完成一件事。我发现它更容易阅读和推理。


    更新:

    虽然我仍然认为上面的版本是 R 惯用的方法,但它不是 R 惯用的数据结构,所以这里有一个通过{reticulate} 使用 Python 的解决方案。这比使用 {jsonlite} 的 JSON 解析器要复杂一些,但事实证明您可以在 Python dicts 中使用数值作为键。

    我们首先必须将= 转换为: ,然后字符串变为有效的字典。然后我们可以启动一个 Python 上下文,运行一个字符串将其分配给一个变量,然后将状态返回给 R。然后它只是将 R 中的列表转换为数据帧。

    library(reticulate)
    library(stringr)
    library(glue)
    library(tibble)
    
    weird <- '{47=4578.0005, 181=23456.7831, 216=7548.2367}'
    
    weird <- str_replace_all(weird, "=", ": ")
    
    x <- py_run_string(
      glue("weird_dict = {weird}"), 
      convert = T
      )
    
    x$weird_dict %>% 
      unlist() %>% 
      enframe()
    #> # A tibble: 3 × 2
    #>   name   value
    #>   <chr>  <dbl>
    #> 1 47     4578.
    #> 2 181   23457.
    #> 3 216    7548.
    

    由reprex package (v2.0.1) 于 2021 年 10 月 21 日创建

    【讨论】:

    • 这可能比 jsonlite 方法更健壮,当数据变为不总是数字时,假设(当然)没有嵌入/转义的逗号或等号。
    • 我的梦想方法类似于parse(text = glue("list({weird})"),但不幸的是,R 中的列表名称也不能是裸数字。
    【解决方案2】:

    JSON 要求它的字典键被引用。虽然此解决方案还引用了这些值,但将as.numeric 应用于这些值应该不难(如果您确定数据集)。

    library(dplyr)
    library(tidyr)
    gsub("=", ":", gsub("(\\b|-?)([0-9.]+)\\b", '"\\1"', weird)) %>%
      jsonlite::fromJSON(.) %>%
      enframe() %>%
      unnest(value)
    # # A tibble: 3 x 2
    #   name  value     
    #   <chr> <chr>     
    # 1 47    4578.0005 
    # 2 181   23456.7831
    # 3 216   7548.2367 
    

    这假定所有键和值都是完全数字的(可能是负数,也可能是十进制),但在此模式中没有科学记数法。包含它并非不可能,因此如果您需要更进一步:https://www.regular-expressions.info/floatingpoint.html、Parsing scientific notation sensibly? 和 Regex for numbers on scientific notation? 可能会有所帮助。

    下一步可能很简单

    gsub("=", ":", gsub("(\\b|-?)([0-9.]+)\\b", '"\\1"', weird)) %>%
      jsonlite::fromJSON(.) %>%
      enframe() %>%
      unnest(value) %>%
      mutate_all(as.numeric)
    # # A tibble: 3 x 2
    #    name  value
    #   <dbl>  <dbl>
    # 1    47  4578.
    # 2   181 23457.
    # 3   216  7548.
    

    (明显的小数位丢失只是一个演示的东西,原始数据仍然有非整数值。)

    【讨论】:

    • 这个超级有用,我特别喜欢enframe这个功能,我一定会记住的小宝石!当我最初发布问题时,我立即加倍并编辑我的问题以指定我希望避免使用正则表达式。我认为您在这些编辑完成之前发布了您的答案,因此您完美地回答了我未经编辑的问题。但是,我怀疑像许多用户一样,我倾向于尽可能避免使用正则表达式,因为它的复杂性令人生畏。
    • 好的,我明白了。布赖恩的答案是最接近没有正则表达式的,尽管str_remove_all 需要被替换。要删除{},您需要substr,但它会盲目地这样做,而不考虑第一个/最后一个字符是什么。很好奇,您 “避免使用正则表达式” 的理由是什么?
    • 我发现它们很难阅读,而且我经常犯一些超级可以避免的错误,这些错误需要我很长时间才能修复。它们显然是一个强大的工具,我应该更熟悉它,但鉴于为大多数常见问题设计的大量解析功能,我很少需要它们,以至于我的日常成本效益似乎很低。
    • 我明白了,xkcd.com/1171。如果您检查stopifnot(startsWith(weird, "{"), endsWith(weird, "}"))(验证第一个/最后一个字符确实是您所期望的),然后使用substr(weird, 2, nchar(weird)-1),您可以使用Brian 的答案afterstr_remove_all,它使用的是正则表达式。其他 str_split 调用(在 IMO 中,并不比基本 R 的 strsplit 好)使用固定模式(在正常正则表达式参数中)。
    【解决方案3】:

    我认为您不能完全避免使用正则表达式,但只需进行一些小的替换,read.table() 就可以轻松阅读。

    wt <- c("{47=4578.0005, 181=23456.7831, 216=7548.2367}")
    
    read.table(text = gsub("[{},]", "\n", wt), sep = "=")
    
       V1        V2
    1  47  4578.001
    2 181 23456.783
    3 216  7548.237
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多