ITPub博客

首页 > > > tidyfst的fst族函数:突破内存限制的大数据处理方案——一个亿的小目标

tidyfst的fst族函数:突破内存限制的大数据处理方案——一个亿的小目标

作者:浪花小雨滴 时间:2020-03-16 23:22:12 0 删除 编辑

作者:黄天元,复旦大学博士在读,热爱数据科学与开源工具(R),致力于利用数据科学迅速积累行业经验优势和科学知识发现,涉猎内容包括但不限于信息计量、机器学习、数据可视化、应用统计建模、知识图谱等,著有《R语言数据高效处理指南》。知乎专栏:R语言数据挖掘。邮箱:huang.tian-yuan@qq.com.欢迎合作交流。


对于大部分用户而言,其实接触的数量都不算大,因此不一定用得到fst包。但是tidyfst包是一个面向大数据的工具,它的宗旨是把单台计算机的速度发挥到极限(暂时没有向分布式进发的打算),同时保持最佳的代码可读性以便于交流。因为R语言需要在内存中进行运算,所以其数据的容纳量受限于随机存取器(RAM)的内存大小。但是,其实我们在做实际分析中,每次计算要用到的数据其实总是要少于所有存在RAM中的总数据,因此我们可以用什么就取什么,用完马上就释放内存。

fst包的logo

在对文件读写性能的追求探索中,我发现了fst包(fstpackage/fst),如获至宝。虽然它的下载量已经非常庞大,我认为它还是最被低估的R包之一(包括tidyfst只有4个包依赖了它)。它的优势:1、快速读写数据框,最快,没有之一。什么save成Rdata,净土宗(tidyverse)推的feather,都无法与fst格式文件的读写速度相比。不过,data.table内置并行多线程读csv的时候,倒是有时候能够与fst媲美。这两个包,都是tidyfst的主打依赖包。2、文件压缩,保存数据框能够给文件进行压缩,这就节省了大数据转移的时间(从硬盘放到电脑或者上传服务器)。压缩的比率是非常感人的,有一个参数可以控制压缩比例,我一般设置到最大。我问过原作者,他跟我解释过,压缩比例一共是100个等级,不压缩的时候读写是最快的,但是使劲压缩,读写依然非常快!亲测确实如此,所以我每次都用最大等级的压缩,并包装了他的函数,把默认压缩率改为100(默认值为50)。

下面,让我稍微表演一下在tidyfst中如何玩转fst包,让你做到此前不可能想象的任务。先来一个亿:

library(tidyfst)

# 构造一个1亿行,4列的数据框
nr_of_rows <- 1e8

df <- data.table(
  Logical = sample(c(TRUE, FALSE, NA), prob = c(0.85, 0.1, 0.05), nr_of_rows, replace = TRUE),
  Integer = sample(1L:100L, nr_of_rows, replace = TRUE),
  Real = sample(sample(1:10000, 20) / 100, nr_of_rows, replace = TRUE),
  Factor = as.factor(sample(labels(UScitiesD), nr_of_rows, replace = TRUE))
)

现在,我们构造了一亿行4列的数据框。看看它有多大:

> object.size(df) %>% print(unit = "auto")

#> 1.9 Gb

差不多2G,不能让它长期占用我们的RAM。我们先把它导出到D盘,然后从内存中清除出去。

sys_time_print({
  export_fst(df,"D:/df.fst")
})

#> [1] "Finished in 28.8s elapsed (00:03:20 cpu)"

rm(df)

导出1个亿的数据框,花了30秒不到的时间(有胆量的同学,可以用write.csv读出来,看看要花多长时间,导出的文件会有多大。如果卡死,用fwrite再试,用save再试)。那么,让我们把这一个亿读回来,但是不是真读,用parse_fst可以对这个数据进行虚拟导入。

parse_fst("D:/df.fst") -> ft
ft
#> <fst file>
#> 1e+08 rows, 4 columns (df.fst)
#> 
#>             Logical   Integer     Real        Factor
#>           <logical> <integer> <double>      <factor>
#> 1              TRUE        77     3.72 Washington.DC
#> 2              TRUE        54    76.11         Miami
#> 3              TRUE        93    79.85       Atlanta
#> 4              TRUE        97    64.63       Seattle
#> 5              TRUE        78    64.63        Denver
#> --               --        --       --            --
#> 99999996       TRUE        79    79.85    LosAngeles
#> 99999997       TRUE        92     3.72         Miami
#> 99999998       TRUE        50    50.31    LosAngeles
#> 99999999      FALSE         1    63.53       Seattle
#> 100000000      TRUE        61    25.27  SanFrancisco

