【问题标题】:R: Replace multiple values in multiple columns of dataframes with NAR:用NA替换多列数据框中的多个值
【发布时间】:2014-11-04 06:32:43
【问题描述】:

我正在尝试实现类似于 this question 的东西,但在大型数据集中使用多个必须替换为 NA 的值。

df <- data.frame(name = rep(letters[1:3], each = 3), foo=rep(1:9),var1 = rep(1:9), var2 = rep(3:5, each = 3))

生成此数据帧:

df
  name foo var1 var2
1    a   1    1    3
2    a   2    2    3
3    a   3    3    3
4    b   4    4    4
5    b   5    5    4
6    b   6    6    4
7    c   7    7    5
8    c   8    8    5
9    c   9    9    5

我想将所有出现的 3 和 4 替换为 NA,但仅限于以“var”开头的列中。

我知道我可以使用[] 运算符的组合来达到我想要的结果:

df[,grep("^var[:alnum:]?",colnames(df))][ 
        df[,grep("^var[:alnum:]?",colnames(df))] == 3 |
        df[,grep("^var[:alnum:]?",colnames(df))] == 4
   ] <- NA

df
  name foo var1 var2
1    a   1    1    NA
2    a   2    2    NA
3    a   3    NA   NA
4    b   4    NA   NA
5    b   5    5    NA
6    b   6    6    NA
7    c   7    7    5
8    c   8    8    5
9    c   9    9    5

现在我的问题如下:

  1. 有没有一种方法可以有效地做到这一点,因为我的实际 数据集大约有 100.000 行,500 个变量中有 400 个开始 用“var”。当我使用时,我的电脑似乎(主观上)很慢 双括号技术。
  2. 如果出现问题,我将如何解决该问题 而不是 2 个值(3 和 4)被 NA 替换,我有一个很长的 例如,100 个不同值的列表?有没有办法通过| 运算符分隔一系列笨拙的条件来指定多个值?

【问题讨论】:

  • 你可以使用%in%

标签: r replace dataframe multiple-columns


【解决方案1】:

这是一种方法:

# the values that should be replaced by NA
values <- c(3, 4)

# index of columns
col_idx <- grep("^var", names(df))
# [1] 3 4

# index of values (within these columns)
val_idx <- sapply(df[col_idx], "%in%", table = values)
#        var1  var2
#  [1,] FALSE  TRUE
#  [2,] FALSE  TRUE
#  [3,]  TRUE  TRUE
#  [4,]  TRUE  TRUE
#  [5,] FALSE  TRUE
#  [6,] FALSE  TRUE
#  [7,] FALSE FALSE
#  [8,] FALSE FALSE
#  [9,] FALSE FALSE

# replace with NA
is.na(df[col_idx]) <- val_idx

df
#   name foo var1 var2
# 1    a   1    1   NA
# 2    a   2    2   NA
# 3    a   3   NA   NA
# 4    b   4   NA   NA
# 5    b   5    5   NA
# 6    b   6    6   NA
# 7    c   7    7    5
# 8    c   8    8    5
# 9    c   9    9    5

