我有这种角色矢量风格:
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
的长度非常长(〜百万元素)所以我想知道是否有更有效的方法来实现这一点。
答案 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