Letakkan kolom bingkai data berdasarkan nama

874

Saya memiliki sejumlah kolom yang ingin saya hapus dari bingkai data. Saya tahu bahwa kami dapat menghapusnya secara individual menggunakan sesuatu seperti:

df$x <- NULL

Tetapi saya berharap untuk melakukan ini dengan lebih sedikit perintah.

Juga, saya tahu bahwa saya dapat menjatuhkan kolom menggunakan pengindeksan bilangan bulat seperti ini:

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

Tetapi saya khawatir bahwa posisi relatif dari variabel saya dapat berubah.

Mengingat betapa kuatnya R, saya pikir mungkin ada cara yang lebih baik daripada menjatuhkan setiap kolom satu per satu.

Btibert3
sumber
13
Bisakah seseorang menjelaskan kepada saya mengapa R tidak memiliki sesuatu yang sederhana seperti df#drop(var_name), dan sebagai gantinya, kita perlu melakukan pekerjaan rumit ini?
ifly6
2
@ ifly6 Fungsi 'subset ()' di R adalah tentang pelit sebagai fungsi 'drop ()' di Python, kecuali Anda tidak perlu menentukan argumen sumbu ... Saya setuju bahwa itu menjengkelkan bahwa tidak dapat hanya menjadi satu, kata kunci / sintaks yang paling mudah diimplementasikan di seluruh papan untuk sesuatu yang sangat mendasar seperti menjatuhkan kolom.
Paul Sochacki

Jawaban:

912

Anda dapat menggunakan daftar nama sederhana:

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

Atau, sebagai alternatif, Anda dapat membuat daftar untuk disimpan dan merujuknya dengan nama:

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

EDIT: Bagi yang masih belum terbiasa dengan dropargumen fungsi pengindeksan, jika Anda ingin menyimpan satu kolom sebagai bingkai data, Anda harus:

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

drop=TRUE(atau tidak menyebutkannya) akan menjatuhkan dimensi yang tidak perlu, dan karenanya mengembalikan vektor dengan nilai kolom y.

Joris Meys
sumber
19
fungsi subset berfungsi lebih baik karena tidak akan mengubah bingkai data dengan satu kolom menjadi vektor
mut1na
3
@ mut1na periksa argumen drop = FALSE dari fungsi pengindeksan.
Joris Meys
4
Tidak seharusnya menjadi DF[,keeps]bukan DF[keeps]?
lindelof
8
@ lindelof No. Itu bisa, tetapi kemudian Anda harus menambahkan drop = FALSE untuk menjaga R dari mengubah bingkai data Anda menjadi vektor jika Anda hanya memilih satu kolom. Jangan lupa bahwa frame data adalah daftar, jadi pemilihan daftar (satu dimensi seperti yang saya lakukan) berfungsi dengan baik dan selalu mengembalikan daftar. Atau bingkai data dalam hal ini, itulah sebabnya saya lebih suka menggunakannya.
Joris Meys
7
@ DjayOhri Ya, tentu saja. Tanpa koma, Anda menggunakan "daftar" cara memilih, yang berarti bahwa bahkan ketika Anda mengekstrak satu kolom, Anda masih mendapatkan bingkai data yang dikembalikan. Jika Anda menggunakan cara "matriks", seperti yang Anda lakukan, Anda harus menyadari bahwa jika Anda hanya memilih satu kolom, Anda mendapatkan vektor, bukan bingkai data. Untuk menghindarinya, Anda perlu menambahkan drop = FALSE. Seperti dijelaskan dalam jawaban saya, dan dalam komentar tepat di atas Anda ...
Joris Meys
453

Ada juga subsetperintahnya, berguna jika Anda tahu kolom mana yang Anda inginkan:

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

DIPERBARUI setelah komentar oleh @hadley: Untuk menjatuhkan kolom a, c Anda dapat melakukan:

