Zamów Paski na wykresie słupkowym ggplot2

301

Próbuję zrobić wykres słupkowy, w którym największy słupek byłby najbliżej osi y, a najkrótszy słupek byłby najdalej. Jest to coś w rodzaju tabeli, którą mam

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

Staram się więc zbudować wykres słupkowy, który pokazuje liczbę graczy według pozycji

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

ale wykres pokazuje najpierw pasek bramkarza, następnie obronę, a na koniec napastnika. Chciałbym, aby wykres był uporządkowany tak, aby pasek obrony znajdował się najbliżej osi y, bramkarza i wreszcie napastnika. Dzięki

Julio Diaz
źródło
12
Czy ggplot nie może zmienić ich kolejności bez konieczności manipulowania tabelą (lub ramką danych)?
tumultous_rooster
1
@ MattO'Brien Uważam, że to niewiarygodne, że nie dzieje się to w jednym prostym poleceniu
Euler_Salter
@Zimano Szkoda, że ​​to właśnie otrzymujesz z mojego komentarza. Moje obserwacje ggplot2
dotyczyły
2
@Euler_Salter Dziękuję za wyjaśnienie, szczerze przepraszam za skakanie na was w ten sposób. Usunąłem pierwotną uwagę.
Zimano

Odpowiedzi:

214

Kluczem do zamawiania jest ustawienie poziomów współczynnika w żądanej kolejności. Uporządkowany czynnik nie jest wymagany; dodatkowe informacje w uporządkowanym współczynniku nie są konieczne, a jeśli dane te są wykorzystywane w jakimkolwiek modelu statystycznym, może to spowodować niewłaściwą parametryzację - kontrasty wielomianowe nie są odpowiednie dla danych nominalnych takich jak ten.

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

wykres słupkowy

W najogólniejszym sensie wystarczy ustawić poziomy współczynników w pożądanej kolejności. Jeśli nie zostanie określony, poziomy współczynnika zostaną posortowane alfabetycznie. Możesz również określić kolejność poziomów w wywołaniu, aby uwzględnić czynnik, jak powyżej, a także inne sposoby są możliwe.

theTable$Position <- factor(theTable$Position, levels = c(...))
Gavin Simpson
źródło
1
@Gavin: 2 uproszczenia: skoro już korzystasz within, nie musisz go używać theTable$Position, a możesz po prostu sort(-table(...))zmniejszyć zamówienie.
Prasad Chalasani
2
@Prasad pierwszy był pozostałością po testach, więc dziękuję za zwrócenie na to uwagi. Jeśli chodzi o to drugie, wolę jawnie poprosić o odwrotne sortowanie niż to, -którego używasz, ponieważ o wiele łatwiej jest uzyskać intencję decreasing = TRUEniż zauważyć -resztę kodu.
Gavin Simpson,
2
@GavinSimpson; Myślę, że część o levels(theTable$Position) <- c(...)prowadzi do niepożądanego zachowania, w którym rzeczywiste wpisy ramki danych zostają uporządkowane, a nie tylko poziomy współczynnika. Zobacz to pytanie . Może powinieneś zmodyfikować lub usunąć te linie?
Anton
2
Zdecydowanie zgadzam się z Antonem. Właśnie zobaczyłem to pytanie i zacząłem się zastanawiać, skąd wzięli złą radę levels<-. Zamierzam edytować tę część, przynajmniej wstępnie.
Gregor Thomas
2
@Anton Dzięki za sugestię (i Gregorowi za edycję); Nigdy nie zrobiłbym tego levels<-()dzisiaj. Jest to coś sprzed 8 lat i nie mogę sobie przypomnieć, czy wtedy było inaczej, czy po prostu się myliłem, ale niezależnie od tego, to jest złe i powinno zostać usunięte! Dzięki!
Gavin Simpson
220

@GavinSimpson: reorderto potężne i skuteczne rozwiązanie tego:

ggplot(theTable,
       aes(x=reorder(Position,Position,
                     function(x)-length(x)))) +
       geom_bar()
