使用R提升数据处理能力

本文概述

  • R中的高性能解决方案
  • 数据转换示例
  • 资源资源
  • 总结
R语言通常被认为是统计学家和数据科学家的语言。很久以前, 这基本上是正确的。但是, 多年来, R通过包提供的灵活性使R成为一种更加通用的语言。 R是1995年开源的, 从那时起R软件包的存储库一直在增长。尽管如此, 与Python之类的语言相比, R还是强烈基于数据。
谈到数据, 表格数据是最常用的数据类型之一, 因此值得特别注意。它是一种数据类型, 对应于数据库中已知的表结构, 其中每一列可以具有不同的类型, 并且该特定数据类型的处理性能是许多应用程序的关键因素。
使用R提升数据处理能力

文章图片
【使用R提升数据处理能力】R可用于表格数据的非常有效的数据处理
鸣叫
在本文中, 我们将介绍如何以有效的方式实现表格数据转换。许多已经使用R进行机器学习的人并不知道在R中可以更快地进行数据处理, 因此他们不需要使用其他工具。
R中的高性能解决方案 Base R在1997年引入了data.frame类, 该类基于之前的S-PLUS。与常用的按行存储数据的数据库不同, R data.frame将数据作为面向列的结构存储在内存中, 因此对于分析中常见的列操作, 它的缓存效率更高。另外, 即使R是一种功能编程语言, 它也不会在开发人员上强制执行。这两个机会已经通过data.table R包得到了很好的解决, 该包可在CRAN存储库中找到。在分组操作时, 它执行得非常快, 并且通过小心实现中间数据子集(例如仅实现特定任务所需的那些列)而特别有效地提高了内存效率。它还在添加或更新列时通过其参考语义避免了不必要的复制。该软件包的第一个版本已于2006年4月发布, 当时显着提高了data.frame的性能。最初的软件包描述是:
该软件包几乎没有。其存在的唯一原因是白皮书指定data.frame必须具有行名。这个包定义了一个新的类data.table, 它的工作方式与data.frame一样, 但是使用的内存减少了10倍, 并且创建(和复制)的速度提高了10倍。它还借此机会允许[]中的subset()和with()之类的表达式。大多数代码是从基本函数复制而来的, 其中删除了操作row.names的代码。
从那时起, data.frame和data.table的实现都得到了改进, 但是data.table仍然比base R快得令人难以置信。事实上, data.table不仅比base R快, 而且似乎比base R快。是目前最快的开源数据整理工具, 可与Python Pandas等工具, 列存储数据库或Spark等大数据应用程序竞争。其在分布式共享基础架构上的性能尚未经过基准测试, 但一次最多可以拥有20亿行的行列, 前景可观。出色的性能与功能齐头并进。另外, 随着最近努力使耗时??的零件并行化以提高性能, 似乎很明确地朝着提高性能极限的方向发展。
数据转换示例 由于R可以交互工作, 因此学习R变得容易一些, 因此我们可以逐步遵循示例, 并随时查看每个步骤的结果。在开始之前, 让我们从CRAN信息库安装data.table软件包。
install.packages("data.table")

有用的提示:我们只要打开带有任何问号的名称, 即?install.packages, 就可以打开任何功能的手册。
将数据加载到R中
有很多软件包可以从各种格式和数据库中提取数据, 其中通常包括本机驱动程序。我们将从CSV文件加载数据, 这是原始表格数据的最常见格式。在以下示例中使用的文件可以在这里找到。我们无需担心CSV的读取性能, 因为fread功能在此基础上进行了高度优化。
为了使用包中的任何函数, 我们需要通过库调用来加载它。
library(data.table) DT < - fread("flights14.csv") print(DT)

