Удалить столбцы фрейма данных по имени


874

У меня есть несколько столбцов, которые я хотел бы удалить из фрейма данных. Я знаю, что мы можем удалить их по отдельности, используя что-то вроде:

df$x <- NULL

Но я надеялся сделать это с меньшим количеством команд.

Кроме того, я знаю, что я мог бы отбросить столбцы, используя целочисленную индексацию следующим образом:

df <- df[ -c(1, 3:6, 12) ]

Но я обеспокоен тем, что относительное положение моих переменных может измениться.

Учитывая, насколько мощный R, я подумал, что может быть лучший способ, чем отбрасывать каждый столбец один за другим.


13
Может кто-нибудь объяснить мне, почему у R нет чего-то простого df#drop(var_name), и вместо этого нам нужно сделать эти сложные обходные пути?
ifly6

2
@ ifly6 Функция 'subset ()' в R примерно такая же экономная, как и функция 'drop ()' в Python, за исключением того, что вам не нужно указывать аргумент оси ... Я согласен, что раздражает то, что быть всего лишь одним, конечным, простым ключевым словом / синтаксисом, реализованным по всем направлениям, для чего-то такого базового, как удаление столбца.
Пол Сохацкий,

Ответы:


912

Вы можете использовать простой список имен:

DF <- data.frame(
  x=1:10,
  y=10:1,
  z=rep(5,10),
  a=11:20
)
drops <- c("x","z")
DF[ , !(names(DF) %in% drops)]

Или, в качестве альтернативы, вы можете составить список тех, которые нужно сохранить, и сослаться на них по имени:

keeps <- c("y", "a")
DF[keeps]

РЕДАКТИРОВАТЬ: Для тех, кто еще не знаком с dropаргументом функции индексации, если вы хотите сохранить один столбец в качестве фрейма данных, вы должны:

keeps <- "y"
DF[ , keeps, drop = FALSE]

drop=TRUE(или не упоминая об этом) отбросит ненужные измерения и, следовательно, вернет вектор со значениями столбца y.


19
функция подмножества работает лучше, так как она не будет преобразовывать фрейм данных с одним столбцом в вектор
mut1na

3
@ mut1na проверьте аргумент drop = FALSE функции индексирования.
Йорис Мейс

4
Разве это не должно быть DF[,keeps]вместо DF[keeps]?
Линделоф

8
Может, но тогда вам нужно добавить drop = FALSE, чтобы R не преобразовывал ваш фрейм данных в вектор, если вы выбрали только один столбец. Не забывайте, что фреймы данных являются списками, поэтому выбор списка (одномерный, как я сделал) работает отлично и всегда возвращает список. Или фрейм данных в этом случае, поэтому я предпочитаю использовать его.
Йорис Мейс

7
@ AjayOhri Да, это так. Без запятой вы используете способ выбора «список», что означает, что даже при извлечении одного столбца вы все равно получите возвращенный фрейм данных. Если вы используете «матричный» способ, вы должны знать, что если вы выберете только один столбец, вы получите вектор вместо фрейма данных. Чтобы избежать этого, вам нужно добавить drop = FALSE. Как объяснено в моем ответе и в комментарии прямо над вашим ...
Joris Meys

453

Также есть subsetкоманда, полезная, если вы знаете, какие столбцы вы хотите:

df <- data.frame(a = 1:10, b = 2:11, c = 3:12)
df <- subset(df, select = c(a, c))

ОБНОВЛЕНО после комментария @hadley: Чтобы удалить столбцы a, c, вы можете сделать:

df <- subset(df, select = -c(a, c))

3
Мне бы очень хотелось, чтобы у subsetфункции R была опция типа «allbut = FALSE», которая «инвертирует» выбор, когда установлен в значение «ИСТИНА», то есть сохраняет все столбцы, кроме тех, которые в selectсписке.
Прасад Чаласани

4
@prasad, смотрите ответ @joris ниже. Подмножество без каких-либо критериев подмножества немного излишне. Попробуйте просто:df[c("a", "c")]
JD Long

