web-dev-qa-db-fra.com

supprimer les NA en pâte ()

En ce qui concerne la prime

La solution paste2- de Ben Bolker produit un "" lorsque les chaînes collées contiennent des variables NA au même emplacement. Comme ça,

> paste2(c("a","b", "c", NA), c("A","B", NA, NA))
[1] "a, A" "b, B" "c"    ""

Le quatrième élément est un "" au lieu d'une NA Comme ceci,

[1] "a, A" "b, B" "c"  NA     

J'offre cette petite prime à quiconque peut y remédier.

Question originale

J'ai lu la page d'aide ?paste, mais je ne comprends pas comment faire pour que R ignore NAs. Je fais ce qui suit,

foo <- LETTERS[1:4]
foo[4] <- NA
foo
[1] "A" "B" "C" NA
paste(1:4, foo, sep = ", ")

et obtenir

[1] "1, A"  "2, B"  "3, C"  "4, NA"

Ce que j'aimerais avoir,

[1] "1, A" "2, B" "3, C" "4"

Je pourrais faire comme ça,

sub(', NA$', '', paste(1:4, foo, sep = ", "))
[1] "1, A" "2, B" "3, C" "4"

mais cela ressemble à un détour.

26
Eric Fail

Pour les besoins de "true-NA": Il semble que l'itinéraire le plus direct consiste simplement à modifier la valeur renvoyée par paste2 pour qu'elle soit NA lorsque la valeur est ""

 paste3 <- function(...,sep=", ") {
     L <- list(...)
     L <- lapply(L,function(x) {x[is.na(x)] <- ""; x})
     ret <-gsub(paste0("(^",sep,"|",sep,"$)"),"",
                 gsub(paste0(sep,sep),sep,
                      do.call(paste,c(L,list(sep=sep)))))
     is.na(ret) <- ret==""
     ret
     }
 val<- paste3(c("a","b", "c", NA), c("A","B", NA, NA))
 val
#[1] "a, A" "b, B" "c"    NA    
25
42-

Une fonction qui fait suite à la réponse de @ ErikShilt et au commentaire de @ agstudy. Il généralise légèrement la situation en permettant à sep d'être spécifié et en traitant les cas où un élément (premier, dernier ou intermédiaire) est NA. (Cela peut casser s'il y a plusieurs valeurs NA dans une ligne ou dans d'autres cas difficiles ...) Soit dit en passant, cette situation est décrite exactement dans le deuxième paragraphe de la section Details de ?paste, ce qui indique qu'au moins les auteurs R sont conscients de la situation (bien qu'aucune solution ne soit proposée).

paste2 <- function(...,sep=", ") {
    L <- list(...)
    L <- lapply(L,function(x) {x[is.na(x)] <- ""; x})
    gsub(paste0("(^",sep,"|",sep,"$)"),"",
                gsub(paste0(sep,sep),sep,
                     do.call(paste,c(L,list(sep=sep)))))
}
foo <- c(LETTERS[1:3],NA)
bar <- c(NA,2:4)
baz <- c("a",NA,"c","d")
paste2(foo,bar,baz)
# [1] "A, a"    "B, 2"    "C, 3, c" "4, d"   

Ceci ne gère pas les suggestions de @ agstudy consistant à (1) incorporer l'argument optionnel collapse; (2) rendre la suppression de NA- facultative en ajoutant un argument na.rm (et en définissant la valeur par défaut sur FALSE pour rendre paste2 compatible avec paste). Si on voulait rendre cela plus sophistiqué (c'est-à-dire supprimer plusieurs NAs séquentielles) ou plus rapide, il pourrait être judicieux de l'écrire en C++ via Rcpp (je ne connais pas grand chose à la gestion des chaînes de C++, mais ce n'est peut-être pas si difficile see convertit Rcpp :: CharacterVector en std :: string et La concaténation de chaînes ne fonctionne pas comme prévu pour commencer ...)

13
Ben Bolker

Comme mentionné - Ben Bolker , les approches ci-dessus peuvent basculer s’il ya plusieurs NA dans une rangée. J'ai essayé une approche différente qui semble surmonter cela. 

paste4 <- function(x, sep = ", ") {
  x <- gsub("^\\s+|\\s+$", "", x) 
  ret <- paste(x[!is.na(x) & !(x %in% "")], collapse = sep)
  is.na(ret) <- ret == ""
  return(ret)
  }

La deuxième ligne supprime les espaces supplémentaires introduits lors de la concaténation de texte et de nombres . Le code ci-dessus peut être utilisé pour concaténer plusieurs colonnes (ou lignes) d'un cadre de données à l'aide de la commande apply nécessaire. 

EDIT

