Подавить NA в пасте ()

Что касается щедрости

Ben Bolkerpaste2-раствор дает "" когда вставленные строки содержат NAв том же положении. Как это,

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

Четвертый элемент "" вместо NA Как это,

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

Я предлагаю эту маленькую награду за каждого, кто может это исправить.

Оригинальный вопрос

Я прочитал страницу помощи ?paste, но я не понимаю, как заставить R игнорировать NAs. Я делаю следующее,

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

и получить

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

Что бы я хотел получить,

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

Я мог бы сделать это,

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

но это похоже на объезд.

15 ответов

Решение

Для цели "true-NA": кажется, что самый прямой путь - это просто изменить значение, возвращаемое paste2 быть NA когда значение ""

 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    

Я знаю, что этому вопросу уже много лет, но он по-прежнему является лучшим результатом Google для r paste na, Я искал быстрое решение того, что я считал простой проблемой, и был несколько озадачен сложностью ответов. Я выбрал другое решение, и выкладываю его здесь на тот случай, если кому-то еще будет интересно.

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

В случае, если это не очевидно, это будет работать на любом количестве vecotrs с NA в любых позициях.

ИМХО, преимущество этого перед существующими ответами - разборчивость. Это однострочный текст, который всегда хорош, и он не опирается на кучу регулярных выражений и утверждений if / else, которые могут сбить с толку ваших коллег или будущее. Ответ Эрика Шитца в основном разделяет эти преимущества, но предполагает, что есть только два вектора и что только последний из них содержит NA.

Мое решение не удовлетворяет требованию в вашем редактировании, потому что мой проект имеет противоположное требование. Однако вы легко можете решить эту проблему, добавив вторую строку, заимствованную из 42-х ответов:

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

Я нашел dplyr/tidyverse решение этого вопроса, на мой взгляд, довольно изящное.

library(data.table)
library(tidyverse)
foo <- LETTERS[1:4] 
foo[4] <- NA 
dt <- data.table(foo, num = 1:4)
dt %>% unite(., col = "New.Col",  num, foo, na.rm=TRUE, sep = ",")
>    New.Col
  1:     1,A
  2:     2,B
  3:     3,C
  4:       4

Функция, которая отслеживает ответ @ErikShilt и комментарий @agstudy. Это немного обобщает ситуацию, позволяя sep указывать и обрабатывать случаи, когда любой элемент (первый, последний или промежуточный) NA, (Это может сломаться, если есть несколько NA значения в строке, или в других сложных случаях...) Кстати, обратите внимание, что эта ситуация описана точно во втором абзаце Details раздел ?pasteЭто указывает на то, что, по крайней мере, авторы R осведомлены о ситуации (хотя решение не предлагается).

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"   

Это не учитывает предложения @agstudy (1), включающие collapse аргумент; (2) изготовление NA-удаление необязательно, добавив na.rm аргумент (и установка по умолчанию FALSE делать paste2 обратная совместимость с paste). Если кто-то хотел сделать это более сложным (то есть удалить несколько последовательных NAs) или быстрее, возможно, имеет смысл написать его в C++ с помощью Rcpp (я не очень разбираюсь в обработке строк в C++, но это может быть не так уж сложно - см. преобразование Rcpp::CharacterVector в std::string и Concatenating строки не работают, как ожидалось для начала...)

Как упомянул Ben Bolker, вышеуказанные подходы могут Ben Bolker, если в ряду будет несколько НС. Я попробовал другой подход, который, кажется, преодолевает это.

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)
  }

Вторая строка удаляет лишние пробелы, введенные при объединении текста и чисел. Приведенный выше код можно использовать для объединения нескольких столбцов (или строк) кадра данных с использованием apply или переупакованы, чтобы при необходимости сначала привести данные в фрейм данных.

EDIT

Через несколько часов я подумал, что следующий код включает в себя приведенные выше предложения, которые позволяют задавать параметры collapse и 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)
      }
    }
}