@JD Я знал это, но мне нравится синтаксическое удобство subsetкоманды, когда вам не нужно ставить кавычки вокруг имен столбцов - я думаю, я не против набрать несколько дополнительных символов только для того, чтобы избежать цитирования имен :)
Прасад Чаласани

11
Обратите внимание, что вы не должны использовать subsetвнутри других функций.
Ари Б. Фридман


196
within(df, rm(x))

вероятно, проще всего, или для нескольких переменных:

within(df, rm(x, y))

Или, если вы имеете дело с data.tables ( как удалить столбец по имени в data.table? ):

dt[, x := NULL]   # Deletes column x by reference instantly.

dt[, !"x"]   # Selects all but x into a new data.table.

или для нескольких переменных

dt[, c("x","y") := NULL]

dt[, !c("x", "y")]

26
within(df, rm(x))является на сегодняшний день самым чистым раствором. Учитывая, что это возможно, любой другой ответ кажется излишне сложным на порядок.
Майлз Эриксон

2
Обратите внимание, что within(df, rm(x))это не будет работать, если есть дублированные столбцы, названные xв df.
MichaelChirico

2
@MichaelChirico, чтобы уточнить, он не удаляет ни один, но, кажется, меняет значения данных. У кого-то есть большие проблемы, если это так, но вот пример: df <- data.frame(x = 1, y = 2); names(df) <- c("x", "x"); within(df, rm(x))возврат data.frame(x = 2, x = 2).
Макс Генис

1
@MilesErickson Проблема в том, что вы полагаетесь на функцию, within()которая является мощной, но также использует NSE. В примечании на странице справки четко указано, что для программирования следует использовать достаточную осторожность.
Йорис Мейс

@MilesErickson Как часто можно встретить фрейм данных с повторяющимися именами в нем?
HSchmale

115

Вы можете использовать %in%как это:

df[, !(colnames(df) %in% c("x","bar","foo"))]

1
Я что-то упустил, или это фактически то же решение, что и первая часть ответа Джорис? DF[ , !(names(DF) %in% drops)]
Даниэль Флетчер

9
@DanielFletcher: это то же самое. Посмотрите на отметки времени в ответах. Мы ответили одновременно ... 5 лет назад. :)
Джошуа Ульрих

5
Чокнутый. identical(post_time_1, post_time_2) [1] TRUE = D
Даниэль Флетчер

54

list (NULL) также работает:

dat <- mtcars
colnames(dat)
# [1] "mpg"  "cyl"  "disp" "hp"   "drat" "wt"   "qsec" "vs"   "am"   "gear"
# [11] "carb"
dat[,c("mpg","cyl","wt")] <- list(NULL)
colnames(dat)
# [1] "disp" "hp"   "drat" "qsec" "vs"   "am"   "gear" "carb"

1
Brilliant! Это естественным образом расширяет назначение NULL на один столбец и (похоже) избегает копирования (хотя я не знаю, что происходит под капотом, так что это может быть не более эффективным в использовании памяти ... но мне кажется, более эффективно синтаксически.)
c-urchin

6
Вам не нужен список (NULL), достаточно NULL. например: dat [, 4] = NULL
кузен Кокаин

8
Вопрос ОП заключался в том, как удалить несколько столбцов. dat [, 4: 5] <- NULL не будет работать. Вот где приходит список (NULL). Он работает для 1 или более столбцов.
Винсент

Это также не работает при попытке удалить дублированное имя столбца.
MichaelChirico

@MichaelChirico прекрасно работает для меня. Либо дайте метку, если вы хотите удалить первый столбец с тем же именем, либо дайте индексы для каждого столбца, который вы хотите удалить. Если у вас есть пример, где это не работает, мне было бы интересно его увидеть. Возможно опубликовать это как новый вопрос?
Винсент

42

Если вы хотите удалить столбцы по ссылке и избежать внутреннего копирования, связанного с этим, data.framesвы можете использовать data.tableпакет и функцию:=