df <- subset(df, select = -c(a, c))
Prasad Chalasani
sumber
3
Saya benar-benar berharap subsetfungsi R memiliki opsi seperti "allbut = FALSE", yang "membalikkan" pilihan ketika diatur ke TRUE, yaitu mempertahankan semua kolom kecuali yang ada dalam selectdaftar.
Prasad Chalasani
4
@prasad, lihat jawaban @ joris di bawah. Subset tanpa kriteria subset sedikit berlebihan. Coba saja:df[c("a", "c")]
JD Long
@JD Saya tahu itu, tapi saya suka kenyamanan sintaksis dari subsetperintah di mana Anda tidak perlu memberi tanda kutip di sekitar nama kolom - Saya kira saya tidak keberatan mengetik beberapa karakter tambahan hanya untuk menghindari mengutip nama :)
Prasad Chalasani
11
Perhatikan bahwa Anda tidak boleh menggunakan subsetdi dalam fungsi lain.
Ari B. Friedman
196
within(df, rm(x))

mungkin paling mudah, atau untuk banyak variabel:

within(df, rm(x, y))

Atau jika Anda berurusan dengan data.tables (per Bagaimana Anda menghapus kolom dengan nama di data.table? ):

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

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

atau untuk beberapa variabel

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

dt[, !c("x", "y")]
Max Ghenis
sumber
26
within(df, rm(x))adalah jauh solusi yang bersih. Mengingat bahwa ini adalah suatu kemungkinan, setiap jawaban lain tampaknya tidak perlu rumit oleh urutan besarnya.
Miles Erickson
2
Perhatikan bahwa within(df, rm(x))akan tidak bekerja jika ada duplikat kolom bernama xdi df.
MichaelChirico
2
@MichaelChirico untuk mengklarifikasi, itu tidak menghapus tetapi tampaknya mengubah nilai data. Seseorang memiliki masalah yang lebih besar jika ini masalahnya, tetapi inilah contohnya: df <- data.frame(x = 1, y = 2); names(df) <- c("x", "x"); within(df, rm(x))pengembalian data.frame(x = 2, x = 2).
Max Ghenis
1
@MilesErickson Masalahnya adalah Anda mengandalkan fungsi within()yang kuat tetapi juga menggunakan NSE. Catatan pada halaman bantuan menyatakan dengan jelas bahwa untuk pemrograman, perawatan yang memadai harus digunakan.
Joris Meys
@MilesErickson Seberapa seringkah seseorang menemukan dataframe dengan nama duplikat di dalamnya?
HSchmale
115

Anda bisa menggunakan %in%seperti ini:

df[, !(colnames(df) %in% c("x","bar","foo"))]
Joshua Ulrich
sumber
1
Apakah saya kehilangan sesuatu, atau apakah ini solusi yang sama efektifnya dengan bagian pertama dari jawaban Joris? DF[ , !(names(DF) %in% drops)]
Daniel Fletcher
9
@DanielFletcher: sama saja. Lihatlah cap waktu pada jawaban. Kami menjawab pada saat yang sama ... 5 tahun yang lalu. :)
Joshua Ulrich
5
Pedas. identical(post_time_1, post_time_2) [1] TRUE = D
Daniel Fletcher
54

daftar (NULL) juga berfungsi:

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"
Vincent
sumber
1
Cemerlang! Ini memperluas penugasan NULL ke satu kolom dengan cara alami, dan (tampaknya) menghindari penyalinan (meskipun saya tidak tahu apa yang terjadi di bawah tenda sehingga mungkin tidak lebih efisien dalam penggunaan memori ... tapi menurut saya jelas lebih efisien secara sintaksis.)
c-urchin
6
Anda tidak perlu daftar (NULL), NULL sudah cukup. mis: dat [, 4] = NULL
CousinCocaine
8
Pertanyaan OP adalah bagaimana cara menghapus banyak kolom. dat [, 4: 5] <- NULL tidak akan berfungsi. Di situlah daftar (NULL) masuk. Ini berfungsi untuk 1 atau lebih kolom.
Vincent
Ini juga tidak berfungsi ketika mencoba untuk menghapus nama kolom yang digandakan.
MichaelChirico
@MichaelChirico Bekerja dengan baik untuk saya. Beri label jika Anda ingin menghapus kolom pertama dengan nama yang sama atau berikan indeks untuk setiap kolom yang ingin Anda hapus. Jika Anda memiliki contoh yang tidak berfungsi, saya akan tertarik melihatnya. Mungkin mempostingnya sebagai pertanyaan baru?
Vincent
42