Alex Brown
źródło
7
Rzeczywiście +1, a zwłaszcza w tym przypadku, gdy istnieje logiczny porządek, który możemy wykorzystać numerycznie. Jeśli weźmiemy pod uwagę dowolne porządkowanie kategorii i nie chcemy alfabetycznie, wówczas równie łatwo (łatwiej?) Można określić poziomy bezpośrednio, jak pokazano.
Gavin Simpson
2
To jest najładniejsze. Zlikwiduj potrzebę modyfikacji oryginalnej ramki danych
T.Fung,
Cudownie, właśnie zauważyłem, że możesz to zrobić nieco bardziej zwięźle, jeśli wszystko, czego chcesz, to uporządkować według funkcji długości, a porządek rosnący jest w porządku, co często robię:ggplot(theTable,aes(x=reorder(Position,Position,length))+geom_bar()
postylem
146

Służy scale_x_discrete (limits = ...)do określania kolejności prętów.

positions <- c("Goalkeeper", "Defense", "Striker")
p <- ggplot(theTable, aes(x = Position)) + scale_x_discrete(limits = positions)
QIBIN LI
źródło
12
Twoje rozwiązanie jest najbardziej odpowiednie w mojej sytuacji, ponieważ chcę zaprogramować wykreślanie, gdzie x jest dowolną kolumną wyrażoną przez zmienną w ramce danych. Inne sugestie byłyby trudniejsze do wyrażenia układu rzędu x przez wyrażenie obejmujące zmienną. Dzięki! W razie zainteresowania mogę udostępnić moje rozwiązanie, korzystając z Twojej sugestii. Jeszcze jeden problem, dodając scale_x_discrete (limit = ...), zauważyłem, że po prawej stronie wykresu jest pusta przestrzeń tak szeroka jak wykres słupkowy. Jak mogę pozbyć się pustej przestrzeni? Ponieważ nie służy to żadnemu celowi.
Yu Shen
Wydaje się to konieczne do zamawiania pasków histogramów
geotheory
9
QIBIN: Wow ... inne odpowiedzi tutaj działają, ale twoja odpowiedź wydaje się nie tylko najbardziej zwięzła i elegancka, ale najbardziej oczywista, gdy myślisz w ramach ggplot. Dziękuję Ci.
Dan Nguyen,
Kiedy próbowałem tego rozwiązania, na moich danych, nie wyświetlałem NA. Czy istnieje sposób na użycie tego rozwiązania i wyświetlanie wykresów NA?
user2460499
To eleganckie i proste rozwiązanie - dziękuję !!
Kalif Vaughn
91

Myślę, że już dostarczone rozwiązania są zbyt szczegółowe. Bardziej zwięzłym sposobem wykonania wykresu słupkowego posortowanego według częstotliwości jest ggplot

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

Jest podobny do sugerowanego przez Alexa Browna, ale nieco krótszy i działa bez niepotrzebnej definicji funkcji.

Aktualizacja

Wydaje mi się, że moje stare rozwiązanie było w tym czasie dobre, ale obecnie wolałbym używać forcats::fct_infreqwspółczynnika sortowania według częstotliwości:

require(forcats)

ggplot(theTable, aes(fct_infreq(Position))) + geom_bar()
Holger Brandl
źródło
Nie rozumiem drugiego argumentu przemawiającego za zmianą funkcji i co ona robi. Czy możesz uprzejmie wyjaśnić, co się dzieje?
user3282777,
1
@ user3282777 czy wypróbowałeś dokumentację stat.ethz.ch/R-manual/R-devel/library/stats/html/… ?
Holger Brandl,
1
Świetne rozwiązanie! Dobrze widzieć innych stosujących rozwiązania typu tidyverse!
Mike
29

Podobnie jak reorder()w odpowiedzi Alexa Browna, moglibyśmy również użyć forcats::fct_reorder(). Zasadniczo posortuje współczynniki określone w 1. arg, zgodnie z wartościami w 2. arg po zastosowaniu określonej funkcji (domyślnie = mediana, której używamy tutaj, ponieważ ma tylko jedną wartość na poziom współczynnika).

Szkoda, że ​​w pytaniu PO wymagana kolejność jest również alfabetyczna, ponieważ jest to domyślna kolejność sortowania podczas tworzenia czynników, więc ukryje to, co ta funkcja faktycznie robi. Aby było bardziej jasne, zastąpię „Goalkeeper” słowem „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')

wprowadź opis zdjęcia tutaj

użytkownik2739472
źródło
1
Najlepszym rozwiązaniem IMHO jako forcats jest również pakiet Tidyverse.
c0bra
kciuki za Zoalkeeper
otwtm
23

Prosta zmiana kolejności czynników oparta na dplyr może rozwiązać ten problem:

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

Musisz tylko określić Positionkolumnę jako czynnik uporządkowany, w którym poziomy są uporządkowane według ich liczby:

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

(Zauważ, że table(Position)tworzy licznik częstotliwości Positionkolumny.)

Następnie twoja ggplotfunkcja pokaże słupki w malejącej kolejności zliczania. Nie wiem, czy jest taka opcja geom_barbez konieczności jawnego tworzenia uporządkowanego czynnika.

Prasad Chalasani
źródło
Nie w pełni parsowałem tam twojego kodu, ale jestem prawie pewien, reorder()że biblioteka statystyk wykonuje to samo zadanie.
Chase
@Zaznaj, jak proponujesz użyć reorder()w tym przypadku? Czynnik wymagający zmiany kolejności musi zostać zmieniony przez jakąś funkcję samą w sobie i staram się znaleźć dobry sposób, aby to zrobić.
Gavin Simpson
ok, with(theTable, reorder(Position, as.character(Position), function(x) sum(duplicated(x))))jest jeden sposób, a drugi, with(theTable, reorder(Position, as.character(Position), function(x) as.numeric(table(x))))ale te są równie zawiłe ...
Gavin Simpson
Uprościłem nieco odpowiedź, by jej użyć sortzamiastorder
Prasad Chalasani
@Gavin - być może źle zrozumiałem oryginalny kod Prasad (nie mam R na tej maszynie do przetestowania ...), ale wyglądało to tak, jakby zmieniał kategorie na podstawie częstotliwości, co reorderjest biegły. Zgadzam się z tym pytaniem, że potrzebne jest coś bardziej zaangażowanego. Przepraszam za zamieszanie.
Chase
17

Oprócz forcats :: fct_infreq, wspomnianego przez @HolgerBrandl, istnieją forcats :: fct_rev, które odwracają kolejność czynników.

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)             

