【问题标题】:suppress NAs in paste()抑制 paste() 中的 NA
【发布时间】:2012-11-20 08:47:47
【问题描述】:

关于赏金

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

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

但这似乎是绕道而行。

【问题讨论】:

  • 如果你有一个经常性的需求,你可以用 na.rm 参数来实现你的 paste2(...,sep,collapse,na.rm=FALE) 。
  • @agstudy,我该怎么做?
  • stringr::str_replace_na(c(NA, "abc", "def"), replacement="") -- 2018年方式
  • 用空字符串替换 NA 不适用于粘贴。从paste(1:4, stringr::str_replace_na(foo, replacement=""), sep=", ") 你得到"1, A" "2, B" "3, C" "4, "

标签: r paste na


【解决方案1】:

出于“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    

【讨论】:

  • 这是有意的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"
  • 这不是故意的。如果需要使用“|”作为分隔符,然后意识到它正在函数内部用作正则表达式模式中的逻辑或。所以应该有一个特定的分隔符和替代处理的陷阱。
【解决方案2】:

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

如果不明显,这将适用于任意位置的任意数量的带有NAs 的向量。

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

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

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

【讨论】:

  • 这对我有用。很简单。我希望这是随粘贴一起发货的。
【解决方案3】:

我找到了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

【讨论】:

  • 如果只是关于unite,为什么还要加载data.table 和所有tidyverse 包?
  • 因为我只是想确保它正常工作并且我同时使用 dplyr 和 data.table。我宁愿加载不必要的代码,也不愿加载不可重现的代码。
  • 完美。这是一个很棒的单线解决方案,可以替代以前笨重(但不需要 dplyr)的解决方案。
  • 再次dplyr 提供了一个优雅的解决方案
【解决方案4】:

跟进@ErikShilt 的回答和@agstudy 的评论的功能。它通过允许指定 sep 并处理 any 元素(第一个、最后一个或中间)是 NA 的情况,稍微概括了这种情况。 (如果连续有多个 NA 值,或者在其他棘手的情况下,它可能会中断......)顺便说一下,这种情况在?pasteDetails 部分的第二段中完全描述了,这表明至少 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 参数使NA-removal 成为可选(并将默认设置为FALSE 以使paste2 向后兼容paste)。如果想让这个更复杂(即删除多个连续的NAs)或更快,那么通过 Rcpp 用 C++ 编写它可能是有意义的(我不太了解 C++ 的字符串处理,但它可能不是太很难 - 请参阅 convert Rcpp::CharacterVector to std::stringConcatenating strings doesn't work as expected 开始...)

【讨论】:

  • 我认为您将 do.call 更改为 do.call(paste,c(L,list(sep=sep,collapse=collapse))))) 并得到您的 collapse 参数
  • 是的,这并不难——我只是没有打扰(还)。如果您愿意,请随意编辑(哎呀,您不能 - 需要 2000 个代表 - 抱歉。)
【解决方案5】:

正如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) &amp; !(x %in% ""),以便在需要时删除空字符串。请注意,使用带有 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" 

【讨论】:

    【解决方案6】:

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

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

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

    【讨论】:

    • 感谢您回答我的问题,但您的代码仍然给我留下了"4, ",我正在寻找的是"4"
    • @EricFail,抱歉,我没有注意到最后一个元素缺少“,”。你的答案是正确的。
    • 这解决了我的问题,谢谢。那么,有没有办法改变past()的行为?
    • @EricFail paste 就是这样。你想用它来做一些非标准的事情,所以你应该做更多的工作来指定你想要的行为是有道理的。它目前的工作方式很好 IMO。
    • @Dason,我并不是说粘贴不好,我只是想解决一个我认为其他人也会遇到的问题。在我的“真实”示例中,我尝试将许多变量组合成一个向量。我想没有捷径可以解决这个问题。无论如何,感谢您的回复!
    【解决方案7】:

    如果使用 tidyverse 处理 df 或 tibbles,我在 pasteunite 之前使用 mutate_allmutate_atstr_replace_na 以避免粘贴 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))
    

    【讨论】:

    • unite 可以选择设置na.rm = TRUE,因此您可以跳过mutate 步骤并调用类似df %&gt;% unite('col3', col1:col2, sep = ' ', na.rm = TRUE) 的名称。值得注意的是,默认的 sep 参数是一个下划线。另外值得注意的是,如果要组合的一个或多个列是数字,na.rm = TRUE 可能无法按预期工作。
    【解决方案8】:

    或者使用 str_replace_all 粘贴后删除 NAs

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

    【讨论】:

    • 还有其他答案提供了 OP 的问题,它们是多年前发布的。发布答案时,请确保添加新的解决方案或更好的解释,尤其是在回答较旧的问题时。
    【解决方案9】:

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

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

    【讨论】:

      【解决方案10】:

      Joe 解决方案 (https://stackoverflow.com/a/49201394/3831096) 的一种变体,它同时尊重 sepcollapse,并在所有值为 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
        }
      }
      

      【讨论】:

        【解决方案11】:

        这是一个表现得更像粘贴的解决方案,并且比当前解决方案处理更多的边缘情况(空字符串、“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 package (v0.3.0) 于 2019-10-01 创建

        【讨论】:

          【解决方案12】:

          这对我有用

          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"
          

          【讨论】:

            【解决方案13】:

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

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

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

            x <- gsub(",$", "", x)
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2022-01-21
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-12-04
              • 1970-01-01
              • 1970-01-01
              • 2015-07-06
              相关资源
              最近更新 更多