有效地分割字符向量

时间:2016-01-13 17:09:13

标签: r list split

我有这种角色矢量风格:

vec <- c("id a; sex m; age 16; type 1;","id a; sex m; age 16;","id a; sex m; age 16; type 3")

vec中的每个元素都是一个“;”分隔的属性列表,其中每个属性都具有“键值”格式(“;”字符只能显示为分隔符。)

所以第一个属性列表是: ID =一 性别=米 年龄= 16 类型= 1

请注意vec中的不同元素可能会略有不同的属性。

我正在寻找一种将vec拆分为列表列表的有效方法。外部列表中的每个元素都是所有属性值的列表,其中元素名称是属性键。这意味着外部列表的长度将是vec元素的长度,每个内部列表的长度将是属性的长度。

我目前有这个实现,它有助于理解我需要的输出:

attributes.list <- sapply(vec, function(x) strsplit(x, split = "(\\;)(\\s+)?", perl = TRUE)[[1]])
attributes.lol <- lapply(attributes.list, function(x) {
  attribute.mat <- sapply(x, function(y) strsplit(y, split = " ")[[1]])
  colnames(attribute.mat) <- NULL
  attribute.list <- as.list(attribute.mat[2,])
  names(attribute.list) <- attribute.mat[1,]
  return(attribute.list)
})

> attributes.lol[[1]]
$id
[1] "a"

$sex
[1] "m"

$age
[1] "16"

$type
[1] "1"

实际上vec的长度非常长(〜百万元素)所以我想知道是否有更有效的方法来实现这一点。

4 个答案:

答案 0 :(得分:4)

我建议将“iotools”和“data.table”结合起来,这就像这样:

library(iotools)
library(data.table)
melt(data.table(ind = seq_along(vec), trimws(mstrsplit(vec, ";"))),
     "ind", na.rm = TRUE)[
      , c("key", "val") := tstrsplit(value, " ", TRUE)][
        , c("variable", "value") := NULL][]

或者,如果你想要一个“宽”的形式(比如@ GGrothendieck的回答):

dcast(
  melt(data.table(ind = seq_along(vec), trimws(mstrsplit(vec, ";"))),
       "ind", na.rm = TRUE)[
         , c("key", "val") := tstrsplit(value, " ", TRUE)][
           , c("variable", "value") := NULL][], ind ~ key, value.var = "val")

我建议如上所述,因为你提到你想要一种有效的方法。比较以下内容:

样本数据长度3,大约100000,大约100万。

vec <- c("id a; sex m; age 16; type 1;","id a; sex m; age 16;","id a; sex m; age 16; type 3")
v100k <- rep(vec, ceiling(100000/length(vec)))
v1M <- rep(vec, ceiling(1000000/length(vec)))

我们想要测试的方法:

library(iotools)
library(data.table)

funAM_l <- function(invec) {
  melt(data.table(ind = seq_along(invec), trimws(mstrsplit(invec, ";"))), "ind", na.rm = TRUE)[
    , c("key", "val") := tstrsplit(value, " ", TRUE)][
      , c("variable", "value") := NULL][]
}

funAM_w <- function(invec) dcast(funAM_l(invec), ind ~ key, value.var = "val")

funMT <- function(v) {
  z <- strsplit(v, split = "(\\;)(\\s+)?", perl = TRUE)
  lapply(z,function(s) {v <- unlist(strsplit(s,' ')); setNames(as.list(v[c(F,T)]),v[c(T,F)]) })
}

funF <- function(invec) rbindlist(lapply(invec, function(x) { fread(gsub(";", "\n", x)) }), idcol = TRUE)

funGG <- function(invec) read.dcf(textConnection(sub(" ",": ",trimws(unlist(strsplit(paste0(invec, ";"),";"))))))

我的建议是不会用小矢量赢得任何比赛:

library(microbenchmark)
microbenchmark(funAM_l(vec), funAM_w(vec), funF(vec), funGG(vec), funMT(vec))
# Unit: microseconds
#          expr      min        lq       mean    median        uq      max neval
#  funAM_l(vec) 1474.163 1525.3765 1614.28414 1573.6325 1601.3815 2828.481   100
#  funAM_w(vec) 3293.376 3482.9510 3741.30381 3553.7240 3714.1730 6787.863   100
#     funF(vec)  690.761  729.4900  830.61645  756.4610  777.6725 4083.904   100
#    funGG(vec)  182.281  209.8405  220.46376  220.8055  232.1820  280.788   100
#    funMT(vec)   57.288   76.5225   84.81496   83.2755   90.3120  166.352   100

但是看看我们扩大向量时会发生什么:

system.time(funAM_l(v100k))
#    user  system elapsed 
#    0.24    0.00    0.24 
system.time(funAM_w(v100k))
#    user  system elapsed 
#   0.296   0.000   0.296 
system.time(funMT(v100k))
#    user  system elapsed 
#   1.768   0.000   1.768 
system.time(funF(v100k))
#    user  system elapsed 
#  21.960   0.136  22.068 
system.time(funGG(v100k))
#    user  system elapsed 
#  30.968   0.004  30.940 

以下是它在长度为100万的向量上的表现。

system.time(funAM_w(v1M))
#    user  system elapsed 
#   4.316   0.092   4.402 

我的另一个建议是从我的“splitstackshape”包中查看cSplit。这比@ Marat的做法要好一点。

这里有100万个值:

library(splitstackshape)
system.time(dcast(
  cSplit(cSplit(data.table(ind = seq_along(v1M), v1M), "v1M", ";", "long"), "v1M", " "), 
  ind ~ v1M_1, value.var = "v1M_2"))
#    user  system elapsed 
#  13.744   0.156  13.882

答案 1 :(得分:3)

以下仅使用基数R.在每条记录上附加分号,以分号分隔记录,删除前导和尾随空格,用冒号和空格替换空格,然后使用read.dcf读入。这给出了一个矩阵m,我们将其转换为数据框并使用type.convert来获取正确的类型。 (如果矩阵足够,则省略第二行。)

m <- read.dcf(textConnection(sub(" ",": ",trimws(unlist(strsplit(paste0(vec, ";"),";"))))))
as.data.frame(lapply(as.data.frame(m, stringsAsFactors = FALSE), type.convert))

,并提供:

  id sex age type
1  a   m  16    1
2  a   m  16   NA
3  a   m  16    3

答案 2 :(得分:2)

您可以尝试这种方法,这与@alexis_laz建议一致:

设定:

vec <- c("id a; sex m; age 16; type 1;","id a; sex m; age 16;","id a; sex m; age 16; type 3")

v <- rep(vec,1e5)

代码:

z <- strsplit(v, split = "(\\;)(\\s+)?", perl = TRUE)

out <- lapply(z,function(s) {v <- unlist(strsplit(s,' ')); setNames(as.list(v[c(F,T)]),v[c(T,F)]) })

答案 3 :(得分:1)

即使这样也没有为您带来相同的输出,您可以尝试替换“;”如下:

require(data.table)
l <- lapply(vec, function(x){
  fread(gsub(";", "\n", x))
})

为您提供一个列表,然后您可以通过

组合
rbindlist(l, idcol = TRUE)

这导致:

   .id   id  a
1:   1  sex  m
2:   1  age 16
3:   1 type  1
4:   2  sex  m
5:   2  age 16
6:   3  sex  m
7:   3  age 16
8:   3 type  3