Как удалить столбцы по имени в кадре данных

У меня большой набор данных, и я хотел бы прочитать определенные столбцы или удалить все остальные.

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. Например:

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

Не используйте -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()!

Ответ 3

Первый, вы можете использовать прямую индексацию (с булевыми векторами) вместо повторного доступа к именам столбцов, если вы работаете с одним и тем же фреймом данных; это будет безопаснее, как указано Истой, и быстрее писать и исполнять. Так что вам нужно будет только:

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.

Наконец, вы можете использовать subset(), но его нельзя действительно использовать в коде (даже файл справки предупреждает об этом). В частности, проблема в том, что если вы хотите напрямую использовать функцию drop 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 graph

Код находится ниже:

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)

Ответ 4

Вы также можете попробовать пакет 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

Ответ 5

Я попытался удалить столбец при использовании пакета 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 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. Вы были предупреждены!

Ответ 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, просто используйте 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

РЕДАКТИРОВАТЬ: Учитывая предложение Грефта Мэтью Лундберга в комментариях ниже:

> 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

Ответ 7

Вот еще одно решение, которое может быть полезным для других. Приведенный ниже код выбирает небольшое количество строк и столбцов из большого набора данных. Столбцы выбираются как в одном из ответов на 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

Ответ 8

df2 <- df[!names(df) %in% c("c1", "c2")]

Ответ 9

Я изменил код на:

# 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 - лучшее решение моей проблемы!

Ответ 10

Я не могу ответить на ваш вопрос в комментариях из-за низкого рейтинга репутации.

Следующий код даст вам ошибку, потому что функция вставки возвращает строку символов

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
}