当前位置: 首页 > 知识库问答 >
问题:

对数据执行行操作。向量值列的表

康言
2023-03-14

(我很抱歉我的例子过于简单,我将尝试解决这个问题,并以更方便的格式格式化我更相关的示例,以便直接复制到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知道它应该单独查看列中的行,而不是作为向量聚在一起,并对向量值格式执行匹配。拆分每行的列,而不是尝试匹配整列行。即使如此,一旦我找到了每行的索引,我必须执行另一个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]

(当然,我也会对年龄做同样的事情)

我正在处理相当大的数据集,因此我希望我的代码尽可能高效。有人对我可以加速或以其他方式优化我的代码的方法有建议吗?

(注意:我真的在寻找正确的方法,而不是使用正确的*Application或*ply或Map函数。如果*(ap)ply或Map真的是正确的方法,我会很感激知道哪种方法最有效或最适合我的情况,但如果有更好的方法来测试行内重复,我更喜欢关于它的建议而不是功能建议。不过,欢迎提出建议)。

事实证明,我的html" target="_blank">示例比它需要的要普遍得多。我只需要两个字段,它们总是格式字符串中的前两个字段,没有变化。第一个字段只是一个文字字符串。然而,第二个字段至少由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,铸造更好的做事方式?

因为我只需要前两个字段,所以最后我修剪了所有的值字符串,删除了那些超过两个逗号的字符串(即超过3个第二字段的数字),将逗号改为冒号,将每一行的格式字符串替换为(现在是3个)字段的名称,并按照@AnandaMahto的建议执行dcast(csplit(melt))。看起来效果不错。

共有2个答案

卢宜然
2023-03-14

我认为你可能会更好地使用一个高大整齐的格式:

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
潘弘扬
2023-03-14

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

我还建议你使用我的< code>cSplit函数。

以下是如何轻松地将数据集转换为长格式:

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

一旦数据处于“长”形式,就可以使用<code>dcast.data轻松地将其转换为“宽”形式。表,如下所示。(我还使用<code>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 

对于您更新的问题,您可以先融化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)。 例如,在这个数据框中, 如果我有兴趣在列上执行此操作,结果应该是 我有以下绝对任务: 但是我不知道如何使用中的实际值。 提前谢谢!

  • 我有一个猫鼬模式 我最初设置了名称和电话字段的集合。我需要将集合更新为消息数组中的新消息和新地址到地址对象中。该函数还必须处理任何单个操作,即在某些情况下我只更新到消息数组或更新到名称和地址。所以我如何在单个函数中执行所有操作。

  • 我正在尝试根据枚举值检索一个值。基本上,假设我有以下枚举: 通过执行auth.key.get()将返回“MyKey”,而auth.mail.get()将返回“MyMail”。我搜索了一下,但没有找到答案,我之前没有尝试过任何事情,因为我完全不知道如何开始。谢谢,祝你有个愉快的一天

  • 我想替换一行数据的每个值。帧,对于大小相等的逻辑矩阵,其值为TRUE,由向量的对应行的值确定。以下是一个例子: 所以结果应该是这样的: 有没有不使用循环的方法来实现这一点?谢谢

  • Redisson 支持对每个操作自动重试的策略并且在每次尝试期会尝试发送命令。 重试策略由设置项 retryAttempts (默认为 3) 和 retryInterval (默认为 1000 ms) 来控制。 每次尝试会在 retryInterval 时间间隔后执行。 Redisson 实例和 Redisson 对象都是完全线程安全的。 带有同步/异步方法的 Redisson 对象可通过 Red

  • 我试图弄清楚如何在使用actor系统时最好地处理数据库操作。事实上,数据库操作正在阻塞,而我们试图在Akka中不阻塞。 我在主文档中提到了一种处理方法,那就是在路由器后面创建一个参与者池,可能是在一个单独的executionContext上,它将处理数据库访问。