Après quelques heures supplémentaires de réflexion, je pense que le code suivant intègre les suggestions ci-dessus pour permettre la spécification des options collapse et na.rm. 

paste5 <- function(..., sep = " ", collapse = NULL, na.rm = F) {
  if (na.rm == F)
    paste(..., sep = sep, collapse = collapse)
  else
    if (na.rm == T) {
      paste.na <- function(x, sep) {
        x <- gsub("^\\s+|\\s+$", "", x)
        ret <- paste(na.omit(x), collapse = sep)
        is.na(ret) <- ret == ""
        return(ret)
      }
      df <- data.frame(..., stringsAsFactors = F)
      ret <- apply(df, 1, FUN = function(x) paste.na(x, sep))

      if (is.null(collapse))
        ret
      else {
        paste.na(ret, sep = collapse)
      }
    }
}

Comme ci-dessus, na.omit(x) peut être remplacé par (x[!is.na(x) & !(x %in% "") pour supprimer également les chaînes vides, le cas échéant. Notez que l'utilisation de collapse avec na.rm = T retourne une chaîne sans aucun "NA", bien que cela puisse être modifié en remplaçant la dernière ligne de code par paste(ret, collapse = collapse).

nth <- paste0(1:12, c("st", "nd", "rd", rep("th", 9)))
mnth <- month.abb
nth[4:5] <- NA
mnth[5:6] <- NA

paste5(mnth, nth)
[1] "Jan 1st"  "Feb 2nd"  "Mar 3rd"  "Apr NA"   "NA NA"    "NA 6th"   "Jul 7th"  "Aug 8th"  "Sep 9th"  "Oct 10th" "Nov 11th" "Dec 12th"

paste5(mnth, nth, sep = ": ", collapse = "; ", na.rm = T)
[1] "Jan: 1st; Feb: 2nd; Mar: 3rd; Apr; 6th; Jul: 7th; Aug: 8th; Sep: 9th; Oct: 10th; Nov: 11th; Dec: 12th"

paste3(c("a","b", "c", NA), c("A","B", NA, NA), c(1,2,NA,4), c(5,6,7,8))
[1] "a, A, 1, 5" "b, B, 2, 6" "c, , 7"     "4, 8" 

paste5(c("a","b", "c", NA), c("A","B", NA, NA), c(1,2,NA,4), c(5,6,7,8), sep = ", ", na.rm = T)
[1] "a, A, 1, 5" "b, B, 2, 6" "c, 7"       "4, 8" 
11
JWilliman

Je sais que cette question a plusieurs années, mais reste le premier résultat de Google pour r paste na. Je cherchais une solution rapide à ce que je pensais être un problème simple et la complexité des réponses m'avait quelque peu surpris. J'ai opté pour une solution différente et je l'affiche ici au cas où quelqu'un d'autre serait intéressé.

bar <- apply(cbind(1:4, foo), 1, function(x) paste(x[!is.na(x)], collapse = ", "))
bar
[1] "1, A" "2, B" "3, C" "4"

Au cas où cela ne serait pas évident, cela fonctionnerait sur un nombre quelconque de personnes avec des AN dans n'importe quel poste.

IMHO, l'avantage de ceci par rapport aux réponses existantes est la lisibilité. C'est un one-liner, qui est toujours agréable, et il ne repose pas sur un tas de regex et de déclarations if/else qui pourraient faire trébucher vos collègues ou votre futur. La réponse de Erik Shitts partage principalement ces avantages, mais suppose qu'il n'y a que deux vecteurs et que seul le dernier d'entre eux contient des NA.

Ma solution ne satisfait pas à l'exigence de votre modification, car mon projet a l'exigence opposée. Cependant, vous pouvez facilement résoudre ce problème en ajoutant une deuxième ligne empruntée à la réponse de 42- :

is.na(bar) <- bar == ""
10
Joe

Vous pouvez utiliser ifelse, une construction if-else vectorisée pour déterminer si une valeur est NA et remplacer un blanc. Vous utiliserez ensuite gsub pour effacer le "," suivi s'il n'est suivi d'aucune autre chaîne.

gsub(", $", "", paste(1:4, ifelse(is.na(foo), "", foo), sep = ", "))

Votre réponse est correcte. Il n'y a pas de meilleure façon de le faire. Ce problème est explicitement mentionné dans coller documentation dans la section Détails.

5
Erik Shilts

Ou faites une mutation après paste () et supprimez les NA:

data <- data.frame(col1= c(rep(NA, 5)), col2 = c(2:6)) %>%
  mutate(col3 = paste(col1, col2)) %>%
  mutate(col3 = gsub('NA', '', col3))
0
Daisywang