r - 在data.table上对向量值列执行按行操作

标签 r data.table

编辑:

(对于我的示例过于简化的事实,我深表歉意,并且我将尝试纠正这一问题,并以更方便的格式来格式化我更相关的示例,以便直接复制到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


假设每个人只需要知道他们的姓名和年龄,就不需要他们的身高或体重。在此示例中,在我的实际数据中,每个格式字符串都具有用于名称和年龄的字段,但可能位于不同的位置(我实际上要查找的字段通常固定在某些列中,但是我不愿意对任何字段进行硬编码索引,因为我并不完全熟悉正在使用的数据文件的生成)。我将首先拆分格式字符串,然后执行match()搜索所需字段的名称。

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知道它应该单独查看列中的行,而不是将它们捆在一起作为一个向量,并且对每个值的vector.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个数字组成,并用逗号分隔(最终,我在第二个字段中过滤出了超过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个第二字段数字)的值,将逗号更改为冒号,将每行的格式字符串替换为(现在为3)字段的名称,并按照@AnandaMahto的建议执行dcast(csplit(melt))。看来运作良好。

最佳答案

@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")

关于r - 在data.table上对向量值列执行按行操作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25813095/

相关文章:

r - 根据元素而不是名称选择列

r - 删除数据框中大于95%的数据

r - 优雅地更新多个 data.table 列

r - 根据分组从列中减去值

r - data.table:二分查找 VS 向量扫描的性能

r - 在 data.table 向量中对列表进行排序

R 顶部 Shiny 的 DT 复选框可勾选/取消勾选下面的所有复选框

r - pkgdown 插图代码块间距

r - 具有加权变量的 K 均值

r - data.table 和自动完成的兼容性