In R how can I make a six-bar chart where each bar contains a range of data?

Viewed 81

I have a spreadsheet that shows the ages and games played for baseball players.

structure(list(Player = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 
12, 13, 14, 15, 16, 17, 18), Games = c(21, 7, 159, 156, 10, 152, 
23, 68, 13, 123, 29, 13, 111, 2, 84, 15, 140, 93), Age = c(34, 
23, 33, 22, 28, 24, 28, 33, 33, 29, 29, 25, 20, 41, 37, 21, 22, 
31), under21 = c(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 
0, 0, 0), Y21to25 = c(0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 
0, 0, 1, 1, 0), Y26to30 = c(0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 
0, 0, 0, 0, 0, 0, 0), Y31to35 = c(1, 0, 1, 0, 0, 0, 0, 1, 1, 
0, 0, 0, 0, 0, 0, 0, 0, 1), Y36to40 = c(0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0), over40 = c(0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0)), row.names = c(NA, 18L), class = "data.frame")

Here is another view of it using glimpse(df). I added the six columns from "under21" thru "over40" for illustrative purposes.

Observations: 18
Variables: 9
$ Player  <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18
$ Games   <dbl> 21, 7, 159, 156, 10, 152, 23, 68, 13, 123, 29, 13, 111, 2, 84…
$ Age     <dbl> 34, 23, 33, 22, 28, 24, 28, 33, 33, 29, 29, 25, 20, 41, 37, 2…
$ under21 <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0
$ Y21to25 <dbl> 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0
$ Y26to30 <dbl> 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0
$ Y31to35 <dbl> 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1
$ Y36to40 <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0
$ over40  <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0

What I need is a bar chart with six bars ("under21" thru "over40") rising from the x-axis. The y-axis, "Games," will show the number of games played by all the players in each age group. So, the "under21" bar will show the total games played by all the players under age 21, and so on.

What is the best way to do that?

2 Answers

You can reshape your dataframe into a longer format using pivot_longer function from tidyr package (part of tidyverse), filter out 0 values and then plot the total of games in function of the category they belong using geom_col:

library(tidyverse)
df %>% pivot_longer(-c(Player, Games, Age), names_to = "var", values_to = "val") %>%
  mutate(var = factor(var, levels = c("under21","Y21to25","Y26to30","Y31to35","Y36to40","over40"))) 
  
# A tibble: 108 x 5
   Player Games   Age var       val
    <dbl> <dbl> <dbl> <fct>   <dbl>
 1      1    21    34 under21     0
 2      1    21    34 Y21to25     0
 3      1    21    34 Y26to30     0
 4      1    21    34 Y31to35     1
 5      1    21    34 Y36to40     0
 6      1    21    34 over40      0
 7      2     7    23 under21     0
 8      2     7    23 Y21to25     1
 9      2     7    23 Y26to30     0
10      2     7    23 Y31to35     0
# … with 98 more rows
library(tidyverse)
df %>% pivot_longer(-c(Player, Games, Age), names_to = "var", values_to = "val") %>%
  mutate(var = factor(var, levels = c("under21","Y21to25","Y26to30","Y31to35","Y36to40","over40"))) %>%
  filter(val != 0) %>%
  ggplot(aes(x = var, y = Games, fill = var))+
  geom_col()

enter image description here

Does it look what you are trying to achieve ?


EDIT: Calculating sum of Games outside ggplot2

You can calculate yourself the sum of Games per categordy by doing:

library(dplyr)
library(tidyr)
df %>% pivot_longer(-c(Player, Games, Age), names_to = "var", values_to = "val") %>%
  mutate(var = factor(var, levels = c("under21","Y21to25","Y26to30","Y31to35","Y36to40","over40"))) %>%
  filter(val != 0) %>%
  group_by(var) %>% 
  summarise(Games = sum(Games)) 

# A tibble: 6 x 2
  var     Games
  <fct>   <dbl>
1 under21   111
2 Y21to25   483
3 Y26to30   185
4 Y31to35   354
5 Y36to40   195
6 over40      2

And you can add pipe sequence to plot it directly by doing:

library(dplyr)
library(tidyr)
library(gpglot2)
df %>% pivot_longer(-c(Player, Games, Age), names_to = "var", values_to = "val") %>%
  mutate(var = factor(var, levels = c("under21","Y21to25","Y26to30","Y31to35","Y36to40","over40"))) %>%
  filter(val != 0) %>%
  group_by(var) %>% 
  summarise(Games = sum(Games)) %>%
  ggplot(aes(x = var, y = Games, fill = var))+
  geom_col()

And you will get the exact same plot

EDIT -- reshape2 has been superseded by tidyr. Please refer to dc37's answer for a more up-to-date approach.


This solution uses melt from the reshape2 package to collapse the age variables into two columns: variable and value. From there, you can subset your data frame to include only rows where value is 1, and then create your bar graph, specifying stat = "identity" to let geom_bar know that you are supplying your own y value.

library(reshape2)
library(ggplot2)

stats <- structure(list(Player = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 
12, 13, 14, 15, 16, 17, 18), Games = c(21, 7, 159, 156, 10, 152, 
23, 68, 13, 123, 29, 13, 111, 2, 84, 15, 140, 93), Age = c(34, 
23, 33, 22, 28, 24, 28, 33, 33, 29, 29, 25, 20, 41, 37, 21, 22, 
31), under21 = c(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 
0, 0, 0), Y21to25 = c(0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 
0, 0, 1, 1, 0), Y26to30 = c(0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 
0, 0, 0, 0, 0, 0, 0), Y31to35 = c(1, 0, 1, 0, 0, 0, 0, 1, 1, 
0, 0, 0, 0, 0, 0, 0, 0, 1), Y36to40 = c(0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0), over40 = c(0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0)), row.names = c(NA, 18L), class = "data.frame")

stats <- melt(stats, id.vars = c("Player","Games","Age"))
stats <- stats[stats$value == 1,]

p <-  ggplot(stats, aes(x = variable, y = Games))
p + geom_bar(stat = "identity") + xlab("")

enter image description here

This code does not account for Player 13 appearing in both under21 and Y36to40 groups.

Related