Ordering Factors in Each Facet of Ggplot by Y-Axis Value

Ordering factors in each facet of ggplot by y-axis value

I've found dplyr doesn't work super well with group_by() when dealing with different factor levels in each of the groups. So one work around is thinking of creating a new factor that's unique for each animal-letter combination and ordering that. First, we create an interaction variable with animal+letter and determine the proper order for each of the letters for the animals

new_order <- my_df %>% 
group_by(animals) %>%
do(data_frame(al=levels(reorder(interaction(.$animals, .$letters, drop=TRUE), .$numbers)))) %>%
pull(al)

Now we create the interaction variable in the data we want to plot, use this new ordering, and finally change the labels so they look like just the letters again

my_df %>% 
mutate(al=factor(interaction(animals, letters), levels=new_order)) %>%
ggplot(aes(x = al, y = numbers)) +
geom_point() + facet_wrap(~animals, ncol = 1, scales = 'free_x') +
scale_x_discrete(breaks= new_order, labels=gsub("^.*\\.", "", new_order))

Sample Image

ggplot facet different Y axis order based on value

The functions reorder_within and scale_*_reordered from the tidytext package might come in handy.

reorder_within recodes the values into a factor with strings in the form of "VARIABLE___WITHIN". This factor is ordered by the values in each group of WITHIN.
scale_*_reordered removes the "___WITHIN" suffix when plotting the axis labels.
Add scales = "free_y" in facet_wrap to make it work as expected.

Here is an example with generated data:

library(tidyverse)

# Generate data
df <- expand.grid(
year = 2019:2021,
group = paste("Group", toupper(letters[1:8]))
)
set.seed(123)
df$value <- rnorm(nrow(df), mean = 10, sd = 2)

df %>%
mutate(group = tidytext::reorder_within(group, value, within = year)) %>%
ggplot(aes(value, group)) +
geom_point() +
tidytext::scale_y_reordered() +
facet_wrap(vars(year), scales = "free_y")

Change order of factors within ggplot facets

How about a revolting hack?

This combines your subsets into a new dataframe. It then adds a column concatenating the set number with the variable in order to make each variable unique. The problem is, this affects your x axis names, as they are the concatenated names. To get around this I followed this hack R: Reorder facet_wrapped x-axis with free_x in ggplot2 to remove the axis names and use geom_text instead.

Sample Image

mtcars_melt <- melt(mtcars)

# Subset data
set1 <- subset(mtcars_melt, variable == "mpg" | variable == "wt" | variable == "qsec")
set2 <- subset(mtcars_melt, variable == "gear" | variable == "cyl" | variable == "mpg")
set3 <- subset(mtcars_melt, variable == "drat" | variable == "vs" | variable == "mpg")

# Order factors
set1$variable <- factor(set1$variable, levels = c("mpg", "wt", "qsec"))
set2$variable <- factor(set2$variable, levels = c("mpg", "gear", "cyl"))
set3$variable <- factor(set3$variable, levels = c("vs", "mpg", "drat"))

names(set1)[2]<-"set1"
names(set2)[2]<-"set2"
names(set3)[2]<-"set3"

mset1<-melt(set1)
mset2<-melt(set2)
mset3<-melt(set3)

library(data.table)
new<-as.data.frame(rbindlist(list(mset1,mset2,mset3)))
names(new)[2]<-'setno'

new$lvl <- with(new,paste(variable,setno,sep="."))
new$lvl<-as.factor(new$lvl)
new$lvl<-factor(new$lvl,levels=c("mpg.set1","wt.set1","qsec.set1","mpg.set2","gear.set2","cyl.set2", "vs.set3" , "mpg.set3","drat.set3" ))

plot1 <- ggplot(new, aes(x = lvl, y = value)) + ylim(0, 35) + geom_boxplot()+
facet_wrap(~setno,scales="free_x")+
ylim(-2,NA)+
geom_text(aes(y=-1,label=variable),angle=45,size=5,hjust=1)+
theme(axis.text.x = element_blank(),
axis.title.x = element_blank(),
axis.line.x = element_blank(),
axis.ticks.x = element_blank())+
geom_hline(aes(yintercept=0))

ggplot: facets with their own order of factors on x axis

Edit after comment

You should slightly modify the reorder_within function, by setting max instead of mean like this:

