web-dev-qa-db-fra.com

Exécuter l'opération dplyr uniquement si la colonne existe

S'inspirant de la discussion sur évaluation dplyr conditionnelle , j'aimerais exécuter de manière conditionnelle une étape du pipeline selon que la colonne de référence existe ou non dans la trame de données transmise.

Exemple

Les résultats générés par1)et2)doivent être identiques.

Colonne existante

# 1)
mtcars %>% 
  filter(am == 1) %>%
  filter(cyl == 4)

# 2)
mtcars %>%
  filter(am == 1) %>%
  {
    if("cyl" %in% names(.)) filter(cyl == 4) else .
  }

Colonne non disponible

# 1)
mtcars %>% 
  filter(am == 1)

# 2)    
mtcars %>%
  filter(am == 1) %>%
  {
    if("absent_column" %in% names(.)) filter(absent_column == 4) else .
  }

Problème

Pour la colonne disponible, l'objet transmis ne correspond pas au bloc de données initial. Le code d'origine renvoie le message d'erreur:

Erreur dans filter(cyl == 4): objet 'cyl' non trouvé

J'ai essayé la syntaxe alternative (sans chance):

>> mtcars %>%
...   filter(am == 1) %>%
...   {
...     if("cyl" %in% names(.)) filter(.$cyl == 4) else .
...   }
 Show Traceback

 Rerun with Debug
 Error in UseMethod("filter_") : 
  no applicable method for 'filter_' applied to an object of class "logical" 

Suivre

Je voulais développer cette question qui tiendrait compte de l’évaluation à droite du==infiltercall. Par exemple, la syntaxe ci-dessous tente de filtrer sur la première valeur disponible. mtcars%>%

filter({
    if ("does_not_ex" %in% names(.))
      does_not_ex
    else
      NULL
  } == {
    if ("does_not_ex" %in% names(.))
      unique(.[['does_not_ex']])
    else
      NULL
  })

De manière attendue, l'appel se traduit par un message d'erreur:

Erreur dans filter_impl(.data, quo): le résultat doit avoir une longueur de 32, pas 0

Lorsqu'il est appliqué à une colonne existante:

mtcars %>%
  filter({
    if ("mpg" %in% names(.))
      mpg
    else
      NULL
  } == {
    if ("mpg" %in% names(.))
      unique(.[['mpg']])
    else
      NULL
  })

Cela fonctionne avec un message d'avertissement:

  mpg cyl disp  hp drat   wt  qsec vs am gear carb
1  21   6  160 110  3.9 2.62 16.46  0  1    4    4

Message d'avertissement: Dans {: la longueur d'un objet plus long n'est pas un multiple de La longueur d'un objet plus court

_ {Question de suivi} _

Existe-t-il un moyen judicieux de développer la syntaxe existante afin d'obtenir une évaluation conditionnelle du côté droit de l'appel filter, en restant idéalement dans le flux de travaux dplyr?

9
Konrad

En raison de la manière dont les portées fonctionnent ici, vous ne pouvez pas accéder à la structure de données à partir de votre instruction if. Heureusement, vous n'en avez pas besoin.

Essayer:

mtcars %>%
  filter(am == 1) %>%
  filter({if("cyl" %in% names(.)) cyl else NULL} == 4)

Ici, vous pouvez utiliser l'objet '.' dans la condition afin de vérifier si la colonne existe et, si elle existe, vous pouvez renvoyer la colonne à la fonction filter.

EDIT: selon le commentaire de docendo discimus sur la question, vous pouvez accéder au dataframe mais pas implicitement - c’est-à-dire que vous devez le référencer spécifiquement avec .

11
Eumenedies

Je sais que je suis en retard à la fête, mais voici une réponse un peu plus conforme à ce que vous pensiez à l’origine:

mtcars %>%
  filter(am == 1) %>%
  {
    if("cyl" %in% names(.)) filter(., cyl == 4) else .
  }

Fondamentalement, il manquait le . dans filter. Notez que ceci est dû au fait que le pipeline n'ajoute pas . à filter(expr) puisqu'il se trouve dans une expression entourée de {}.

1
Felipe Gerard

Edit: Malheureusement, c'était trop beau pour être vrai

Je pourrais être un peu en retard à la fête. Mais est

mtcars %>% 
 filter(am == 1) %>%
 try(filter(absent_column== 4))

une solution?

0
Johannes