web-dev-qa-db-fra.com

Comment ignorer une erreur dans une boucle

Je veux ignorer une erreur (s'il y en a) dans une boucle et continuer l'itération suivante. Je veux calculer 100 matrices inverses d'une matrice 2 par 2 avec des éléments échantillonnés au hasard à partir de {0, 1, 2}. Il est possible d'avoir une matrice singulière (par exemple,

1 0
2 0

Voici mon code

set.seed(1)
count <- 1
inverses <- vector(mode = "list", 100)
repeat {
    x <- matrix(sample(0:2, 4, replace = T), 2, 2)
    inverses[[count]] <- solve(x)
    count <- count + 1
    if (count > 100) break
}

À la troisième itération, la matrice est singulière et le code cesse de fonctionner avec un message d'erreur. En pratique, je voudrais contourner cette erreur et passer à la boucle suivante. Je sais que je dois utiliser une fonction try ou tryCatch mais je ne sais pas comment les utiliser. Des questions similaires ont été posées ici, mais elles sont toutes très compliquées et les réponses sont bien au-delà de ma compréhension. Si quelqu'un peut me donner un code complet spécifiquement pour cette question, je l'apprécie vraiment.

25
Patrick Li

Cela mettrait NULLs dans inverses pour les matrices singulières:

inverses[[count]] <- tryCatch(solve(x), error=function(e) NULL)

Si la première expression d'un appel à tryCatch déclenche une erreur, elle s'exécute et renvoie la valeur de la fonction fournie à son argument error. La fonction fournie à l'argument error doit prendre l'erreur elle-même comme argument (ici je l'appelle e), mais vous n'avez rien à faire avec.

Vous pouvez ensuite supprimer les entrées NULL avec inverses[! is.null(inverses)].

Vous pouvez également utiliser le niveau inférieur try. Le choix est vraiment une question de goût.

count <- 0
repeat {
    if (count == 100) break
    count <- count + 1
    x <- matrix(sample(0:2, 4, replace = T), 2, 2)
    x.inv <- try(solve(x), silent=TRUE)
    if ('try-error' %in% class(x.inv)) next
    else inverses[[count]] <- x.inv
}

Si votre expression génère une erreur, try renvoie un objet avec la classe try-error. Il imprimera le message à l'écran si silent=FALSE. Dans ce cas, si x.inv A la classe try-error, Nous appelons next pour arrêter l'exécution de l'itération en cours et passer à la suivante, sinon nous ajoutons x.inv à inverses.

Éditer:

Vous pourriez éviter d'utiliser la boucle repeat avec replicate et lapply.

matrices <- replicate(100, matrix(sample(0:2, 4, replace=T), 2, 2), simplify=FALSE)
inverses <- lapply(matrices, function(mat) if (det(mat) != 0) solve(mat))

Il est intéressant de noter que le deuxième argument de replicate est traité comme un expression, ce qui signifie qu'il est exécuté à nouveau pour chaque réplication. Cela signifie que vous pouvez utiliser replicate pour créer un list d'un nombre quelconque d'objets aléatoires générés à partir de la même expression.

29
Matthew Plourde

Au lieu d'utiliser tryCatch, vous pouvez simplement calculer le déterminant de la matrice avec la fonction det. Une matrice est singulière si et seulement si le déterminant est nul.

Par conséquent, vous pouvez tester si le déterminant est différent de zéro et calculer l'inverse uniquement si le test est positif:

set.seed(1)
count <- 1
inverses <- vector(mode = "list", 100)
repeat {
  x <- matrix(sample(0:2, 4, replace = T), 2, 2)
  # if (det(x)) inverses[[count]] <- solve(x)
  # a more robust replacement for the above line (see comment):
  if (is.finite(determinant(x)$modulus)) inverses[[count]] <- solve(x)
  count <- count + 1
  if (count > 100) break
}

Mise à jour:

Il est cependant possible d'éviter de générer des matrices singulières. Le déterminant d'une matrice 2 par 2 mat est défini comme mat[1] * mat[4] - mat[3] * mat[2]. Vous pouvez utiliser ces connaissances pour échantillonner des nombres aléatoires. N'échantillonnez simplement pas les nombres qui produiront une matrice singulière. Cela, bien sûr, dépend des nombres échantillonnés auparavant.

set.seed(1)
count <- 1
inverses <- vector(mode = "list", 100)

set <- 0:2 # the set of numbers to sample from

repeat {

  # sample the first value
  x <- sample(set, 1)
  # if the first value is zero, the second and third one are not allowed to be zero.
  new_set <- ifelse(x == 0, setdiff(set, 0), set)
  # sample the second and third value
  x <- c(x, sample(new_set, 2, replace = T))
  # calculate which 4th number would result in a singular matrix
  not_allowed <- abs(-x[3] * x[2] / x[1])
  # remove this number from the set
  new_set <- setdiff(0:2, not_allowed)
  # sample the fourth value and build the matrix
  x <- matrix(c(x, sample(new_set, 1)), 2, 2)

  inverses[[count]] <- solve(x)
  count <- count + 1
  if (count > 100) break
}

Cette procédure est une garantie que toutes les matrices générées auront un inverse.

9
Sven Hohenstein

try n'est qu'un moyen de dire à R: "Si vous commettez une erreur entre les parenthèses suivantes, sautez-la et continuez."

Donc, si vous craignez que x <- matrix(sample(0:2, 4, replace = T), 2, 2) ne vous donne une erreur, alors tout ce que vous avez à faire est de:

try(x <- matrix(sample(0:2, 4, replace = T), 2, 2))

Cependant, gardez à l'esprit que x ne sera pas défini si vous le faites et qu'il finit par ne pas être en mesure de calculer la réponse. Cela pourrait provoquer un problème lorsque vous arrivez à solve(x) - vous pouvez donc soit définir x avant try ou simplement "essayer" le tout:

try(
      {
      x <- matrix(sample(0:2, 4, replace = T), 2, 2)
      inverses[[count]] <- solve(x)
      }
    )
8
Señor O

La documentation pour essayer explique assez bien votre problème. Je vous suggère de le parcourir complètement.

Edit: L'exemple de documentation semblait assez simple et très similaire à la question de l'op. Merci pour la suggestion. Voici la réponse en suivant l'exemple de la page de documentation:

# `idx` is used as a dummy variable here just to illustrate that
# all 100 entries are indeed calculated. You can remove it.
set.seed(1)
mat_inv <- function(idx) {
    print(idx)
    x <- matrix(sample(0:2, 4, replace = T), nrow = 2)
    solve(x)
}
inverses <- lapply(1:100, function(idx) try(mat_inv(idx), TRUE))
5
Arun