Thả cột khung dữ liệu theo tên


874

Tôi có một số cột mà tôi muốn xóa khỏi khung dữ liệu. Tôi biết rằng chúng ta có thể xóa chúng riêng lẻ bằng cách sử dụng một cái gì đó như:

df$x <- NULL

Nhưng tôi đã hy vọng làm điều này với ít lệnh hơn.

Ngoài ra, tôi biết rằng tôi có thể thả các cột bằng cách lập chỉ mục số nguyên như thế này:

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

Nhưng tôi lo ngại rằng vị trí tương đối của các biến của tôi có thể thay đổi.

Dựa vào mức độ mạnh mẽ của R, tôi nghĩ rằng có thể có một cách tốt hơn là bỏ từng cột một.


13
Ai đó có thể giải thích cho tôi tại sao R không có thứ gì đó đơn giản như thế không df#drop(var_name), và thay vào đó, chúng ta cần thực hiện những công việc phức tạp này?
ifly6

2
@ ifly6 Hàm 'subset ()' trong R gần giống như hàm 'drop ()' trong Python, ngoại trừ bạn không cần chỉ định đối số trục ... Tôi đồng ý rằng thật khó chịu khi không thể chỉ là một từ khóa, cú pháp / cú pháp dễ dàng được thực hiện trên bảng cho một cái gì đó cơ bản như bỏ một cột.
Paul Sochacki

Câu trả lời:


912

Bạn có thể sử dụng một danh sách tên đơn giản:

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)]

Hoặc, thay vào đó, bạn có thể lập danh sách những người cần giữ và tham khảo chúng theo tên:

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

EDIT: Đối với những người vẫn chưa làm quen với dropđối số của hàm lập chỉ mục, nếu bạn muốn giữ một cột làm khung dữ liệu, bạn thực hiện:

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

drop=TRUE(hoặc không đề cập đến nó) sẽ bỏ các kích thước không cần thiết và do đó trả về một vectơ với các giá trị của cột y.


19
hàm tập hợp con hoạt động tốt hơn vì nó sẽ không chuyển đổi khung dữ liệu với một cột thành một vectơ
mut1na

3
@ mut1na kiểm tra đối số drop = FALSE của hàm lập chỉ mục.
Joris Meys

4
Không nên DF[,keeps]thay thế DF[keeps]?
lindelof

8
@lindelof Không. Có thể, nhưng sau đó bạn phải thêm drop = FALSE để ngăn R chuyển đổi khung dữ liệu của bạn thành một vectơ nếu bạn chỉ chọn một cột duy nhất. Đừng quên rằng khung dữ liệu là danh sách, vì vậy lựa chọn danh sách (một chiều như tôi đã làm) hoạt động hoàn hảo và luôn trả về danh sách. Hoặc một khung dữ liệu trong trường hợp này, đó là lý do tại sao tôi thích sử dụng nó.
Joris Meys

7
@AjayOhri Vâng, nó sẽ. Nếu không có dấu phẩy, bạn sử dụng cách chọn "danh sách", điều đó có nghĩa là ngay cả khi bạn trích xuất một cột duy nhất, bạn vẫn nhận được khung dữ liệu được trả về. Nếu bạn sử dụng cách "ma trận", như bạn làm, bạn nên lưu ý rằng nếu bạn chỉ chọn một cột duy nhất, bạn sẽ có được một vectơ thay vì khung dữ liệu. Để tránh điều đó, bạn cần thêm drop = FALSE. Như đã giải thích trong câu trả lời của tôi, và trong phần bình luận ngay phía trên của bạn ...
Joris Meys 7/07/2015

453

Ngoài ra còn có subsetlệnh, hữu ích nếu bạn biết cột nào bạn muốn:

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

CẬP NHẬT sau khi nhận xét bởi @hadley: Để thả cột a, c bạn có thể làm:

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

3
Tôi thực sự muốn subsethàm R có một tùy chọn như "allbut = FALSE", "đảo ngược" lựa chọn khi được đặt thành TRUE, tức là giữ lại tất cả các cột trừ các cột trong selectdanh sách.
Prasad Chalasani

