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

更新于:2023年1月17日

232 次浏览

启动您的职业生涯

完成课程获得认证

开始
广告