2
votes

I'd like to produce a facet_wrap where the order of factors within facets is based on the one of the column factor order. The heart of the problem is each group has duplicated factor levels and when I do plotting only one factor level is ordered correctly in the facet_wrap. (See the graph below)

I try to order factor levels in each group and each factor level should be ordered correctly inside of each facet.

Here is my attempt

df_pattern<- data.frame(address = rep(rep(LETTERS[1:3]),3)) 

df_TP <- data.frame(No=rep(seq(1:3)),
                    clas=c("Good","Bad","Ugly"),stringsAsFactors = F)

set.seed(12)
df_ex <- df_pattern%>%
  mutate(No=rep(seq(1:3),each=3))%>%
  left_join(df_TP)%>%
  mutate(clas=sample(clas))%>%
  group_by(No)

#      address    No  clas
#       <fctr> <int> <chr>
#    1       A     1  Good
#    2       B     1  Ugly
#    3       C     1  Ugly
#    4       A     2  Good
#    5       B     2  Ugly
#    6       C     2   Bad
#    7       A     3   Bad
#    8       B     3   Bad
#    9       C     3  Good

Now lets try to sort address levels according to user defined clas column order

set.seed(12)
df_ex <- df_pattern%>%
  mutate(No=rep(seq(1:3),each=3))%>%
  left_join(df_TP)%>%
  mutate(clas=sample(clas))%>%
  group_by(No)%>%
  mutate(clas=factor(clas,levels=c("Good","Bad","Ugly")))%>%
  mutate(address=factor(address,levels=unique(address[order(clas)])))%>%
  mutate(address=as.character(address))%>%
  arrange(No,clas) 

      address    No  clas
#       <fctr> <int> <ord>
#    1       A     1  Good
#    2       B     1  Ugly
#    3       C     1  Ugly
#    4       A     2  Good
#    5       C     2   Bad
#    6       B     2  Ugly
#    7       C     3  Good
#    8       A     3   Bad
#    9       B     3   Bad

As you can see only the No=1 group ordered correctly in the plot. Maybe this because only one factor level in the data set.

> levels(df_ex$address)
[1] "A" "B" "C"

How can we order factor levels in each group and show them in the facet_wrap? according to clas levels in each facet_wrap?

Thanks!

ggplot code

ggplot(df_ex, aes(x=address,y="",fill=clas)) + #x axis bias voltage dependence
  geom_tile() + 
  scale_fill_manual(values=c('Good'="green","Bad"="Blue","Ugly"="black"))+
  facet_wrap(~No,ncol=1,scales = "free_x")+
  theme(legend.position = "top",axis.text.y = element_text(size = 20,angle = 90),axis.text.x = element_text(size=12,face="bold",colour = "black"),
        axis.title.y = element_text(face="bold",size = 20, colour = "black"),
        axis.title.x = element_text(face="bold",size = 20 , colour = "black"),
        strip.text = element_text(size=26, face="bold"),
        strip.background = element_rect(fill="#FFFF66", colour="black", size=0.5),
        plot.title=element_text(face="bold",color="red",size=14),
        legend.title = element_text(colour="black", size=26,face="bold"),
        legend.text = element_text(colour="black", size=18))+
  labs(x = "address",y = "")

enter image description here

2
@NathanDay sure. I am sorry I forgot:)Alexander
to my knowledge, currently ggplot doesn't let you define breaks or limits for each facet manually github.com/tidyverse/ggplot2/issues/187, so you are tied to the overall order of df_ex$address which here is "A,B,C" if you make 3 separate plots you could use grid.arrange or cowplot::plot_grid to build a similar figure.Nate
@NathanDay I see your comment. I thought there is some solution made to this problem. But as you mentioned still not! I guess I will stick with grid.arrange.Alexander

2 Answers

4
votes

This solution makes each group unique and arranges in the desired order, then changes the names back to your original names.