Jika Anda ingin menghapus kolom dengan referensi dan menghindari penyalinan internal yang terkait dengan data.framesmaka Anda dapat menggunakan data.tablepaket dan fungsinya:=

Anda dapat melewatkan nama-nama vektor karakter ke sisi kiri menu := operator, dan NULLsebagai 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]

Jika Anda ingin menentukan nama sebagai vektor karakter di luar panggilan [, bungkus nama objek di dalam ()atau {}untuk memaksa LHS dievaluasi dalam ruang lingkup panggilan bukan sebagai nama dalam ruang lingkupDT .

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.   

Anda juga dapat menggunakan set, yang menghindari overhead [.data.table, dan juga berfungsi untukdata.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)
mnel
sumber
41

Ada strategi yang berpotensi lebih kuat berdasarkan fakta bahwa grep () akan mengembalikan vektor numerik. Jika Anda memiliki daftar panjang variabel seperti yang saya lakukan di salah satu dataset saya, beberapa variabel yang berakhiran ".A" dan yang lainnya berakhiran ".B" dan Anda hanya ingin yang berakhiran ".A" (bersama dengan semua variabel yang tidak cocok dengan pola mana pun, lakukan ini:

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

Untuk kasus yang dihadapi, menggunakan contoh Joris Meys, mungkin tidak sekompak, tetapi akan menjadi:

DF <- DF[, -grep( paste("^",drops,"$", sep="", collapse="|"), names(DF) )]
IRTFM
sumber
1
Jika kita mendefinisikan dropsdi tempat pertama sebagai paste0("^", drop_cols, "$"), ini menjadi jauh lebih baik (baca: lebih kompak) dengan sapply:DF[ , -sapply(drops, grep, names(DF))]
MichaelChirico
30

dplyrJawaban lain Jika variabel Anda memiliki beberapa struktur penamaan yang sama, Anda dapat mencoba starts_with(). Sebagai contoh

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

Jika Anda ingin menjatuhkan urutan variabel dalam bingkai data, Anda bisa menggunakan :. Sebagai contoh jika Anda ingin menjatuhkan var2, var3dan semua variabel di antara, Anda baru saja ditinggalkan dengan var1:

df2 <- df1 %>% select(-c(var2:var3) )  
df2
#        var1
#1 0.75879754
#2 0.31168919
#3 0.03983268
Pat W.
sumber
1
Tidak melupakan semua peluang lain yang datang bersama select(), seperti contains()atau matches(), yang juga menerima regex.
ha_pu
23

Kemungkinan lain:

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

atau

df <- df[, grep('^(a|c)$', names(df), invert=TRUE)]
scentoni
sumber
2
Sayang sekali bahwa ini tidak lebih dipilih karena penggunaan setdiffyang optimal terutama dalam kasus jumlah kolom yang sangat besar.
ctbrown
Sudut lain dalam hal ini: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

Keluaran:

    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)

Keluaran:

        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
Kun Ren
sumber
23

Solusi Dplyr

Saya ragu ini akan mendapat banyak perhatian di sini, tetapi jika Anda memiliki daftar kolom yang ingin Anda hapus, dan Anda ingin melakukannya dalam dplyrrantai yang saya gunakan one_of()diselect klausa:

Berikut adalah contoh sederhana dan dapat direproduksi:

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

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

Dokumentasi dapat ditemukan dengan menjalankan ?one_ofatau di sini:

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

Pengguna632716
sumber
22

Karena ketertarikan, ini menandai salah satu inkonsistensi sintaksis ganda R yang aneh. Misalnya diberi kerangka data dua kolom:

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

Ini memberikan bingkai data

subset(df, select=-y)

tetapi ini memberikan vektor

df[,-2]

Ini semua dijelaskan ?[tetapi itu bukan perilaku yang diharapkan. Yah setidaknya tidak bagiku ...

jkeirstead
sumber
18

Ini dplyrcara untuk melakukannya:

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

Saya suka ini karena intuitif untuk membaca & memahami tanpa penjelasan dan kuat untuk kolom yang mengubah posisi dalam bingkai data. Ini juga mengikuti idiom vektor yang digunakan -untuk menghapus elemen.

c.gutierrez
sumber
Menambahkan ini bahwa (1) pengguna ingin mengganti df asli (2) magrittr memiliki %<>% operator untuk mengganti objek input itu dapat disederhanakan menjadidf %<>% select(-col.to.drop.1, -col.to.drop.2, ..., -col.to.drop.6)
Marek
1
Jika Anda memiliki daftar panjang kolom untuk dijatuhkan, dengan dplyr, mungkin akan lebih mudah untuk mengelompokkannya dan hanya menempatkan satu minus:df.cut <- df %>% select(-c(col.to.drop.1, col.to.drop.2, ..., col.to.drop.n))
iNyar
14

Saya terus berpikir harus ada idiom yang lebih baik, tetapi untuk pengurangan kolom dengan nama, saya cenderung melakukan hal berikut:

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
JD Long
sumber
4
Bukan ide yang baik untuk meniadakan pertandingan -df[,-match(c("e","f"),names(df))]
Hadley
. @ JDLong - Bagaimana jika saya ingin meletakkan kolom tempat nama kolom dimulai -?
Chetan Arvind Patil
12

Ada fungsi yang disebut dropNamed()dalam BBmiscpaket Bernd Bischl yang melakukan hal ini.

BBmisc::dropNamed(df, "x")

Keuntungannya adalah ia menghindari pengulangan argumen bingkai data dan karenanya cocok untuk disalurkan magrittr(seperti dplyrpendekatan):

df %>% BBmisc::dropNamed("x")
krlmlr
sumber
9

Solusi lain jika Anda tidak ingin menggunakan @ hadley di atas: Jika "COLUMN_NAME" adalah nama kolom yang ingin Anda jatuhkan:

df[,-which(names(df) == "COLUMN_NAME")]
Nick Keramaris
sumber
1
(1) Masalahnya adalah untuk menjatuhkan beberapa kolom sekaligus. (2) Ini tidak akan berfungsi jika COLUMN_NAMEtidak di df(periksa sendiri:) df<-data.frame(a=1,b=2). (3) df[,names(df) != "COLUMN_NAME"]lebih sederhana dan tidak menderita (2)
Marek
Bisakah Anda memberi lebih banyak informasi tentang jawaban ini?
Akash Nayak
8

Di luar yang select(-one_of(drop_col_names))diperlihatkan dalam jawaban sebelumnya, ada beberapa dplyropsi lain untuk menjatuhkan kolom menggunakan select()yang tidak melibatkan mendefinisikan semua nama kolom tertentu (menggunakan data sampel starwars dplyr untuk beberapa variasi dalam nama kolom):

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 

Jika Anda perlu menjatuhkan kolom yang mungkin atau mungkin tidak ada dalam bingkai data, inilah sedikit twist menggunakan select_if()yang tidak seperti menggunakan one_of()tidak akan memberikan Unknown columns:peringatan jika nama kolom tidak ada. Dalam contoh ini 'bad_column' bukan kolom di bingkai data:

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

Berikan bingkai data dan serangkaian nama yang dipisahkan koma untuk dihapus:

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

Penggunaan :

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

masukkan deskripsi gambar di sini

Berhubung dgn sibernetika
sumber
1

Temukan indeks kolom yang ingin Anda jatuhkan which. Berikan indeks ini tanda negatif ( *-1). Kemudian subset pada nilai-nilai itu, yang akan menghapusnya dari dataframe. Ini sebuah contoh.

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
Milan
sumber
1

Jika Anda memiliki data.framememori yang besar dan rendah [ . . . . atau rmdanwithin untuk menghapus kolom adata.frame , seperti subsetsaat ini (R 3.6.2) menggunakan lebih banyak memori - di samping petunjuk manual untuk menggunakan secara subsetinteraktif .

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
GKi
sumber