Existe-t-il un moyen d'utiliser tryCatch (ou similaire) dans R en tant que boucle, ou de manipuler l'expression dans l'argument d'avertissement?

Nov 27 2020

J'ai un modèle de régression ( lmou glmou lmer...) et je fais fitmodel <- lm(inputs)inputschange à l'intérieur d'une boucle (la formule et les données). Ensuite, si la fonction modèle ne produit aucun avertissement, je veux garder fitmodel, mais si j'obtiens un avertissement, je veux updatele modèle et je veux que l'avertissement ne soit pas imprimé, alors je le fais à l' fitmodel <- lm(inputs)intérieur tryCatch. Donc, si cela produit un avertissement, à l'intérieur warning = function(w){f(fitmodel)}, ce f(fitmodel)serait quelque chose comme

fitmodel <- update(fitmodel, something suitable to do on the model)

En fait, cette affectation serait à l'intérieur d'une if-elsestructure de telle sorte qu'en fonction de l'avertissement if(w$message satisfies something)j'adapterais l' suitable to do on the modelintérieur update.

Le problème est que je reçois Error in ... object 'fitmodel' not found. Si j'utilise withCallingHandlersavec invokeRestarts, il termine juste le calcul du modèle avec l'avertissement sans updatelui. Si j'ajoute à nouveau à l' fitmodel <- lm(inputs)intérieur something suitable to do on the model, je reçois l'avertissement imprimé; maintenant je pense que je pourrais essayer suppresswarnings(fitmodel <- lm(inputs)), mais pourtant je pense que ce n'est pas une solution élégante, puisque je dois ajouter 2 fois la ligne fitmodel <- lm(inputs), faisant 2 fois tout le calcul (intérieur expret intérieur warning).

En résumé, ce que je voudrais mais échoue, c'est:

tryCatch(expr = {fitmodel <- lm(inputs)},
         warning = function(w) {if (w$message satisfies something) {
                                    fitmodel <- update(fitmodel, something suitable to do on the model)
                                } else if (w$message satisfies something2){
                                    fitmodel <- update(fitmodel, something2 suitable to do on the model)

                                }
         }
)

Que puis-je faire?

La partie de la boucle de la question est parce que je pensais comme suit (est peut - être une autre question, mais pour le moment je laisse ici): il peut arriver que , après le updateje reçois un avertissement, alors je ferais quelque chose comme while(get a warning on update){update}; d'une certaine manière, cet updateintérieur warningdoit être compris aussi comme expr. Est-ce que quelque chose comme ça est possible?

Merci beaucoup!


Version générique de la question avec un exemple minimal:

Disons que j'ai un tryCatch(expr = {result <- operations}, warning = function(w){f(...)}et si je reçois un avertissement dans expr(produit en fait dans operations) je veux faire quelque chose avec result, alors je le ferais warning = function(w){f(result)}, mais alors je reçois Error in ... object 'result' not found.

Un exemple minimal:

y <- "a"
tryCatch(expr = {x <- as.numeric(y)},
    warning = function(w) {print(x)})
Error in ... object 'x' not found

J'ai essayé d'utiliser withCallingHandlersau lieu de tryCatchsans succès, et aussi en utilisant invokeRestartmais cela fait la partie expression, pas ce que je veux faire quand je reçois un avertissement.

Pourriez-vous m'aider?

Je vous remercie!

Réponses

2 KonradRudolph Nov 28 2020 at 14:24

Le problème, fondamentalement, est que le gestionnaire est appelé avant que l'affectation ne se produise. Et même si ce n'était pas le cas, le gestionnaire s'exécute dans une étendue différente de celle de l' tryCatchexpression, de sorte que le gestionnaire ne peut pas accéder aux noms de l'autre étendue.

Nous devons séparer la gestion de la transformation de valeur.

Pour les erreurs (mais pas les avertissements), la base R fournit la fonction tryqui s'enroule tryCatchpour obtenir cet effet. Cependant, l'utilisation tryest déconseillée, car son type de retour est incorrect . 1 Comme mentionné dans la réponse d'ekoam , «purrr» fournit des enveloppes fonctionnelles correctement typées (par exemple safely) pour obtenir un effet similaire.

Cependant, nous pouvons également créer le nôtre, ce qui pourrait être mieux adapté dans cette situation:

with_warning = function (expr) {
    self = environment()
    warning = NULL

    result = withCallingHandlers(expr, warning = function (w) {
        self$warning = w
        tryInvokeRestart('muffleWarning')
    })
    list(result = result, warning = warning)
}

Cela nous donne un wrapper qui fait la distinction entre la valeur du résultat et un avertissement. Nous pouvons maintenant l'utiliser pour mettre en œuvre votre exigence:

