(我为我的例子过于简单而道歉,我将尝试对此进行补救,并以更方便的格式格式化我更相关的示例,以便直接复制到R.特别是,有多个值列,以及一些不需要解析的其他信息的前面的列。)
我是R的新手,也是data.table的新手,所以我很感激我找到的问题的输入。我正在使用数据表,其中一列是冒号分隔的格式字符串,用作其他冒号分隔列中值的图例。为了解析它,我必须首先将其拆分为其组件,然后搜索我需要稍后索引值字符串所需的组件的索引。以下是我可能正在处理的情况的简化示例
DT <- data.table(number=c(1:5),
format=c("name:age","age:name","age:name:height","height:age:name","weight:name:age"),
person1=c("john:30","40:bill","20:steve:100","300:70:george","140:fred:20"),
person2=c("jane:31","42:ivan","21:agnes:120","320:72:vivian","143:rose:22"))
评估时,我们得到
> DT
number format person1 person2
1: 1 name:age john:30 jane:31
2: 2 age:name 40:bill 42:ivan
3: 3 age:name:height 20:steve:100 21:agnes:120
4: 4 height:age:name 300:70:george 320:72:vivian
5: 5 weight:name:age 140:fred:20 143:rose:22
让我们说每个人,我只需知道他们的名字和年龄,不需要他们的身高或体重;在这个例子中,在我的实际数据中,每个格式字符串都有名称和年龄的字段,但可能位于不同的位置(我实际寻找的字段通常固定在某些列中,但我不愿意硬编码任何字段索引,因为我不完全熟悉我正在使用的数据文件的生成)。我首先拆分格式字符串,然后匹配()搜索我想要的字段的名称。
DT[, format.split := strsplit(format, ":")]
此时,我用来执行匹配的唯一方法是vapply:
DT[, index.name := vapply(format.split, function (x) match('name', x), 0L)]
DT[, index.age := vapply(format.split, function (x) match('age', x), 0L)]
因为我不知道有任何其他方法让R知道它应该单独查看列中的行,而不是作为向量聚集在一起,并在向量值格式上执行匹配.split每行的列,而不是尝试匹配整行的行。即使这样,一旦我找到每一行的索引,我就必须执行另一个strsplit,然后用mapply来解析每个人的值字符串中的名称 - 值和年龄值:
DT[, person1.split := strsplit(person1, ':')]
DT[, person1.name := mapply(function (x,y) x[y], person1.split, index.name]
DT[, person1.age := mapply(function (x,y) x[y], person1.split, index.age]
DT[, person2.split := strsplit(person2, ':')]
DT[, person2.name := mapply(function (x,y) x[y], person2.split, index.name]
DT[, person2.age := mapply(function (x,y) x[y], person2.split, index.age]
(当然,我也会为年龄做同样的事情)
我正在使用相当大的数据集,所以我希望我的代码尽可能高效。有没有人建议我可以加快或优化我的代码?
(注意:我真的在寻找正确的方法,而不是正确的* apply或* ply或Map功能。如果*(ap)ply或Map确实是正确的方法,我会很高兴知道哪个对我的情况来说是最有效或最合适的,但是如果有更好的方法来测试行内重复,我希望有关于这方面的建议来提供功能建议。欢迎提出建议。
事实证明,我的例子比它需要的更为笼统。我只需要两个字段,它们始终是格式字符串中的前两个字段,没有变化。第一个字段只是一个文字字符串。但是,第二个字段由至少2个数字组成,用逗号分隔(最终,我在第二个字段中过滤掉任何超过2个数字的行,因此只有在解析后发生过滤时才有可能更多) 。对于每个(3)值字符串,我只需要创建三列:第一个字段的字符列和两个数字列,第二个字段中的逗号分隔对的每个成员一个。任何其他字段都无关紧要。我当前的方法,可能是非常低效的,是使用sub()在所需的字段和带有反向引用的子字段上进行模式匹配。
> DT <- data.table(id=1:5,
format=c(rep("A:B:C:D:E", 5)),
person1=paste(paste0("foo",LETTERS[1:5]), paste(1:5, 10:6, sep=','), "blah", "bleh", "bluh", sep=':'),
person2=paste(paste0("bar",LETTERS[1:5]), paste(16:20, 5:1, sep=','), "blah", "bleh", "bluh", sep=':'),
person3=paste(paste0("baz",LETTERS[1:5]), paste(0:4, 12:8, sep=','), "blah", "bleh", "bluh", sep=':'))
> DT
id format person1 person2 person3
1: 1 A:B:C:D:E fooA:1,10:blah:bleh:bluh barA:16,5:blah:bleh:bluh bazA:0,12:blah:bleh:bluh
2: 2 A:B:C:D:E fooB:2,9:blah:bleh:bluh barB:17,4:blah:bleh:bluh bazB:1,11:blah:bleh:bluh
3: 3 A:B:C:D:E fooC:3,8:blah:bleh:bluh barC:18,3:blah:bleh:bluh bazC:2,10:blah:bleh:bluh
4: 4 A:B:C:D:E fooD:4,7:blah:bleh:bluh barD:19,2:blah:bleh:bluh bazD:3,9:blah:bleh:bluh
5: 5 A:B:C:D:E fooE:5,6:blah:bleh:bluh barE:20,1:blah:bleh:bluh bazE:4,8:blah:bleh:bluh
我的代码然后执行此操作:
DT[, `:=`(person1.A=sub("^([^:]*):.*$","\\1", person1),
person2.A=sub("^([^:]*):.*$","\\1", person2),
person3.A=sub("^([^:]*):.*$","\\1", person3),
person1.B.first=sub("^[^:]*:([^:,]*),.*$","\\1", person1),
person1.B.second=sub("^[^:]*:[^:,]*,([^:,]*)(,[^:,]*)*:.*$","\\1", person1),
person2.B.first=sub("^[^:]*:([^:,]*),.*$","\\1", person2),
person2.B.second=sub("^[^:]*:[^:,]*,([^:,]*)(,[^:,]*)*:.*$","\\1", person2),
person3.B.first=sub("^[^:]*:([^:,]*),.*$","\\1", person3),
person3.B.second=sub("^[^:]*:[^:,]*,([^:,]*)(,[^:,]*)*:.*$","\\1", person3))]
用于拆分,过滤器用
DT <- DT[grepl("^[^:]*:[^:,]*,[^:,]*:.*$", person1) &
grepl("^[^:]*:[^:,]*,[^:,]*:.*$", person2) &
grepl("^[^:]*:[^:,]*,[^:,]*:.*$", person3) ]
我知道这种方法效率可能非常低,但这是我在重复应用strsplit的旧方法中提出的第一个改进。考虑到新的条件,有没有更好的做事方式而不是融化,csplit,dcast?
因为我只需要前两个字段,所以我最终修剪了所有值字符串,删除了带有两个以上逗号的字符串(即超过3个第二字段数字),将逗号更改为冒号,替换格式字符串每行都有(现在3个)字段的名称,并执行@AnandaMahto建议的dcast(csplit(融化))。它似乎运作良好。
答案 0 :(得分:4)
@bskaggs有一个正确的想法,将数据放入长格式甚至是结构化的宽格式可能更有意义。
我将向您展示两个选项,但首先,以其他人可以实际使用的方式共享您的数据总是更好:
DT <- data.table(
format = c("name:age", "name:age:height", "age:height:name",
"height:weight:name:age", "name:age:weight:height",
"name:age:height:weight"),
values = c("john:30", "rene:33:183", "100:10:speck",
"100:400:sumo:11", "james:43:120:120",
"plink:2:300:400"))
我还建议您使用my cSplit
function。
以下是将数据集轻松转换为长格式的方法:
cSplit(DT, c("format", "values"), ":", "long")
# format values
# 1: name john
# 2: age 30
# 3: name rene
# 4: age 33
# 5: height 183
# 6: age 100
# 7: height 10
# 8: name speck
# 9: height 100
# 10: weight 400
# 11: name sumo
# 12: age 11
# 13: name james
# 14: age 43
# 15: weight 120
# 16: height 120
# 17: name plink
# 18: age 2
# 19: height 300
# 20: weight 400
一旦数据处于“长”形式,您就可以使用dcast.data.table
将其轻松转换为“宽”形式,就像这样。 (我还使用setcolorder
对列进行了重新排序,这样您无需复制即可重新排列数据。)
X <- dcast.data.table(
cSplit(cbind(id = 1:nrow(DT), DT),
c("format", "values"), ":", "long"),
id ~ format, value.var = "values")
setcolorder(X, c("id", "name", "age", "height", "weight"))
X
# id name age height weight
# 1: 1 john 30 NA NA
# 2: 2 rene 33 183 NA
# 3: 3 speck 100 10 NA
# 4: 4 sumo 11 100 400
# 5: 5 james 43 120 120
# 6: 6 plink 2 300 400
这在速度方面如何?
首先,一个非常温和的数据集:
DT <- rbindlist(replicate(2000, DT, FALSE))
dim(DT)
# [1] 12000 2
## @bskaggs's suggestion
system.time(colonMelt(DT))
# user system elapsed
# 0.27 0.00 0.27
## cSplit. It would be even faster if you already had
## an id column and didn't need to cbind one in
system.time(cSplit(cbind(id = 1:nrow(DT), DT),
c("format", "values"), ":", "long"))
# user system elapsed
# 0.02 0.00 0.01
## cSplit + dcast.data.table
system.time(dcast.data.table(
cSplit(cbind(id = 1:nrow(DT), DT),
c("format", "values"), ":", "long"),
id ~ format, value.var = "values"))
# user system elapsed
# 0.08 0.00 0.08
对于您更新的问题,您可以先melt
使用“data.table”,然后继续进行:
library(reshape2)
## Melting, but no reshaping -- a nice long format
cSplit(melt(DT, id.vars = c("number", "format")),
c("format", "value"), ":", "long")
## Try other combinations for the LHS and RHS of the
## formula. This seems to be what you might be after
dcast.data.table(
cSplit(melt(DT, id.vars = c("number", "format")),
c("format", "value"), ":", "long"),
number ~ variable + format, value.var = "value")
答案 1 :(得分:2)
我认为使用高大的整洁格式可能会更好:
colonMelt <- function(DT) {
formats <- strsplit(DT$format, ":")
rows <- rep(row.names(DT), sapply(formats, length))
data.frame(row = rows,
key = unlist(formats),
value = unlist(strsplit(DT$values, ":"))
)
}
newDT <- colonMelt(DT)
结果是一种格式更容易进行搜索和过滤而不会一直分割字符串:
row key value
1 1 name john
2 1 age 30
3 2 name rene
4 2 age 33
5 2 height 183
6 3 age 100
7 3 height 10
8 3 name speck