##year month day dep_delay arr_delay carrier origin dest air_time ##1: 2014111413AAJFKLAX359 ##2: 201411-313AAJFKLAX363 ##3: 20141129AAJFKLAX351 ##4: 201411-8-26AALGAPBI157 ##5: 20141121AAJFKLAX350 ##--- ## 253312: 201410311-30UALGAIAH201 ## 253313: 20141031-5-14UAEWRIAH189 ## 253314: 20141031-816MQLGARDU83 ## 253315: 20141031-415MQLGADTW75 ## 253316: 20141031-51MQLGASDF110 ##distance hour ##1:24759 ##2:247511 ##3:247519 ##4:10357 ##5:247513 ##--- ## 253312:141614 ## 253313:14008 ## 253314:43111 ## 253315:50211 ## 253316:6598

如果我们的数据没有很好地建模以进行进一步处理, 因为它们需要从长到宽或从宽到长(也称为透视和非透视)格式进行重塑, 我们可以看一下dcast和melt函数, 从reshape2包中知道。但是, data.table为data.table / data.frame类实现了更快且内存效率更高的方法。
用data.table语法查询
如果你熟悉data.frame 查询data.table与查询data.frame非常相似。在对i参数进行过滤时, 我们可以直接使用列名, 而无需使用$符号访问它们, 例如df [df $ col> 1, ]。当提供下一个参数j时, 我们提供了一个要在data.table范围内求值的表达式。要传递非表达式j参数, 请使用= FALSE。 data.frame方法中不存在的第三个参数定义了组, 从而使j中的表达式可以被组评估。
# data.frame DF[DF$col1 > 1L, c("col2", "col3")] # data.table DT[col1 > 1L, .(col2, col3), ...] # by group using: `by = col4`

如果你熟悉数据库 查询data.table在许多方面都与更多人可能熟悉的SQL查询相对应。下面的DT表示data.table对象, 并且对应于SQLs FROM子句。
DT[ i = where, j = select | update, by = group by] [ having, ... ] [ order by, ... ] [ ... ] ... [ ... ]

使用R提升数据处理能力

文章图片
排序行和重新排序列
数据排序是时间序列的重要转换, 也是导入数据提取和表示的基础。可以通过向i参数提供行顺序的整数矢量来实现排序, 方法与data.frame相同。查询顺序中的第一个参数(carrier, -dep_delay)将在carrier字段中以升序选择数据, 而在dep_delay度量中以降序选择数据。如上一节所述, 第二个参数j定义要返回的列(或表达式)及其顺序。
ans < - DT[order(carrier, -dep_delay), .(carrier, origin, dest, dep_delay)] head(ans)

##carrier origin dest dep_delay ## 1:AAEWRDFW1498 ## 2:AAJFKBOS1241 ## 3:AAEWRDFW1071 ## 4:AAEWRDFW1056 ## 5:AAEWRDFW1022 ## 6:AAEWRDFW989

为了通过引用对数据重新排序, 而不是按特定的顺序查询数据, 我们使用set *函数。
setorder(DT, carrier, -dep_delay) leading.cols < - c("carrier", "dep_delay") setcolorder(DT, c(leading.cols, setdiff(names(DT), leading.cols))) print(DT)

##carrier dep_delay year month day arr_delay origin dest air_time ##1:AA1498 20141041494EWRDFW200 ##2:AA1241 20144151223JFKBOS39 ##3:AA1071 20146131064EWRDFW175 ##4:AA1056 20149121115EWRDFW198 ##5:AA1022 20146161073EWRDFW178 ##--- ## 253312:WN-12 201439-21LGABNA115 ## 253313:WN-13 2014310-18EWRMDW112 ## 253314:WN-13 2014517-30LGAHOU202 ## 253315:WN-13 201461510LGAMKE101 ## 253316:WN-13 2014819-30LGACAK63 ##distance hour ##1:13727 ##2:18713 ##3:137210 ##4:13726 ##5:13727 ##--- ## 253312:76416 ## 253313:71120 ## 253314:142817 ## 253315:73820 ## 253316:39716

通常, 我们既不需要原始数据集, 也不需要排序/排序的数据集。默认情况下, 与其他函数式编程语言类似, R语言将返回已排序的数据作为新对象, 因此将需要比按引用排序的内存多一倍的内存。
子集查询
让我们为航班起点” JFK” 和从6到9的月份创建一个子集数据集。在第二个参数中, 我们将结果子集到列出的列中, 并添加一个计算出的变量sum_delay。
ans < - DT[origin == "JFK" & month %in% 6:9, .(origin, month, arr_delay, dep_delay, sum_delay = arr_delay + dep_delay)] head(ans)

