Cara menjatuhkan kolom dengan nama dalam bingkai data

304

Saya memiliki kumpulan data besar dan saya ingin membaca kolom tertentu atau menjatuhkan yang lainnya.

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

Saya memilih kolom yang saya tidak tertarik:

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

dan daripada saya ingin melakukan sesuatu seperti:

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

untuk menjatuhkan semua kolom yang tidak diinginkan. Apakah ini solusi optimal?

leroux
sumber
1
tidur di atas masalah, saya berpikir yang subset(data, select=c(...))membantu dalam kasus saya untuk menjatuhkan vars. pertanyaannya terutama tentang paste("data$",var.out[i],sep="")bagian untuk mengakses kolom yang menarik di dalam loop. bagaimana saya bisa menempel atau entah bagaimana menulis nama kolom? Terima kasih kepada semua orang atas perhatian dan bantuan Anda
leroux
7
Kemungkinan duplikat kolom Drop dalam bingkai data R
jangorecki

Jawaban:

380

Anda harus menggunakan pengindeksan atau subsetfungsinya. Sebagai contoh :

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

Kemudian Anda dapat menggunakan whichfungsi dan -operator dalam indeksasi kolom:

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

Atau, lebih sederhana, gunakan selectargumen subsetfungsi: Anda kemudian dapat menggunakan -operator langsung pada vektor nama kolom, dan Anda bahkan dapat menghilangkan tanda kutip di sekitar nama!

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

Perhatikan bahwa Anda juga dapat memilih kolom yang Anda inginkan daripada menjatuhkan yang lain:

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
juba
sumber
2
yang selectargumen dari subsetfungsi melakukan pekerjaan yang sempurna! Juba terima kasih!
leroux
2
whichtidak perlu, lihat jawaban Ista. Tetapi bagian dengan -bagus! Tidak tahu itu!
TMS
5
subsetterlihat bagus, tetapi cara itu diam-diam menjatuhkan nilai yang hilang tampaknya cukup berbahaya bagi saya.
static_rtti
2
subsetmemang sangat nyaman, tetapi ingatlah untuk menghindari menggunakannya kecuali jika Anda menggunakan R secara interaktif. Lihat Peringatan dalam dokumentasi fungsi dan pertanyaan SO ini untuk lebih lanjut.
Waldir Leoncio
4
"Anda bahkan dapat menghilangkan tanda kutip di sekitar nama!", Anda benar-benar harus menghilangkan tanda kutip, jika tidak Anda akan mendapatkan argumen yang tidak valid untuk operator unary. Jika Anda memiliki karakter tertentu (misalnya "-") di nama Anda, Anda tidak dapat menggunakan metode ini sama sekali karena menjatuhkan tanda kutip akan menyebabkan R tidak dapat menguraikan kode Anda dengan benar.
oh54
122

Jangan gunakan -which()untuk ini, ini sangat berbahaya. Mempertimbangkan:

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...

Alih-alih gunakan subset atau !fungsi:

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

Saya telah belajar ini dari pengalaman yang menyakitkan. Jangan berlebihan which()!

Ista
sumber
31
setdiffjuga berguna:setdiff(names(dat), c("foo", "bar"))
Hadley
The setdiffproposal @hadley sangat baik untuk daftar panjang nama-nama.
JASC
48

Pertama , Anda dapat menggunakan pengindeksan langsung (dengan vektor booleans) alih-alih mengakses kembali nama kolom jika Anda bekerja dengan bingkai data yang sama; itu akan lebih aman seperti yang ditunjukkan oleh Ista, dan lebih cepat untuk menulis dan mengeksekusi. Jadi yang Anda butuhkan hanyalah:

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

dan kemudian, cukup menetapkan kembali data:

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

Kedua , lebih cepat menulis, Anda dapat langsung menetapkan NULL ke kolom yang ingin Anda hapus:

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

Terakhir , Anda dapat menggunakan subset (), tetapi itu tidak benar-benar dapat digunakan dalam kode (bahkan file bantuan memperingatkannya). Secara khusus, masalah bagi saya adalah bahwa jika Anda ingin secara langsung menggunakan fitur drop dari susbset () Anda perlu menulis tanpa mengutip ekspresi yang sesuai dengan nama kolom:

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

Sebagai bonus , berikut ini adalah tolok ukur kecil dari opsi yang berbeda, yang dengan jelas menunjukkan bahwa himpunan bagian lebih lambat, dan bahwa metode pengalihan yang pertama lebih cepat:

                                        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

Grafik Microbench

