压制 paste() 中的缺失值

65

关于奖励

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忽略NA。我按照以下步骤操作:

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"

但那似乎是一个绕路。


1
如果您有经常需要,可以使用na.rm参数实现paste2(...,sep,collapse,na.rm = FALSE)的功能。 - agstudy
@agstudy,我该怎么做? - Eric Fail
stringr::str_replace_na(c(NA, "abc", "def"), replacement="") -- 2018 年的方法 - Ufos
1
用空字符串替换NA在paste中不起作用。从paste(1:4, stringr::str_replace_na(foo, replacement=""), sep=", "),你会得到"1, A" "2, B" "3, C" "4, " - Dannid
14个回答

56

我知道这个问题已经很多年了,但它仍然是谷歌搜索中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"

如果不明显的话,这将适用于任何数量的向量,其中包含任意位置的NA

我认为这比现有答案的优点在于易读性。它只有一行代码,这总是很好的,而且它不依赖于一堆正则表达式和if / else语句,这可能会让你的同事或未来的自己感到困惑。 Erik Shitts的答案大多都具备这些优点,但它假设只有两个向量,并且只有最后一个向量包含NA

我的解决方案不能满足您的编辑要求,因为我的项目需要相反的要求。 但是,您可以轻松地通过从42-的答案借用第二行来解决此问题:

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

3
这对我很有效。非常简单。我希望这可以随paste一起发布。 - Murray

48

为了实现“真正的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    

这是故意这样写的吗? paste3(c("a", "b", "c", NA), c("A", "B", NA, NA), sep = "|") 返回 [1] "|a|||A|" "|b|||B|" "|c|||" "|||" ? 相反地, paste(c("a", "b", "c", NA), c("A", "B", NA, NA), sep = "|") 返回 [1] "a|A" "b|B" "c|NA" "NA|NA" - jaydee
这不是本意。如果您需要使用“|”作为分隔符,则应该意识到它在函数内部用作正则表达式模式中的逻辑OR。因此,应该针对该特定分隔符和备用处理设置陷阱。 - IRTFM

46

我发现了一个dplyr/tidyverse的解决方案,我认为这相当优雅。

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

如果只是关于 unite,为什么要加载 data.table 和所有的 tidyverse 包呢? - markus
因为我只是想确保它能够正常工作,而且我同时使用了dplyr和data.table。我宁愿加载比必要的更多内容,也不愿意有不可重现的代码。 - hannes101
完美。这是一个很棒的一行解决方案,可以替换以前笨重的(但在没有dplyr的情况下必需的)解决方案。 - Justin
2
再次,dplyr 提供了一个优雅的解决方案。 - Jeff Bezos
请注意:unite 函数在 tidyr 包中,因此如果您需要保持依赖关系的整洁性,则无需加载 data.table 和整个 tidyverse - Valentin_Ștefan
我已经移除了不必要的依赖项。 :-) - hannes101

16
一个函数跟进@ErikShilt的答案和@agstudy的评论。它稍微概括了一下情况,允许指定sep并处理任何元素(第一个、最后一个或中间)是NA的情况。(如果有多个连续的NA值,或其他棘手的情况...可能会出问题)。顺便说一句,注意到这种情况在?paste的第二段Details部分中被描述得非常清楚,这表明至少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.rm参数(并将默认值设置为FALSE,以使paste2向后兼容paste),使NA移除变成可选项。如果想要让它更加高级(即删除多个连续的NA)或者更快,可能最好使用Rcpp写成C++代码(我不太了解C++的字符串处理,但这里有些起点:看看如何将Rcpp::CharacterVector转化为std::string, 以及如何进行字符串连接...)。


1
我认为你可以通过使用 do.call(paste, c(L, list(sep=sep, collapse=collapse))) 改变你的 do.call,然后你就能得到你想要的 collapse 参数。 - agstudy
是的,这并不难--我只是还没有费心(但如果您愿意,可以自由编辑)(哎呀,您不能--需要2000声望--抱歉)。 - Ben Bolker

13

正如Ben Bolker所提到的,以上方法可能会因为出现连续多个NA而失效。我尝试了一种不同的方法,似乎可以克服这个问题。

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% ""),如果需要的话也可以删除空字符串。请注意,使用na.rm = T和collapse一起返回一个没有任何“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" 

7
你可以使用矢量化的if-else结构ifelse来确定一个值是否为NA,然后将其替换为空白。然后,如果末尾没有跟随任何其他字符串,你可以使用gsub去除结尾的", "。
gsub(", $", "", paste(1:4, ifelse(is.na(foo), "", foo), sep = ", "))

你的答案是正确的。没有更好的方法来做这件事。在paste文档的详细信息部分中明确提到了这个问题。


1
谢谢您回答我的问题,但是您的代码仍然让我得到了“4,”,而我想要的是“4”。 - Eric Fail
@EricFail,抱歉我没有注意到最后一个元素缺少“,”。你的答案是正确的。 - Erik Shilts
这解决了我的问题,谢谢。所以,没有办法改变past()的行为? - Eric Fail
2
@EricFail paste现在的状态很好。如果您想使用它来执行非标准操作,那么需要做更多的工作来指定所需的行为,这是有意义的。在我看来,它目前的工作方式很好。 - Dason
2
@Dason,我并不是说粘贴不好,我只是试图解决一个我认为其他人也会遇到的问题。在我的“真实”示例中,我有很多变量,我正在尝试将它们组合成一个向量。我想这个问题没有捷径可走。无论如何,感谢回复! - Eric Fail
如果上面的代码适用于x,如下所示: x = gsub(",$", "", paste(1:4, ifelse(is.na(foo), "", foo), sep = ",")) 那么为了去掉它末尾的",",只需再次重复执行即可: x = gsub(",$", "", x) 这对于我来说已经奏效,可以消除那个讨厌的“4,”最后的逗号。 - IAmBotmaker

6

如果使用tidyverse处理df或tibbles,我会在使用pasteunite之前使用mutate_allmutate_atstr_replace_na一起来避免粘贴NAs。

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

1
unite有一个选项可以设置na.rm = TRUE,因此您可以跳过mutate步骤并调用类似于df%>% unite('col3',col1:col2,sep =' ',na.rm = TRUE)的内容。值得注意的是,默认的sep参数是下划线。还值得注意的是,如果要合并的一个或多个列是数字,则na.rm = TRUE可能无法按预期工作。 - sbha

3
这可以在一行代码中实现。 例如,
vec<-c("A","B",NA,"D","E")
res<-paste(vec[!is.na(vec)], collapse=',' )
print(res)
[1] "A,B,D,E"

2

或者在使用str_replace_all粘贴后删除NAs

最初的回答

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

1
有其他回答提供了OP的问题,并且它们是多年前发布的。在发表答案时,请确保添加新解决方案或更全面的解释,特别是在回答较旧的问题时。 - help-info.de

1
这里有一个更像粘贴并处理更多边缘情况的解决方案,比当前解决方案更好(空字符串,“NA”字符串,超过2个参数,使用collapse参数...)。
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,"

这段内容是由reprex package (v0.3.0)于2019年10月01日创建的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接