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

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

Ответы:

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функции сделали работу отлично! Спасибо, Джуба!
leroux
2
whichне обязательно, см. ответ Исты. Но подмножество с -это хорошо! Не знал этого!
TMS
5
subsetвыглядит хорошо, но способ, которым он молча отбрасывает пропущенные значения, кажется мне довольно опасным.
static_rtti
2
subsetэто действительно очень удобно, но не используйте его, если вы не используете R в интерактивном режиме. См. Предупреждение в документации по функции и этот вопрос SO для получения дополнительной информации.
Вальдир Леонсио
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()!

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

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

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

Микро скамейка график

Код ниже:

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()
Darwin PC
3
@DarwinPC Да. Если вы обращаетесь напрямую к одному элементу вектора (с помощью $или [[), использование <- list(NULL)приведет к неправильным результатам. Если вы обращаетесь к подмножеству фрейма данных с одним или несколькими столбцами, <- list(NULL)этот путь можно использовать, даже если он не нужен для фрейма данных с одним столбцом (поскольку при необходимости он df['myColumns']будет приведен к вектору).
Антуан Лизе
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

Вот быстрое решение для этого. Скажем, у вас есть фрейм данных 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 в комментариях ниже:

> 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вместо этого.
Мэтью Лундберг
6

Я пытался удалить столбец при использовании пакета 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'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. На самом деле существует множество отличий, см. FAQ 1.1 и FAQ 2.17. Вы были предупреждены!

Марк Миллер
источник
1
Или вы можете использовать, DT[,var.out := NULL]чтобы удалить столбцы, которые вы хотите сделать.
mnel
Метод subset (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]

В любом случае, ответ Джубы - лучшее решение моей проблемы!

Леру
источник
Почему вы хотите сделать это в цикле? Ответы Джуба ответ показывает вам, как сделать это за один шаг. Зачем делать это сложнее?
Иста
Конечно, я использую selectаргумент subsetфункции в моем коде. я просто хотел посмотреть, как я могу получить доступ к произвольным столбцам в цикле на случай, если я захочу сделать что-то еще, кроме простого удаления столбца. исходный набор данных имеет около 1200 переменных, и я заинтересован только в использовании 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 находится в столбце № 8, am - в столбце № 9

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

Абхилаш Поннам
источник