วิธีวางคอลัมน์ตามชื่อใน data frame


304

ฉันมีชุดข้อมูลขนาดใหญ่และฉันต้องการอ่านคอลัมน์ที่เฉพาะเจาะจงหรือวางอื่น ๆ ทั้งหมด

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
}

เพื่อวางคอลัมน์ที่ไม่ต้องการทั้งหมด นี่เป็นทางออกที่ดีที่สุดหรือไม่?


1
การนอนหลับเหนือปัญหาฉันคิดว่าsubset(data, select=c(...))จะช่วยในกรณีของการวาง vars คำถามที่ว่าส่วนใหญ่เกี่ยวกับการpaste("data$",var.out[i],sep="")เข้าถึงคอลัมน์ที่น่าสนใจในวง ฉันจะวางหรือสร้างชื่อคอลัมน์ได้อย่างไร? ขอบคุณทุกคนสำหรับความสนใจและความช่วยเหลือของคุณ
leroux

คำตอบ:


380

คุณควรใช้การจัดทำดัชนีหรือ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

หรือง่ายกว่ามากใช้selectอาร์กิวเมนต์ของsubsetฟังก์ชัน: จากนั้นคุณสามารถใช้-โอเปอเรเตอร์กับเวกเตอร์ของชื่อคอลัมน์ได้โดยตรงและคุณสามารถละเว้นเครื่องหมายคำพูดรอบชื่อได้!

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

2
selectอาร์กิวเมนต์ของsubsetฟังก์ชั่นได้งานได้อย่างสมบูรณ์แบบ! ขอบคุณ juba!
leroux

2
whichไม่จำเป็นต้องดูคำตอบของ Ista แต่เซตย่อยของ-มันดีมาก! ไม่รู้เหมือนกัน!
TMS

5
subsetดูดี แต่วิธีการลดค่าที่หายไปอย่างเงียบ ๆ ดูเหมือนว่าค่อนข้างอันตรายสำหรับฉัน
static_rtti

2
subsetสะดวกมากจริง ๆ แต่อย่าลืมที่จะหลีกเลี่ยงการใช้มันนอกจากว่าคุณกำลังใช้ R แบบโต้ตอบ ดูคำเตือนในเอกสารของฟังก์ชั่นและคำถาม SO นี้สำหรับข้อมูลเพิ่มเติม
Waldir Leoncio

4
"คุณสามารถละเว้นเครื่องหมายคำพูดรอบ ๆ ชื่อได้!" คุณต้องละเว้นเครื่องหมายอัญประกาศมิฉะนั้นคุณจะได้รับอาร์กิวเมนต์ที่ไม่ถูกต้องสำหรับผู้ประกอบการที่ไม่มีชื่อ หากคุณมีอักขระบางตัว (เช่น "-") ในชื่อของคุณคุณไม่สามารถใช้วิธีนี้ได้เลยเนื่องจากการปล่อยคำพูดจะทำให้ R ไม่สามารถแยกวิเคราะห์รหัสของคุณได้อย่างถูกต้อง
oh54

122

อย่าใช้-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()!


31
setdiffยังเป็นประโยชน์:setdiff(names(dat), c("foo", "bar"))
hadley

setdiffข้อเสนอ @hadley เป็นสิ่งที่ดีมากสำหรับรายการยาวของชื่อ
JASC

48

ก่อนอื่นคุณสามารถใช้การจัดทำดัชนีโดยตรง (กับเวกเตอร์บูลีน) แทนการเข้าถึงชื่อคอลัมน์อีกครั้งหากคุณกำลังทำงานกับกรอบข้อมูลเดียวกัน มันจะปลอดภัยกว่าที่ 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

ประการที่สองเร็วกว่าในการเขียนคุณสามารถกำหนด NULL ให้กับคอลัมน์ที่คุณต้องการลบได้โดยตรง:

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

สุดท้ายคุณสามารถใช้ชุดย่อย () แต่ไม่สามารถใช้งานได้จริงในรหัส (แม้ไฟล์ช่วยเหลือจะเตือนเกี่ยวกับมัน) โดยเฉพาะปัญหาสำหรับฉันคือถ้าคุณต้องการใช้คุณลักษณะการปล่อยโดยตรงของ susbset () คุณต้องเขียนโดยไม่ต้องใส่เครื่องหมายคำพูดที่สอดคล้องกับชื่อคอลัมน์:

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

ในฐานะโบนัสนี่เป็นเกณฑ์มาตรฐานขนาดเล็กของตัวเลือกที่แตกต่างกันซึ่งแสดงให้เห็นอย่างชัดเจนว่าเซตย่อยช้าลงและวิธีแรกที่กำหนดใหม่ได้เร็วขึ้น:

                                        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

รหัสด้านล่าง:

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)

2
ฉันเหมือนทางเลือกที่สองของคุณโดยใช้NULLแต่ทำไมเมื่อคุณใส่มากกว่าสองชื่อมีความจำเป็นต้องกำหนดด้วยlist(NULL)? ฉันแค่อยากรู้ว่ามันใช้งานได้อย่างไรเพราะฉันลองด้วยชื่อเพียงชื่อเดียวและไม่ต้องการlist()
ดาร์วินพีซี

