首页 文章

如何在数据框中按名称删除列

提问于
浏览
240

我有一个大型数据集,我想阅读特定列或删除所有其他列 .

data <- read.dta("file.dta")

我选择了我不感兴趣的列:

var.out <- names(data)[!names(data) %in% c("iden", "name", "x_serv", "m_serv")]

而且我想做的事情如下:

for(i in 1:length(var.out)) {
   paste("data$", var.out[i], sep="") <- NULL
}

删除所有不需要的列 . 这是最佳解决方案吗?

10 回答

  • 1

    不要使用 -which() ,这是非常危险的 . 考虑:

    dat <- data.frame(x=1:5, y=2:6, z=3:7, u=4:8)
    dat[ , -which(names(dat) %in% c("z","u"))] ## works as expected
    dat[ , -which(names(dat) %in% c("foo","bar"))] ## deletes all columns! Probably not what you wanted...
    

    而是使用子集或 ! 函数:

    dat[ , !names(dat) %in% c("z","u")] ## works as expected
    dat[ , !names(dat) %in% c("foo","bar")] ## returns the un-altered data.frame. Probably what you want
    

    我从痛苦的经历中学到了这一点 . 不要过度使用 which()

  • 1

    您也可以尝试 dplyr 包:

    R> df <- data.frame(x=1:5, y=2:6, z=3:7, u=4:8)
    R> df
      x y z u
    1 1 2 3 4
    2 2 3 4 5
    3 3 4 5 6
    4 4 5 6 7
    5 5 6 7 8
    R> library(dplyr)
    R> dplyr::select(df2, -c(x, y))  # remove columns x and y
      z u
    1 3 4
    2 4 5
    3 5 6
    4 6 7
    5 7 8
    
  • 6

    这是一个快速的解决方案 . 比如,你有一个数据框X,有三列A,B和C:

    > X<-data.frame(A=c(1,2),B=c(3,4),C=c(5,6))
    > X
      A B C
    1 1 3 5
    2 2 4 6
    

    如果我想删除一个列,比如B,只需在colnames上使用grep来获取列索引,然后可以使用该索引来省略该列 .

    > X<-X[,-grep("B",colnames(X))]
    

    您的新X数据框将如下所示(此时没有B列):

    > X
      A C
    1 1 5
    2 2 6
    

    grep的美妙之处在于您可以指定与正则表达式匹配的多个列 . 如果我有五列X(A,B,C,D,E):

    > X<-data.frame(A=c(1,2),B=c(3,4),C=c(5,6),D=c(7,8),E=c(9,10))
    > X
      A B C D  E
    1 1 3 5 7  9
    2 2 4 6 8 10
    

    取出B栏和D栏:

    > X<-X[,-grep("B|D",colnames(X))]
    > X
      A C  E
    1 1 5  9
    2 2 6 10
    

    编辑:考虑Matthew Lundberg在下面的评论中的grepl建议:

    > X<-data.frame(A=c(1,2),B=c(3,4),C=c(5,6),D=c(7,8),E=c(9,10))
    > X
      A B C D  E
    1 1 3 5 7  9
    2 2 4 6 8 10
    > X<-X[,!grepl("B|D",colnames(X))]
    > X
      A C  E
    1 1 5  9
    2 2 6 10
    

    如果我尝试删除一个不存在的列,则不会发生任何事情:

    > X<-X[,!grepl("G",colnames(X))]
    > X
      A C  E
    1 1 5  9
    2 2 6 10
    
  • 40
    df2 <- df[!names(df) %in% c("c1", "c2")]
    
  • 101

    我将代码更改为:

    # read data
    dat<-read.dta("file.dta")
    
    # vars to delete
    var.in<-c("iden", "name", "x_serv", "m_serv")
    
    # what I'm keeping
    var.out<-setdiff(names(dat),var.in)
    
    # keep only the ones I want       
    dat <- dat[var.out]
    

    无论如何,朱巴的答案是我的问题的最佳解决方案!

  • 1

    您应该使用索引或 subset 函数 . 例如 :

    R> df <- data.frame(x=1:5, y=2:6, z=3:7, u=4:8)
    R> df
      x y z u
    1 1 2 3 4
    2 2 3 4 5
    3 3 4 5 6
    4 4 5 6 7
    5 5 6 7 8
    

    然后,您可以在列索引中使用 which 函数和 - 运算符:

    R> df[ , -which(names(df) %in% c("z","u"))]
      x y
    1 1 2
    2 2 3
    3 3 4
    4 4 5
    5 5 6
    

    或者,更简单的是,使用 subset 函数的 select 参数:然后可以直接在列名矢量上使用 - 运算符,甚至可以省略名称周围的引号!

    R> subset(df, select=-c(z,u))
      x y
    1 1 2
    2 2 3
    3 3 4
    4 4 5
    5 5 6
    

    请注意,您还可以选择所需的列,而不是删除其他列:

    R> df[ , c("x","y")]
      x y
    1 1 2
    2 2 3
    3 3 4
    4 4 5
    5 5 6
    
    R> subset(df, select=c(x,y))
      x y
    1 1 2
    2 2 3
    3 3 4
    4 4 5
    5 5 6
    
  • -1

    由于声誉得分低,我无法在评论中回答您的问题 .

    下一个代码会给你一个错误,因为paste函数返回一个字符串

    for(i in 1:length(var.out)) {
       paste("data$", var.out[i], sep="") <- NULL
    }
    

    这是一个可能的解决方案:

    for(i in 1:length(var.out)) {
    
      text_to_source <- paste0 ("data$", var.out[i], "<- NULL") # Write a line of your
                                                      # code like a character string
      eval (parse (text=text_to_source)) # Source a text that contains a code
    }
    

    或者只是做:

    for(i in 1:length(var.out)) {
      data[var.out[i]] <- NULL
    }
    
  • 311

    First ,如果使用相同的数据帧,可以使用直接索引(使用布尔值向量)而不是重新访问列名;如Ista所指出的那样更安全,写入和执行速度更快 . 所以你只需要:

    var.out.bool <- !names(data) %in% c("iden", "name", "x_serv", "m_serv")
    

    然后,只需重新分配数据:

    data <- data[,var.out.bool] # or...
    data <- data[,var.out.bool, drop = FALSE] # You will need this option to avoid the conversion to an atomic vector if there is only one column left
    

    Second ,写入速度更快,可以直接为要删除的列分配NULL:

    data[c("iden", "name", "x_serv", "m_serv")] <- list(NULL) # You need list() to respect the target structure.
    

    Finally ,您可以使用subset(),但它不能真正用在代码中(甚至帮助文件也会对其进行警告) . 具体来说,问题是如果你想直接使用susbset()的drop特性,你需要在没有引号的情况下编写与列名对应的表达式:

    subset( data, select = -c("iden", "name", "x_serv", "m_serv") ) # WILL NOT WORK
    subset( data, select = -c(iden, name, x_serv, m_serv) ) # WILL
    

    As a bonus ,这里是不同选项的小基准,清楚地表明子集较慢,而第一个重新分配方法更快:

    re_assign(dtest, drop_vec)  46.719  52.5655  54.6460  59.0400  1347.331
                                          null_assign(dtest, drop_vec)  74.593  83.0585  86.2025  94.0035  1476.150
                   subset(dtest, select = !names(dtest) %in% drop_vec) 106.280 115.4810 120.3435 131.4665 65133.780
     subset(dtest, select = names(dtest)[!names(dtest) %in% drop_vec]) 108.611 119.4830 124.0865 135.4270  1599.577
                                      subset(dtest, select = -c(x, y)) 102.026 111.2680 115.7035 126.2320  1484.174
    

    Microbench graph

    Code 如下:

    dtest <- data.frame(x=1:5, y=2:6, z = 3:7)
    drop_vec <- c("x", "y")
    
    null_assign <- function(df, names) {
      df[names] <- list(NULL)
      df
    }
    
    re_assign <- function(df, drop) {
      df <- df [, ! names(df) %in% drop, drop = FALSE]
      df
    }
    
    res <- microbenchmark(
      re_assign(dtest,drop_vec),
      null_assign(dtest,drop_vec),
      subset(dtest, select = ! names(dtest) %in% drop_vec),
      subset(dtest, select = names(dtest)[! names(dtest) %in% drop_vec]),
      subset(dtest, select = -c(x, y) ),
    times=5000)
    
    plt <- ggplot2::qplot(y=time, data=res[res$time < 1000000,], colour=expr)
    plt <- plt + ggplot2::scale_y_log10() + 
      ggplot2::labs(colour = "expression") + 
      ggplot2::scale_color_discrete(labels = c("re_assign", "null_assign", "subset_bool", "subset_names", "subset_drop")) +
      ggplot2::theme_bw(base_size=16)
    print(plt)
    
  • 18

    我试图在使用包 data.table 时删除一列,并得到了意想不到的结果 . 我认为以下可能值得发布 . 只是一点警示 .

    [马修编辑......]

    DF = read.table(text = "
         fruit state grade y1980 y1990 y2000
         apples Ohio   aa    500   100   55
         apples Ohio   bb      0     0   44
         apples Ohio   cc    700     0   33
         apples Ohio   dd    300    50   66
    ", sep = "", header = TRUE, stringsAsFactors = FALSE)
    
    DF[ , !names(DF) %in% c("grade")]   # all columns other than 'grade'
       fruit state y1980 y1990 y2000
    1 apples  Ohio   500   100    55
    2 apples  Ohio     0     0    44
    3 apples  Ohio   700     0    33
    4 apples  Ohio   300    50    66
    
    library('data.table')
    DT = as.data.table(DF)
    
    DT[ , !names(dat4) %in% c("grade")]    # not expected !! not the same as DF !!
    [1]  TRUE  TRUE FALSE  TRUE  TRUE  TRUE
    
    DT[ , !names(DT) %in% c("grade"), with=FALSE]    # that's better
        fruit state y1980 y1990 y2000
    1: apples  Ohio   500   100    55
    2: apples  Ohio     0     0    44
    3: apples  Ohio   700     0    33
    4: apples  Ohio   300    50    66
    

    基本上, data.table 的语法与 data.frame 不完全相同 . 实际上存在很多差异,请参阅FAQ 1.1和FAQ 2.17 . 你被警告了!

  • 5

    这是另一种可能对其他人有帮助的解决方案 . 下面的代码从大型数据集中选择少量行和列 . 除了我使用粘贴函数选择一组名称按顺序编号的列之外,这些列在juba的答案中被选中:

    df = read.table(text = "
    
    state county city  region  mmatrix  X1 X2 X3    A1     A2     A3      B1     B2     B3      C1      C2      C3
    
      1      1     1      1     111010   1  0  0     2     20    200       4      8     12      NA      NA      NA
      1      2     1      1     111010   1  0  0     4     NA    400       5      9     NA      NA      NA      NA
      1      1     2      1     111010   1  0  0     6     60     NA      NA     10     14      NA      NA      NA
      1      2     2      1     111010   1  0  0    NA     80    800       7     11     15      NA      NA      NA
    
      1      1     3      2     111010   0  1  0     1      2      1       2      2      2      10      20      30
      1      2     3      2     111010   0  1  0     2     NA      1       2      2     NA      40      50      NA
      1      1     4      2     111010   0  1  0     1      1     NA      NA      2      2      70      80      90
      1      2     4      2     111010   0  1  0    NA      2      1       2      2     10     100     110     120
    
      1      1     1      3     010010   0  0  1    10     20     10     200    200    200       1       2       3
      1      2     1      3     001000   0  0  1    20     NA     10     200    200    200       4       5       9
      1      1     2      3     101000   0  0  1    10     10     NA     200    200    200       7       8      NA
      1      2     2      3     011010   0  0  1    NA     20     10     200    200    200      10      11      12
    
    ", sep = "", header = TRUE, stringsAsFactors = FALSE)
    df
    
    df2 <- df[df$region == 2, names(df) %in% c(paste("C", seq_along(1:3), sep=''))]
    df2
    
    #    C1  C2  C3
    # 5  10  20  30
    # 6  40  50  NA
    # 7  70  80  90
    # 8 100 110 120
    

相关问题