【问题标题】:Drop data frame columns by name按名称删除数据框列
【发布时间】:2011-06-04 01:29:18
【问题描述】:

我想从数据框中删除一些列。我知道我们可以使用以下方法单独删除它们:

df$x <- NULL

但我希望用更少的命令来做到这一点。

另外,我知道我可以像这样使用整数索引来删除列:

df <- df[ -c(1, 3:6, 12) ]

但我担心我的变量的相对位置可能会改变。

鉴于 R 的强大功能,我认为可能有比逐列删除更好的方法。

【问题讨论】:

  • 有人可以向我解释为什么 R 没有像 df#drop(var_name) 这样简单的东西,而是我们需要做这些复杂的变通办法吗?
  • @ifly6 R 中的 'subset()' 函数与 Python 中的 'drop()' 函数一样简洁,除非你不需要指定轴参数......我同意令人讨厌的是,对于删除一列这样基本的事情,不能只实现一个最终的、简单的关键字/语法。

标签: r dataframe r-faq


【解决方案1】:

您可以使用简单的名称列表:

DF <- data.frame(
  x=1:10,
  y=10:1,
  z=rep(5,10),
  a=11:20
)
drops <- c("x","z")
DF[ , !(names(DF) %in% drops)]

或者,或者,您可以列出要保留的内容并按名称引用它们:

keeps <- c("y", "a")
DF[keeps]

编辑: 对于那些还不熟悉索引函数的drop 参数的人,如果您想将一列保留为数据框,您可以这样做:

keeps <- "y"
DF[ , keeps, drop = FALSE]

drop=TRUE(或不提及它)将删除不必要的维度,并因此返回具有列 y 的值的向量。

【讨论】:

  • subset 函数效果更好,因为它不会将具有一列的数据框转换为向量
  • @mut1na 检查索引函数的参数 drop=FALSE。
  • 不应该是DF[,keeps] 而不是DF[keeps] 吗?
  • @lindelof 否。可以,但是如果您只选择一列,则必须添加 drop=FALSE 以防止 R 将数据框转换为向量。不要忘记数据框是列表,因此列表选择(像我一样是一维的)效果很好,并且总是返回一个列表。或者在这种情况下是一个数据框,这就是我更喜欢使用它的原因。
  • @AjayOhri 是的,它会的。没有逗号,您使用“列表”的选择方式,这意味着即使您提取单个列,您仍然会返回一个数据框。如果你像你一样使用“矩阵”方式,你应该知道,如果你只选择一个列,你会得到一个向量而不是一个数据框。为避免这种情况,您需要添加 drop=FALSE。正如我的回答和你上面的评论中所解释的那样......
【解决方案2】:

还有subset 命令,如果您知道需要哪些列,这很有用:

df <- data.frame(a = 1:10, b = 2:11, c = 3:12)
df <- subset(df, select = c(a, c))

@hadley 发表评论后更新:drop 列 a、c 你可以这样做:

df <- subset(df, select = -c(a, c))

【讨论】:

  • 我真的希望 R subset 函数有一个像“allbut = FALSE”这样的选项,当设置为 TRUE 时它会“反转”选择,即保留所有列 except select 列表中的那些。
  • @prasad,请参阅下面的@joris 回答。没有任何子集标准的子集有点矫枉过正。简单尝试:df[c("a", "c")]
  • @JD 我知道,但我喜欢 subset 命令的语法便利,您不需要在列名周围加上引号——我想我不介意输入几个额外的字符只是为了避免引用名称:)
  • 请注意,您不应该在其他函数中使用subset
【解决方案3】:
within(df, rm(x))

可能是最简单的,或者对于多个变量:

within(df, rm(x, y))

或者,如果您正在处理 data.tables(根据 How do you delete a column by name in data.table?):

dt[, x := NULL]   # Deletes column x by reference instantly.

dt[, !"x"]   # Selects all but x into a new data.table.

或用于多个变量

dt[, c("x","y") := NULL]

dt[, !c("x", "y")]

