【问题标题】:R data.table speed up SI / Metric ConversionR data.table 加速 SI / Metric 转换
【发布时间】:2014-03-30 11:41:30
【问题描述】:

所以情况就是这样。我有一个 8500 万行的表,有 18 列。其中三列的值采用公制前缀/SI 表示法(参见 Wikipedia 上的 Metric Prefix)。

这意味着我有这样的数字:

  • .1M 而不是 100000 或 1e+5,或
  • 1K 而不是 1000 或 1e+3

示例data.table是

          V1     V2   V3  V4  V5  V6 V7 V8 V9  V10 V11 V12 V13 V14 V15 V16 V17 V18
 1: 2014-03-25 12:15:12 58300 3010 44.0  4.5 0.0   0   0  0.8  50 0.8 10K 303 21K   0     a   56
 2: 2014-03-25 12:15:12 56328 3010 28.0 12.0 0.0   0   0  0.3  60 0.0  59  62 .1M   0     a   66
 3: 2014-03-25 12:15:12 21082 3010 10.0  1.7 0.0   0   0 14.0  72 0.3  4K 208  8K   1     a   80
 4: 2014-03-25 12:15:12 59423 3010 12.0  0.0 0.2   0   0 88.0   0 0.0  20  16  71   0     a   26
 5: 2014-03-25 12:15:12 59423 3010  9.6  1.4 0.0   0   0 60.0  29 0.2  2K 251  6K   0     a   56
 6: 2014-03-25 12:15:12 24193 3010  8.3  1.9 0.0   0   0  9.9  80 0.3  3K 264  8K   1     a   71
 7: 2014-03-25 12:15:12 21082 3010  7.1  1.7 0.4   0   0  6.3  83 0.3  3K 197  7K   0     a   71
 8: 2014-03-25 12:15:12 59423 3010  4.6  1.2 0.0   0   0 57.0  37 0.1 998  81  7K   0     a  118

我修改了 Hans-Jörg Bibiko 编写的函数,他用它来修改 ggplot2 比例。如果您有兴趣,请参阅网站here。我最终使用的功能是:

sitor <- function(x)
{
  conv <- paste("E", c(seq(-24 ,-3, by=3), -2, -1, 0, seq(3, 24, by=3)), sep="")
  names(conv) <- c("y","z","a","f","p","n","µ","m","c","d","","K","M","G","T","P","E","Z","Y")
  x <- as.character(x)
  num <- function(x) as.numeric(
      paste(
        strsplit(x,"[A-z|µ]")[[1]][3],
        ifelse(substr(paste(strsplit(x,"[0-9|\\.]")[[1]], sep="", collapse=""), 1, 1) == "",
               "",
               conv[substr(paste(strsplit(x,"[0-9|\\.]")[[1]], sep="", collapse=""), 1, 1)]
        ),
        sep=""
      )
    )
  return(lapply(x,num))
}

我将其应用于数据表以更新 3 列,例如

temp[ ,`:=`(V13=sitor(V13),V14=sitor(V14),V15=sitor(V15)) ]

我已经使用

将 data.table 键向量应用于临时表
setkeyv(temp,c("V1","V2","V3","V18"))

任何 61 分钟后,我仍然在这里等待结果...鉴于我的数据大小即将增长 4 到 5 倍,有关如何加快转换速度的一些提示将非常方便。

