作者:黄天元,复旦大学博士在读,热爱数据科学与开源工具(R),致力于利用数据科学迅速积累行业经验优势和科学知识发现,涉猎内容包括但不限于信息计量、机器学习、数据可视化、应用统计建模、知识图谱等,著有《R语言数据高效处理指南》。知乎专栏:R语言数据挖掘。邮箱:huang.tian-yuan@qq.com.欢迎合作交流。
对于大部分用户而言,其实接触的数量都不算大,因此不一定用得到fst包。但是tidyfst包是一个面向大数据的工具,它的宗旨是把单台计算机的速度发挥到极限(暂时没有向分布式进发的打算),同时保持最佳的代码可读性以便于交流。因为R语言需要在内存中进行运算,所以其数据的容纳量受限于随机存取器(RAM)的内存大小。但是,其实我们在做实际分析中,每次计算要用到的数据其实总是要少于所有存在RAM中的总数据,因此我们可以用什么就取什么,用完马上就释放内存。

在对文件读写性能的追求探索中,我发现了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 50017393秒内搞定,而且,我们并没有把数据读进来,内存里面并没有被占满。需要的,可以把这个变量存起来,然后后面使用。如果我们只需要计算其中的目标行,可以用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原文作者:;如有侵权,请联系管理员删除。