4
@prasad, xem @joris trả lời bên dưới. Một tập hợp con không có bất kỳ tiêu chí tập hợp con là một chút quá mức. Hãy thử một cách đơn giản:df[c("a", "c")]
JD Long

@JD Tôi biết điều đó, nhưng tôi thích sự tiện lợi cú pháp của subsetlệnh mà bạn không cần đặt dấu ngoặc kép quanh tên cột - Tôi đoán tôi không ngại gõ thêm một vài ký tự chỉ để tránh trích dẫn tên :)
Prasad Chalasani

11
Lưu ý rằng bạn không nên sử dụng subsetbên trong các chức năng khác.
Ari B. Friedman


196
within(df, rm(x))

có lẽ là dễ nhất hoặc cho nhiều biến:

within(df, rm(x, y))

Hoặc nếu bạn đang xử lý data.tables (mỗi Làm thế nào để bạn xóa một cột theo tên trong data.table? ):

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

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

hoặc cho nhiều biến

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

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

26
within(df, rm(x))bởi đến nay các giải pháp sạch. Cho rằng đây là một khả năng, mọi câu trả lời khác dường như phức tạp không cần thiết bởi một thứ tự cường độ.
Miles Erickson

2
Lưu ý rằng within(df, rm(x))sẽ không hoạt động nếu có các cột trùng lặp có tên xtrong df.
MichaelChirico

2
@MichaelChirico để làm rõ, nó không xóa nhưng dường như thay đổi giá trị của dữ liệu. Người ta có vấn đề lớn hơn nếu đây là trường hợp, nhưng đây là một ví dụ: df <- data.frame(x = 1, y = 2); names(df) <- c("x", "x"); within(df, rm(x))trả về data.frame(x = 2, x = 2).
Max Ghenis

1
@MilesErickson Vấn đề là bạn dựa vào một chức năng within()mạnh mẽ nhưng cũng sử dụng NSE. Lưu ý trên trang trợ giúp nêu rõ rằng để lập trình cần có sự quan tâm đầy đủ.
Joris Meys

@MilesErickson Bạn có thường xuyên gặp phải một khung dữ liệu có tên trùng lặp trong đó không?
HSchmale

115

Bạn có thể sử dụng %in%như thế này:

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

1
Tôi có thiếu điều gì không, hay đây có phải là giải pháp hiệu quả giống như phần đầu tiên trong câu trả lời của Joris không? DF[ , !(names(DF) %in% drops)]
Daniel Fletcher

9
@DanielFletcher: nó giống nhau. Nhìn vào dấu thời gian trên các câu trả lời. Chúng tôi đã trả lời cùng một lúc ... 5 năm trước. :)
Joshua Ulrich

5
Hạt dẻ. identical(post_time_1, post_time_2) [1] TRUE = D
Daniel Fletcher

54

danh sách (NULL) cũng hoạt động:

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"

1
Xuất sắc! Điều này mở rộng việc gán NULL thành một cột theo cách tự nhiên và (dường như) tránh sao chép (mặc dù tôi không biết điều gì xảy ra dưới mui xe nên có thể không hiệu quả hơn trong việc sử dụng bộ nhớ ... nhưng đối với tôi rõ ràng hiệu quả hơn về mặt cú pháp.)
c-nhím

6
Bạn không cần danh sách (NULL), NULL là đủ. ví dụ: dat [, 4] = NULL
CousinCocaine

8
Câu hỏi của OP là làm thế nào để xóa nhiều cột. dat [, 4: 5] <- NULL sẽ không hoạt động. Đó là nơi danh sách (NULL) xuất hiện. Nó hoạt động cho 1 hoặc nhiều cột.
Vincent

Điều này cũng không hoạt động khi cố gắng loại bỏ một tên cột trùng lặp.
MichaelChirico

@MichaelChirico Hoạt động tốt với tôi. Hoặc đưa ra một nhãn nếu bạn muốn xóa đầu tiên của các cột có cùng tên hoặc đưa ra các chỉ số cho mỗi cột bạn muốn xóa. Nếu bạn có một ví dụ nơi nó không hoạt động, tôi rất muốn thấy nó. Có lẽ gửi nó như một câu hỏi mới?
Vincent

