Zmień kolejność poziomów czynnika bez zmiany kolejności wartości

124

Mam ramkę danych z niektórymi zmiennymi numerycznymi i niektórymi factorzmiennymi kategorialnymi . Kolejność poziomów dla tych czynników nie jest taka, jaką bym chciał.

numbers <- 1:4
letters <- factor(c("a", "b", "c", "d"))
df <- data.frame(numbers, letters)
df
#   numbers letters
# 1       1       a
# 2       2       b
# 3       3       c
# 4       4       d

Jeśli zmienię kolejność poziomów, litery nie będą już z odpowiadającymi im numerami (moje dane są kompletnie bezsensowne od tego momentu).

levels(df$letters) <- c("d", "c", "b", "a")
df
#   numbers letters
# 1       1       d
# 2       2       c
# 3       3       b
# 4       4       a

Chcę po prostu zmienić kolejność poziomów , więc podczas kreślenia słupki są wyświetlane w żądanej kolejności - która może różnić się od domyślnej kolejności alfabetycznej.

Crangos
źródło
1
Czy ktoś mógłby mi podpowiedzieć, dlaczego przypisanie do poziomów (...) zmienia kolejność wpisów w ramce danych, jak pokazuje crangos w pytaniu? Wydaje mi się to strasznie nieintuicyjne i niepożądane. Spędziłem dziś trochę czasu na debugowaniu problemu spowodowanego przez to. Myślę, że może istnieć powód takiego zachowania, którego nie widzę, lub przynajmniej rozsądne wyjaśnienie, dlaczego tak się dzieje.
Anton

Odpowiedzi:

120

Użyj levelsargumentu factor:

df <- data.frame(f = 1:4, g = letters[1:4])
df
#   f g
# 1 1 a
# 2 2 b
# 3 3 c
# 4 4 d

levels(df$g)
# [1] "a" "b" "c" "d"

df$g <- factor(df$g, levels = letters[4:1])
# levels(df$g)
# [1] "d" "c" "b" "a"

df
#   f g
# 1 1 a
# 2 2 b
# 3 3 c
# 4 4 d
Jonathan Chang
źródło
1
Dziękuję, zadziałało. Z jakiegoś dziwnego powodu ggplot teraz poprawnie zmienił kolejność w legendzie, ale nie w fabule. Dziwne.
crangos
7
ggplot2 wymagało ode mnie zmiany zarówno kolejności poziomów (patrz wyżej), jak i kolejności wartości ramki danych. df <- df [nrow (df): 1,] # reverse
crangos
@crangos, myślę, że ggplot używa alfabetycznej kolejności poziomów i czasami ignoruje niestandardowe poziomy współczynników. Potwierdź i dołącz numer wersji.
smci
22

trochę więcej, tak dla przypomnienia

## reorder is a base function
df$letters <- reorder(df$letters, new.order=letters[4:1])

library(gdata)
df$letters <- reorder.factor(df$letters, letters[4:1])

Możesz również znaleźć przydatne Relevel i connect_factor .

George Dontas
źródło
2
Twoja pierwsza odpowiedź mi nie odpowiada. Ale to działa:reorder(df$letters, seq(4,1))
Alex Holcombe
1
Mam bardzo dziwną sytuację, w której „reorder” działa na jednym zestawie danych, a nie na innym. W drugim zbiorze danych zgłasza błąd „Błąd w tapply (X = X, INDEX = x, FUN = FUN, ...): brak argumentu„ X ”, brak wartości domyślnej”. Nie jestem pewien, jakie jest rozwiązanie tego problemu. Nie mogę znaleźć żadnej istotnej różnicy między zbiorami danych.
CoderGuy123
10

Ponieważ to pytanie było ostatnio aktywne, Hadley opublikował swój nowy forcatspakiet do manipulowania czynnikami i uważam go za niezwykle przydatny. Przykłady z ramki danych PO:

levels(df$letters)
# [1] "a" "b" "c" "d"

Aby odwrócić poziomy:

library(forcats)
fct_rev(df$letters) %>% levels
# [1] "d" "c" "b" "a"

Aby dodać więcej poziomów:

fct_expand(df$letters, "e") %>% levels
# [1] "a" "b" "c" "d" "e"

I wiele innych przydatnych fct_xxx()funkcji.

Joe
źródło
Czy to jest w dalszym ciągu dostępne?
Joshua Rosenberg
1
Chcesz napisać kod jak poniżej: df %>% mutate(letters = fct_rev(letters)).
jazzurro
9

więc w leksykonie R chcesz zmienić tylko etykiety dla danej zmiennej czynnikowej (tj. pozostawić dane oraz poziomy czynników niezmienione).

df$letters = factor(df$letters, labels=c("d", "c", "b", "a"))