##origin month arr_delay dep_delay sum_delay ## 1:JFK79259261851 ## 2:JFK87277721499 ## 3:JFK6466451917 ## 4:JFK7414450864 ## 5:JFK6411442853 ## 6:JFK6333343676

默认情况下, 在单列data.table上设置数据集时, 将自动为该列创建索引。这将对该列上的所有进一步过滤调用产生实时答案。
更新数据集
使用:=运算符通过引用添加新列, 它会将变量分配到适当的数据集中。这样可以避免在内存中复制数据集, 因此我们无需将结果分配给每个新变量。
DT[, sum_delay := arr_delay + dep_delay] head(DT)

##carrier dep_delay year month day arr_delay origin dest air_time ## 1:AA1498 20141041494EWRDFW200 ## 2:AA1241 20144151223JFKBOS39 ## 3:AA1071 20146131064EWRDFW175 ## 4:AA1056 20149121115EWRDFW198 ## 5:AA1022 20146161073EWRDFW178 ## 6:AA989 2014611991EWRDFW194 ##distance hour sum_delay ## 1:137272992 ## 2:187132464 ## 3:1372102135 ## 4:137262171 ## 5:137272095 ## 6:1372111980

要一次添加更多变量, 可以从数据集中查询时使用DT [, :=(sum_delay = arr_delay + dep_delay)]语法, 类似于。(sum_delay = arr_delay + dep_delay)。
仅通过与i参数组合, 就可以通过引用进行子分配, 仅更新特定的行。
DT[origin=="JFK", distance := NA] head(DT)

##carrier dep_delay year month day arr_delay origin dest air_time ## 1:AA1498 20141041494EWRDFW200 ## 2:AA1241 20144151223JFKBOS39 ## 3:AA1071 20146131064EWRDFW175 ## 4:AA1056 20149121115EWRDFW198 ## 5:AA1022 20146161073EWRDFW178 ## 6:AA989 2014611991EWRDFW194 ##distance hour sum_delay ## 1:137272992 ## 2:NA132464 ## 3:1372102135 ## 4:137262171 ## 5:137272095 ## 6:1372111980

汇总数据
为了汇总数据, 我们在方括号中提供第三个参数。然后, 在j中, 我们需要提供聚合函数调用, 以便可以实际聚合数据。 j参数中使用的.N符号对应于每个组中所有观测值的数量。如前所述, 聚合可以与行和选择列上的子集结合。
ans < - DT[, .(m_arr_delay = mean(arr_delay), m_dep_delay = mean(dep_delay), count = .N), .(carrier, month)] head(ans)

##carrier month m_arr_delay m_dep_delay count ## 1:AA105.5419597.5914972705 ## 2:AA41.9033243.9870082617 ## 3:AA68.69006711.4764752678 ## 4:AA9-1.2351603.3070782628 ## 5:AA84.0274748.9140542839 ## 6:AA79.15988611.6659532802

通常, 我们可能需要将行的值与其在组中的汇总进行比较。在SQL中, 我们通过以下方法将聚合应用于分区:AVG(arr_delay)OVER(按载体分类, 月份)。
ans < - DT[, .(arr_delay, carrierm_mean_arr = mean(arr_delay), dep_delay, carrierm_mean_dep = mean(dep_delay)), .(carrier, month)] head(ans)

##carrier month arr_delay carrierm_mean_arr dep_delay carrierm_mean_dep ## 1:AA1014945.54195914987.591497 ## 2:AA108405.5419598487.591497 ## 3:AA103175.5419593387.591497 ## 4:AA102925.5419593317.591497 ## 5:AA103225.5419593047.591497 ## 6:AA103065.5419592997.591497

如果我们不想使用这些汇总查询数据, 而是通过引用将其放入实际的表更新中, 则可以使用:=运算符来实现。这样可以避免数据集在内存中的复制, 因此我们无需将结果分配给新变量。
DT[, `:=`(carrierm_mean_arr = mean(arr_delay), carrierm_mean_dep = mean(dep_delay)), .(carrier, month)] head(DT)

