Upuść kolumny ramki danych według nazwy

874

Mam wiele kolumn, które chciałbym usunąć z ramki danych. Wiem, że możemy je usuwać indywidualnie za pomocą:

df$x <- NULL

Miałem jednak nadzieję, że zrobię to z mniejszą liczbą poleceń.

Wiem też, że mogłem upuścić kolumny za pomocą indeksowania liczb całkowitych w następujący sposób:

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

Obawiam się jednak, że względne położenie moich zmiennych może ulec zmianie.

Biorąc pod uwagę moc R, pomyślałem, że może być lepszy sposób niż upuszczanie każdej kolumny jedna po drugiej.

Btibert3
źródło
13
Czy ktoś może mi wyjaśnić, dlaczego R nie ma czegoś tak prostego df#drop(var_name), a zamiast tego musimy wykonać te skomplikowane obejścia?
ifly6,
2
@ ifly6 Funkcja „subset ()” w języku R jest tak samo oszczędna, jak funkcja „drop ()” w Pythonie, z tym wyjątkiem, że nie musisz określać argumentu osi ... Zgadzam się, że denerwujące jest to, że nie można być jednym, najlepszym, łatwym w użyciu słowem kluczowym / składnią zaimplementowanym na całym forum dla czegoś tak podstawowego jak upuszczenie kolumny.
Paul Sochacki

Odpowiedzi:

911

Możesz użyć prostej listy nazw:

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

Ewentualnie możesz utworzyć listę tych, którzy będą przechowywać, i odwołać się do nich według nazwy:

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

EDYCJA: Jeśli nadal nie dropznasz argumentu funkcji indeksowania, jeśli chcesz zachować jedną kolumnę jako ramkę danych, wykonaj następujące czynności:

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

drop=TRUE(lub nie wspominając o nim) upuści niepotrzebne wymiary, a zatem zwróci wektor z wartościami kolumny y.

Joris Meys
źródło
19
funkcja podzestawu działa lepiej, ponieważ nie konwertuje ramki danych z jedną kolumną na wektor
mut1na
3
@ mut1na sprawdź argument drop = FALSE funkcji indeksującej.
Joris Meys
4
Nie powinno to być DF[,keeps]zamiast DF[keeps]?
Lindelof
8
@lindelof Nie. Może, ale musisz dodać drop = FALSE, aby powstrzymać R przed konwersją ramki danych na wektor, jeśli wybierzesz tylko jedną kolumnę. Nie zapominaj, że ramki danych są listami, więc wybór listy (jednowymiarowy jak ja) działa idealnie dobrze i zawsze zwraca listę. Lub ramka danych w tym przypadku, dlatego wolę z niej korzystać.
Joris Meys,
7
@AjayOhri Tak, tak. Bez przecinka korzystasz z opcji „listy”, co oznacza, że ​​nawet po wyodrębnieniu pojedynczej kolumny nadal otrzymujesz ramkę danych. Jeśli korzystasz z metody „macierzowej”, tak jak robisz, powinieneś mieć świadomość, że jeśli wybierzesz tylko jedną kolumnę, otrzymasz wektor zamiast ramki danych. Aby tego uniknąć, musisz dodać drop = FALSE. Jak wyjaśniono w mojej odpowiedzi oraz w komentarzu bezpośrednio nad twoim ...
Joris Meys,
453

Jest też subsetpolecenie przydatne, jeśli wiesz, które kolumny chcesz:

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

AKTUALIZACJA po komentarzu @hadley: Aby upuścić kolumny a, c możesz:

