Reordenar los niveles de un factor sin cambiar el orden de los valores

124

Tengo un marco de datos con algunas variables numéricas y algunas factorvariables categóricas . El orden de los niveles para esos factores no es como yo quiero que sean.

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

Si cambio el orden de los niveles, las letras ya no están con sus números correspondientes (a partir de este momento, mis datos no tienen sentido).

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

Simplemente quiero cambiar el orden de nivel , por lo que al trazar, las barras se muestran en el orden deseado, que puede diferir del orden alfabético predeterminado.

crangos
fuente
1
¿Podría alguien darme una pista de por qué la asignación a niveles (...) cambia el orden de las entradas en el marco de datos, como lo muestra crangos en la pregunta? Me parece terriblemente poco intuitivo e indeseable para mí. Pasé algún tiempo depurando un problema causado por esto hoy mismo. Sin embargo, creo que podría haber una razón para este comportamiento que no puedo ver, o al menos una explicación razonable de por qué sucede.
Anton

Respuestas:

120

Use el levelsargumento de 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
fuente
1
Gracias, esto funcionó. Por alguna extraña razón, ggplot ahora cambió correctamente el orden en la leyenda, pero no en la trama. Extraño.
crangos
77
ggplot2 me obligó a cambiar tanto el orden de los niveles (ver arriba) como el orden de los valores del marco de datos. df <- df [nrow (df): 1,] # reverse
crangos
@crangos, creo que ggplot usa el orden alfabético de los niveles, y a veces ignora los niveles de factores personalizados. Confirme e incluya el número de versión.
smci
22

un poco más, solo para el registro

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

También puede encontrar Relevel útil y combine_factor .

George Dontas
fuente
2
Tu primera respuesta no me funciona. Pero esto funciona:reorder(df$letters, seq(4,1))
Alex Holcombe
1
Tengo una situación muy extraña en la que el "pedido" funciona en un conjunto de datos, no en otro. En el otro conjunto de datos, arroja un error "Error en tapply (X = X, INDEX = x, FUN = FUN, ...): falta el argumento" X ", sin ningún valor predeterminado". No estoy seguro de cuál es la solución a este problema. No puedo encontrar ninguna diferencia relevante entre los conjuntos de datos.
CoderGuy123
10

Desde que esta pregunta estuvo activa por última vez, Hadley ha lanzado su nuevo forcatspaquete para manipular factores y lo encuentro escandalosamente útil. Ejemplos del marco de datos del OP:

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

Para revertir niveles:

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

Para agregar más niveles:

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

Y muchas más fct_xxx()funciones útiles .

Joe
fuente
¿Todavía está disponible?
Joshua Rosenberg
1
¿Quieres escribir un código como el siguiente: df %>% mutate(letters = fct_rev(letters)).
jazzurro
9

entonces, lo que desea, en el léxico R, es cambiar solo las etiquetas para una variable de factor dada (es decir, dejar los datos y los niveles de los factores , sin cambios).

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

dado que desea cambiar solo la asignación de punto de datos a etiqueta y no los datos o el esquema de factores (cómo se agrupan los puntos de datos en ubicaciones individuales o valores de factores, puede ser útil saber cómo se establece originalmente la asignación cuando crea inicialmente el factor.

las reglas son simples:

  • las etiquetas se asignan a niveles por valor de índice (es decir, el valor en los niveles [2] recibe la etiqueta, etiqueta [2]);
  • los niveles de factores se pueden establecer explícitamente pasándolos a través del argumento de los niveles ; o
  • si no se proporciona ningún valor para el argumento de niveles, se utiliza el valor predeterminado, que es el resultado que llama único en el vector de datos pasado (para el argumento de datos );
  • las etiquetas se pueden establecer explícitamente mediante el argumento de etiquetas; o
  • Si no se proporciona ningún valor para el argumento de las etiquetas, se utiliza el valor predeterminado, que es solo el vector de niveles
Doug
fuente
1
No sé por qué esto no es tan votado como la respuesta aceptada. Esto es mucho más informativo.
Rambatino
12
Si utiliza este enfoque, sus datos están mal etiquetados.
Nazer
44
en realidad sí, no sé qué hacer con esto, la respuesta parece tener la intención de etiquetar mal los datos en aras de la trama? ugh revertido a original. usuarios tengan cuidado
rawr
7

Tratar con factores en R es un trabajo bastante peculiar, debo admitir ... Al reordenar los niveles de factores, no reordenas los valores numéricos subyacentes. Aquí hay una pequeña demostración:

> 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" 

Ahora, si convierte este factor a numérico, obtendrá:

# 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

Como puede ver ... al cambiar los niveles, solo cambia los niveles (¿quién lo diría, eh?), ¡No los valores numéricos! Pero, cuando usa la factorfunción como sugirió @Jonathan Chang, sucede algo diferente: usted mismo cambia los valores numéricos.

levelsRecibes un error una vez más porque lo haces y luego intentas volver a nivelarlo factor. No lo hagas !!! No lo use levelso arruinará las cosas (a menos que sepa exactamente lo que está haciendo).

Una pequeña sugerencia: evite nombrar sus objetos con un nombre idéntico al de los objetos de R ( dfes la función de densidad para la distribución de F, lettersda letras minúsculas del alfabeto). En este caso en particular, su código no sería defectuoso, pero a veces puede ser ... pero esto puede crear confusión, y no queremos eso, ¿verdad? =)

En su lugar, use algo como esto (volveré desde el principio una vez más):

> 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

Tenga en cuenta que también puede nombrarlo data.framecon dfy en letterslugar de g, y el resultado estará bien. En realidad, este código es idéntico al publicado, solo se cambian los nombres. ¡Esta parte factor(dtf$letter, levels = letters[4:1])no arrojaría un error, pero puede ser confuso!

¡Lea el ?factormanual a fondo! ¿Cuál es la diferencia entre factor(g, levels = letters[4:1])y factor(g, labels = letters[4:1])? ¿Qué es similar en levels(g) <- letters[4:1]y g <- factor(g, labels = letters[4:1])?

¡Puedes poner la sintaxis de ggplot, para que podamos ayudarte más en este!

¡¡¡Salud!!!

Editar:

ggplot2en realidad requiere cambiar tanto los niveles como los valores? Hm ... voy a cavar este ...

aL3xa
fuente
3

Deseo agregar otro caso en el que los niveles podrían ser cadenas con números junto con algunos caracteres especiales: como en el ejemplo a continuación

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

Los niveles predeterminados de xes:

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

Aquí, si queremos reordenar los niveles de factores de acuerdo con el valor numérico, sin escribir explícitamente los niveles, lo que podríamos hacer es

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

Espero que esto pueda considerarse como información útil para futuros lectores.

joel.wilson
fuente
0

Aquí está mi función para reordenar los factores de un marco de datos dado:

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

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

Boern
fuente