【讨论】:

  • within(df, rm(x))到目前为止最干净的解决方案。鉴于这是一种可能性,其他所有答案似乎都不必要地复杂了一个数量级。
  • 请注意,如果 df 中有名为 x 的重复列,within(df, rm(x))不起作用工作。
  • @MichaelChirico 澄清一下,它既不删除,但似乎改变了数据的值。如果是这种情况,会有更大的问题,但这里有一个例子:df &lt;- data.frame(x = 1, y = 2); names(df) &lt;- c("x", "x"); within(df, rm(x)) 返回data.frame(x = 2, x = 2)
  • @MilesErickson 问题是您依赖于功能强大但也使用 NSE 的函数 within()。帮助页面上的注释清楚地指出,对于编程,应该使用足够的谨慎。
  • @MilesErickson 多久会遇到一个包含重复名称的数据框?
【解决方案4】:

你可以像这样使用%in%

df[, !(colnames(df) %in% c("x","bar","foo"))]

【讨论】:

  • 我是否遗漏了什么,或者这实际上与 Joris 回答的第一部分的解决方案相同? DF[ , !(names(DF) %in% drops)]
  • @DanielFletcher:是一样的。查看答案上的时间戳。我们同时回答... 5 年前。 :)
  • 疯了。 identical(post_time_1, post_time_2) [1] TRUE =D
  • 为什么不去掉逗号?我看不出为什么df[!(colnames(df) %in% c("x","bar","foo"))] 不等同。
  • 为什么当我在 data.table 数据帧上运行时它返回 FALSE/TRUE 向量。为什么?
【解决方案5】:

list(NULL) 也可以:

dat <- mtcars
colnames(dat)
# [1] "mpg"  "cyl"  "disp" "hp"   "drat" "wt"   "qsec" "vs"   "am"   "gear"
# [11] "carb"
dat[,c("mpg","cyl","wt")] <- list(NULL)
colnames(dat)
# [1] "disp" "hp"   "drat" "qsec" "vs"   "am"   "gear" "carb"

【讨论】:

  • 太棒了!这以自然的方式将 NULL 分配扩展到单个列,并且(似乎)避免了复制(尽管我不知道幕后发生了什么,所以它可能不会更有效地使用内存......但在我看来很清楚在语法上更有效。)
  • 你不需要list(NULL),NULL就足够了。例如:dat[,4]=NULL
  • OP 的问题是如何删除多个列。 dat[,4:5]
  • 这在尝试删除重复的列名时也不起作用。
  • @MichaelChirico 适合我。如果要删除具有相同名称的第一个列,请提供标签,或者为要删除的每一列提供索引。如果您有一个不起作用的示例,我很想看看它。也许将其作为一个新问题发布?
【解决方案6】:

如果您想通过引用删除列并避免与data.frames 关联的内部复制,那么您可以使用data.table 包和函数:=

您可以将字符向量名称传递到 := 运算符的左侧,并将 NULL 作为 RHS。

library(data.table)

df <- data.frame(a=1:10, b=1:10, c=1:10, d=1:10)
DT <- data.table(df)
# or more simply  DT <- data.table(a=1:10, b=1:10, c=1:10, d=1:10) #

DT[, c('a','b') := NULL]