dat<-data.frame(Pal=rep(c("A","B","C","D"),times=c(20,40,60,30)),
Rol=c(rep("aa",15),rep("bb",5),rep("aa",40),rep("cc",60),rep("aa",30)),
Cel=rep(c("home","tree","hat","ball","arm","leg","beer","stick","pen","rope"),times=c(15,13,12,12,13,12,15,18,17,23)),
Value=c(runif(n=20,min = 7000, max = 100000),runif(n=40,min = 100, max = 100000),runif(n=60,min = 1000, max = 1000000),runif(n=30,min = 100000, max = 10000000)),
Col=rep(c("red","black"),each=5,times=15),
Effect=c(rep(c("length","height"),times=c(15,5)),rep(c("weight","length","age of youngest individual found miles from the closest coastline"),times=c(10,7,23)),rep(c("pressure","speed","rate","length"),times=c(10,3,7,40)),rep(c("length","rate","O2","fecundity"),times=c(3,4,7,16)))
)

library(ggplot2)
library(forcats)

reorder_within <- function(x, by, within, fun = max, sep = "___", ...) {
new_x <- paste(x, within, sep = sep)
stats::reorder(new_x, by, FUN = fun)
}

scale_x_reordered <- function(..., sep = "___") {
reg <- paste0(sep, ".+$")
ggplot2::scale_x_discrete(labels = function(x) gsub(reg, "", x), ...)
}

highlights<-data.frame(Pal=c("A","B","C","D"))
highlights$Pal<-factor(highlights$Pal,levels=c("A","B","C","D"))

ggplot() +
geom_rect(data=highlights,aes(xmin=-Inf, xmax=Inf, ymin=1, ymax=1000000000), fill=c("yellow","blue","red","green"), alpha=0.05) +
geom_point(data = dat, aes(x=reorder_within(Effect, Value, Pal), y=Value, shape=Rol, col=Col)) +
scale_color_manual(breaks=unique(dat$Col), values=as.character(unique(dat$Col))) +
labs(x="",y="Activity") + facet_grid(.~Pal, scales = "free_x")+
scale_y_log10(limits=c(1,1000000000),breaks = c(1,10,100,1000,10000,100000,1000000,10000000,100000000,100000000,1000000000)) +
scale_x_reordered() +
theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1),
panel.background = element_rect(fill = "white", colour = "white"),
strip.background = element_rect(fill = "white", colour = "black"),
legend.key = element_rect(fill = "white"))

Sample Image

Created on 2022-07-20 by the reprex package (v2.0.1)

Old answer

You can use the functions reorder_within and scale_x_reordered from this GitHub like this:

dat<-data.frame(Pal=rep(c("A","B","C","D"),each=5),
Rol=c("aa","aa","aa","aa","bb","aa","aa","aa","aa","aa","cc","cc","cc","cc","cc","aa","aa","aa","aa","aa"),
Cel=rep(c("home","tree","hat","ball","pen","rope"),times=c(5,3,2,5,2,3)),
Value=c(7701.1,59897.3,59897.3,59897.3,744438.1,1226.4,1454.6,1454.6,1454.6,1454.6,56600,92400,5010000,7010000,15740000,28.5,34.2,39.9,48.5,57),
Col=c("black","red","black","black","red","red","red","black","black","black","red","red","red","red","red","red","black","black","black","black"),
Effect=c("length","length","length","length","height","weight","length","length","age of youngest individual found miles from the closest coastline","age of youngest individual found miles from the closest coastline","pressure","speed","rate","rate","length","length","rate","rate","O2","fecundity")
)

library(ggplot2)
library(forcats)

reorder_within <- function(x, by, within, fun = mean, sep = "___", ...) {
new_x <- paste(x, within, sep = sep)
stats::reorder(new_x, by, FUN = fun)
}

scale_x_reordered <- function(..., sep = "___") {
reg <- paste0(sep, ".+$")
ggplot2::scale_x_discrete(labels = function(x) gsub(reg, "", x), ...)
}

highlights<-data.frame(Pal=c("A","B","C","D"))
highlights$Pal<-factor(highlights$Pal,levels=c("A","B","C","D"))

ggplot() +
geom_rect(data=highlights,aes(xmin=-Inf, xmax=Inf, ymin=1, ymax=1000000000), fill=c("yellow","blue","red","green"), alpha=0.05) +
geom_point(data = dat, aes(x=reorder_within(Effect, Value, Pal), y=Value, shape=Rol, col=Col)) +
scale_color_manual(breaks=unique(dat$Col), values=as.character(unique(dat$Col))) +
labs(x="",y="Activity") + facet_grid(.~Pal, scales = "free_x")+
scale_y_log10(limits=c(1,1000000000),breaks = c(1,10,100,1000,10000,100000,1000000,10000000,100000000,100000000,1000000000)) +
scale_x_reordered() +
theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1),
panel.background = element_rect(fill = "white", colour = "white"),
strip.background = element_rect(fill = "white", colour = "black"),
legend.key = element_rect(fill = "white"))

