51

关于赏金

当粘贴的字符串在同一位置包含's 时, Ben Bolker的 -paste2解决方案会产生 a 。像这样,""NA

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

第四个元素是 an""而不是 an NALike this,

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

我为任何可以解决这个问题的人提供这个小赏金。

原始问题

我已经阅读了帮助页面?paste,但我不明白如何让 R ignore 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"

但这似乎是一个弯路。

4

13 回答 13

48

出于“true-NA”的目的:似乎最直接的方法是将返回的值修改paste2NA当值是""

 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    
于 2013-03-28T02:23:57.703 回答
42

我知道这个问题已经有很多年了,但它仍然是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任何位置的任意数量的 s 向量。

恕我直言,与现有答案相比,它的优势在于易读性。这是一个单行的,总是很好,它不依赖于一堆正则表达式和 if/else 语句,这可能会让你的同事或未来的自己绊倒。Erik Shitts 的回答大多具有这些优势,但假设只有两个向量,并且只有最后一个向量包含NAs。

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

is.na(bar) <- bar == ""
于 2018-03-09T20:06:08.113 回答
22

我找到了该问题的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
于 2019-12-23T14:12:35.547 回答
15

跟进@ErikShilt 的回答和@agstudy 的评论的函数。它通过允许sep指定和处理任何元素(第一个、最后一个或中间)是NA. (如果一行中有多个值,或者在其他棘手的情况下,它可能会中断......)顺便说一下,请注意,这种情况在部分的NA第二段中准确描述,这表明至少 R 作者了解情况(尽管没有提供解决方案)。Details?paste

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参数使 -removal 成为可选(并将默认设置为FALSE向后paste2兼容paste)。如果想让这个更复杂(即删除多个顺序NAs)或更快,通过 Rcpp 用 C++ 编写它可能是有意义的(我不太了解 C++ 的字符串处理,但它可能不会太难——请参阅将 Rcpp::CharacterVector 转换为 std::string并且连接字符串在开始时无法按预期工作......)

于 2012-12-02T22:52:35.183 回答
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" 
于 2015-07-20T04:16:35.470 回答
6

您可以使用ifelse向量化的 if-else 构造来确定值是否为 NA 并替换为空白。然后,如果后面没有任何其他字符串,您将使用 gsub 去除尾随的“,”。

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

你的答案是正确的。没有更好的方法来做到这一点。此问题在详细信息部分的粘贴文档中明确提及。

于 2012-12-02T21:21:34.197 回答
4

如果使用 tidyverse 处理 df 或 tibbles,我会使用mutate_allor mutate_atwith str_replace_nabeforepasteunite避免粘贴 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))
于 2020-01-14T00:20:44.803 回答
2

或使用 str_replace_all 粘贴后删除 NA

data$1 <- str_replace_all(data$1, "NA", "")
于 2019-04-25T10:05:37.230 回答
2

这可以在一行中实现。例如,

vec<-c("A","B",NA,"D","E")
res<-paste(vec[!is.na(vec)], collapse=',' )
print(res)
[1] "A,B,D,E"
于 2021-08-03T20:07:32.343 回答
1

乔的解决方案(https://stackoverflow.com/a/49201394/3831096)的一个变体,当所有值都是 NA 时,它同时尊重sepcollapse返回 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
  }
}
于 2019-09-13T15:08:31.167 回答
1

这是一个表现得更像粘贴的解决方案,并且比当前解决方案处理更多的边缘情况(空字符串、“NA”字符串、超过 2 个参数、使用折叠参数......)。

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 包(v0.3.0)于 2019 年 10 月 1 日创建

于 2019-10-01T14:28:12.263 回答
0

更新@Erik Shilts 解决方案以去掉最后一个逗号:

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

然后为了摆脱尾随的最后一次"," ,只需再次重复:

x <- gsub(",$", "", x)
于 2021-01-02T21:20:48.177 回答
0

这对我有用

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"
于 2021-06-30T19:28:55.140 回答