Ordenar barras en el gráfico de barras ggplot2

301

Estoy tratando de hacer un gráfico de barras donde la barra más grande estaría más cerca del eje y y la barra más corta estaría más lejos. Así que esto es algo así como la tabla que tengo

    Name   Position
1   James  Goalkeeper
2   Frank  Goalkeeper
3   Jean   Defense
4   Steve  Defense
5   John   Defense
6   Tim    Striker

Así que estoy tratando de construir un gráfico de barras que muestre el número de jugadores según la posición.

p <- ggplot(theTable, aes(x = Position)) + geom_bar(binwidth = 1)

pero el gráfico muestra la barra del portero primero, luego la defensa, y finalmente la del delantero. Me gustaría que el gráfico se ordene de modo que la barra de defensa esté más cerca del eje y, la del portero y finalmente la del delantero. Gracias

Julio Diaz
fuente
12
¿No puede ggplot reordenarlos por usted sin tener que jugar con la tabla (o el marco de datos)?
tumultous_rooster
1
@ MattO'Brien Me parece increíble que esto no se haga en un solo comando simple
Euler_Salter
@Zimano Lástima que eso es lo que obtienes de mi comentario. Mi observación fue hacia los creadores de ggplot2, no el OP
Euler_Salter
2
@Euler_Salter Gracias por aclararme, mis sinceras disculpas por saltar sobre ti así. He eliminado mi comentario original.
Zimano

Respuestas:

214

La clave para ordenar es establecer los niveles del factor en el orden que desee. No se requiere un factor ordenado; la información adicional en un factor ordenado no es necesaria y si estos datos se están utilizando en algún modelo estadístico, podría producirse una parametrización incorrecta: los contrastes polinómicos no son adecuados para datos nominales como este.

## set the levels in order we want
theTable <- within(theTable, 
                   Position <- factor(Position, 
                                      levels=names(sort(table(Position), 
                                                        decreasing=TRUE))))
## plot
ggplot(theTable,aes(x=Position))+geom_bar(binwidth=1)

figura de diagrama de barras

En el sentido más general, simplemente necesitamos establecer los niveles de factores para que estén en el orden deseado. Si no se especifica, los niveles de un factor se ordenarán alfabéticamente. También puede especificar el orden de nivel dentro de la llamada al factor como se indicó anteriormente, y también son posibles otras formas.

theTable$Position <- factor(theTable$Position, levels = c(...))
Gavin Simpson
fuente
1
@Gavin: 2 simplificaciones: dado que ya está usando within, no hay necesidad de usar theTable$Position, y podría hacerlo sort(-table(...))para disminuir el orden.
Prasad Chalasani
2
@Prasad el primero fue un sobrante de las pruebas, así que gracias por señalarlo. En cuanto a esto último, prefiero pedir explícitamente el tipo invertido que el -que usa, ya que es mucho más fácil obtener la intención de decreasing = TRUEnotar -el resto del código.
Gavin Simpson
2
@GavinSimpson; Creo que la parte sobre levels(theTable$Position) <- c(...)conduce a un comportamiento no deseado donde las entradas reales del marco de datos se reordenan, y no solo los niveles del factor. Ver esta pregunta . ¿Tal vez deberías modificar o eliminar esas líneas?
Anton
2
Muy de acuerdo con Anton. Acabo de ver esta pregunta y busqué dónde obtuvieron los malos consejos para usar levels<-. Voy a editar esa parte, al menos tentativamente.
Gregor Thomas
2
@Anton Gracias por la sugerencia (y a Gregor por la edición); Nunca haría esto levels<-()hoy. Esto es algo de hace 8 años y no puedo recordar si las cosas eran diferentes en ese momento o si simplemente estaba equivocado, pero de todos modos, ¡está mal y debería borrarse! ¡Gracias!
Gavin Simpson
220

@GavinSimpson: reorderes una solución poderosa y efectiva para esto:

ggplot(theTable,
       aes(x=reorder(Position,Position,
                     function(x)-length(x)))) +
       geom_bar()