wyjście gplot

Robert McDonald
źródło
„fct_infreq (Position)” to mała rzecz, która robi tyle, dzięki !!
Paul
12

Zgadzam się z Zach, że liczenie w ramach dplyr jest najlepszym rozwiązaniem. Odkryłem, że jest to najkrótsza wersja:

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

Będzie to również znacznie szybsze niż wcześniejsze zmienianie poziomów czynników, ponieważ liczenie odbywa się w dplyr, a nie w ggplot lub użyciu table.

Alexandru Papiu
źródło
12

Jeśli kolumny wykresu pochodzą ze zmiennej numerycznej, jak w ramce danych poniżej, możesz zastosować prostsze rozwiązanie:

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

Znak minus przed zmienną sortowania (-Qty) kontroluje kierunek sortowania (rosnąco / malejąco)

Oto niektóre dane do testowania:

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

Kiedy znalazłem ten wątek, to była odpowiedź, której szukałem. Mam nadzieję, że jest to przydatne dla innych.

JColares
źródło
8

Kolejna alternatywa polegająca na zmianie kolejności w celu uporządkowania poziomów czynnika. W porządku rosnącym (n) lub malejącym (-n) na podstawie liczby. Bardzo podobny do tego używającego fct_reorderz forcatspakietu:

Kolejność malejąca

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

wprowadź opis zdjęcia tutaj

Rosnąco

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

wprowadź opis zdjęcia tutaj

Ramka danych:

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

Ponieważ patrzymy tylko na rozkład pojedynczej zmiennej („Pozycja”), a nie na relację między dwiema zmiennymi , być może histogram byłby bardziej odpowiednim wykresem. ggplot ma geom_histogram (), który ułatwia:

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

wprowadź opis zdjęcia tutaj

Za pomocą geom_histogram ():

Myślę, że geom_histogram ( ) jest trochę dziwaczny, ponieważ inaczej traktuje dane ciągłe i dyskretne.

Aby uzyskać ciągłe dane , możesz po prostu użyć geom_histogram () bez parametrów. Na przykład, jeśli dodamy wektor numeryczny „Wynik” ...

    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

i użyj geom_histogram () w zmiennej „Score” ...

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

wprowadź opis zdjęcia tutaj

W przypadku danych dyskretnych, takich jak „Pozycja”, musimy określić obliczoną statystykę obliczoną na podstawie estetyki, aby podać wartość y wysokości prętów za pomocą stat = "count":

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

Uwaga: w ciekawy i mylący sposób możesz również używać stat = "count"do ciągłego przesyłania danych i myślę, że zapewnia on bardziej estetyczny wykres.

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

wprowadź opis zdjęcia tutaj

Edycje : Rozszerzona odpowiedź w odpowiedzi na pomocne sugestie DebanjanB .

niewątpliwie
źródło
0

Uważam to za bardzo denerwujące, że ggplot2nie oferuje „automatycznego” rozwiązania tego problemu. Dlatego stworzyłem tę bar_chart()funkcję w ggcharts.

ggcharts::bar_chart(theTable, Position)

wprowadź opis zdjęcia tutaj

Domyślnie bar_chart()sortuje paski i wyświetla poziomy wykres. Aby zmienić ten zestaw horizontal = FALSE. Ponadto bar_chart()usuwa nieestetyczną „szczelinę” między prętami i osią.

Thomas Neitmann
źródło