biorąc pod uwagę, że chcesz zmienić tylko mapowanie punktu danych do etykiety, a nie dane lub schemat czynników (w jaki sposób punkty danych są kategoryzowane w poszczególnych pojemnikach lub wartościach czynników, warto wiedzieć, w jaki sposób mapowanie jest pierwotnie ustawione podczas tworzenia Czynnik.

zasady są proste:

  • etykiety są odwzorowywane na poziomy według wartości indeksu (tj. wartość na poziomach [2] otrzymuje etykietę, etykieta [2]);
  • poziomy czynników można ustawić jawnie, przekazując je za pomocą argumentu poziomów ; lub
  • jeśli nie podano wartości dla argumentu poziomów, używana jest wartość domyślna, która jest wynikiem wywołania unikalnego w przekazanym wektorze danych (dla argumentu data );
  • etykiety można ustawić jawnie za pomocą argumentu labels; lub
  • jeśli dla argumentu labels nie podano wartości, używana jest wartość domyślna, czyli po prostu wektor poziomów
Doug
źródło
1
Nie wiem, dlaczego głosowanie nie jest tak dobre, jak zaakceptowana odpowiedź. To jest o wiele bardziej pouczające.
Rambatino,
12
Jeśli zastosujesz tę metodę, Twoje dane zostaną niewłaściwie oznaczone.
Nazer
4
właściwie tak, nie wiem, co z tym zrobić, wydaje się, że odpowiedź ma na celu błędne oznaczenie danych ze względu na wykreślenie? ugh. przywrócony do oryginału. uwaga użytkowników
rawr
7

Radzenie sobie z czynnikami w R jest dość dziwną pracą, muszę przyznać ... Zmieniając kolejność poziomów czynników, nie zmieniasz kolejności podstawowych wartości liczbowych. Oto mała demonstracja:

> numbers = 1:4
> letters = factor(letters[1:4])
> dtf <- data.frame(numbers, letters)
> dtf
  numbers letters
1       1       a
2       2       b
3       3       c
4       4       d
> sapply(dtf, class)
  numbers   letters 
"integer"  "factor" 

Teraz, jeśli zamienisz ten współczynnik na liczbowy, otrzymasz:

# return underlying numerical values
1> with(dtf, as.numeric(letters))
[1] 1 2 3 4
# change levels
1> levels(dtf$letters) <- letters[4:1]
1> dtf
  numbers letters
1       1       d
2       2       c
3       3       b
4       4       a
# return numerical values once again
1> with(dtf, as.numeric(letters))
[1] 1 2 3 4

Jak widzisz ... zmieniając poziomy, zmieniasz tylko poziomy (kto by to powiedział, co?), A nie wartości liczbowe! Ale kiedy używasz factorfunkcji, jak sugerował @Jonathan Chang, dzieje się coś innego: zmieniasz same wartości liczbowe.

Ponownie otrzymujesz błąd, ponieważ robisz to, levelsa następnie próbujesz go dopasować factor. Nie rób tego !!! Czy nie używać levelsalbo będziesz rzeczy bałagan (chyba że dokładnie wiesz co robisz).

Jedna sugestia: unikaj nazywania swoich obiektów identyczną nazwą jak obiekty R ( dfjest funkcją gęstości dla rozkładu F, lettersdaje małe litery alfabetu). W tym konkretnym przypadku twój kod nie byłby wadliwy, ale czasami może być ... ale może to spowodować zamieszanie, a tego nie chcemy, prawda?!? =)

Zamiast tego użyj czegoś takiego (jeszcze raz przejdę od początku):

> dtf <- data.frame(f = 1:4, g = factor(letters[1:4]))
> dtf
  f g
1 1 a
2 2 b
3 3 c
4 4 d
> with(dtf, as.numeric(g))
[1] 1 2 3 4
> dtf$g <- factor(dtf$g, levels = letters[4:1])
> dtf
  f g
1 1 a
2 2 b
3 3 c
4 4 d
> with(dtf, as.numeric(g))
[1] 4 3 2 1

Zauważ, że możesz również nazwać Cię za data.framepomocą dfi letterszamiast g, a wynik będzie OK. Właściwie ten kod jest identyczny z tym, który opublikowałeś, zmieniane są tylko nazwy. Ta część factor(dtf$letter, levels = letters[4:1])nie spowodowałaby błędu, ale może być myląca!

Przeczytaj ?factoruważnie instrukcję! Jaka jest różnica między factor(g, levels = letters[4:1])i factor(g, labels = letters[4:1])? Co jest podobne w levels(g) <- letters[4:1]i g <- factor(g, labels = letters[4:1])?

Możesz wprowadzić składnię ggplot, abyśmy mogli Ci bardziej pomóc w tym!

Twoje zdrowie!!!

Edytować:

ggplot2faktycznie wymaga zmiany zarówno poziomów, jak i wartości? Hm ... wykopię to ...

aL3xa
źródło
3

Chciałbym dodać kolejny przypadek, w którym poziomy mogą być łańcuchami zawierającymi liczby wraz z kilkoma znakami specjalnymi: jak na poniższym przykładzie

df <- data.frame(x = c("15-25", "0-4", "5-10", "11-14", "100+"))

Domyślne poziomy xto:

df$x
# [1] 15-25 0-4   5-10  11-14 100+ 
# Levels: 0-4 100+ 11-14 15-25 5-10

Tutaj, jeśli chcemy zmienić kolejność poziomów czynników zgodnie z wartością liczbową, bez jawnego zapisywania poziomów, moglibyśmy zrobić

library(gtools)
df$x <- factor(df$x, levels = mixedsort(df$x))

df$x
# [1] 15-25 0-4   5-10  11-14 100+ 
# Levels: 0-4 5-10 11-14 15-25 100+
as.numeric(df$x)
# [1] 4 1 2 3 5

Mam nadzieję, że można to uznać za przydatną informację dla przyszłych czytelników.

joel.wilson
źródło
0

Oto moja funkcja zmiany kolejności czynników w danej ramce danych:

reorderFactors <- function(df, column = "my_column_name", 
                           desired_level_order = c("fac1", "fac2", "fac3")) {

  x = df[[column]]
  lvls_src = levels(x) 

  idxs_target <- vector(mode="numeric", length=0)
  for (target in desired_level_order) {
    idxs_target <- c(idxs_target, which(lvls_src == target))
  }

  x_new <- factor(x,levels(x)[idxs_target])

  df[[column]] <- x_new

  return (df)
}

Stosowanie: reorderFactors(df, "my_col", desired_level_order = c("how","I","want"))

Boern
źródło