Видаліть стовпці кадру даних за назвою


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]

EDIT: Для тих, хто ще не знайомий з dropаргументом функції індексації, якщо ви хочете зберегти один стовпець як кадр даних, зробіть:

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

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


19
функція підмножини працює краще, оскільки не перетворить кадр даних з одного стовпця у вектор
mut1na

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

4
Чи не повинно бути це DF[,keeps]замість DF[keeps]?
lindelof

8
@lindelof Ні. Це може, але тоді вам доведеться додати 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
Я дійсно бажаю R subsetфункція була можливість , як «allbut = FALSE», яка «перевертає» вибір , коли значення ІСТИНА, тобто зберігає всі стовпці , за винятком тих , в selectсписку.
Прасад Чаласані

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

@JD Я це знав, але мені подобається синтаксична зручність subsetкоманди, де вам не потрібно ставити лапки навколо імен стовпців - я думаю, я не проти вводити кілька зайвих символів, щоб уникнути цитування імен :)
Prasad Chalasani

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

список (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
Блискуче! Це природним чином розширює призначення NULL до одного стовпця і (здавалося б) уникає копіювання (хоча я не знаю, що відбувається під кришкою, тому воно може бути не більш ефективним у використанні пам'яті ... але мені здається чітко більш ефективний синтаксично.)
c-urchin

6
Вам не потрібен список (NULL), достатньо NULL. наприклад: dat [, 4] = NULL
CousinCocaine

8
Питання OP полягало в тому, як видалити кілька стовпців. 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)) ]

У цьому випадку, на прикладі Йоріс Мейс, він може бути не таким компактним, але це буде:

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

Це не цікавить, що це позначає одну з дивних декількох невідповідностей синтаксису. Наприклад, заданий кадр даних з двома стовпцями:

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)
Marek

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

Ще одне рішення, якщо ви не хочете використовувати @ hadley вище: Якщо "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 для деякого різноманіття в назвах стовпців):

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.