Как указано выше, na.omit(x) можно заменить на (x[!is.na(x) & !(x %in% "") также опускать пустые строки, если это необходимо. Обратите внимание, что использование collapse с na.rm = T возвращает строку без "NA", хотя это можно изменить, заменив последнюю строку кода на 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" 

Ты можешь использовать ifelse- векторизованная конструкция if-else для определения значения NA и замены пробела. Затем вы будете использовать gsub для удаления конечного ", ", если за ним не следует никакая другая строка.

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

Ваш ответ правильный. Нет лучшего способа сделать это. Эта проблема явно упоминается в документации по вставке в разделе "Подробности".

Если вы работаете с df или tibbles с помощью tidyverse, я использую mutate_all или mutate_at с str_replace_na перед paste или unite чтобы избежать вставки NA.

library(tidyverse)
new_df <- df  %>%
mutate_all(~str_replace_na(., "")) %>%
mutate(combo_var = paste0(var1, var2, var3))

ИЛИ

new_df <- df  %>%
mutate_at(c('var1', 'var2'), ~str_replace_na(., "")) %>%
mutate(combo_var = paste0(var1, var2))

Это может быть достигнуто в одной строке. Например,

      vec<-c("A","B",NA,"D","E")
res<-paste(vec[!is.na(vec)], collapse=',' )
print(res)
[1] "A,B,D,E"

Или удалите NA после вставки с помощью str_replace_all

data$1 <- str_replace_all(data$1, "NA", "")

Вот решение, которое больше похоже на вставку и обрабатывает больше крайних случаев, чем текущие решения (пустые строки, строки "NA", более двух аргументов, использование аргумента свертывания...).

paste2 <- function(..., sep = " ", collapse = NULL, na.rm = FALSE){
  # in default case, use paste 
  if(!na.rm) return(paste(..., sep = sep, collapse = collapse))
  # cbind is convenient to recycle, it warns though so use suppressWarnings
  dots <- suppressWarnings(cbind(...))
  res <- apply(dots, 1, function(...) {
    if(all(is.na(c(...)))) return(NA)
    do.call(paste, as.list(c(na.omit(c(...)), sep = sep)))
  })
  if(is.null(collapse)) res else
   paste(na.omit(res), collapse = collapse)
}

# behaves like `paste()` by default
paste2(c("a","b", "c", NA), c("A","B", NA, NA))
#> [1] "a A"   "b B"   "c NA"  "NA NA"

# trigger desired behavior by setting `na.rm = TRUE` and `sep = ", "`
paste2(c("a","b", "c", NA), c("A","B", NA, NA), sep = ",", na.rm = TRUE)
#> [1] "a,A" "b,B" "c"   NA

# handles hedge cases
paste2(c("a","b", "c", NA, "", "",   ""),
       c("a","b", "c", NA, "", "", "NA"),
       c("A","B",  NA, NA, NA, "",   ""), 
       sep = ",", na.rm = TRUE)
#> [1] "a,a,A" "b,b,B" "c,c"   NA      ","     ",,"    ",NA,"

Создано 01.10.2019 пакетом REPEX (v0.3.0)

Вариант решения Джо ( /questions/24178519/podavit-na-v-paste/24178525#24178525), который уважает обаsep а также collapse и возвращает NA, когда все значения равны NA:

paste_missing <- function(..., sep=" ", collapse=NULL) {
  ret <-
    apply(
      X=cbind(...),
      MARGIN=1,
      FUN=function(x) {
        if (all(is.na(x))) {
          NA_character_
        } else {
          paste(x[!is.na(x)], collapse = sep)
        }
      }
    )
  if (!is.null(collapse)) {
    paste(ret, collapse=collapse)
  } else {
    ret
  }
}

Небольшой обзорtidyverseрешения:

      library(tidyverse)
dat <- tibble(x = c("a", "b", NA,  NA),
              y = c("A",  NA, NA, "D"))

### str_c()
### missing values are "infectious"
dat %>% 
  mutate(z = str_c(x, y))

### str_c() and str_replace_na()
### difficult sytax
dat %>% 
  mutate(across(c(x, y), ~ str_replace_na(.x, replacement = ""), .names = "{.col}r"),
         z = str_c(xr, yr))

### unite()
### unintuitive to use something different than mutate()...
dat %>% 
  unite(col = "z", x, y, sep = "", remove = FALSE, na.rm = TRUE)

### User defined function paste2()
paste2 <- function(x, sep = "") {paste(x[!is.na(x)], collapse = sep)}
dat %>% 
  rowwise() %>% 
  mutate(z = paste2(c(x, y)))

Добавьте следующее в конец канала, если результат должен быть таким, когда все элементыNA

      mutate(z = if_else(z == "", NA, z))

Это работает для меня

      library(stringr)

foo <- LETTERS[1:4]
foo[4] <- NA
foo
# [1] "A" "B" "C" NA 

if_else(!is.na(foo),
    str_c(1:4, str_replace_na(foo, ""), sep = ", "),
    str_c(1:4, str_replace_na(foo, ""), sep = "")
    )
# [1] "1, A" "2, B" "3, C" "4"

Или сделайте мутацию после paste() и удалите NA:

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

Обновление решения @Erik Shilts, чтобы избавиться от последней запятой:
x = gsub(",$", "", paste(1:4, ifelse(is.na(foo), "", foo), sep = ","))

Затем, чтобы избавиться от последней "," в нем просто повторите еще раз:
x <- gsub(",$", "", x)

Другие вопросы по тегам