df <- subset(df, select = -c(a, c))
Prasad Chalasani
źródło
3
Naprawdę chciałbym, aby subsetfunkcja R miała opcję typu „allbut = FALSE”, która „odwraca” zaznaczenie, gdy jest ustawiona na PRAWDA, tzn. Zachowuje wszystkie kolumny oprócz tych z selectlisty.
Prasad Chalasani
4
@prasad, patrz @joris odpowiedź poniżej. Podzbiór bez żadnych kryteriów podzbioru to trochę przesada. Spróbuj po prostu:df[c("a", "c")]
JD Long
@JD Wiedziałem o tym, ale podoba mi się wygoda syntaktyczna subsetpolecenia, w którym nie trzeba umieszczać cudzysłowów wokół nazw kolumn - chyba nie mam nic przeciwko wpisywaniu kilku dodatkowych znaków, aby uniknąć cytowania nazw :)
Prasad Chalasani
11
Pamiętaj, że nie powinieneś używać subsetwewnątrz innych funkcji.
Ari B. Friedman
196
within(df, rm(x))

jest prawdopodobnie najłatwiejszy lub dla wielu zmiennych:

within(df, rm(x, y))

Lub jeśli masz do czynienia z data.tables (według Jak usunąć kolumnę według nazwy w data.table? ):

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

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

lub dla wielu zmiennych

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

dt[, !c("x", "y")]
Max Ghenis
źródło
26
within(df, rm(x))jest zdecydowanie najczystszym rozwiązaniem. Biorąc pod uwagę, że jest to możliwe, każda inna odpowiedź wydaje się niepotrzebnie skomplikowana o rząd wielkości.
Miles Erickson
2
Zauważ, że within(df, rm(x))będzie nie działać, jeśli nie są zduplikowane kolumny nazwane xw df.
MichaelChirico
2
@MichaelChirico, aby wyjaśnić, nie usuwa ani, ale wydaje się zmieniać wartości danych. W takim przypadku mamy większe problemy, ale oto przykład: df <- data.frame(x = 1, y = 2); names(df) <- c("x", "x"); within(df, rm(x))zwraca data.frame(x = 2, x = 2).
Max Ghenis,
1
@MilesErickson Problem polega na tym, że polegasz na funkcji, within()która jest potężna, ale używa również NSE. Uwaga na stronie pomocy wyraźnie stwierdza, że ​​do programowania należy zachować wystarczającą ostrożność.
Joris Meys,
@MilesErickson Jak często można spotkać ramkę danych ze zduplikowanymi nazwami?
HSchmale,
115

Możesz użyć %in%tego w następujący sposób:

df[, !(colnames(df) %in% c("x","bar","foo"))]
Joshua Ulrich
źródło
1
Czy coś pomijam, czy jest to faktycznie to samo rozwiązanie, co pierwsza część odpowiedzi Jorisa? DF[ , !(names(DF) %in% drops)]
Daniel Fletcher
9
@DanielFletcher: jest taki sam. Spójrz na znaczniki czasu na odpowiedzi. Odpowiedzieliśmy w tym samym czasie ... 5 lat temu. :)
Joshua Ulrich
5
Orzechowy. identical(post_time_1, post_time_2) [1] TRUE = D
Daniel Fletcher
54

lista (NULL) działa również:

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"
Vincent
źródło
1
Znakomity! To naturalnie rozszerza przypisanie NULL do pojedynczej kolumny i (pozornie) unika kopiowania (chociaż nie wiem, co dzieje się pod maską, więc może nie być bardziej wydajne w użyciu pamięci ... ale wydaje mi się, że wyraźnie bardziej wydajne składniowo.)
c-urchin
6
Nie potrzebujesz listy (NULL), NULL jest wystarczająca. np .: dat [, 4] = NULL
CousinCocaine
8
Pytanie OP dotyczyło sposobu usunięcia wielu kolumn. dat [, 4: 5] <- NULL nie będzie działać. Właśnie tam pojawia się lista (NULL). Działa dla 1 lub więcej kolumn.
Vincent
To również nie działa, gdy próbujesz usunąć zduplikowaną nazwę kolumny.
MichaelChirico
@MichaelChirico Działa dobrze dla mnie. Podaj etykietę, jeśli chcesz usunąć pierwszą z kolumn o tej samej nazwie, lub podaj indeksy dla każdej kolumny, którą chcesz usunąć. Jeśli masz przykład, w którym to nie działa, chciałbym go zobaczyć. Może opublikujesz to jako nowe pytanie?
Vincent
42

