编写高效的R代码

编写高效的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中编写高效的代码。我们讨论了基准测试、不同的矢量技术和并行编程。我希望这个教程肯定能帮助你扩展你在数据科学领域的知识。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程