42

Nếu bạn muốn xóa các cột theo tham chiếu và tránh sao chép nội bộ được liên kết với data.framesthì bạn có thể sử dụng data.tablegói và hàm:=

Bạn có thể chuyển tên vectơ ký tự sang phía bên trái của :=toán tử và NULLdưới dạng 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]

Nếu bạn muốn xác định trước các tên dưới dạng vectơ ký tự bên ngoài lệnh gọi [, hãy bọc tên của đối tượng ()hoặc {}buộc LHS được đánh giá trong phạm vi gọi không phải là tên trong phạm vi 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.   

Bạn cũng có thể sử dụng set, để tránh chi phí hoạt động[.data.table , và cũng hoạt động cho 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)

41

Có một chiến lược tiềm năng mạnh mẽ hơn dựa trên thực tế là grep () sẽ trả về một vectơ số. Nếu bạn có một danh sách dài các biến như tôi làm trong một trong các tập dữ liệu của tôi, một số biến kết thúc bằng ".A" và các biến khác kết thúc bằng ".B" và bạn chỉ muốn các biến kết thúc bằng ".A" (cùng với tất cả các biến không khớp với một trong hai mẫu, hãy làm điều này:

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

Đối với trường hợp trong tầm tay, sử dụng ví dụ của Joris Meys, nó có thể không nhỏ gọn, nhưng nó sẽ là:

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

1
Nếu chúng ta xác định dropsở vị trí đầu tiên là paste0("^", drop_cols, "$"), điều này sẽ trở nên đẹp hơn (đọc: nhỏ gọn hơn) với sapply:DF[ , -sapply(drops, grep, names(DF))]
MichaelChirico

30

Một dplyrcâu trả lời khác . Nếu các biến của bạn có một số cấu trúc đặt tên phổ biến, bạn có thể thử starts_with(). Ví dụ

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

Nếu bạn muốn thả một chuỗi các biến trong khung dữ liệu, bạn có thể sử dụng :. Ví dụ, nếu bạn muốn thả var2, var3tất cả các biến ở giữa, bạn sẽ chỉ được trái với var1:

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

1
Đừng quên tất cả các cơ hội khác đi kèm select(), chẳng hạn như contains(), hoặc matches()cũng chấp nhận regex.
ha_pu

23

Khả năng khác:

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

hoặc là

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

2
Quá tệ là điều này không được nâng cấp nhiều hơn vì sử dụng setdifflà tối ưu, đặc biệt trong trường hợp số lượng cột rất lớn.
ctbrown

Một góc khác về điều này:df <- df[ , -which(grepl('a|c', names(df)))]
Joe

23
DF <- data.frame(
  x=1:10,
  y=10:1,
  z=rep(5,10),
  a=11:20
)
DF

Đầu ra:

    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)

Đầu ra:

        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

23

Giải pháp Dplyr

Tôi nghi ngờ điều này sẽ nhận được nhiều sự chú ý ở đây, nhưng nếu bạn có một danh sách các cột mà bạn muốn xóa và bạn muốn thực hiện nó trong một dplyrchuỗi tôi sử dụng one_of()trong selectmệnh đề:

Đây là một ví dụ đơn giản, có thể tái tạo:

undesired <- c('mpg', 'cyl', 'hp')

mtcars <- mtcars %>%
  select(-one_of(undesired))

Tài liệu có thể được tìm thấy bằng cách chạy ?one_ofhoặc ở đây:

http://genomics class.github.io/book/pages/dplyr_tutorial.html


22

Không quan tâm, điều này đánh dấu một trong những mâu thuẫn nhiều cú pháp kỳ lạ của R. Ví dụ: đưa ra một khung dữ liệu hai cột:

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

Điều này cung cấp một khung dữ liệu

subset(df, select=-y)

nhưng điều này mang lại một vectơ

df[,-2]

Tất cả điều này được giải thích ?[nhưng đó không phải là hành vi dự kiến ​​chính xác. Ít nhất là không phải với tôi ...


18

Đây là một dplyrcách để đi về nó:

#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()

Tôi thích điều này bởi vì nó trực quan để đọc và hiểu mà không cần chú thích và mạnh mẽ cho các cột thay đổi vị trí trong khung dữ liệu. Nó cũng tuân theo thành ngữ vectorized sử dụng -để loại bỏ các phần tử.


Thêm vào đó (1) người dùng muốn thay thế df (2) magrittr ban đầu có %<>% toán tử để thay thế đối tượng đầu vào, nó có thể được đơn giản hóa thànhdf %<>% select(-col.to.drop.1, -col.to.drop.2, ..., -col.to.drop.6)
Marek

1
Nếu bạn có một danh sách dài các cột cần thả, với dplyr, có thể dễ dàng hơn để nhóm chúng và chỉ đặt một điểm trừ:df.cut <- df %>% select(-c(col.to.drop.1, col.to.drop.2, ..., col.to.drop.n))
iNyar

14

Tôi cứ nghĩ phải có một thành ngữ tốt hơn, nhưng để trừ các cột theo tên, tôi có xu hướng làm như sau:

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

4
Không phải là một ý tưởng tốt để phủ nhận trận đấu -df[,-match(c("e","f"),names(df))]
hadley

. @ JDLong - Điều gì xảy ra nếu tôi muốn thả cột trong đó tên cột bắt đầu bằng -?
Chetan Arvind Patil

12

Có một chức năng được gọi dropNamed()trong BBmiscgói của Bernd Bischl thực hiện chính xác điều này.

BBmisc::dropNamed(df, "x")

Ưu điểm là nó tránh lặp lại đối số khung dữ liệu và do đó phù hợp với đường ống trong magrittr(giống như các dplyrcách tiếp cận):

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

9

Một giải pháp khác nếu bạn không muốn sử dụng @ hadley ở trên: Nếu "COLUMN_NAME" là tên của cột bạn muốn thả:

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

1
(1) Vấn đề là thả nhiều cột cùng một lúc. (2) Nó sẽ không hoạt động nếu COLUMN_NAMEkhông có df(kiểm tra chính bạn df<-data.frame(a=1,b=2):). (3) df[,names(df) != "COLUMN_NAME"]đơn giản hơn và không bị (2)
Marek

Bạn có thể cung cấp thêm một số thông tin về câu trả lời này?
Akash Nayak

8

Ngoài việc select(-one_of(drop_col_names))thể hiện trong các câu trả lời trước đó, có một số dplyrtùy chọn khác để bỏ các cột sử dụng select()không liên quan đến việc xác định tất cả các tên cột cụ thể (sử dụng dữ liệu mẫu của dplyr starwars cho một số tên trong cột):

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 

Nếu bạn cần thả một cột có thể tồn tại hoặc không tồn tại trong khung dữ liệu, thì đây là một bước ngoặt nhẹ bằng cách sử dụng select_if()không giống như sử dụng one_of()sẽ không đưa ra Unknown columns:cảnh báo nếu tên cột không tồn tại. Trong ví dụ này, 'bad_column' không phải là một cột trong khung dữ liệu:

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

4

Cung cấp khung dữ liệu và một chuỗi các tên được phân tách bằng dấu phẩy để xóa:

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

Cách sử dụng :

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

nhập mô tả hình ảnh ở đây


1

Tìm chỉ mục của các cột bạn muốn thả bằng cách sử dụng which. Cho các chỉ số này một dấu âm ( *-1). Sau đó, tập hợp con trên các giá trị đó, sẽ loại bỏ chúng khỏi khung dữ liệu. Đây là một ví dụ.

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

1

Nếu bạn có dung lượng lớn data.framevà sử dụng ít bộ nhớ [ . . . . hoặc rmwithin để loại bỏ các cột của adata.frame , như subsethiện tại (R 3.6.2) bằng cách sử dụng nhiều bộ nhớ hơn - bên cạnh gợi ý của hướng dẫn sử dụng để sử dụng subsettương tác .

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.