将 JavaScript 生成的数据导入 R 或 Stata

Import data generated by JavaScript into R or Stata

我得到了一个如下所示的 csv 文件:

id,v2,class
1,1,"{2=22.7, 0=4.8, 3=321}"
2,1,"{2=929.5, 1=11.2, 3=1229.8}"
3,2,"{2=50.9, 1=28.8}"

我需要制作此表格——更具体地说,将其导入 R 数据框或 Stata。在一天结束时,我想要一个看起来像这样的 table:

id  v2  class_0 class_1 class_2 class_3
1   1   4.8     0       22.7    321
2   1   0       11.2    929.5   1229.8
3   2   0       28.8    50.9    0

有问题的“列”(class) 包含关于在 table 中应成为 4 列的数据。但是请注意,值 0 不会显示在原始文件中。

背景

此文件是在使用 JavaScript 的 Google Earth Engine 中生成的。除了它确实是一个逗号分隔值文件这一事实之外,有问题的“列”(class)对我来说看起来像一个 JSON 对象(有道理,因为该文件是由 JavaScript) 生成的。但是,很明显这不是 JSON 文件。

鉴于在 R 中,我尝试将罪魁祸首列解释为 JSON,并执行了以下操作:

library("rjson")
df = read.csv(csv_file)
json_data = fromJSON(paste(df$waterClass, collapse=""))
> Error in fromJSON(paste(df$waterClass, collapse = "")) :
  unexpected character "1"; expecting opening string quote (") for key value

运气不好。

我想我可以采用更暴力的方法,但我希望首先有一个更优雅的解决方案。

我们可以将每一行转换为DCF格式记录,然后使用read.dcf读取它。首先将其读入DF0,然后将每一行改造成DCF格式的记录,给出v。然后使用 read.dcf 读取它,重新排列列并将它们转换为数字,给出数字矩阵。最后用 0 替换 NAs。没有使用包。

DF0 <- read.csv(text = Lines, as.is = TRUE)
v <- with(DF0, paste0("\nid:", id, "\nv2:", v2, chartr("{}=,", "\n :\n", class)))
v <- gsub(" ", "", v)
m <- read.dcf(textConnection(v))
nms <- c("id", "v2", sort(setdiff(colnames(m), c("id", "v2"))))
mm <- apply(m[, nms], 2, as.numeric)
mm[is.na(mm)] <- 0
mm

给出这个数字矩阵:

     id v2   0    1     2      3
[1,]  1  1 4.8  0.0  22.7  321.0
[2,]  2  1 0.0 11.2 929.5 1229.8
[3,]  3  2 0.0 28.8  50.9    0.0

备注

可重现形式的输入:

Lines <- 'id,v2,class
1,1,"{2=22.7, 0=4.8, 3=321}"
2,1,"{2=929.5, 1=11.2, 3=1229.8}"
3,2,"{2=50.9, 1=28.8}"'

Tidyverse 方法:

library(tidyverse)

df <- read_csv('id,v2,class
1,1,"{2=22.7, 0=4.8, 3=321}"
2,1,"{2=929.5, 1=11.2, 3=1229.8}"
3,2,"{2=50.9, 1=28.8}"')

df_clean <- df %>% 
    separate_rows(class, sep = ',') %>%    # separate dict elements
    separate(class, c('key', 'value'), sep = '=') %>%    # split key-value pairs
    mutate(key = paste0('class_', parse_number(key)),    # clean up
           value = parse_number(value)) %>% 
    spread(key, value, fill = 0)    # spread back to wide form

df_clean
#> # A tibble: 3 x 6
#>      id    v2 class_0 class_1 class_2 class_3
#>   <dbl> <dbl>   <dbl>   <dbl>   <dbl>   <dbl>
#> 1     1     1     4.8     0      22.7    321 
#> 2     2     1     0      11.2   930.    1230.
#> 3     3     2     0      28.8    50.9      0

您可以稍微修改一下以添加一些引号,然后 jsonlite 应该能够处理它:

library(jsonlite)
cbind(
  dat[c("id","v2")],
  stream_in(textConnection(
    gsub("(\s+|\{)(.+?)=(.+?)(,|})", '\1"\2":\3\4', dat$class))
  )
)
# Imported 3 records. Simplifying...
#  id v2     2    0      3    1
#1  1  1  22.7  4.8    321 <NA>
#2  2  1 929.5 <NA> 1229.8 11.2
#3  3  2  50.9 <NA>   <NA> 28.8

这是一个 Stata 解决方案,示例数据为 data.csv

import delimited using data.csv, clear
split class, parse(,) 
drop class 
reshape long class, i(id) j(which) 
replace class = subinstr(class, "{", "", .) 
replace class = subinstr(class, "}", "", .) 
split class, parse("=") destring 
drop class which 
rename (class?) (which class_) 
replace class_ = 0 if missing(class_) 
drop if missing(which) 
reshape wide class_, i(id) j(which) 
mvencode class_*, mv(0) 

list

     +-------------------------------------------------+
     | id   class_0   class_1   class_2   class_3   v2 |
     |-------------------------------------------------|
  1. |  1       4.8         0      22.7       321    1 |
  2. |  2         0      11.2     929.5    1229.8    1 |
  3. |  3         0      28.8      50.9         0    2 |
     +-------------------------------------------------+