編寫高效的 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 中編寫高效的程式碼。我們討論了基準測試、不同的向量化技術和並行程式設計。我希望本教程肯定幫助您擴充套件了在資料科學領域的知識。