Sample Image

Created on 2022-07-20 by the reprex package (v2.0.1)

ordering y axis with facet

You can set your factor on the group column using the currently ordered values as the levels (reversed to get the ordering you want). This will lock in the ordering.

library(dplyr)
dat =data.frame(parent = c("J","J","F","F"),group= c("A(4)","C(3)","A(4)","D(5)"),value=c(1,2,3,4),
count = c(4,3,4,5))
dat <- dat %>% arrange(parent,-count )

dat$group <- factor(dat$group, levels = rev(unique(dat$group)))

ggplot(dat, aes(x = group, y= value))+
geom_bar(stat ="identity",position = "dodge")+
coord_flip()+
facet_wrap(~parent, scale = "free_y")

R / Tidyverse: Ordering factors within group with duplicate labels and plotting using facet_wrap

tidytext::reorder_within() does something similar, and in combination with tidytext::scale_y_reordered() helps with tidying the output to look like your goal.

library(tidytext)

dummy_data %>%
mutate(y_var = reorder_within(y_var, x_var, group_var)) %>%
ggplot() +
geom_point(aes(x = x_var, y = y_var, color = group_var), size = 5) +
scale_y_reordered() +
facet_wrap(~group_var, scales = 'free', dir = 'v')

Sample Image

R ggplot2 facet wrap dot plot reorder each

This could be achieved via reorder_within + scale_y_reordered from the tidytext package like so:

  1. Reorder you y axis variable by values within facets via reorder_within
  2. Use scale_y_reordered
  3. Set the scales free for your y-axis too.
library(ggplot2)
library(dplyr)
library(tidyr)
library(tidytext)

mtcars2 <- as_tibble(mtcars, rownames = 'car')

mtcars_long_numeric_with_mpg <- mtcars2 %>%
select(car, mpg, disp, hp, drat, wt, qsec) %>%
pivot_longer(names_to = 'names', values_to = 'values', 2:7) %>%
mutate(car = tidytext::reorder_within(car, values, names))

ggplot(mtcars_long_numeric_with_mpg, aes(x = values, y = reorder(car, values))) +
geom_point() +
tidytext::scale_y_reordered() +
facet_wrap(~names, scales = 'free')+
theme(text = element_text(size=6))

Sample Image

How to achieve a facet specific ordering of axis entries in ggplot geom_point?

One option would be to indeed use factor levels as you described, but use a prefix that seperates the levels for the different facets. Later, you can then use the labels argument of the scale to remove the prefix. Example below:

set.seed(1)
m1 = c('1A','1B','1C','1D')
m2 = c('2A','2B','2C')
s = c(rep('s1',4),rep('s2',3))
d = data.frame(m = c(m1,m2),
s=factor(s),
v=sample(1:10, replace=T,7),
stringsAsFactors = FALSE)
d$m <- factor(d$m, levels = c("1A", "1C", "1B", "1D", "2B", "2A", "2C"))

library(ggplot2)
ggplot(d, aes(x=m, y=v)) +
geom_point(size=2) +
coord_flip() +
facet_grid( s~. , scales='free', space='free') +
scale_x_discrete(
labels = function(x){substr(x, 2, nchar(x))}
)

Sample Image

Varying factor order in each facet of ggplot2

Unfortunately factors can only have one set of levels. The only way i've found to do this is actually to create two separate data.frames from your data and re-level the factor in each. For example

data <- data.frame(
x = c(LETTERS[1:10],LETTERS[1:3],LETTERS[11:17]),
y = rnorm(n=20,10,2),
type= c(rep("J",10),rep("K",10))
)
data$type <- as.factor(data$type)

J<-subset(data, type=="J")
J$x <- reorder(J$x, J$y, max)
K<-subset(data, type=="K")
K$x <- reorder(K$x, K$y, max)

Now we can plot them with

ggplot(mapping = aes(x=y, y=x, xend=0, yend=x)) + 
geom_segment(data=J, colour="grey50") +
geom_point(data=J, size=3, aes(colour=type)) +
geom_segment(data=K, colour="grey50") +
geom_point(data=K, size=3, aes(colour=type)) +
theme_bw() +
theme(panel.grid.major.y = element_blank()) +
facet_grid(type ~ ., scales="free_y", space="free_y")

which results in

Sample Image



Related Topics



Leave a reply



Submit