【问题讨论】:

  • 顶部输出:PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND4878 neurozen 20 0 18.7g 18g 11m R 100.1 62.8 63:38.95 rsession
  • 什么是运行 61 分钟? setkeyv(temp,c("V1","V2","V3","V18"))temp[ ,:=(V13=sitor(V13),V14=sitor(V14),V15=sitor(V15)) ]?为什么要对 temp 进行排序?
  • sitor 返回一个列表...您的列在 dt 类型列表中吗?
  • @Dave 使用 temp[ ,:=(V13=sitor(V13),V14=sitor(V14),V15=sitor(V15)) 将sitor 函数应用于temp 是耗时 61 分钟(实际上仍在进行中)。我不打算对 temp 进行排序,它只是在应用密钥之后。 setkeyv ontemp 大约需要 90 秒。
  • @Michele 好点。我正在应用的列的类sitorcharacter 类。您是否建议我不应该在函数的返回值中使用lapply?告诉我更多...

标签: r performance data.table metric


【解决方案1】:

你为什么不试试sitools库?

library(data.table)
dt<-data.table(var = sample(x=1:1e5, size=1e6, replace=T))
library(sitools)
> system.time(dt[, var2 := f2si(var)])
   user  system elapsed 
  10.08    0.09   10.89

编辑:这是一个基于 data.table 的函数,它从 sitools 包中反转 f2si

si2f<-function(x){
  if(is.numeric(x)) return(x)
  require(data.table)
  dt<-data.table(lab=c("y","z","a","f","p","n","µ","m","c","d","", "da", "h", "k","M","G","T","P","E","Z","Y"),
                 mul=c(1e-24, 1e-21, 1e-18, 1e-15, 1e-12, 1e-9, 1e-6, 1e-3, 1e-2, 1e-1, 1L, 10L, 1e2, 1e3, 1e6, 1e9, 1e12, 1e15, 1e18, 1e21, 1e24),
                 key="lab")
  res<-as.numeric(gsub("[^0-9|\\.]","", x))
  x<-gsub("[0-9]|\\s+|\\.","", x)
  .subset2(dt[.(x)], "mul")*res
}

> system.time(dt[, var3 := si2f(var2)])
   user  system elapsed 
  13.18    0.03   13.31 

> dt[, all.equal(var,var3)]
[1] TRUE

【讨论】:

  • 哇,太棒了。我要试一试!
  • 我无法为我的 R 版本安装 scitools 包:`'scitools' 不可用(对于 R 版本 3.0.3)`
  • @neurozen 这是sitools
  • @Michelle 非常感谢。我已经起床太久了。
  • f2sci 功能很棒,但我实际上想做相反的事情。正如我上面的示例数据,它有字符单位,如 K 和 M 表示千 (1e3),M 表示 (1e6),我希望它是一个数字(带指数或其他)
【解决方案2】:

这是一种方法,在我的计算机上大约需要 10 秒才能转换具有 10M 值的向量。您可以将其扩展到“K”、“M”和“G”以外的范围

> f_conv <- function(val){
+     # create matrix indexed by name for exponent
+     key <- c(Zero = ""
+          , K = "E3"
+          , M = "E6"
+          , G = "E9"
+          )
+     # extract where the original exponent is 
+     indx <- regexpr("[KMG]", val)
+     # extract the exponent
+     exp <- substring(val, indx)
+     # if there was none, the use "Zero"
+     exp[indx == -1L] <- "Zero"
+     # put fake length
+     indx[indx == -1L] <- 20L
+     # do the conversion
+     as.numeric(paste0(substring(val, 1L, indx - 1L)
+                  , key[exp]
+                  )
+              )
+ }
> 
> # test data
> n <- 10000000
> result <- paste0(sample(1:999, n, TRUE)
+             , sample(c("K", "M", "G", ""), n, TRUE)
+             )
> 
> system.time(x <- f_conv(result))
   user  system elapsed 
   8.48    0.13    8.63 
> cbind(result[1:50], x[1:50])
      [,1]   [,2]          
 [1,] "562K" "562000"      
 [2,] "946"  "946"         
 [3,] "313G" "313000000000"
 [4,] "538M" "538000000"   
 [5,] "697K" "697000"      
 [6,] "486G" "486000000000"
 [7,] "814G" "814000000000"
 [8,] "842"  "842"         
 [9,] "993M" "993000000"   
[10,] "440K" "440000"      
[11,] "435G" "435000000000"
[12,] "407M" "407000000"   
[13,] "919K" "919000"      
[14,] "840"  "840"         
[15,] "766G" "766000000000"
[16,] "977"  "977"         
[17,] "139"  "139"         
[18,] "195G" "195000000000"
[19,] "609M" "609000000"   
[20,] "69"   "69"          
[21,] "147M" "147000000"   
[22,] "104M" "104000000"   
[23,] "509K" "509000"      
[24,] "951M" "951000000"   
[25,] "278"  "278"         
[26,] "797G" "797000000000"
[27,] "106K" "106000"      
[28,] "667K" "667000"      
[29,] "521K" "521000"      
[30,] "9"    "9"           
[31,] "17K"  "17000"       
[32,] "673M" "673000000"   

【讨论】:

  • 我会试一试的。 @Michelle @Data Munger 仅供参考,我还想使用 grep 来选择仅包含公制单位的行 temp[grep("[KMG]$",V1), V1:=sitor(V1)]) .问题是您必须返回字符,然后再次在列中运行 as.numeric。
  • 我试了一下,效果很好。对 8370 万行数据表的 3 列运行耗时 237.6 秒。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-06
相关资源
最近更新 更多