编写高效的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()加载文件需要大量的时间。高效的处理方法是先读取并保存.rds格式的.csv文件,然后再读取二进制文件。R为我们提供了saveRDS()函数以.rds格式的.csv文件。
示例
# 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中编写高效的代码。我们讨论了基准测试、不同的矢量技术和并行编程。我希望这个教程肯定能帮助你扩展你在数据科学领域的知识。