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

194

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

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(...)) помогает в моем случае отбрасывать переменные. вопрос был, главным образом, в части paste("data$",var.out[i],sep="") для доступа к интересующим столбцам внутри цикла. Как я могу вставить или как-то составить имя столбца? Спасибо всем за внимание и вашу помощь
  • 4
    Возможная копия столбцов удаления в кадре данных R
Теги:
dataframe
subset

10 ответов

255
Лучший ответ

Вы должны использовать индексирование или функцию 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
  • 1
    Аргумент select функции subset отлично справился со своей задачей! Спасибо, Джуба!
  • 1
    which не обязательно, см. ответ Исты. Но подмножество с - приятно! Не знал этого!
Показать ещё 6 комментариев
94

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

  • 29
    setdiff также полезен: setdiff(names(dat), c("foo", "bar"))
  • 0
    Предложение setdiff от @hadley очень хорошо подходит для длинных списков имен.
40

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

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

Изображение 4329

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

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)
  • 0
    Мне нравится ваш второй вариант, использующий NULL , но почему, когда вы вводите более двух имен, необходимо назначить его со list(NULL) ? Мне только интересно узнать, как это работает, потому что я пробовал только с одним именем, и мне не нужен list()
  • 1
    @DarwinPC Да. Если вы обращаетесь напрямую к одному векторному элементу (с помощью $ или [[ )), использование <- list(NULL) фактически приведет к неверным результатам. Если вы обращаетесь к подмножеству фрейма данных с одним или несколькими столбцами, <- list(NULL) - путь, даже если он не нужен для df['myColumns'] с одним столбцом (потому что df['myColumns'] будет приведен к вектор при необходимости).
8

Вы также можете попробовать пакет 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
  • 6
    Я не могу поверить, что больше людей не используют это. Это, безусловно, самый простой и эффективный.
  • 3
    Использование dplyr::select(df2, -one_of(c('x','y'))) все равно будет работать (с предупреждением), даже если некоторые из названных столбцов не существуют
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. Вы были предупреждены!

  • 1
    Или вы можете использовать DT[,var.out := NULL] чтобы удалить столбцы, которые вы хотите сделать.
  • 0
    Метод subset (x, select = ...) работает для классов data.frame и data.table
4

Вот быстрое решение для этого. Скажем, у вас есть кадр данных 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
  • 2
    X[,-grep("B",colnames(X))] будет возвращать столбцы в случае, когда ни одно имя столбца не содержит B , вместо того, чтобы возвращать все столбцы, как хотелось бы. Рассмотрим для примера X <- iris . Это проблема с использованием отрицательных индексов с вычисленными значениями. Рассмотрим вместо этого grepl .
1
df2 <- df[!names(df) %in% c("c1", "c2")]
1

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

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

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

  • 0
    Почему вы хотите сделать это в цикле? Ответы Джуба ответ показывает вам, как сделать это за один шаг. Зачем делать это сложнее?
  • 0
    конечно, я использую аргумент select функции subset в моем коде. я просто хотел посмотреть, как я могу получить доступ к произвольным столбцам в цикле на случай, если я захочу сделать что-то еще, кроме простого удаления столбца. исходный набор данных имеет около 1200 переменных, и я заинтересован только в использовании 4 из них, не зная, где именно они находятся.
-2

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

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

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
}

Ещё вопросы

Сообщество Overcoder
Наверх
Меню