【讨论】:

    【解决方案2】:

    你也可以这样做:

    col_idx <- grep("^var", names(df))
    values <- c(3, 4)
    m1 <- as.matrix(df[,col_idx])
    m1[m1 %in% values] <- NA
    df[col_idx]  <- m1
    df
    #   name foo var1 var2
    #1    a   1    1   NA
    #2    a   2    2   NA
    #3    a   3   NA   NA
    #4    b   4   NA   NA
    #5    b   5    5   NA
    #6    b   6    6   NA
    #7    c   7    7    5
    #8    c   8    8    5
    #9    c   9    9    5
    

    【讨论】:

    • 谢谢。根据我的数据,这个解决方案比 sapply 方法快 6 到 7 倍。
    • @Peutch - 我想我已经用replace 提高了一点速度 - 你能测试你的实际数据吗?
    【解决方案3】:

    我还没有为这个选项计时,但是我写了一个名为 makemeNA 的函数,它是 my GitHub-only "SOfun" package 的一部分。

    使用该功能,方法将是这样的:

    library(SOfun)
    
    Cols <- grep("^var", names(df))
    df[Cols] <- makemeNA(df[Cols], NAStrings = as.character(c(3, 4)))
    df
    #   name foo var1 var2
    # 1    a   1    1   NA
    # 2    a   2    2   NA
    # 3    a   3   NA   NA
    # 4    b   4   NA   NA
    # 5    b   5    5   NA
    # 6    b   6    6   NA
    # 7    c   7    7    5
    # 8    c   8    8    5
    # 9    c   9    9    5
    

    该函数使用type.convert 中的na.strings 参数转换为NA


    安装包:

    library(devtools)
    install_github("SOfun", "mrdwab")
    

    (或者你最喜欢的从 GitHub 安装包的方法)。


    这里有一些基准测试。我决定让事情变得有趣,并用NA 替换数字和非数字值,看看比较结果如何。

    这是示例数据:

    n <- 1000000
    set.seed(1)
    df <- data.frame(
      name1 = sample(letters[1:3], n, TRUE), 
      name2 = sample(letters[1:3], n, TRUE),
      name3 = sample(letters[1:3], n, TRUE),
      var1 = sample(9, n, TRUE), 
      var2 = sample(5, n, TRUE),
      var3 = sample(9, n, TRUE))
    

    以下是要测试的功能:

    fun1 <- function() {
      Cols <- names(df)
      df[Cols] <- makemeNA(df[Cols], NAStrings = as.character(c(3, 4, "a")))
      df
    }
    
    fun2 <- function() {
      values <- c(3, 4, "a")
      col_idx <- names(df)
      m1 <- as.matrix(df)
      m1[m1 %in% values] <- NA
      df[col_idx]  <- m1
      df
    }
    
    fun3 <- function() {
      values <- c(3, 4, "a")
      col_idx <- names(df)
      val_idx <- sapply(df[col_idx], "%in%", table = values)
      is.na(df[col_idx]) <- val_idx
      df
    }
    
    fun4 <- function() {
      sel <- names(df)
      df[sel] <- lapply(df[sel], function(x) 
        replace(x, x %in% c(3, 4, "a"), NA))
      df
    }
    

    我正在爆发fun2fun3。我对fun2 并不感到疯狂,因为它将所有内容都转换为相同的类型。我也预计fun3 会更慢。

    system.time(fun2())
    #    user  system elapsed 
    #    4.45    0.33    4.81 
    
    system.time(fun3())
    #    user  system elapsed 
    #   34.31    0.38   34.74 
    

    所以现在轮到我和 Thela...

    library(microbenchmark)
    microbenchmark(fun1(), fun4(), times = 50)
    # Unit: seconds
    #    expr      min       lq   median       uq      max neval
    #  fun1() 2.934278 2.982292 3.070784 3.091579 3.617902    50
    #  fun4() 2.839901 2.964274 2.981248 3.128327 3.930542    50
    

    去你的塞拉!

    【讨论】:

    • +1 我将其转换为矩阵,因为示例显示要比较的 numeric
    【解决方案4】:

    您也可以使用replace

    sel <- grepl("var",names(df))
    df[sel] <- lapply(df[sel], function(x) replace(x,x %in% 3:4, NA) )
    df
    
    #  name foo var1 var2
    #1    a   1    1   NA
    #2    a   2    2   NA
    #3    a   3   NA   NA
    #4    b   4   NA   NA
    #5    b   5    5   NA
    #6    b   6    6   NA
    #7    c   7    7    5
    #8    c   8    8    5
    #9    c   9    9    5
    

    使用一百万行数据样本进行的一些快速基准测试表明,这比其他答案更快。

    【讨论】:

    • 在我的数据上,我从 17 秒缩短到 1.8 秒,与 @akrun 方法相比减少了十倍!谢谢!
    • 知道是否可以计算出有多少值已更改?
    【解决方案5】:

    这是一个 dplyr 解决方案:

    # Define replace function
    repl.f <- function(x) ifelse(x%in%c(3,4), NA,x)
    
    library(dplyr)
    cbind(select(df, -starts_with("var")),
      mutate_each(select(df, starts_with("var")), funs(repl.f)))
    
      name foo var1 var2
    1    a   1    1   NA
    2    a   2    2   NA
    3    a   3   NA   NA
    4    b   4   NA   NA
    5    b   5    5   NA
    6    b   6    6   NA
    7    c   7    7    5
    8    c   8    8    5
    9    c   9    9    5
    

    【讨论】:

    • 我不认为以这种方式使用mutate_each() 或它的最新等效mutate_all() 是有意义的(不再)。我不确定这在 2015 年是否可行,但现在你应该使用mutate_at(vars(starts_with("var"), ...),它比mutate_each()-select()-cbind()-approach 更优雅、更快
    • 同意 statmerkur,尝试改用更新的 dplyr 语言,mutate(across(...)),正如 elsehwere 所指出的那样。
    【解决方案6】:

    我认为dplyr 非常适合这项任务。
    按照@thelatemail 的建议使用replace(),您可以执行以下操作:

    library("dplyr")
    df <- df %>% 
      mutate_at(vars(starts_with("var")),
                funs(replace(., . %in% c(3, 4), NA)))
    
    df
    #   name foo var1 var2
    # 1    a   1    1   NA
    # 2    a   2    2   NA
    # 3    a   3   NA   NA
    # 4    b   4   NA   NA
    # 5    b   5    5   NA
    # 6    b   6    6   NA
    # 7    c   7    7    5
    # 8    c   8    8    5
    # 9    c   9    9    5
    

    【讨论】:

    • 娱乐已被弃用
    【解决方案7】:

    dplyr 1.0.0(2020 年初)以来,我认为 dplyr 方法将是:

    library(dplyr)
    df %>% mutate(across(starts_with('var'), ~replace(., . %in% c(3,4), NA)))
    
      name foo var1 var2
    1    a   1    1   NA
    2    a   2    2   NA
    3    a   3   NA   NA
    4    b   4   NA   NA
    5    b   5    5   NA
    6    b   6    6   NA
    7    c   7    7    5
    8    c   8    8    5
    9    c   9    9    5
    

    使用 naniar 包的另一种方法,它使用谓词函数(此处使用str_detect())巧妙地将缺失值归入所选列:

    library(dplyr)
    library(stringr)
    library(naniar)
    
    df%>%replace_with_na_if(str_detect(names(.), '^var'), ~.%in%c(3,4))
    

    很高兴看到 naniar 包更新为与当前带有 across() 的 tidyselect 合成器及其选择助手一起使用,例如: df%&gt;%mutate(across(starts_with('var'), replace_with_na_all(condition=~.%in% c(3, 4))))

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-01-03
      • 1970-01-01
      • 2014-08-24
      • 2020-09-06
      • 2017-06-25
      • 1970-01-01
      • 1970-01-01
      • 2018-08-25
      相关资源
      最近更新 更多