这个过程肯定就是秒读,然后我们可以看到数据的前后5行。一共1亿行4列,每一列都是不同的数据类型。现在比如我们要统计第一列(Logical),对它进行计数。那么,事实上我们只需要第一列,我们可以用select_fst把它选择出来,然后进行计数。

 sys_time_print({
   ft %>% 
     select_fst(Logical) %>% 
     count_dt(Logical) -> res
 })
#> [1] "Finished in 2.510s elapsed (4.810s cpu)"
 
 res
#>    Logical        n
#>     <lgcl>    <int>
#> 1:    TRUE 84991618
#> 2:   FALSE 10006643
#> 3:      NA  5001739

3秒内搞定,而且,我们并没有把数据读进来,内存里面并没有被占满。需要的,可以把这个变量存起来,然后后面使用。如果我们只需要计算其中的目标行,可以用slice_fst来选中,比如我们只要统计前1000行,按照Factor计算Integer列的均值:

 sys_time_print({
   ft %>% 
     slice_fst(1:1000) %>% 
     group_dt(
       by = Factor,
       summarise_dt(avg_int = mean(Integer)) 
     )-> res
 })
#> [1] "Finished in 0.060s elapsed (0.040s cpu)"
 
 res
#>            Factor  avg_int
#>            <fctr>    <num>
#>  1: Washington.DC 52.28571
#>  2:         Miami 50.73196
#>  3:       Atlanta 55.14444
#>  4:       Seattle 53.86458
#>  5:        Denver 50.75248
#>  6:       Houston 49.84211
#>  7:  SanFrancisco 44.53465
#>  8:       Chicago 46.62162
#>  9:    LosAngeles 49.64762
#> 10:       NewYork 52.78761

1秒内就能完成。我们注意到,fst族中的select和slice最后都会返回data.table,所以后续进行的计算要用以“_dt”为后缀的函数来对它们进行处理。这还不算完,tidyfst还能够支持快速的筛选,比如我们要选择Real大于等于50的条目,然后对Factor进行计数:

 sys_time_print({
   ft %>% 
     filter_fst(Real >= 50) %>% 
     count_dt(Factor)-> res
 })
#> [1] "Finished in 10.1s elapsed (14.9s cpu)"
 
 res
#>            Factor       n
#>            <fctr>   <int>
#>  1:         Miami 5004964
#>  2:       Atlanta 5004203
#>  3:       Chicago 5001750
#>  4:       Houston 5001656
#>  5:       NewYork 5001138
#>  6:        Denver 5000384
#>  7:  SanFrancisco 5000057
#>  8:       Seattle 4999924
#>  9: Washington.DC 4998391
#> 10:    LosAngeles 4996567

因为要进行条件判断,所以会稍微慢一点,10秒多,也可以接受,毕竟我们面对的是一亿行的数据框。

也就是说,在tidyfst中,你可以对远超自己内存的数据进行处理,只要保证每次读入RAM的数据量少于最大缓存即可。前有fst包压缩快速读写,后有data.table对数据进行高速处理,让你能够对缓存(RAM)承受范围内的海量数据随心所欲地进行操作,这就是tidyfst的设计。它的小目标就是,单机大数据处理之王,因为我个人本机计算机限制,这次就先玩一个亿。

现在,让我们删掉这一个亿的数据。

unlink("D:/df.fst")

最后看看系统环境:

sessionInfo()
#> R version 3.6.3 (2020-02-29)
#> Platform: x86_64-w64-mingw32/x64 (64-bit)
#> Running under: Windows 10 x64 (build 18362)
#> 
#> Matrix products: default
#> 
#> locale:
#> [1] LC_COLLATE=Chinese (Simplified)_China.936 
#> [2] LC_CTYPE=Chinese (Simplified)_China.936   
#> [3] LC_MONETARY=Chinese (Simplified)_China.936
#> [4] LC_NUMERIC=C                              
#> [5] LC_TIME=Chinese (Simplified)_China.936    
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> loaded via a namespace (and not attached):
#>  [1] compiler_3.6.3  magrittr_1.5    tools_3.6.3     htmltools_0.4.0
#>  [5] yaml_2.2.1      Rcpp_1.0.3      stringi_1.4.6   rmarkdown_2.1  
#>  [9] highr_0.8       knitr_1.28      stringr_1.4.0   xfun_0.12      
#> [13] digest_0.6.25   rlang_0.4.5     evaluate_0.14

原文作者:;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2018-05-16

  • 博文量
    4259
  • 访问量
    39921