Jeśli chcesz usunąć kolumny przez odniesienie i uniknąć powiązanego z nimi wewnętrznego kopiowania data.frames, możesz użyć data.tablepakietu i funkcji:=

Nazwy wektorów znaków możesz przekazać po lewej stronie :=operatora i NULLjako 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]

Jeśli chcesz wstępnie zdefiniować nazwy jako wektor znaków poza wywołaniem [, zawiń nazwę obiektu ()lub {}wymuś, aby LHS był oceniany w zakresie wywołującym, a nie jako nazwa w zakresie 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.   

Możesz także użyć set, co pozwala uniknąć narzutu [.data.table, a także działa 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)
mnel
źródło
41

Istnieje potencjalnie silniejsza strategia oparta na fakcie, że grep () zwróci wektor liczbowy. Jeśli masz długą listę zmiennych, tak jak ja, w jednym z moich zbiorów danych, niektóre zmienne, które kończą się na „.A” i inne, które kończą się na „.B”, a chcesz tylko te, które kończą się na „.A” (razem ze wszystkimi zmiennymi, które nie pasują do żadnego wzorca, wykonaj następujące czynności:

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

W omawianym przypadku, na przykładzie Jorisa Meysa, może on nie być tak kompaktowy, ale byłby to:

DF <- DF[, -grep( paste("^",drops,"$", sep="", collapse="|"), names(DF) )]
IRTFM
źródło
1
Jeśli zdefiniujemy dropsprzede wszystkim jako paste0("^", drop_cols, "$"), stanie się to znacznie ładniejsze (czytaj: bardziej kompaktowe) z sapply:DF[ , -sapply(drops, grep, names(DF))]
MichaelChirico
30

Kolejna dplyrodpowiedź. Jeśli twoje zmienne mają jakąś wspólną strukturę nazewnictwa, możesz spróbować starts_with(). Na przykład

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

Jeśli chcesz upuścić sekwencję zmiennych w ramce danych, możesz użyć :. Na przykład, jeśli chcesz upuścić var2, var3a wszystkie zmienne pomiędzy, będziesz po prostu z var1:

df2 <- df1 %>% select(-c(var2:var3) )  
df2
#        var1
#1 0.75879754
#2 0.31168919
#3 0.03983268
Pat W.
źródło
1
Nie zapomnij o wszystkich innych możliwościach select(), takich jak contains()lub matches(), które również akceptują wyrażenie regularne.
ha_pu
23

Inna możliwość:

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

lub

df <- df[, grep('^(a|c)$', names(df), invert=TRUE)]
scentoni
źródło
2
Szkoda, że ​​nie jest to bardziej zalecane, ponieważ użycie setdiffjest optymalne, szczególnie w przypadku bardzo dużej liczby kolumn.
ctbrown
Kolejny punkt widzenia na ten temat:df <- df[ , -which(grepl('a|c', names(df)))]
Joe
23
DF <- data.frame(
  x=1:10,
  y=10:1,
  z=rep(5,10),
  a=11:20
)
DF

Wynik:

    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)

Wynik:

        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
Kun Ren
źródło
23

Rozwiązanie Dplyr

Wątpię, czy to przyniesie wiele uwagi tutaj, ale jeśli masz listę kolumn, które chcesz usunąć i chcesz to zrobić w dplyrłańcuchu, którego używam one_of()w selectklauzuli:

Oto prosty, odtwarzalny przykład:

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

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

Dokumentację można znaleźć, uruchamiając ?one_oflub tutaj:

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

Użytkownik632716
źródło
22

Z braku zainteresowania oznacza to jedną z dziwnych wielokrotnych niespójności składniowych R. Na przykład biorąc pod uwagę dwukolumnową ramkę danych:

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

Daje to ramkę danych

subset(df, select=-y)

ale to daje wektor

df[,-2]

Wszystko to wyjaśniono w, ?[ale nie jest to dokładnie oczekiwane zachowanie. Cóż, przynajmniej nie dla mnie ...

jkeirstead
źródło
18

Oto dplyrjak to zrobić:

#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()

Podoba mi się to, ponieważ jest intuicyjny w czytaniu i zrozumieniu bez adnotacji i niezawodny dla kolumn zmieniających położenie w ramce danych. Podąża również za wektoryzowanym idiomem używanym -do usuwania elementów.

c.gutierrez
źródło
Dodając do tego, że (1) użytkownik chce zastąpić oryginalny plik df (2) magrittr ma %<>% operator do zastąpienia obiektu wejściowego, do którego można by to uprościćdf %<>% select(-col.to.drop.1, -col.to.drop.2, ..., -col.to.drop.6)
Marek
1
Jeśli masz długą listę kolumn do upuszczenia, dplyrłatwiej może być pogrupować je i umieścić tylko jeden minus:df.cut <- df %>% select(-c(col.to.drop.1, col.to.drop.2, ..., col.to.drop.n))
iNyar
14

Ciągle myślę, że musi istnieć lepszy idiom, ale do odejmowania kolumn według nazwy mam tendencję do wykonywania następujących czynności:

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
JD Long
źródło
4
Nie jest dobrym pomysłem, aby negować mecz -df[,-match(c("e","f"),names(df))]
hadley
. @ JDLong - Co zrobić, jeśli chcę upuścić kolumnę tam, gdzie zaczyna się nazwa kolumny -?
Chetan Arvind Patil
12

W pakiecie dropNamed()Bernda Bischla znajduje się funkcja, BBmiscktóra właśnie to robi.

BBmisc::dropNamed(df, "x")

Zaletą jest to, że unika się powtarzania argumentu ramki danych, a zatem nadaje się do przesyłania strumieniowego magrittr(podobnie jak dplyrpodejścia):

df %>% BBmisc::dropNamed("x")
krlmlr
źródło
9

Inne rozwiązanie, jeśli nie chcesz używać powyższych @ hadley: Jeśli „COLUMN_NAME” to nazwa kolumny, którą chcesz upuścić:

df[,-which(names(df) == "COLUMN_NAME")]
Nick Keramaris
źródło
1
(1) Problem polega na upuszczeniu wielu kolumn jednocześnie. (2) To nie zadziała, jeśli go COLUMN_NAMEnie ma df(sprawdź sam:) df<-data.frame(a=1,b=2). (3) df[,names(df) != "COLUMN_NAME"]jest prostszy i nie cierpi na (2)
Marek
Czy możesz podać więcej informacji na temat tej odpowiedzi?
Akash Nayak
8

Oprócz select(-one_of(drop_col_names))wcześniejszych odpowiedzi, istnieje kilka innych dplyropcji usuwania kolumn select(), które nie wymagają zdefiniowania wszystkich konkretnych nazw kolumn (użycie przykładowych danych Dplyr Starwars dla różnych odmian nazw kolumn):

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 

Jeśli chcesz upuścić kolumnę, która może istnieć w ramce danych, możesz ją nieco zmienić select_if(), w przeciwieństwie do użyciaone_of() , nie rzuci Unknown columns:ostrzeżenia, jeśli nazwa kolumny nie istnieje. W tym przykładzie „bad_column” nie jest kolumną w ramce danych:

starwars %>% 
  select_if(!names(.) %in% c('height', 'mass', 'bad_column'))
sbha
źródło
4

Podaj ramkę danych i ciąg nazw oddzielonych przecinkami, aby usunąć:

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

Zastosowanie :

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

wprowadź opis zdjęcia tutaj

Cybernetyczny
źródło
1

Znajdź indeks kolumn, które chcesz upuścić which. Nadaj tym indeksom znak ujemny ( *-1). Następnie podzestaw na te wartości, które usuną je z ramki danych. To jest przykład.

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
Mediolan
źródło
1

Jeśli masz duży data.framei masz mało pamięci [ . . . . lub rmiwithin aby usunąć kolumny zdata.frame , jak subsetjest obecnie (R 3.6.2), wykorzystując więcej pamięci - oprócz wskazówek zawartych w instrukcji, aby używać subsetinteraktywnie .

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
GKi
źródło