Вы можете передать имена векторных символов в левую часть :=оператора и NULLкак RHS.

library(data.table)

df <- data.frame(a=1:10, b=1:10, c=1:10, d=1:10)
DT <- data.table(df)
# or more simply  DT <- data.table(a=1:10, b=1:10, c=1:10, d=1:10) #

DT[, c('a','b') := NULL]

Если вы хотите предварительно определить имена как символьный вектор вне вызова [, оберните имя объекта в ()или {}заставьте LHS быть оцененным в вызывающей области, а не как имя в пределах области DT.

del <- c('a','b')
DT <- data.table(a=1:10, b=1:10, c=1:10, d=1:10)
DT[, (del) := NULL]
DT <-  <- data.table(a=1:10, b=1:10, c=1:10, d=1:10)
DT[, {del} := NULL]
# force or `c` would also work.   

Вы также можете использовать set, что позволяет избежать накладных расходов [.data.table, а также работает на data.frames!

df <- data.frame(a=1:10, b=1:10, c=1:10, d=1:10)
DT <- data.table(df)

# drop `a` from df (no copying involved)

set(df, j = 'a', value = NULL)
# drop `b` from DT (no copying involved)
set(DT, j = 'b', value = NULL)

41

Существует потенциально более мощная стратегия, основанная на том факте, что grep () возвращает числовой вектор. Если у вас длинный список переменных, как у меня в одном из моего набора данных, некоторые переменные заканчиваются на «.A», а другие заканчиваются на «.B», и вам нужны только те, которые заканчиваются на «.A» (вместе со всеми переменными, которые не соответствуют ни одному шаблону, сделайте это:

dfrm2 <- dfrm[ , -grep("\\.B$", names(dfrm)) ]

Для рассматриваемого случая, на примере Joris Meys, он может быть не таким компактным, но это будет:

DF <- DF[, -grep( paste("^",drops,"$", sep="", collapse="|"), names(DF) )]

1
Если мы определим dropsв первую очередь как paste0("^", drop_cols, "$"), это станет намного лучше (читай: более компактно) с sapply:DF[ , -sapply(drops, grep, names(DF))]
MichaelChirico

30

Еще один dplyrответ. Если ваши переменные имеют некоторую общую структуру именования, вы можете попробовать starts_with(). Например

library(dplyr)
df <- data.frame(var1 = rnorm(5), var2 = rnorm(5), var3 = rnorm (5), 
                 var4 = rnorm(5), char1 = rnorm(5), char2 = rnorm(5))
df
#        var2      char1        var4       var3       char2       var1
#1 -0.4629512 -0.3595079 -0.04763169  0.6398194  0.70996579 0.75879754
#2  0.5489027  0.1572841 -1.65313658 -1.3228020 -1.42785427 0.31168919
#3 -0.1707694 -0.9036500  0.47583030 -0.6636173  0.02116066 0.03983268
df1 <- df %>% select(-starts_with("char"))
df1
#        var2        var4       var3       var1
#1 -0.4629512 -0.04763169  0.6398194 0.75879754
#2  0.5489027 -1.65313658 -1.3228020 0.31168919
#3 -0.1707694  0.47583030 -0.6636173 0.03983268

Если вы хотите удалить последовательность переменных во фрейме данных, вы можете использовать :. Например, если вы хотите удалить var2, var3и все переменные между ними, вы бы просто оставили var1:

df2 <- df1 %>% select(-c(var2:var3) )  
df2
#        var1
#1 0.75879754
#2 0.31168919
#3 0.03983268

1
Не забывайте обо всех других возможностях select(), таких как contains()или matches(), которые также принимают регулярные выражения.
ha_pu

23

Другая возможность:

df <- df[, setdiff(names(df), c("a", "c"))]

или

df <- df[, grep('^(a|c)$', names(df), invert=TRUE)]

2
Жаль, что за это больше не голосуют, потому что использование setdiffявляется оптимальным, особенно в случае очень большого количества столбцов.
ctbrown

Под другим углом зрения:df <- df[ , -which(grepl('a|c', names(df)))]
Джо

23
DF <- data.frame(
  x=1:10,
  y=10:1,
  z=rep(5,10),
  a=11:20
)
DF

Вывод:

    x  y z  a
1   1 10 5 11
2   2  9 5 12
3   3  8 5 13
4   4  7 5 14
5   5  6 5 15
6   6  5 5 16
7   7  4 5 17
8   8  3 5 18
9   9  2 5 19
10 10  1 5 20

DF[c("a","x")] <- list(NULL)

Вывод:

        y z
    1  10 5
    2   9 5
    3   8 5
    4   7 5
    5   6 5
    6   5 5
    7   4 5
    8   3 5    
    9   2 5
    10  1 5

23

Решение Dplyr

Я сомневаюсь, что здесь будет много внимания, но если у вас есть список столбцов, которые вы хотите удалить, и вы хотите сделать это в dplyrцепочке, которую я использую one_of()вselect предложении:

Вот простой, воспроизводимый пример:

undesired <- c('mpg', 'cyl', 'hp')

mtcars <- mtcars %>%
  select(-one_of(undesired))

Документацию можно найти, запустив ?one_ofили здесь:

http://genomicsclass.github.io/book/pages/dplyr_tutorial.html


22

Из интереса это указывает на одну из странных множественных синтаксических несоответствий R. Например, для данных с двумя столбцами:

df <- data.frame(x=1, y=2)

Это дает фрейм данных

subset(df, select=-y)

но это дает вектор

df[,-2]

Это все объясняется, ?[но это не совсем ожидаемое поведение. Ну, по крайней мере, не для меня ...


18

Вот dplyrспособ сделать это:

#df[ -c(1,3:6, 12) ]  # original
df.cut <- df %>% select(-col.to.drop.1, -col.to.drop.2, ..., -col.to.drop.6)  # with dplyr::select()

Мне нравится это, потому что это интуитивно понятно и понятно без аннотации и устойчиво к изменению положения столбцов во фрейме данных. Он также следует векторизации, используемой -для удаления элементов.


Кроме того, (1) пользователь хочет заменить исходный df (2) У magrittr есть %<>% оператор для замены входного объекта, к которому его можно упроститьdf %<>% select(-col.to.drop.1, -col.to.drop.2, ..., -col.to.drop.6)
Марек

1
Если у вас есть длинный список столбцов для удаления, с dplyrними может быть проще сгруппировать их и поставить только один минус:df.cut <- df %>% select(-c(col.to.drop.1, col.to.drop.2, ..., col.to.drop.n))
iNyar

14

Я продолжаю думать, что должна быть лучшая идиома, но для вычитания столбцов по имени я склонен делать следующее:

df <- data.frame(a=1:10, b=1:10, c=1:10, d=1:10)

# return everything except a and c
df <- df[,-match(c("a","c"),names(df))]
df

4
Не хорошая идея, чтобы df[,-match(c("e","f"),names(df))]
отменить

. @ JDLong - Что делать, если я хочу удалить столбец, с которого начинается имя столбца -?
Четан Арвинд Патил

12

В пакете dropNamed()Бернда Бишля есть функция, BBmiscкоторая делает именно это.

BBmisc::dropNamed(df, "x")

Преимущество состоит в том, что он избегает повторения аргумента фрейма данных и, таким образом, подходит для передачи magrittr(так же, как dplyrподходы):

df %>% BBmisc::dropNamed("x")

9

Другое решение, если вы не хотите использовать @adley выше: если «COLUMN_NAME» - это имя столбца, который вы хотите удалить:

df[,-which(names(df) == "COLUMN_NAME")]

1
(1) Проблема заключается в удалении нескольких столбцов одновременно. (2) Он не будет работать, если COLUMN_NAMEего нет df(проверьте себя:) df<-data.frame(a=1,b=2). (3) df[,names(df) != "COLUMN_NAME"]проще и не страдают от (2)
Марек

Можете ли вы дать больше информации об этом ответе?
Акаш Наяк

8

Помимо того, что было select(-one_of(drop_col_names))продемонстрировано в предыдущих ответах, есть несколько других dplyrвариантов удаления столбцов, select()которые не включают в себя определение всех определенных имен столбцов (с использованием образца данных dplyr starwars для некоторого разнообразия в именах столбцов):

library(dplyr)
starwars %>% 
  select(-(name:mass)) %>%        # the range of columns from 'name' to 'mass'
  select(-contains('color')) %>%  # any column name that contains 'color'
  select(-starts_with('bi')) %>%  # any column name that starts with 'bi'
  select(-ends_with('er')) %>%    # any column name that ends with 'er'
  select(-matches('^f.+s$')) %>%  # any column name matching the regex pattern
  select_if(~!is.list(.)) %>%     # not by column name but by data type
  head(2)

# A tibble: 2 x 2
homeworld species
  <chr>     <chr>  
1 Tatooine  Human  
2 Tatooine  Droid 

Если вам нужно отбросить столбец, который может существовать или не существовать во фрейме данных, приведем небольшой поворот, select_if()который, в отличие от использования one_of(), не выдаст Unknown columns:предупреждение, если имя столбца не существует. В этом примере bad_column не является столбцом во фрейме данных:

starwars %>% 
  select_if(!names(.) %in% c('height', 'mass', 'bad_column'))

4

Укажите фрейм данных и строку имен, разделенных запятыми, для удаления:

remove_features <- function(df, features) {
  rem_vec <- unlist(strsplit(features, ', '))
  res <- df[,!(names(df) %in% rem_vec)]
  return(res)
}

Использование :

remove_features(iris, "Sepal.Length, Petal.Width")

введите описание изображения здесь


1

Найдите индекс столбцов, которые вы хотите удалить, используя which. Дайте этим индексам отрицательный знак ( *-1). Затем подмножество тех значений, которые будут удалять их из кадра данных. Это пример.

DF <- data.frame(one=c('a','b'), two=c('c', 'd'), three=c('e', 'f'), four=c('g', 'h'))
DF
#  one two three four
#1   a   d     f    i
#2   b   e     g    j

DF[which(names(DF) %in% c('two','three')) *-1]
#  one four
#1   a    g
#2   b    h

1

Если у вас большой data.frameи мало памяти, используйте [ . , , , или rmиwithin чтобы удалить столбцыdata.frame , так как subsetв настоящее время (R 3.6.2) с использованием дополнительной памяти - рядом намек на руководство , чтобы использовать в subsetинтерактивном режиме .

getData <- function() {
  n <- 1e7
  set.seed(7)
  data.frame(a = runif(n), b = runif(n), c = runif(n), d = runif(n))
}

DF <- getData()
tt <- sum(.Internal(gc(FALSE, TRUE, TRUE))[13:14])
DF <- DF[setdiff(names(DF), c("a", "c"))] ##
#DF <- DF[!(names(DF) %in% c("a", "c"))] #Alternative
#DF <- DF[-match(c("a","c"),names(DF))]  #Alternative
sum(.Internal(gc(FALSE, FALSE, TRUE))[13:14]) - tt
#0.1 MB are used

DF <- getData()
tt <- sum(.Internal(gc(FALSE, TRUE, TRUE))[13:14])
DF <- subset(DF, select = -c(a, c)) ##
sum(.Internal(gc(FALSE, FALSE, TRUE))[13:14]) - tt
#357 MB are used

DF <- getData()
tt <- sum(.Internal(gc(FALSE, TRUE, TRUE))[13:14])
DF <- within(DF, rm(a, c)) ##
sum(.Internal(gc(FALSE, FALSE, TRUE))[13:14]) - tt
#0.1 MB are used

DF <- getData()
tt <- sum(.Internal(gc(FALSE, TRUE, TRUE))[13:14])
DF[c("a", "c")]  <- NULL ##
sum(.Internal(gc(FALSE, FALSE, TRUE))[13:14]) - tt
#0.1 MB are used
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.