Kode di bawah ini:

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)
Antoine Lizée
sumber
2
Saya suka menggunakan alternatif kedua Anda NULL, tetapi mengapa ketika Anda memasukkan lebih dari dua nama diperlukan untuk menetapkannya list(NULL)? Saya hanya ingin tahu bagaimana cara kerjanya, karena saya mencoba hanya dengan satu nama dan saya tidak perlulist()
Darwin PC
3
@ DarwinPC Ya. Jika Anda mengakses langsung satu elemen vektor (dengan $atau [[), menggunakan <- list(NULL)sebenarnya akan menyebabkan hasil yang salah. Jika Anda mengakses subset dari kerangka data dengan satu atau beberapa kolom, <- list(NULL)adalah cara untuk pergi, bahkan jika itu tidak diperlukan untuk kerangka data satu kolom (karena df['myColumns']akan dicor ke vektor jika diperlukan).
Antoine Lizée
27

Anda juga dapat mencoba dplyrpaket:

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
Megatron
sumber
4
Menggunakan dplyr::select(df2, -one_of(c('x','y')))masih akan bekerja (dengan peringatan) bahkan jika beberapa kolom bernama tidak ada
divibisan
13

Inilah solusi cepat untuk ini. Katakanlah, Anda memiliki kerangka data X dengan tiga kolom A, B dan 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

Jika saya ingin menghapus kolom, katakan B, cukup gunakan grep di nama coln untuk mendapatkan indeks kolom, yang kemudian dapat Anda gunakan untuk menghilangkan kolom.

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

Kerangka data X baru Anda akan terlihat seperti berikut (kali ini tanpa kolom B):

> X
  A C
1 1 5
2 2 6

Keindahan grep adalah Anda dapat menentukan beberapa kolom yang cocok dengan ekspresi reguler. Jika saya memiliki X dengan lima kolom (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

Keluarkan kolom B dan D:

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

EDIT: Mempertimbangkan saran grepl dari Matthew Lundberg dalam komentar di bawah ini:

> 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

Jika saya mencoba untuk menjatuhkan kolom yang tidak ada, tidak ada yang terjadi:

> X<-X[,!grepl("G",colnames(X))]
> X
  A C  E
1 1 5  9
2 2 6 10
Joben R. Ilagan
sumber
3
X[,-grep("B",colnames(X))]tidak akan mengembalikan kolom dalam kasus di mana tidak ada nama kolom berisi B, daripada mengembalikan semua kolom seperti yang diinginkan. Pertimbangkan dengan X <- irismisalnya. Ini adalah masalah dengan menggunakan indeks negatif dengan nilai yang dihitung. Pertimbangkan greplsaja.
Matius Lundberg
6

Saya mencoba menghapus kolom saat menggunakan paket data.tabledan mendapatkan hasil yang tidak terduga. Saya agak berpikir hal berikut ini layak untuk dikirim. Hanya sedikit peringatan.

[Diedit oleh Matthew ...]

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

Pada dasarnya, sintaks untuk data.tableTIDAK persis sama dengan data.frame. Sebenarnya ada banyak perbedaan, lihat FAQ 1.1 dan FAQ 2.17. Anda telah diperingatkan!

Mark Miller
sumber
1
Atau Anda dapat menggunakan DT[,var.out := NULL]untuk menghapus kolom yang ingin Anda lakukan.
mnel
Metode subset (x, select = ...) bekerja untuk keduanya data.framedan data.tablekelas
momeara
3

Saya mengubah kode menjadi:

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

Bagaimanapun, jawaban juba adalah solusi terbaik untuk masalah saya!

leroux
sumber
Mengapa Anda ingin melakukan ini dalam satu lingkaran? Jawaban jawaban juba menunjukkan kepada Anda bagaimana melakukannya dalam satu langkah. Mengapa membuatnya lebih rumit?
Ista
tentu saja saya menggunakan selectargumen subsetfungsi dalam kode saya. saya hanya ingin melihat bagaimana saya bisa mengakses kolom yang berubah-ubah dalam satu lingkaran jika saya ingin melakukan sesuatu yang lain daripada hanya menjatuhkan kolom. set data asli memiliki sekitar 1.200 vars dan saya hanya tertarik untuk menggunakan 4 dari mereka tanpa mengetahui di mana sebenarnya mereka.
leroux
2

Berikut ini solusi lain yang mungkin bisa membantu orang lain. Kode di bawah ini memilih sejumlah kecil baris dan kolom dari kumpulan data besar. Kolom dipilih seperti dalam salah satu jawaban juba kecuali bahwa saya menggunakan fungsi tempel untuk memilih satu set kolom dengan nama yang diberi nomor secara berurutan:

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
Mark Miller
sumber
2
df2 <- df[!names(df) %in% c("c1", "c2")]
Marvin W
sumber
-1

Saya tidak dapat menjawab pertanyaan Anda di komentar karena skor reputasi rendah.

Kode selanjutnya akan memberi Anda kesalahan karena fungsi tempel mengembalikan string karakter

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

Berikut ini adalah solusi yang mungkin:

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
}

atau lakukan saja:

for(i in 1:length(var.out)) {
  data[var.out[i]] <- NULL
}
Andriy T.
sumber
-1
df = mtcars 
hapus vs dan saya karena mereka kategoris. Di dataset vs di kolom nomor 8, saya di kolom nomor 9

dfnum = df[,-c(8,9)]

Abhilash Ponnam
sumber