迷你課程:R語言-7~錯誤的處理

Quick look

在 R 中,處理異常(Exception)錯誤(Error)是開發過程中的關鍵,能有效避免因不預期的錯誤導致程式崩潰。本篇文章將介紹如何使用 trytryCatchcondition handling 來應對錯誤情境,確保程式能夠順利執行。


什麼是錯誤與例外?

當執行函數時,可能會遇到錯誤。例如,嘗試對非 square 矩陣求逆時,會拋出錯誤:

inverse <- function(M) { solve(M) }

mat <- matrix(1:10, 5, 2)  # 非方陣
inverse(mat)
# Error: 'a' (5 × 2) must be square

mat <- diag(x = 1:5)  # 可逆方陣
inverse(mat)  # 正常運行

mat <- diag(x = rep(0, 5))  # 奇異矩陣
inverse(mat)
# Error: system is computationally singular: reciprocal condition number = 0

當矩陣不是方陣或是奇異矩陣(行列式為 0),函數 solve() 會拋出錯誤,導致程式終止。因此,我們需要適當的錯誤處理機制。


使用 try 忽略錯誤

try() 允許程式在錯誤發生時繼續執行,而不會中斷:

mat <- diag(x = rep(0, 5))
res <- try(invmat <- inverse(mat))  # 錯誤不會終止程式

mat <- diag(x = rep(2, 5))
res <- try(invmat <- inverse(mat))  # 正常運行

這樣,即使 inverse(mat) 出錯,程式也不會崩潰。


使用 tryCatch 進行錯誤處理

tryCatch() 提供更細緻的錯誤處理方式,允許我們針對特定錯誤自訂處理方式:

library("rlang")

mat <- matrix(1:10, 5, 2)
res <- catch_cnd(inverse(mat))  # 捕捉錯誤訊息

舉例來說,我們可以寫一個函數來計算一組矩陣的逆,並對錯誤做適當處理:

inverse_list <- function(matrix_list) {
    inverse_list <- list()
    for (j in 1:length(matrix_list)) {
        print(j)
        inverse_list[[j]] <- tryCatch(
            expr = { solve(matrix_list[[j]]) },
            error = function(cnd) { print(cnd) }  # 當錯誤發生時,輸出錯誤訊息
        )
    }
    return(inverse_list)
}

matrix_list <- list(diag(x = 1:5), diag(x = rep(0, 5)), matrix(1:10, 5, 2))
ilist <- inverse_list(matrix_list)  # 執行函數
ilist

當發生錯誤時,程式不會崩潰,而是輸出錯誤訊息並繼續執行後續的矩陣求逆。


讓 tryCatch() 返回更具意義的結果

當錯誤發生時,除了輸出錯誤訊息,還可以讓函數返回更具意義的值,例如 NA:

inverse_list <- function(matrix_list) {
    inverse_list <- list()
    for (j in 1:length(matrix_list)) {
        print(j)
        inverse_list[[j]] <- tryCatch(
            expr = { solve(matrix_list[[j]]) },
            error = function(cnd) {
                print(cnd)
                return(NA)  # 發生錯誤時,返回 NA
            }
        )
    }
    return(inverse_list)
}

ilist <- inverse_list(matrix_list)
ilist  # 錯誤矩陣返回 NA

這樣當矩陣無法求逆時,我們可以用 NA 表示,而不是直接崩潰。


根據錯誤訊息進一步處理

我們可以檢查錯誤訊息,根據不同錯誤類型做不同處理:

cnd1 <- catch_cnd(solve(matrix_list[[2]]))
str(cnd1)  # 檢視錯誤訊息
cnd1$message
cnd1$call

cnd2 <- catch_cnd(solve(matrix_list[[3]]))
str(cnd2)
cnd2$message
cnd2$call

利用 grepl() 檢查錯誤類型:

grepl("is exactly singular", cnd1$message)  # TRUE
grepl("is exactly singular", cnd2$message)  # FALSE
grepl("must be square", cnd1$message)       # FALSE
grepl("must be square", cnd2$message)       # TRUE

這些條件幫助我們決定如何處理錯誤。


進一步改進錯誤處理邏輯

根據錯誤訊息,提供更具體的處理方式:

inverse_list <- function(matrix_list) {
    inverse_list <- list()
    for (j in 1:length(matrix_list)) {
        print(j)
        inverse_list[[j]] <- tryCatch(
            expr = { solve(matrix_list[[j]]) },
            error = function(cnd) {
                if (grepl("is exactly singular", cnd$message)) {
                    return(array(NA, dim(matrix_list[[j]])))  # 奇異矩陣回傳 NA 矩陣
                } else if (grepl("must be square", cnd$message)) {
                    stop(cnd$message)  # 非方陣直接終止
                } else {
                    stop(cnd$message)  # 其他錯誤同樣終止
                }
            }
        )
    }
    return(inverse_list)
}

ilist <- inverse_list(matrix_list)

這樣:

  • 奇異矩陣 返回全 NA 矩陣。
  • 非方陣 直接終止程式。

提前檢查矩陣是否符合條件

為了提高效率,我們可以事先檢查矩陣是否為 square,避免不必要的運算:

inverse_list <- function(matrix_list) {
    inverse_list <- list()

    for (j in 1:length(matrix_list)) {
        if (nrow(matrix_list[[j]]) != ncol(matrix_list[[j]])) {
            stop(paste("Matrix", j, "is not square."))
        }
    }

    for (j in 1:length(matrix_list)) {
        print(j)
        inverse_list[[j]] <- tryCatch(
            expr = { solve(matrix_list[[j]]) },
            error = function(cnd) {
                if (grepl("is exactly singular", cnd$message)) {
                    warning(paste("Matrix", j, "is singular"))
                    return(array(NA, dim(matrix_list[[j]])))
                } else {
                    stop(cnd$message)
                }
            }
        )
    }
    return(inverse_list)
}

這樣可以事先篩選不符合條件的矩陣,提高效率。

課程小結

在 R 語言的開發過程中,錯誤處理至關重要,尤其是在需要確保程式能夠持續執行而不崩潰的情境下。本篇文章介紹了 try()、tryCatch() 以及 condition handling 的使用方式,幫助我們有效管理錯誤並根據不同情境做適當處理。

我們首先透過 try() 讓程式在遇到錯誤時不會中斷,但 try() 只能簡單忽略錯誤,無法提供詳細錯誤處理機制。因此,tryCatch() 提供了更進階的解法,允許我們針對特定錯誤定義不同的回應方式,例如記錄錯誤訊息、返回特定值(如 NA)、甚至根據錯誤類型執行不同處理邏輯。

此外,我們進一步結合錯誤訊息解析(catch_cnd()),透過 grepl() 檢查錯誤內容,實現更細緻的錯誤分類與應對,例如:

奇異矩陣(Singular Matrix) → 返回 全 NA 矩陣 以確保數據格式一致。 非方陣(Non-Square Matrix) → 直接終止程式,避免不必要的運算。

Yang-Hong Dai 戴揚紘
Yang-Hong Dai 戴揚紘
主治醫師

我的研究興趣為結合數據分析來探討各種癌症生物學。

comments powered by Disqus