##carrier dep_delay year month day arr_delay origin dest air_time ## 1:AA1498 20141041494EWRDFW200 ## 2:AA1241 20144151223JFKBOS39 ## 3:AA1071 20146131064EWRDFW175 ## 4:AA1056 20149121115EWRDFW198 ## 5:AA1022 20146161073EWRDFW178 ## 6:AA989 2014611991EWRDFW194 ##distance hour sum_delay carrierm_mean_arr carrierm_mean_dep ## 1:1372729925.5419597.591497 ## 2:NA1324641.9033243.987008 ## 3:13721021358.69006711.476475 ## 4:137262171-1.2351603.307078 ## 5:1372720958.69006711.476475 ## 6:13721119808.69006711.476475

联接数据集
数据集的基本R加入和合并被认为是子集操作的一种特殊类型。我们提供一个想要加入第一个方括号参数i的数据集。对于提供给i的数据集中的每一行, 我们匹配使用[的数据集中的行。如果我们只想保留匹配的行(内部联接), 那么我们传递一个额外的参数nomatch = 0L。我们使用on参数来指定要在其上连接两个数据集的列。
# create reference subset carrierdest < - DT[, .(count=.N), .(carrier, dest) # count by carrier and dest ][1:10# just 10 first groups ]# chaining `[...][...]` as subqueries print(carrierdest)

##carrier dest count ##1:AADFW5877 ##2:AABOS1173 ##3:AAORD4798 ##4:AASEA298 ##5:AAEGE85 ##6:AALAX3449 ##7:AAMIA6058 ##8:AASFO1312 ##9:AAAUS297 ## 10:AADCA172

# outer join ans < - carrierdest[DT, on = c("carrier", "dest")] print(ans)

##carrier dest count dep_delay year month day arr_delay origin ##1:AADFW58771498 20141041494EWR ##2:AABOS11731241 20144151223JFK ##3:AADFW58771071 20146131064EWR ##4:AADFW58771056 20149121115EWR ##5:AADFW58771022 20146161073EWR ##--- ## 253312:WNBNANA-12 201439-21LGA ## 253313:WNMDWNA-13 2014310-18EWR ## 253314:WNHOUNA-13 2014517-30LGA ## 253315:WNMKENA-13 201461510LGA ## 253316:WNCAKNA-13 2014819-30LGA ##air_time distance hour sum_delay carrierm_mean_arr ##1:2001372729925.541959 ##2:39NA1324641.903324 ##3:17513721021358.690067 ##4:198137262171-1.235160 ##5:1781372720958.690067 ##--- ## 253312:11576416-336.921642 ## 253313:11271120-316.921642 ## 253314:202142817-4322.875845 ## 253315:10173820-314.888889 ## 253316:6339716-437.219670 ##carrierm_mean_dep ##1:7.591497 ##2:3.987008 ##3:11.476475 ##4:3.307078 ##5:11.476475 ##--- ## 253312:11.295709 ## 253313:11.295709 ## 253314:30.546453 ## 253315:24.217560 ## 253316:17.038047

# inner join ans < - DT[carrierdest, # for each row in carrierdest nomatch = 0L, # return only matching rows from both tables on = c("carrier", "dest")]# joining on columns carrier and dest print(ans)

##carrier dep_delay year month day arr_delay origin dest air_time ##1:AA1498 20141041494EWRDFW200 ##2:AA1071 20146131064EWRDFW175 ##3:AA1056 20149121115EWRDFW198 ##4:AA1022 20146161073EWRDFW178 ##5:AA989 2014611991EWRDFW194 ##--- ## 23515:AA-8 20141011-13JFKDCA53 ## 23516:AA-9 2014521-12JFKDCA52 ## 23517:AA-9 201465-6JFKDCA53 ## 23518:AA-9 2014102-21JFKDCA51 ## 23519:AA-11 201452710JFKDCA55 ##distance hour sum_delay carrierm_mean_arr carrierm_mean_dep count ##1:1372729925.5419597.5914975877 ##2:13721021358.69006711.4764755877 ##3:137262171-1.2351603.3070785877 ##4:1372720958.69006711.4764755877 ##5:13721119808.69006711.4764755877 ##--- ## 23515:NA15-215.5419597.591497172 ## 23516:NA15-214.1501728.733665172 ## 23517:NA15-158.69006711.476475172 ## 23518:NA15-305.5419597.591497172 ## 23519:NA15-14.1501728.733665172

请注意, 由于与基本R子集的一致性, 默认情况下, 外部联接为RIGHT OUTER。如果要寻找LEFT OUTER, 则需要交换表, 如上例所示。确切的行为也可以在merge data.table方法中轻松控制, 使用与R基合并data.frame相同的API。
如果我们只想在数据集中查找列, 则可以在加入时使用j参数中的:=运算符有效地做到这一点。正如我们在” 更新数据集” 部分中所述, 通过引用进行子分配的方式相同, 我们现在刚刚从要加入的数据集中通过引用添加一列。这样可以避免在内存中复制数据, 因此我们不需要将结果分配给新变量。
DT[carrierdest, # data.table to join with lkp.count := count, # lookup `count` column from `carrierdest` on = c("carrier", "dest")]# join by columns head(DT)

##carrier dep_delay year month day arr_delay origin dest air_time ## 1:AA1498 20141041494EWRDFW200 ## 2:AA1241 20144151223JFKBOS39 ## 3:AA1071 20146131064EWRDFW175 ## 4:AA1056 20149121115EWRDFW198 ## 5:AA1022 20146161073EWRDFW178 ## 6:AA989 2014611991EWRDFW194 ##distance hour sum_delay carrierm_mean_arr carrierm_mean_dep lkp.count ## 1:1372729925.5419597.5914975877 ## 2:NA1324641.9033243.9870081173 ## 3:13721021358.69006711.4764755877 ## 4:137262171-1.2351603.3070785877 ## 5:1372720958.69006711.4764755877 ## 6:13721119808.69006711.4764755877

对于加入时的汇总, 请使用= .EACHI。它执行的联接不会实现中间联接结果, 并且会动态应用聚合, 从而提高存储效率。
滚动连接是一项罕见功能, 旨在处理有序数据。它非常适合处理时间数据以及一般的时间序列。它基本上将匹配条件下的匹配滚动到下一个匹配值。加入时通过提供roll参数来使用它。
快速重叠联接通过使用各种重叠运算符(包括在, 在, 开始, 结束中的任何一个), 根据周期及其重叠处理来连接数据集。
当前正在开发使用非等式条件连接数据集的非等式连接功能。
分析数据
在浏览数据集时, 有时我们可能希望收集有关该主题的技术信息, 以更好地理解数据的质量。
描述性统计
summary(DT)

##carrierdep_delayyearmonth ##Length:253316Min.:-112.00Min.:2014Min.: 1.000 ##Class :character1st Qu.:-5.001st Qu.:20141st Qu.: 3.000 ##Mode:characterMedian :-1.00Median :2014Median : 6.000 ##Mean:12.47Mean:2014Mean: 5.639 ##3rd Qu.:11.003rd Qu.:20143rd Qu.: 8.000 ##Max.:1498.00Max.:2014Max.:10.000 ## ##dayarr_delayorigindest ##Min.: 1.00Min.:-112.000Length:253316Length:253316 ##1st Qu.: 8.001st Qu.: -15.000Class :characterClass :character ##Median :16.00Median :-4.000Mode:characterMode:character ##Mean:15.89Mean:8.147 ##3rd Qu.:23.003rd Qu.:15.000 ##Max.:31.00Max.:1494.000 ## ##air_timedistancehoursum_delay ##Min.: 20.0Min.:80.0Min.: 0.00Min.:-224.00 ##1st Qu.: 86.01st Qu.: 529.01st Qu.: 9.001st Qu.: -19.00 ##Median :134.0Median : 762.0Median :13.00Median :-5.00 ##Mean:156.7Mean: 950.4Mean:13.06Mean:20.61 ##3rd Qu.:199.03rd Qu.:1096.03rd Qu.:17.003rd Qu.:23.00 ##Max.:706.0Max.:4963.0Max.:24.00Max.:2992.00 ##NA's:81483 ##carrierm_mean_arr carrierm_mean_deplkp.count ##Min.:-22.403Min.:-4.500Min.:85 ##1st Qu.:2.6761st Qu.: 7.8151st Qu.:3449 ##Median :6.404Median :11.354Median :5877 ##Mean:8.147Mean:12.465Mean:4654 ##3rd Qu.: 11.5543rd Qu.:17.5643rd Qu.:6058 ##Max.: 86.182Max.:52.864Max.:6058 ##NA's:229797

基数 我们可以使用uniqueN函数检查数据的唯一性, 并将其应用于每一列。以下查询中的对象.SD对应于Data.table的子集:
DT[, lapply(.SD, uniqueN)]

##carrier dep_delay year month day arr_delay origin dest air_time ## 1:14570110316163109509 ##distance hour sum_delay carrierm_mean_arr carrierm_mean_dep lkp.count ## 1:15225102113413411

NA比率 为了计算每一列的未知值(R中为NA, SQL中为NULL)的比率, 我们提供了所需的函数以应用于每一列。
DT[, lapply(.SD, function(x) sum(is.na(x))/.N)]

##carrier dep_delay year month day arr_delay origin dest air_time ## 1:000000000 ##distance hour sum_delay carrierm_mean_arr carrierm_mean_dep lkp.count ## 1: 0.32166540000 0.9071555

汇出资料
data.table包还提供了将表格数据快速导出为CSV格式的功能。
tmp.csv < - tempfile(fileext=".csv") fwrite(DT, tmp.csv) # preview exported data cat(system(paste("head -3", tmp.csv), intern=TRUE), sep="\n")

## carrier, dep_delay, year, month, day, arr_delay, origin, dest, air_time, distance, hour, sum_delay, carrierm_mean_arr, carrierm_mean_dep, lkp.count ## AA, 1498, 2014, 10, 4, 1494, EWR, DFW, 200, 1372, 7, 2992, 5.54195933456561, 7.59149722735674, 5877 ## AA, 1241, 2014, 4, 15, 1223, JFK, BOS, 39, , 13, 2464, 1.90332441727168, 3.98700802445548, 1173

撰写本文时, fwrite函数尚未发布到CRAN存储库。要使用它, 我们需要安装data.table开发版本, 否则我们可以使用base R write.csv函数, 但不要指望它会很快。
资源资源 有大量可用资源。除了每个功能可用的手册之外, 还有软件包小插图, 它们是针对特定主题的教程。这些可以在” 入门” 页面上找到。此外, “ 演示文稿” 页面列出了来自全球data.table演示文稿的30多种材料(幻灯片, 视频等)。同样, 社区支持也在不断增长, 最近在Stack Overflow data.table标签上达到了第4000个问题, 仍然有很高的回答率(91.9%)。下图显示了随时间推移堆栈溢出的data.table标签问题的数量。
使用R提升数据处理能力

文章图片
SO每月针对data.table提问-仅标记data.table的问题, 不回答带有data.table的问题(已接受)
总结 本文提供了一些示例, 可以使用data.table包在R中进行有效的表格数据转换。可以通过寻找可重复的基准来检查性能的实际数字。我发表了一篇有关data.table解决方案的总结博客文章, 涉及关于R语言的前50个最受好评的StackOverflow问题, 称为” 使用data.table有效解决常见R问题” , 你可以在其中找到大量图形和可复制的代码。包data.table使用本机实现的快速基数排序进行分组操作, 并使用二进制搜索快速子集/联接。此基数排序已从3.3.0版合并到base R中。此外, 该算法最近已在H2O机器学习平台中实现, 并在H2O集群上并行化, 从而可以在10B x 10B行上进行有效的大联接。
相关:使用Supergroup.js进行最终的内存中数据收集操作

    推荐阅读