Alex Brown
fuente
77
De hecho +1, y especialmente en este caso donde hay un orden lógico que podemos explotar numéricamente. Si consideramos el ordenamiento arbitrario de las categorías y no queremos alfabético, entonces es igual de fácil (¿más fácil?) Especificar los niveles directamente como se muestra.
Gavin Simpson
2
Este es el más lindo. Anule la necesidad de modificar el marco de datos original
T.Fung
Encantador, acabo de notar que puedes hacer esto un poco más sucintamente, si todo lo que quieres es ordenar por la función de longitud y el orden ascendente está bien, que es algo que a menudo quiero hacer:ggplot(theTable,aes(x=reorder(Position,Position,length))+geom_bar()
postylem
146

Utilizando scale_x_discrete (limits = ...)para especificar el orden de las barras.

positions <- c("Goalkeeper", "Defense", "Striker")
p <- ggplot(theTable, aes(x = Position)) + scale_x_discrete(limits = positions)
QIBIN LI
fuente
12
Su solución es la más adecuada para mi situación, ya que quiero programar para trazar con x siendo una columna arbitraria expresada por una variable en un data.frame. Las otras sugerencias serían más difíciles de expresar la disposición del orden de x por una expresión que involucra la variable. ¡Gracias! Si hay interés, puedo compartir mi solución usando su sugerencia. Solo un problema más, agregando scale_x_discrete (límites = ...), descubrí que hay un espacio en blanco tan ancho como el gráfico de barras, a la derecha del gráfico. ¿Cómo puedo deshacerme del espacio en blanco? Como no sirve para nada.
Yu Shen
Esto parece necesario para ordenar barras de histograma
geotheory
99
QIBIN: Wow ... las otras respuestas aquí funcionan, pero su respuesta, con mucho, parece no solo la más concisa y elegante, sino la más obvia cuando se piensa desde el marco de trabajo de ggplot. Gracias.
Dan Nguyen
Cuando probé esta solución, en mis datos, no graficaba NA. ¿Hay alguna manera de usar esta solución y hacer que grafique los NA?
user2460499
Esta es una solución elegante y simple, ¡gracias!
Kalif Vaughn el
91

Creo que las soluciones ya proporcionadas son demasiado detalladas. Una forma más concisa de hacer un diagrama de barras ordenado por frecuencia con ggplot es

ggplot(theTable, aes(x=reorder(Position, -table(Position)[Position]))) + geom_bar()

Es similar a lo que sugirió Alex Brown, pero un poco más corto y funciona sin una definición de función cualquiera.

Actualizar

Creo que mi solución anterior era buena en ese momento, pero hoy en día prefiero usar forcats::fct_infreqlos niveles de factor de clasificación por frecuencia:

require(forcats)

ggplot(theTable, aes(fct_infreq(Position))) + geom_bar()
Holger Brandl
fuente
No entiendo el segundo argumento para reordenar la función y qué hace. ¿Puedes explicar amablemente lo que está sucediendo?
user3282777
1
@ user3282777 ¿ha probado los documentos stat.ethz.ch/R-manual/R-devel/library/stats/html/… ?
Holger Brandl
1
Gran solución! ¡Es bueno ver a otros que emplean soluciones tidyverse!
Mike
29

Como reorder()en la respuesta de Alex Brown, también podríamos usar forcats::fct_reorder(). Básicamente, ordenará los factores especificados en el primer argumento, de acuerdo con los valores en el segundo argumento después de aplicar una función específica (predeterminado = mediana, que es lo que usamos aquí ya que solo tenemos un valor por nivel de factor).

Es una pena que en la pregunta del OP, el orden requerido también sea alfabético, ya que ese es el orden de clasificación predeterminado cuando crea factores, por lo que ocultará lo que esta función está haciendo realmente. Para que quede más claro, reemplazaré "Portero" con "Zoalkeeper".

library(tidyverse)
library(forcats)

theTable <- data.frame(
                Name = c('James', 'Frank', 'Jean', 'Steve', 'John', 'Tim'),
                Position = c('Zoalkeeper', 'Zoalkeeper', 'Defense',
                             'Defense', 'Defense', 'Striker'))

theTable %>%
    count(Position) %>%
    mutate(Position = fct_reorder(Position, n, .desc = TRUE)) %>%
    ggplot(aes(x = Position, y = n)) + geom_bar(stat = 'identity')

ingrese la descripción de la imagen aquí

usuario2739472
fuente
1
En mi humilde opinión, la mejor solución como forcats es así como dplyr un paquete tidyverse.
c0bra
aprobado por Zoalkeeper
otwtm
23

Un simple reordenamiento de factores basado en dplyr puede resolver este problema:

library(dplyr)

#reorder the table and reset the factor to that ordering
theTable %>%
  group_by(Position) %>%                              # calculate the counts
  summarize(counts = n()) %>%
  arrange(-counts) %>%                                # sort by counts
  mutate(Position = factor(Position, Position)) %>%   # reset factor
  ggplot(aes(x=Position, y=counts)) +                 # plot 
    geom_bar(stat="identity")                         # plot histogram
zach
fuente
19

Solo necesita especificar que la Positioncolumna sea un factor ordenado donde los niveles están ordenados por sus cuentas:

theTable <- transform( theTable,
       Position = ordered(Position, levels = names( sort(-table(Position)))))

(Tenga en cuenta que table(Position)produce un conteo de frecuencia de la Positioncolumna).

Luego, su ggplotfunción mostrará las barras en orden decreciente de conteo. No sé si hay una opción geom_barpara hacer esto sin tener que crear explícitamente un factor ordenado.

Prasad Chalasani
fuente
No analicé completamente su código allí, pero estoy bastante seguro reorder()de que la biblioteca de estadísticas realiza la misma tarea.
Chase
@Chase, ¿cómo propones usarlo reorder()en este caso? El factor que requiere reordenamiento necesita ser reordenado por alguna función de sí mismo y estoy luchando por ver una buena manera de hacerlo.
Gavin Simpson
ok, with(theTable, reorder(Position, as.character(Position), function(x) sum(duplicated(x))))es de una manera, y de otra, with(theTable, reorder(Position, as.character(Position), function(x) as.numeric(table(x))))pero estos son igual de complicados ...
Gavin Simpson
Simplifiqué un poco la respuesta para usar en sortlugar deorder
Prasad Chalasani
@Gavin: tal vez no entendí bien el código original de Prasad (no tengo R en esta máquina para probar ...) pero parecía que estaba reordenando las categorías en función de la frecuencia, que reorderes hábil para hacerlo. Estoy de acuerdo con esta pregunta de que se necesita algo más complicado. Perdón por la confusion.
Chase
17

Además de forcats :: fct_infreq, mencionado por @HolgerBrandl, hay forcats :: fct_rev, que invierte el orden del factor.

theTable <- data.frame(
    Position= 
        c("Zoalkeeper", "Zoalkeeper", "Defense",
          "Defense", "Defense", "Striker"),
    Name=c("James", "Frank","Jean",
           "Steve","John", "Tim"))

p1 <- ggplot(theTable, aes(x = Position)) + geom_bar()
p2 <- ggplot(theTable, aes(x = fct_infreq(Position))) + geom_bar()
p3 <- ggplot(theTable, aes(x = fct_rev(fct_infreq(Position)))) + geom_bar()

gridExtra::grid.arrange(p1, p2, p3, nrow=3)             

salida de gplot

Robert McDonald
fuente
"fct_infreq (Position)" es lo poco que hace tanto, ¡gracias!
Paul
12

Estoy de acuerdo con zach en que contar dentro de dplyr es la mejor solución. He encontrado que esta es la versión más corta:

dplyr::count(theTable, Position) %>%
          arrange(-n) %>%
          mutate(Position = factor(Position, Position)) %>%
          ggplot(aes(x=Position, y=n)) + geom_bar(stat="identity")

Esto también será significativamente más rápido que reordenar los niveles de factor de antemano ya que el conteo se realiza en dplyr, no en ggplot o usando table.

Alexandru Papiu
fuente
12

Si las columnas del gráfico provienen de una variable numérica como en el marco de datos a continuación, puede usar una solución más simple:

ggplot(df, aes(x = reorder(Colors, -Qty, sum), y = Qty)) 
+ geom_bar(stat = "identity")  

El signo menos antes de la variable de clasificación (-Qty) controla la dirección de clasificación (ascendente / descendente)

Aquí hay algunos datos para probar:

df <- data.frame(Colors = c("Green","Yellow","Blue","Red","Yellow","Blue"),  
                 Qty = c(7,4,5,1,3,6)
                )

**Sample data:**
  Colors Qty
1  Green   7
2 Yellow   4
3   Blue   5
4    Red   1
5 Yellow   3
6   Blue   6

Cuando encontré este hilo, esa fue la respuesta que estaba buscando. Espero que sea útil para otros.

JColares
fuente
8

Otra alternativa usando reordenar para ordenar los niveles de un factor. En orden ascendente (n) o descendente (-n) según el recuento. Muy similar a la que utiliza fct_reorderdesde elforcats paquete:

Orden descendiente

df %>%
  count(Position) %>%
  ggplot(aes(x = reorder(Position, -n), y = n)) +
  geom_bar(stat = 'identity') +
  xlab("Position")

ingrese la descripción de la imagen aquí

Orden ascendente

df %>%
  count(Position) %>%
  ggplot(aes(x = reorder(Position, n), y = n)) +
  geom_bar(stat = 'identity') +
  xlab("Position")

ingrese la descripción de la imagen aquí

Marco de datos:

df <- structure(list(Position = structure(c(3L, 3L, 1L, 1L, 1L, 2L), .Label = c("Defense", 
"Striker", "Zoalkeeper"), class = "factor"), Name = structure(c(2L, 
1L, 3L, 5L, 4L, 6L), .Label = c("Frank", "James", "Jean", "John", 
"Steve", "Tim"), class = "factor")), class = "data.frame", row.names = c(NA, 
-6L))
mpalanco
fuente
5

Como solo estamos viendo la distribución de una sola variable ("Posición") en lugar de mirar la relación entre dos variables , entonces quizás un histograma sería la gráfica más apropiada. ggplot tiene geom_histogram () que lo hace fácil:

ggplot(theTable, aes(x = Position)) + geom_histogram(stat="count")

ingrese la descripción de la imagen aquí

Usando geom_histogram ():

Creo que geom_histogram ( ) es un poco peculiar ya que trata los datos continuos y discretos de manera diferente.

Para datos continuos , puede usar geom_histogram () sin parámetros. Por ejemplo, si agregamos un vector numérico "Puntaje" ...

    Name   Position   Score  
1   James  Goalkeeper 10
2   Frank  Goalkeeper 20
3   Jean   Defense    10
4   Steve  Defense    10
5   John   Defense    20
6   Tim    Striker    50

y use geom_histogram () en la variable "Score" ...

ggplot(theTable, aes(x = Score)) + geom_histogram()

ingrese la descripción de la imagen aquí

Para datos discretos como "Posición" tenemos que especificar una estadística calculada calculada por la estética para dar el valor y para la altura de las barras usando stat = "count":

 ggplot(theTable, aes(x = Position)) + geom_histogram(stat = "count")

Nota: Curiosa y confusamente, también puede usar stat = "count"datos continuos y creo que proporciona un gráfico más estéticamente agradable.

ggplot(theTable, aes(x = Score)) + geom_histogram(stat = "count")

ingrese la descripción de la imagen aquí

Ediciones : Respuesta extendida en respuesta a las útiles sugerencias de DebanjanB .

indudablemente
fuente
0

Me pareció muy molesto que ggplot2no ofrezca una solución 'automática' para esto. Por eso creé la bar_chart()función en ggcharts.

ggcharts::bar_chart(theTable, Position)

ingrese la descripción de la imagen aquí

Por defecto bar_chart()ordena las barras y muestra un diagrama horizontal. Para cambiar ese conjunto horizontal = FALSE. Además, bar_chart()elimina el desagradable 'espacio' entre las barras y el eje.

Thomas Neitmann
fuente