df_ex$names<-paste(df_ex$address,df_ex$clas,df_ex$No)
df_ex$names<-factor(df_ex$names,levels=c("A Good 1","B Ugly 1","C Ugly 1", "A Good 2", "C Bad 2", "B Ugly 2", "C Good 3", "A Bad 3", "B Bad 3"))


ggplot(df_ex, aes(x=names,y="",fill=clas)) + #x axis bias voltage dependence
  geom_tile() + 
  scale_fill_manual(values=c('Good'="green","Bad"="Blue","Ugly"="black"))+
  facet_wrap(~No,ncol=1,scales = "free_x")+
  theme(legend.position = "top",axis.text.y = element_text(size = 20,angle = 90),axis.text.x = element_text(size=12,face="bold",colour = "black"),
        axis.title.y = element_text(face="bold",size = 20, colour = "black"),
        axis.title.x = element_text(face="bold",size = 20 , colour = "black"),
        strip.text = element_text(size=26, face="bold"),
        strip.background = element_rect(fill="#FFFF66", colour="black", size=0.5),
        plot.title=element_text(face="bold",color="red",size=14),
        legend.title = element_text(colour="black", size=26,face="bold"),
        legend.text = element_text(colour="black", size=18))+
  labs(x = "address",y = "")+
  scale_x_discrete(breaks=df_ex$names, labels=df_ex$address)

enter image description here

4
votes

This old question has already an accepted answer. But as it is being used as a dupe target, I feel obliged to suggest a slightly improved and more concise variant.

It's based on the recent enhancements of the ggplot2 package, namely the labels parameter to scale_x_discrete(), and Hadley's forcats package released to CRAN in August 2016. The proposed solution enhances the accepted answer by using material from this answer.

Prepare data

df_ex as provided by the OP needs to be modified to include a variable which guarantees an overall sort order across all facets:

library(dplyr)   # version 0.5.0 used 
df_ex <- df_ex %>% mutate(ordered = paste0(No, address) %>% 
                   forcats::fct_inorder())

The additional column to df_ex now looks as follows:

  address    No   clas ordered
    <chr> <int> <fctr>  <fctr>
1       A     1   Good      1A
2       B     1   Ugly      1B
3       C     1   Ugly      1C
4       A     2   Good      2A
5       C     2    Bad      2C
6       B     2   Ugly      2B
7       C     3   Good      3C
8       A     3    Bad      3A
9       B     3    Bad      3B

As df_ex already had been sorted in the desired order using arrange(), fct_inorder() returns the new column ordered with the levels in the same order as their first appearance.

Plotting

Instead of address, ordered is plotted on the x-axis. The parameter scales = "free_x" to facet_wrap() ensures that unused levels will be dropped from the facets. However, the labels on the x-axis need to be be replaced by supplying a named vector to the labels parameter of scale_x_discrete().

library(ggplot2)   # version 2.2.1 used
ggplot(df_ex, aes(x=ordered,y="",fill=clas)) + #x axis bias voltage dependence
  geom_tile() + 
  scale_fill_manual(values=c('Good'="green","Bad"="Blue","Ugly"="black"))+
  facet_wrap(~No,ncol=1,scales = "free_x")+
  theme(legend.position = "top",axis.text.y = element_text(size = 20,angle = 90),axis.text.x = element_text(size=12,face="bold",colour = "black"),
        axis.title.y = element_text(face="bold",size = 20, colour = "black"),
        axis.title.x = element_text(face="bold",size = 20 , colour = "black"),
        strip.text = element_text(size=26, face="bold"),
        strip.background = element_rect(fill="#FFFF66", colour="black", size=0.5),
        plot.title=element_text(face="bold",color="red",size=14),
        legend.title = element_text(colour="black", size=26,face="bold"),
        legend.text = element_text(colour="black", size=18))+
  labs(x = "address",y = "") + 
  scale_x_discrete(labels = setNames(df_ex$address, df_ex$ord)) +
  

enter image description here