如果您想在调用[ 之外将名称预定义为字符向量,请将对象名称包装在(){} 中以强制在调用范围内评估LHS,而不是作为DT 范围内的名称。

del <- c('a','b')
DT <- data.table(a=1:10, b=1:10, c=1:10, d=1:10)
DT[, (del) := NULL]
DT <-  <- data.table(a=1:10, b=1:10, c=1:10, d=1:10)
DT[, {del} := NULL]
# force or `c` would also work.   

也可以使用set,这样可以避免[.data.table的开销,同样适用于data.frames

df <- data.frame(a=1:10, b=1:10, c=1:10, d=1:10)
DT <- data.table(df)

# drop `a` from df (no copying involved)

set(df, j = 'a', value = NULL)
# drop `b` from DT (no copying involved)
set(DT, j = 'b', value = NULL)

【讨论】:

    【解决方案7】:

    基于 grep() 将返回数字向量这一事实,有一个可能更强大的策略。如果您像我在我的一个数据集中所做的那样有一长串变量,一些以“.A”结尾的变量和其他以“.B”结尾的变量,您只想要以“.A”结尾的变量(以及对于与任一模式都不匹配的所有变量,请执行以下操作:

    dfrm2 <- dfrm[ , -grep("\\.B$", names(dfrm)) ]
    

    对于手头的案例,以 Joris Meys 为例,它可能没有那么紧凑,但它会是:

    DF <- DF[, -grep( paste("^",drops,"$", sep="", collapse="|"), names(DF) )]
    

    【讨论】:

    • 如果我们首先将drops定义为paste0("^", drop_cols, "$"),使用sapply会变得更好(阅读:更紧凑):DF[ , -sapply(drops, grep, names(DF))]
    【解决方案8】:

    另一个dplyr 答案。 使用select(-column)

    如果你的变量有一些共同的命名结构,你可以试试starts_with()。例如

    library(dplyr)
    df <- data.frame(var1 = rnorm(5), var2 = rnorm(5), var3 = rnorm (5), 
                     var4 = rnorm(5), char1 = rnorm(5), char2 = rnorm(5))
    df
    #        var2      char1        var4       var3       char2       var1
    #1 -0.4629512 -0.3595079 -0.04763169  0.6398194  0.70996579 0.75879754
    #2  0.5489027  0.1572841 -1.65313658 -1.3228020 -1.42785427 0.31168919
    #3 -0.1707694 -0.9036500  0.47583030 -0.6636173  0.02116066 0.03983268
    
    df1 <- df %>% select(-starts_with("char"))
    
    df1
    #        var2        var4       var3       var1
    #1 -0.4629512 -0.04763169  0.6398194 0.75879754
    #2  0.5489027 -1.65313658 -1.3228020 0.31168919
    #3 -0.1707694  0.47583030 -0.6636173 0.03983268
    

    如果要在数据框中删除一系列变量,可以使用:。例如,如果您想删除 var2var3 和介于两者之间的 所有 变量,则只剩下 var1

    df2 <- df1 %>% select(-c(var2:var3) )  
    df2
    #        var1
    #1 0.75879754
    #2 0.31168919
    #3 0.03983268
    

    【讨论】:

    • 不要忘记select() 带来的所有其他机会,例如contains()matches(),它们也接受正则表达式。
    【解决方案9】:

    Dplyr 解决方案

    我怀疑这会在这里引起很多关注,但是如果您有一个要删除的列列表,并且您想在 dplyr 链中执行此操作,我在 select 子句中使用 one_of()

    这是一个简单的、可重现的例子:

    undesired <- c('mpg', 'cyl', 'hp')
    
    mtcars <- mtcars %>%
      select(-one_of(undesired))
    

    可以通过运行?one_of 或在此处找到文档:

    http://genomicsclass.github.io/book/pages/dplyr_tutorial.html

    【讨论】:

      【解决方案10】:

      另一种可能性:

      df <- df[, setdiff(names(df), c("a", "c"))]
      

      df <- df[, grep('^(a|c)$', names(df), invert=TRUE)]
      

      【讨论】:

      • 太糟糕了,这没有得到更多的支持,因为使用setdiff 是最佳选择,尤其是在大量列的情况下。
      • 另一个角度:df &lt;- df[ , -which(grepl('a|c', names(df)))]
      【解决方案11】:
      DF <- data.frame(
        x=1:10,
        y=10:1,
        z=rep(5,10),
        a=11:20
      )
      DF
      

      输出:

          x  y z  a
      1   1 10 5 11
      2   2  9 5 12
      3   3  8 5 13
      4   4  7 5 14
      5   5  6 5 15
      6   6  5 5 16
      7   7  4 5 17
      8   8  3 5 18
      9   9  2 5 19
      10 10  1 5 20
      

      DF[c("a","x")] <- list(NULL)
      

      输出:

              y z
          1  10 5
          2   9 5
          3   8 5
          4   7 5
          5   6 5
          6   5 5
          7   4 5
          8   3 5    
          9   2 5
          10  1 5
      

      【讨论】:

        【解决方案12】:

        出于兴趣,这标志着 R 的一种奇怪的多重语法不一致。例如给定一个两列数据框:

        df <- data.frame(x=1, y=2)
        

        这给出了一个数据框

        subset(df, select=-y)
        

        但这给出了一个向量

        df[,-2]
        

        这一切都在?[ 中进行了解释,但这并不是完全预期的行为。好吧,至少对我来说不是……

        【讨论】:

          【解决方案13】:

          这是一个dplyr 的方法:

          #df[ -c(1,3:6, 12) ]  # original
          df.cut <- df %>% select(-col.to.drop.1, -col.to.drop.2, ..., -col.to.drop.6)  # with dplyr::select()
          

          我喜欢这样,因为它在没有注释的情况下易于阅读和理解,并且对数据框中的列位置变化具有鲁棒性。它还遵循使用- 删除元素的矢量化习语。

          【讨论】:

          • 此外,(1) 用户想要替换原始 df (2) magrittr 有 %&lt;&gt;% 运算符来替换输入对象,它可以简化为 df %&lt;&gt;% select(-col.to.drop.1, -col.to.drop.2, ..., -col.to.drop.6)
          • 如果您有很长的列要删除,使用dplyr,将它们分组并只放一个减号可能会更容易:df.cut &lt;- df %&gt;% select(-c(col.to.drop.1, col.to.drop.2, ..., col.to.drop.n))
          【解决方案14】:

          我一直认为一定有更好的成语,但是对于按名称减去列,我倾向于执行以下操作:

          df <- data.frame(a=1:10, b=1:10, c=1:10, d=1:10)
          
          # return everything except a and c
          df <- df[,-match(c("a","c"),names(df))]
          df
          

          【讨论】:

          • 否定匹配不是一个好主意 - df[,-match(c("e","f"),names(df))]
          • .@JDLong - 如果我想删除列名以- 开头的列怎么办?
          【解决方案15】:

          Bernd Bischl 的 BBmisc 包中有一个名为 dropNamed() 的函数正是这样做的。

          BBmisc::dropNamed(df, "x")
          

          优点是它避免了重复数据框参数,因此适用于magrittr 中的管道(就像dplyr 方法一样):

          df %>% BBmisc::dropNamed("x")
          

          【讨论】:

            【解决方案16】:

            如果您不想使用上述@hadley 的另一种解决方案:如果“COLUMN_NAME”是您要删除的列的名称:

            df[,-which(names(df) == "COLUMN_NAME")]
            

            【讨论】:

            • (1) 问题是一次删除多个列。 (2) 如果COLUMN_NAME 不在df 中,它将不起作用(检查自己:df&lt;-data.frame(a=1,b=2))。 (3)df[,names(df) != "COLUMN_NAME"]更简单,不吃(2)
            • 您能否提供有关此答案的更多信息?
            【解决方案17】:

            除了前面的答案中演示的 select(-one_of(drop_col_names)) 之外,还有几个其他 dplyr 选项用于使用 select() 删除列,这些选项不涉及定义所有特定列名(使用 dplyr starwars 示例数据来处理列中的某些变化名称):

            library(dplyr)
            starwars %>% 
              select(-(name:mass)) %>%        # the range of columns from 'name' to 'mass'
              select(-contains('color')) %>%  # any column name that contains 'color'
              select(-starts_with('bi')) %>%  # any column name that starts with 'bi'
              select(-ends_with('er')) %>%    # any column name that ends with 'er'
              select(-matches('^f.+s$')) %>%  # any column name matching the regex pattern
              select_if(~!is.list(.)) %>%     # not by column name but by data type
              head(2)
            
            # A tibble: 2 x 2
            homeworld species
              <chr>     <chr>  
            1 Tatooine  Human  
            2 Tatooine  Droid 
            

            如果您需要删除数据框中可能存在或不存在的列,这里使用 select_if() 略有不同,与使用 one_of() 不同,如果列名称不存在,则不会引发 Unknown columns: 警告.在此示例中,“bad_column”不是数据框中的列:

            starwars %>% 
              select_if(!names(.) %in% c('height', 'mass', 'bad_column'))
            

            【讨论】:

              【解决方案18】:

              提供要删除的数据框和一串逗号分隔的名称

              remove_features <- function(df, features) {
                rem_vec <- unlist(strsplit(features, ', '))
                res <- df[,!(names(df) %in% rem_vec)]
                return(res)
              }
              

              用法

              remove_features(iris, "Sepal.Length, Petal.Width")
              

              【讨论】:

                【解决方案19】:

                在数据框中按列名删除和删除列。

                A <- df[ , c("Name","Name1","Name2","Name3")]
                

                【讨论】:

                  【解决方案20】:

                  使用which 查找要删除的列的索引。给这些索引一个负号 (*-1)。然后对这些值进行子集化,这将从数据框中删除它们。这是一个例子。

                  DF <- data.frame(one=c('a','b'), two=c('c', 'd'), three=c('e', 'f'), four=c('g', 'h'))
                  DF
                  #  one two three four
                  #1   a   d     f    i
                  #2   b   e     g    j
                  
                  DF[which(names(DF) %in% c('two','three')) *-1]
                  #  one four
                  #1   a    g
                  #2   b    h
                  

                  【讨论】:

                    【解决方案21】:

                    如果您有一个很大的data.frame 并且内存不足,请使用[ . . . .rm and within删除data.frame 的列,因为 subset 当前 (R 3.6.2) 使用更多内存 - 除了手册提示以交互方式使用 subset

                    getData <- function() {
                      n <- 1e7
                      set.seed(7)
                      data.frame(a = runif(n), b = runif(n), c = runif(n), d = runif(n))
                    }
                    
                    DF <- getData()
                    tt <- sum(.Internal(gc(FALSE, TRUE, TRUE))[13:14])
                    DF <- DF[setdiff(names(DF), c("a", "c"))] ##
                    #DF <- DF[!(names(DF) %in% c("a", "c"))] #Alternative
                    #DF <- DF[-match(c("a","c"),names(DF))]  #Alternative
                    sum(.Internal(gc(FALSE, FALSE, TRUE))[13:14]) - tt
                    #0.1 MB are used
                    
                    DF <- getData()
                    tt <- sum(.Internal(gc(FALSE, TRUE, TRUE))[13:14])
                    DF <- subset(DF, select = -c(a, c)) ##
                    sum(.Internal(gc(FALSE, FALSE, TRUE))[13:14]) - tt
                    #357 MB are used
                    
                    DF <- getData()
                    tt <- sum(.Internal(gc(FALSE, TRUE, TRUE))[13:14])
                    DF <- within(DF, rm(a, c)) ##
                    sum(.Internal(gc(FALSE, FALSE, TRUE))[13:14]) - tt
                    #0.1 MB are used
                    
                    DF <- getData()
                    tt <- sum(.Internal(gc(FALSE, TRUE, TRUE))[13:14])
                    DF[c("a", "c")]  <- NULL ##
                    sum(.Internal(gc(FALSE, FALSE, TRUE))[13:14]) - tt
                    #0.1 MB are used
                    

                    【讨论】:

                      【解决方案22】:

                      另一个尚未发布的data.table 选项是使用特殊动词.SD,它代表数据的子集。与 .SDcols 参数一起,您可以按名称或索引选择/删除列。

                      require(data.table)
                      # data
                      dt = data.table(
                        A = LETTERS[1:5],
                        B = 1:5,
                        C = rep(TRUE, 5)
                      )
                      # delete B
                      dt[ , .SD, .SDcols =! 'B' ]
                      # delete all matches (i.e. all columns)
                      cols = grep('[A-Z]+', names(dt), value = TRUE)
                      dt[ , .SD, .SDcols =! cols ]
                      

                      可以在 data.table 中找到此类任务的所有选项的摘要here

                      【讨论】:

                        【解决方案23】:

                        有很多方法可以做...

                        选项 1:

                        df[ , -which(names(df) %in% c("name1","name2"))]
                        

                        选项 2:

                        df[!names(df) %in% c("name1", "name2")]
                        

                        选项 3:

                        subset(df, select=-c(name1,name2))
                        

                        【讨论】:

                          【解决方案24】:
                          df <- data.frame(
                          +   a=1:5,
                          +   b=6:10,
                          +   c=rep(22,5),
                          +   d=round(runif(5)*100, 2),
                          +   e=round(runif(5)*100, 2),
                          +   f=round(runif(5)*100, 2),
                          +   g=round(runif(5)*100, 2),
                          +   h=round(runif(5)*100, 2)
                          + )
                          > df
                            a  b  c     d     e     f     g     h
                          1 1  6 22 76.31 39.96 66.62 72.75 73.14
                          2 2  7 22 53.41 94.85 96.02 97.31 85.32
                          3 3  8 22 98.29 38.95 12.61 29.67 88.45
                          4 4  9 22 20.04 53.53 83.07 77.50 94.99
                          5 5 10 22  5.67  0.42 15.07 59.75 31.21
                          
                          > # remove cols: d g h
                          > newDf <- df[, c(1:3, 5), drop=TRUE]
                          > newDf
                            a  b  c     e
                          1 1  6 22 39.96
                          2 2  7 22 94.85
                          3 3  8 22 38.95
                          4 4  9 22 53.53
                          5 5 10 22  0.42
                          

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 2011-07-11
                            • 2012-07-18
                            • 1970-01-01
                            相关资源
                            最近更新 更多