Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Layered axes in ggplot?

Tags:

r

ggplot2

I'm wondering if it's possible to make layered/segmented axes in GGLPOT2 (or another graphics package; I just prefer ggplot).

What I want to do is take the below data, make a stacked bar chart that has Period on the x-axis, but within each period, each animal as well. Then the colors of the bars within each animal will be the 'color' variable

set.seed(1234)
data <- data.frame(
    animal = sample(c('bear','tiger','lion'), 50, replace=T),
    color = sample(c('black','brown','orange'), 50, replace=T),
    period = sample(c('first','second','third'), 50, replace=T),
    value = sample(1:100, 50, replace=T))

then putting this into a stacked bar chart:

library(ggplot2)
plot <- ggplot(data, aes(x=period, y=value, fill=color)) + 
    geom_bar(stat='identity')

Which produces this:

enter image description here

But what I really want is, within each bar for period, three separate stacked bars for each animal (stacked by color).

I have a feeling I'm missing a simple piece of syntax here, but the 'obvious' things don't seem to work, eg,

plot <- ggplot(data, aes(x=c(period,animal), y=value, fill=color)) + 
    geom_bar(stat='identity')
like image 353
Marc Tulla Avatar asked Dec 26 '22 04:12

Marc Tulla


2 Answers

Two steps to doing this:

  1. Add the group=animal aesthetic to the plot (tell it to group by animal)

  2. Add position="dodge" to your geom_bar layer (tell it the bars should be separate)

Thus:

ggplot(data, aes(x=period, y=value, fill=color, group=animal, color=animal)) +
        geom_bar(stat="identity", position="dodge")

This looks like:

enter image description here

One of the issues here is that it doesn't describe which animal is which: there isn't a particularly easy way to fix that. That's why I would probably make this plot through faceting:

ggplot(data, aes(x=animal, y=value, fill=color)) + geom_bar(stat="identity") +
    facet_wrap(~ period)

enter image description here

like image 60
David Robinson Avatar answered Dec 28 '22 07:12

David Robinson


Even if there already a good answer, I want to add my solution. I always use this kind of visualization if I have 3 categorical variables and I do not want to use faceting or similar visualizations.

This chart is produced by the following code, even if the code looks cluttered, I am already used to it :-)

Basically I just use the geom_rect to draw my barchart

enter image description here

And here is my code

library(data.table)
library(ggplot2)

the data

set.seed(1234)
data <- data.frame(
animal = sample(c('bear','tiger','lion'), 50, replace=T),
color = sample(c('black','brown','orange'), 50, replace=T),
period = sample(c('first','second','third'), 50, replace=T),
value = sample(1:100, 50, replace=T))

just for convenience, I'm more familiar with the data.table as with the basic data.frame

dt <- as.data.table(data)

grouping the basic data

groups <- c("period", "animal", "color")
thevalue <- c("value")
dt.grouped <- dt[,lapply(.SD, sum), by = groups, .SDcols = thevalue]

the inner group

xaxis.inner.member <- unique(dt.grouped$animal)
xaxis.inner.count <- length(unique(xaxis.inner.member))
xaxis.inner.id <- seq(1:xaxis.inner.count)
setkey(dt.grouped, animal)
dt.grouped <- dt.grouped[J(xaxis.inner.member, inner.id = xaxis.inner.id)]

the outer group

xaxis.outer.member <- unique(dt.grouped$period)
xaxis.outer.count <- length(unique(xaxis.outer.member))
xaxis.outer.id <- seq(1:xaxis.outer.count)
setkey(dt.grouped, period)
dt.grouped <- dt.grouped[J(xaxis.outer.member, outer.id = xaxis.outer.id)]

charting parameters

xaxis.outer.width <- 0.9
xaxis.inner.width <- (xaxis.outer.width / xaxis.inner.count)
xaxis.inner.width.adjust <- 0.01 / 2

dt.ordered <- dt.grouped[order(outer.id,inner.id, color),]
dt.ordered[,value.cum := cumsum(value), by = list(period, animal)]
dt.ordered[,xmin := (outer.id - xaxis.outer.width / 2) + xaxis.inner.width * (inner.id - 1) +     xaxis.inner.width.adjust] 
dt.ordered[,xmax := (outer.id - xaxis.outer.width / 2) + xaxis.inner.width * inner.id -    xaxis.inner.width.adjust]
dt.ordered[,ymin := value.cum - value]
dt.ordered[,ymax := value.cum]

building the data.table for the text labels of the inner xaxis

dt.text <- data.table(
period = rep(xaxis.outer.member, each = xaxis.inner.count)
,animal = rep(xaxis.inner.member, times = xaxis.inner.count)
)
setkey(dt.text, animal)
dt.text <- dt.text[J(xaxis.inner.member,inner.id = xaxis.inner.id),]
setkey(dt.text, period)
dt.text <- dt.text[J(xaxis.outer.member,outer.id = xaxis.outer.id),]
dt.text[, xaxis.inner.label := animal]
dt.text[, xaxis.inner.label.x := (outer.id - xaxis.outer.width / 2) + xaxis.inner.width * inner.id - (xaxis.inner.width / 2) ]

the plotting starts here

p <- ggplot()
p <- p + geom_rect(data = dt.ordered,
aes(
,x = period
,xmin = xmin
,xmax = xmax 
,ymin = ymin
,ymax = ymax
,fill = color)
)

adding the values as labels

p <- p + geom_text(data = dt.ordered,
aes(
label = value
,x = (outer.id - xaxis.outer.width / 2) + xaxis.inner.width * inner.id - (xaxis.inner.width / 2)
,y = value.cum
)
,colour = "black"
,vjust = 1.5               
)

adding the labels for the inner xaxis

p <- p + geom_text(data = dt.text,
aes(
label = xaxis.inner.label
,x = xaxis.inner.label.x
,y = 0
)
,colour = "darkgrey"
,vjust = 1.5   
)

finally plotting the chart

p
like image 32
Tom Martens Avatar answered Dec 28 '22 08:12

Tom Martens