编写高效的 R 代码
编写高效的代码非常重要,因为它可以加快开发时间,并使我们的程序易于理解、调试和维护。我们将讨论各种技术,例如基准测试、矢量化和并行编程,以加快我们的 R 代码速度。如果你渴望成为一名数据科学家,就必须学习这些技术。那么,让我们开始吧!
基准测试
最简单的优化方法之一是使用最新的 R 版本。新版本不会修改我们现有的代码,但它总是包含功能强大的库函数,这些函数可以提高执行时间。
以下 R 命令显示 R 的版本信息列表:
print(version)
输出
_ platform x86_64-pc-linux-gnu arch x86_64 os linux-gnu system x86_64, linux-gnu status Patched major 4 minor 2.2 year 2022 month 11 day 10 svn rev 83330 language R version.string R version 4.2.2 Patched (2022-11-10 r83330) nickname Innocent and Trusting
将 CSV 文件读取为 RDS 文件
使用 read.csv() 加载文件需要很长时间。高效的方法是先将 .csv 文件读取并保存为 .rds 格式,然后再读取二进制文件。R 提供 saveRDS() 函数将 .csv 文件保存为 .rds 格式。
示例
考虑以下程序,该程序对以两种不同格式(两种格式的文件相同)存在的文件的读取时间差异进行基准测试:
# Display the time taken to read file using read.csv() print(system.time(read.csv( "https://people.sc.fsu.edu/~jburkardt/data/csv/snakes_count_10000.csv"))) # Save the file in .rds format saveRDS("https://people.sc.fsu.edu/~jburkardt/data/csv/snakes_count_10000.csv", "myFile.rds" ) # Display the time taken to read in binary format print(system.time(readRDS("myFile.rds")))
输出
user system elapsed 0.017 0.002 0.603 user system elapsed 0 0 0
注意两种方法的执行时间差异。以 .RDS 格式读取相同文件所需的时间几乎可以忽略不计。因此,读取 RDS 文件比读取 CSV 文件更有效率。
使用 “<-” 和 “=” 运算符进行赋值
R 提供了几种将变量和文件赋值给对象的方法。两种运算符被广泛用于此目的:“<-” 和 “=”。有趣的是,当我们在函数内部使用 “<-” 运算符时,它会创建新对象或覆盖现有对象。由于我们要存储结果,因此在 system.time() 函数内部使用 “<-” 运算符非常有用。
经过时间微基准测试函数
system.time() 函数可以可靠地计算某些操作所需的时间,但它有一个限制,即无法同时比较多个操作。
R 提供了一个微基准测试库,该库提供了一个 microbenchmark() 函数,我们可以使用该函数来比较两个函数或操作所需的时间。
示例
考虑以下程序,该程序使用 microbenchmark() 函数来比较以两种不同格式(CSV 和 RDS)存在的相同文件:
library(microbenchmark) # Save the file in .rds format saveRDS("https://people.sc.fsu.edu/~jburkardt/data/csv/snakes_count_10000.csv", "myFile.rds" ) # Compare using microbenchmark() function difference <- microbenchmark(read.csv( "https://people.sc.fsu.edu/~jburkardt/data/csv/snakes_count_10000.csv"), readRDS("myFile.rds"), times = 2) # Display the time difference print(difference)
输出
min lq mean median uq max neval 405062.028 405062.028 409947.146 409947.146 414832.264 414832.264 2 41.151 41.151 102.355 102.355 163.559 163.559 2
注意两种方法的执行时间差异。
高效的矢量化
随着代码的执行,矢量大小的增加在编程中是不可取的,应尽可能避免。这是因为它会消耗大量时间并使程序效率低下。
示例
例如,以下源代码增加了矢量的大小:
# expand() function expand <- function(n) { myVector <- NULL for(currentNumber in 1:n) myVector <- c(myVector, currentNumber) myVector } # Using system.time() function system.time(res_grow <- expand(1000))
输出
user system elapsed 0.003 0.000 0.003
正如您在输出中看到的,expand() 函数正在消耗大量时间。
示例
我们可以通过预分配矢量来优化上述代码。例如,考虑以下程序:
# expand() function expand <- function(n) { myVector <- numeric(n) for(currentNumber in 1:n) myVector[currentNumber] = currentNumber } # Using system.time() function system.time(res_grow <- expand(10000))
输出
user system elapsed 0.001 0.000 0.001
正如您在输出中看到的,执行时间已大大减少。
我们应该尽可能对代码进行矢量化。
示例
例如,考虑以下程序,该程序使用简单的循环方法添加矢量中的值:
# Initialize a vector with random values myVector1 <- rnorm(20) # Declare another vector myVector2 <- numeric(length(myVector1)) # Compute the sum for(index in 1:20) myVector2[index] <- myVector1[index] + myVector1[index] # Display print(myVector2)
输出
[1] 1.31044581 -1.98035551 0.14009657 -1.62789103 1.23248277 0.49893302 [7] -0.53349928 -0.02553238 -0.06886832 1.16296981 0.90072271 0.20713797 [13] -1.72293906 0.62083278 2.77900829 4.15732558 1.71227621 2.09531955 [19] -0.06520153 0.62591177
输出表示相应矢量值本身的总和。
示例
以下代码执行与上述相同的操作,但这次我们将使用矢量化方法,这将减少我们的代码大小并提高执行时间:
myVector1 <- rnorm(20) myVector2 <- numeric(length(myVector1)) # Add using vectorization myVector2 <- myVector1 + myVector1 # Display print(myVector2)
输出
[1] -1.0100098 3.2932186 -3.5650312 -3.2800819 0.1513545 -1.5786916 [7] 2.0485566 2.6009810 -0.8015987 -0.6965471 -1.4298714 1.1251865 [13] 1.2536663 2.6258258 1.1093443 -1.7895628 0.3472878 -1.4783578 [19] -0.7717328 -2.2734743
输出表示相应矢量值本身的总和,但这次我们使用了矢量化方法。
请注意,我们甚至可以将矢量化技术应用于 R 内置函数。
示例
例如,考虑以下程序,该程序计算矢量中各个值的对数:
myVector1 <- c(8, 10, 13, 16, 32, 64, 57, 88, 100, 110) myVector2 <- numeric(length(myVector1)) # Compute the sum for(index in 1:10) myVector2[index] <- log(myVector1[index]) # Display the vector print(myVector2)
输出
[1] 2.079442 2.302585 2.564949 2.772589 3.465736 4.158883 4.043051 4.477337 [9] 4.605170 4.700480
正如您在输出中看到的,已经显示了相应矢量值的对数。
示例
现在让我们尝试使用矢量化技术来实现相同的结果:
myVector1 <- c(8, 10, 13, 16, 32, 64, 57, 88, 100, 110) myVector2 <- numeric(length(myVector1)) myVector2 <- log(myVector1) # Display print(myVector2)
输出
[1] 2.079442 2.302585 2.564949 2.772589 3.465736 4.158883 4.043051 4.477337 [9] 4.605170 4.700480
正如您在输出中看到的,已经显示了相应矢量值的的对数,但这次我们使用了矢量化方法。
示例
与数据框相比,包含相同数据类型元素的矩阵具有更快的列访问速度。例如,考虑以下程序:
library(microbenchmark) # Create a matrix myMatrix <- matrix(c(1:12), nrow = 4, byrow = TRUE) # Display print(myMatrix) # Create rows data1 <- c(1, 4, 7, 10) data2 <- c(2, 5, 8, 11) data3 <- c(3, 6, 9, 12) # Create a dataframe myDataframe <- data.frame(data1, data2, data3) # Display the dataframe print(microbenchmark(myMatrix[,1], myDataframe[,1]))
输出
[,1] [,2] [,3] [1,] 1 2 3 [2,] 4 5 6 [3,] 7 8 9 [4,] 10 11 12 Unit: nanoseconds expr min lq mean median uq max neval myMatrix[, 1] 493 525.0 669.64 595.5 661 5038 100 myDataframe[, 1] 6880 7110.5 8003.56 7247.0 7437 53752 100
您可以发现矩阵和数据框的列访问方法的执行时间差异。
用于高效 R 代码的并行编程
R 提供了一个并行包,我们可以使用它来编写高效的 R 代码。并行化在大多数情况下都有利于在更短的时间内完成工作,并充分利用系统资源。R 中的并行包为我们提供了 parApply() 函数,该函数使用以下步骤并行运行程序:
使用 makeCluster() 函数创建一个集群。
编写一些语句。
最终,使用 stopCluster() 函数停止集群。
示例
以下源代码使用 R 中的 parApply() 函数计算所有列的平均值:
library(parallel) library(microbenchmark) # Create rows data1 <- c(1, 4, 7, 10) data2 <- c(2, 5, 8, 11) data3 <- c(3, 6, 9, 12) # Create a dataframe myDataframe <- data.frame(data1, data2, data3) # Create a cluster cluster <- makeCluster(2) # Apply parApply() function print(parApply(cluster, myDataframe, 2, mean)) # Stop the cluster stopCluster(cluster)
输出
data1 data2 data3 5.5 6.5 7.5
正如您在输出中看到的,已经使用并行编程计算了相应列的平均值,这更快。
结论
在本文中,我们简要讨论了如何在 R 中编写高效的代码。我们讨论了基准测试、不同的矢量化技术和并行编程。我希望本教程肯定帮助您扩展了在数据科学领域的知识。