fitmodel = with(with_warning(lm(inputs)), {
    if (! is.null(warning)) {
        if (conditionMessage(warning) satisfies something) {
            update(result, something suitable to do on the model)
        } else {
            update(result, something2 suitable to do on the model)
        }
    } else {
        result
    }
})

1 Cela signifie que tryle type de retour de s ne fait pas la distinction entre une erreur et une valeur non-erreur de type try-error. Il s'agit d'une situation réelle qui peut se produire, par exemple, lors de l'imbrication de plusieurs tryappels.

2 ekoam Nov 27 2020 at 11:37

Il semble que vous recherchiez un wrapper fonctionnel qui capture à la fois la valeur renvoyée et les effets secondaires d'un appel de fonction. Je pense que purrr::quietlyc'est un candidat parfait pour ce genre de tâche. Considérez quelque chose comme ça

quietly <- purrr::quietly

foo <- function(x) {
  if (x < 3)
    warning(x, " is less than 3")
  if (x < 4)
    warning(x, " is less than 4")
  x
}

update_foo <- function(x, y) {
  x <- x + y
  foo(x)
}

keep_doing <- function(inputs) {
  out <- quietly(foo)(inputs)
  repeat {
    if (length(out$warnings) < 1L) return(out$result)
    
    cat(paste0(out$warnings, collapse = ", "), "\n") # This is for you to see the process. You can delete this line. if (grepl("less than 3", out$warnings[[1L]])) {
      out <- quietly(update_foo)(out$result, 1.5) } else if (grepl("less than 4", out$warnings[[1L]])) {
      out <- quietly(update_foo)(out$result, 1)
    }
  }
}

Production

> keep_doing(1)
1 is less than 3, 1 is less than 4 
2.5 is less than 3, 2.5 is less than 4 
[1] 4

> keep_doing(3)
3 is less than 4 
[1] 4
1 RuiBarradas Nov 27 2020 at 10:38

Cherchez-vous quelque chose comme ce qui suit? S'il est exécuté avec y <- "123", le "OK"message sera imprimé.

y <- "a"
#y <- "123"
x <- tryCatch(as.numeric(y),
              warning = function(w) w
)
if(inherits(x, "warning")){
  message(x$message)
} else{
  message(paste("OK:", x))
}

Il est plus facile de tester plusieurs valeurs d'argument avec le code ci-dessus réécrit en tant que fonction.

testWarning <- function(x){
  out <- tryCatch(as.numeric(x),
                  warning = function(w) w
  )
  if(inherits(out, "warning")){
    message(out$message)
  } else{
    message(paste("OK:", out))
  }
  invisible(out)
}

testWarning("a")
#NAs introduced by coercion
testWarning("123")
#OK: 123
JoaoPedroMacalos Nov 27 2020 at 10:04

Peut-être pourriez-vous attribuer à xnouveau la condition de manipulation?

tryCatch(
  warning = function(cnd) {
    x <- suppressWarnings(as.numeric(y))
    print(x)},
  expr = {x <- as.numeric(y)}
)
#> [1] NA

Peut-être pas la réponse la plus élégante, mais résout votre exemple de jouet.

user2554330 Nov 27 2020 at 10:05

Ne mettez pas le devoir dans l' tryCatchappel, mettez-le à l'extérieur. Par exemple,

y <- "a"
x <- tryCatch(expr = {as.numeric(y)},
    warning = function(w) {y})

Cela affecte yà x, mais vous pouvez mettre quelque chose dans le corps d'avertissement, et le résultat sera affecté à x.

Votre exemple "ce que je voudrais" est plus compliqué, car vous souhaitez accéder à la exprvaleur, mais elle n'a été attribuée nulle part au moment où l'avertissement est généré. Je pense que vous devrez le recalculer:

fitmodel <- tryCatch(expr = {lm(inputs)},
         warning = function(w) {if (w$message satisfies something) { update(lm(inputs), something suitable to do on the model) } else if (w$message satisfies something2){
                                    update(lm(inputs), something2 suitable to do on the model)

                                }
         }
)

Modifié pour ajouter:

Pour permettre à l'évaluation de se terminer avant de traiter l'avertissement, vous ne pouvez pas utiliser tryCatch. Le evaluatepackage a une fonction (également appelée evaluate) qui peut le faire. Par exemple,

y <- "a"
res <- evaluate::evaluate(quote(x <- as.numeric(y)))
for (i in seq_along(res)) {
    if (inherits(res[[i]], "warning") && 
        conditionMessage(res[[i]]) == gettext("NAs introduced by coercion",
                                              domain = "R"))
        x <- y
}

Quelques notes: la resliste contiendra beaucoup de choses différentes, y compris des messages, des avertissements, des erreurs, etc. Mon code ne regarde que les avertissements. J'avais l'habitude conditionMessaged'extraire le message d'avertissement, mais il sera traduit dans la langue locale, vous devriez donc l'utiliser gettextpour traduire la version anglaise du message à des fins de comparaison.