3
@DarwinPC ใช่ หากคุณเข้าถึงองค์ประกอบเวกเตอร์เดียว (ด้วย$หรือ[[) การใช้<- list(NULL)จะนำไปสู่ผลลัพธ์ที่ผิด หากคุณเข้าถึงชุดย่อยของ dataframe ที่มีหนึ่งหรือหลายคอลัมน์<- list(NULL)เป็นวิธีที่จะไปแม้ว่ามันจะไม่จำเป็นสำหรับ dataframe คอลัมน์เดียว (เพราะdf['myColumns']จะได้รับการโยนเวกเตอร์ถ้าจำเป็น)
Antoine Lizée

27

คุณสามารถลอง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

4
การใช้dplyr::select(df2, -one_of(c('x','y')))จะยังคงทำงาน (พร้อมคำเตือน) แม้ว่าคอลัมน์ที่มีชื่อบางรายการจะไม่มีอยู่จริง
divibisan

13

นี่คือวิธีแก้ปัญหาอย่างรวดเร็วสำหรับสิ่งนี้ สมมติว่าคุณมี data frame 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 เพียงใช้ grep กับ colnames เพื่อรับดัชนีคอลัมน์ซึ่งคุณสามารถใช้เพื่อละเว้นคอลัมน์ได้

> 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

แก้ไข: พิจารณาข้อเสนอแนะ grepl ของ Matthew Lundberg ในความคิดเห็นด้านล่าง:

> 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

3
X[,-grep("B",colnames(X))]จะไม่ส่งคืนคอลัมน์ในกรณีที่ไม่มีชื่อคอลัมน์อยู่Bแทนที่จะส่งคืนคอลัมน์ทั้งหมดตามที่ต้องการ ลองพิจารณาด้วยX <- irisตัวอย่าง นี่เป็นปัญหาของการใช้ดัชนีติดลบที่มีค่าที่คำนวณ พิจารณาgreplแทน
Matthew Lundberg

6

ฉันพยายามลบคอลัมน์ขณะใช้งานแพ็กเกจdata.tableและได้ผลลัพธ์ที่ไม่คาดคิด ฉันคิดว่าสิ่งต่อไปนี้อาจคุ้มค่าการโพสต์ ขอเตือนเล็กน้อย

[แก้ไขโดย 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

โดยทั่วไปไวยากรณ์สำหรับการไม่ตรงเช่นเดียวกับdata.table data.frameในความเป็นจริงมีความแตกต่างมากมายดูคำถามที่พบบ่อย 1.1 และคำถามที่พบบ่อย 2.17 คุณได้รับการเตือน!


1
หรือคุณสามารถใช้DT[,var.out := NULL]เพื่อลบคอลัมน์ที่คุณต้องการ
mnel

ชุดย่อย (x, select = ... ) วิธีการทำงานสำหรับทั้งสองdata.frameและdata.tableเรียน
momeara

3

ฉันเปลี่ยนรหัสเป็น:

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

อย่างไรก็ตามคำตอบของ juba คือทางออกที่ดีที่สุดสำหรับปัญหาของฉัน!


ทำไมคุณต้องการทำเช่นนี้ในวง? คำตอบของ juba แสดงให้คุณเห็นว่าต้องทำอย่างไรในขั้นตอนเดียว ทำไมจึงซับซ้อนกว่านี้?
Ista

แน่นอนฉันใช้selectอาร์กิวเมนต์ของsubsetฟังก์ชั่นในรหัสของฉัน ฉันแค่อยากจะดูว่าฉันสามารถเข้าถึงคอลัมน์โดยพลการในวงในกรณีที่ฉันต้องการทำอย่างอื่นมากกว่าแค่วางคอลัมน์ ชุดข้อมูลดั้งเดิมมีประมาณ 1200 vars และฉันสนใจใช้เพียง 4 รายการโดยไม่ทราบว่าอยู่ที่ไหน
leroux

2

นี่เป็นอีกวิธีการหนึ่งที่อาจเป็นประโยชน์ต่อผู้อื่น รหัสด้านล่างนี้เลือกแถวและคอลัมน์จำนวนเล็กน้อยจากชุดข้อมูลขนาดใหญ่ คอลัมน์ถูกเลือกเหมือนกับหนึ่งในคำตอบของ 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


-1

ฉันไม่สามารถตอบคำถามของคุณในความคิดเห็นเนื่องจากคะแนนชื่อเสียงต่ำ

รหัสต่อไปจะทำให้คุณมีข้อผิดพลาดเพราะฟังก์ชั่นการวางกลับสตริงตัวอักษร

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
}

-1
df = mtcars 
ลบ vs และ am เพราะมันเป็นหมวดหมู่ ในชุดข้อมูล vs อยู่ในคอลัมน์หมายเลข 8 am อยู่ในคอลัมน์หมายเลข 9

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.