Related
I'd like to draw bar plot like this but in dual Y axis
(https://i.stack.imgur.com/ldMx0.jpg)
the first three indexs range from 0 to 1,
so I want the left y-axis (corresponding to NSE, KGE, VE) to range from 0 to 1,
and the right y-axis (corresponding to PBIAS) to range from -15 to 5.
the following is my data and code:
library("ggplot2")
## data
data <- data.frame(
value=c(0.82,0.87,0.65,-3.39,0.75,0.82,0.63,1.14,0.85,0.87,0.67,-7.03),
sd=c(0.003,0.047,0.006,4.8,0.003,0.028,0.006,4.77,0.004,0.057,0.014,4.85),
index=c("NSE","KGE","VE","PBIAS","NSE","KGE","VE","PBIAS","NSE","KGE","VE","PBIAS"),
period=c("all","all","all","all","calibration","calibration","calibration","calibration","validation","validation","validation","validation")
)
## fix index sequence
data$index <- factor(data$index, levels = c('NSE','KGE','VE',"PBIAS"))
data$period <- factor(data$period, levels = c('all','calibration', 'validation'))
## bar plot
ggplot(data, aes(x=index, y=value, fill=period))+
geom_bar(position="dodge", stat="identity")+
geom_errorbar(aes(ymin=value-sd, ymax=value+sd),
position = position_dodge(0.9), width=0.2 ,alpha=0.5, size=1)+
theme_bw()
I try to scale and shift the second y-axis,
but PBIAS bar plot was removed because of out of scale limit as follow:
(https://i.stack.imgur.com/n6Jfm.jpg)
the following is my code with dual y axis:
## bar plot (scale and shift the second y-axis with slope/intercept in 20/-15)
ggplot(data, aes(x=index, y=value, fill=period))+
geom_bar(position="dodge", stat="identity")+
geom_errorbar(aes(ymin=value-sd, ymax=value+sd),
position = position_dodge(0.9), width=0.2 ,alpha=0.5, size=1)+
theme_bw()+
scale_y_continuous(limits = c(0,1), name = "value", sec.axis = sec_axis(~ 20*.- 15, name="value"))
Any advice for move bar_plot or other solution?
Taking a different approach, instead of using a dual axis one option would be to make two separate plots and glue them together using patchwork. IMHO that is much easier than fiddling around with the rescaling the data (that's the step you missed, i.e. if you want to have a secondary axis you also have to rescale the data) and makes it clearer that the indices are measured on a different scale:
library(ggplot2)
library(patchwork)
data$facet <- data$index %in% "PBIAS"
plot_fun <- function(.data) {
ggplot(.data, aes(x = index, y = value, fill = period)) +
geom_bar(position = "dodge", stat = "identity") +
geom_errorbar(aes(ymin = value - sd, ymax = value + sd),
position = position_dodge(0.9), width = 0.2, alpha = 0.5, size = 1
) +
theme_bw()
}
p1 <- subset(data, !facet) |> plot_fun() + scale_y_continuous(limits = c(0, 1))
p2 <- subset(data, facet) |> plot_fun() + scale_y_continuous(limits = c(-15, 15), position = "right")
p1 + p2 +
plot_layout(guides = "collect", width = c(3, 1))
A second but similar option would be to use ggh4x which via ggh4x::facetted_pos_scales allows to set the limits for facet panels individually. One drawback, the panels have the same width. (I failed in making this approach work with facet_grid and space="free")
library(ggplot2)
library(ggh4x)
data$facet <- data$index %in% "PBIAS"
ggplot(data, aes(x = index, y = value, fill = period)) +
geom_bar(position = "dodge", stat = "identity") +
geom_errorbar(aes(ymin = value - sd, ymax = value + sd),
position = position_dodge(0.9), width = 0.2, alpha = 0.5, size = 1
) +
facet_wrap(~facet, scales = "free") +
facetted_pos_scales(
y = list(
facet ~ scale_y_continuous(limits = c(-15, 15), position = "right"),
!facet ~ scale_y_continuous(limits = c(0, 1), position = "left")
)
) +
theme_bw() +
theme(strip.text.x = element_blank())
I wanted the barplot to appear in two forms, so I created repeated data and used it as an input.
So I used the data in the form below.
I put the data in the form above and wrote the following code to use it.
Select <- "Mbp"
if(Select == "Mbp"){
Select <- "Amount of sequence (Mbp)"
} else if (Select == "Gbp"){
Select <- "Amount of sequence (Gbp)"
}
ggplot(G4, aes(x = INDV, y = Bp, fill = Group)) + theme_light() +
geom_bar(stat = 'identity', position = 'dodge', width = 0.6) + coord_flip() +
scale_x_discrete(limits = rev(unname(unlist(RAW_TRIM[1])))) +
scale_fill_discrete(breaks = c("Raw data","Trimmed data"))+
scale_y_continuous(labels = scales::comma, position = "right") +
theme(axis.text = element_text(colour = "black", face = "bold", size = 15)) +
theme(legend.position = "bottom", legend.text = element_text(face = "bold", size = 15),
legend.title = element_blank()) + ggtitle(Select) + xlab("") + ylab("") +
theme(plot.title = element_text(size = 25, face = "bold", hjust = 0.5))
Then I can get a plot like the one below, where I want the red graph to be on top of the green graph.
I also tried changing the order of the data, and several sites such as the Internet and Stack Overflow provided solutions and used them, but not a single solution was able to solve them.
If you know a solution, please let me know how to modify the code or change the data.
thank you.
You seem to be asking more than one question at once here, but the main one is: why do the bars for Raw appear under those for Trimmed? The short answer is: factor levels and the behaviour of coord_flip().
Let's make a toy dataset:
library(tidyverse)
G4 <- data.frame(INDV = c("C_01", "C_01", "C_41", "C_41"),
Group = c("Raw data", "Trimmed data", "Raw data", "Trimmed data"),
Bp = c(200, 100, 500, 400))
A simple dodged bar chart. Note that Raw comes before Trimmed, because R is before T in the alphabet:
G4 %>%
ggplot(aes(INDV, Bp)) +
geom_col(aes(fill = Group),
position = "dodge")
Now we coord_flip:
G4 %>%
ggplot(aes(INDV, Bp)) +
geom_col(aes(fill = Group),
position = "dodge") +
coord_flip()
This has the effect of reversing the variables, so Raw is now below Trimmed.
We can fix that by altering factor levels. As there are only two groups we can just reverse them using fct_rev() from the forcats package:
G4 %>%
ggplot(aes(INDV, Bp)) +
geom_col(aes(fill = fct_rev(Group)),
position = "dodge") +
coord_flip()
The bar for Raw is now on top but unfortunately, the colours are now reversed so that Raw bars are green. We can fix that using scale_fill_manual():
G4 %>%
ggplot(aes(INDV, Bp)) +
geom_col(aes(fill = fct_rev(Group)),
position = "dodge") +
coord_flip() +
scale_fill_manual(values = c("#00BFC4", "#F8766D"))
Now the Raw bars are on top, and they are red.
I would like to plot a line + point plot. But my data contain "<" Is it possible to make the special point for the point with "<"? Any suggestion on how to better present those info?
Sample data:
df<-structure(list(Day = c(1, 3, 6, 7, 9, 12, 15), Score = c("0.1",
"0.5", "<1.3", "0.2", "<1.55", "0.8", "1.2")), row.names = c(NA,
-7L), class = c("tbl_df", "tbl", "data.frame"))
Here is my plot code and sample:
df<- df %>%
mutate(Score1=gsub("<", "", Score))
ggplot(data=df26, aes(x=Day,y=Score1, group=1)) +
geom_line()+
geom_point()
BTW, your Score1 is still in character type, so it is not plotting proportional to its value. Here's one approach to use the value without "<" but the label including the "<".
There are lots of options here. A few below:
add the "<" to the axis labels
add a visual indicator (could be color, text, an arrow, etc.) to note "smaller than" values.
Color differently and use a legend. I like ggtext for this as you can use markup to color in specific words, which is great for incorporating color legends into explanatory text.
Perhaps "<1.3" could be interpreted, based on situational knowledge, that the measurement was somewhere below 1.3 but not below 1.2. Then we could show simulated possibilities.
ggplot(data=df, aes(x=Day,y=as.numeric(Score1), group=1)) +
geom_line()+
geom_point() +
scale_y_continuous(breaks = as.numeric(df$Score1), labels = df$Score,
minor_breaks = NULL)
Or you might indicate visually that the values are smaller, esp. if there's some plausible range that they might be lower.
ggplot(data=df, aes(x=Day,y=as.numeric(Score1), group=1)) +
geom_line()+
geom_point() +
geom_segment(data = . %>% filter(Score1 != Score),
aes(xend = Day, yend = as.numeric(Score1) - 0.2),
arrow = arrow(length = unit(0.02, "npc")), color = "gray60") +
scale_y_continuous(breaks = as.numeric(df$Score1), labels = df$Score, minor_breaks = NULL)
library(ggtext)
ggplot(data=df, aes(x=Day, y=as.numeric(Score1), group = 1,
shape = Score1 == Score)) +
geom_line()+
geom_point(aes(color = Score1 == Score)) +
scale_shape_discrete(guide = FALSE) +
scale_color_manual(values = c("red", "black"), guide = FALSE) +
labs(caption = "<span style = 'color:#FF0000'>Red dots</span> were recorded with a '<'") +
theme(plot.caption = element_markdown())
Another idea is we might show possibilities that are consistent with the measurement based on our situational understanding of what "<1.3" means -- ie maybe it means the value was "somewhere between 1.2 and 1.3."
df_possibilities <- df %>%
filter(Score1 != Score) %>%
uncount(10) %>%
rowwise() %>%
mutate(adjusted = as.numeric(Score1) - runif(1, max = 0.1))
ggplot(data=df, aes(x=Day,y=as.numeric(Score1), group=1)) +
geom_line()+
geom_point() +
scale_y_continuous(breaks = as.numeric(df$Score1), labels = df$Score,
minor_breaks = NULL) +
geom_point(data = df_possibilities,
aes(y = adjusted), alpha = 0.1)
Couple of alternatives, inclulded in the same graph:
by a key using a coloured geom_point, or
by annotation with geom_text
This is just to give an impression, both methods can be enhanced and modified to provide the appearance you think provides the best visualisation.
library(ggplot2)
library(dplyr)
library(stringr)
df1 <-
df%>%
mutate(y = as.numeric(str_extract(Score, "\\d.\\d{1,2}")),
less_than = if_else(str_detect(Score, "<"), TRUE, FALSE))
ggplot(df1, aes(Day, y))+
geom_point(aes(colour = less_than))+
geom_line()+
geom_text(aes(label = Score), hjust = -0.2)
Created on 2021-04-15 by the reprex package (v2.0.0)
UPDATE
Labels idea from Peter. Thanks.
You can use shape for different shapes.
with ggpubr more sophisticated. Here a overview of the numbers:
ggplot(data=df, aes(x=factor(Day),y=Score1, group=1)) +
geom_line()+
geom_point() +
geom_point(data=df[c(3,5),], aes(x=factor(Day), y=Score1), colour="red", size=5, shape=25) +
geom_text(aes(label = Score), hjust = -0.2)+
theme_bw()
I have a data frame(t1) and I want to illustrate the shares of companies in relation to their size
I added a Dummy variable in order to make a filled barplot and not 3:
t1$row <- 1
The size of companies are separated in medium, small and micro:
f_size <- factor(t1$size,
ordered = TRUE,
levels = c("medium", "small", "micro"))
The plot is build up with the economic_theme:
ggplot(t1, aes(x = "Size", y = prop.table(row), fill = f_size)) +
geom_col() +
geom_text(aes(label = as.numeric(f_size)),
position = position_stack(vjust = 0.5)) +
theme_economist(base_size = 14) +
scale_fill_economist() +
theme(legend.position = "right",
legend.title = element_blank()) +
theme(axis.title.y = element_text(margin = margin(r = 20))) +
ylab("Percentage") +
xlab(NULL)
How can I modify my code to get the share for medium, small and micro in the middle of the three filled parts in the barplot?
Thanks in advance!
Your question isn't quite clear to me and I suggest you re-phrase it for clarity. But I believe you're trying to get the annotations to be accurately aligned on the Y-axis. For this use, pre-calculate the labels and then use annotate
library(data.table)
library(ggplot2)
set.seed(3432)
df <- data.table(
cat= sample(LETTERS[1:3], 1000, replace = TRUE)
, x= rpois(1000, lambda = 5)
)
tmp <- df[, .(pct= sum(x) / sum(df[,x])), cat][, cumsum := cumsum(pct)]
ggplot(tmp, aes(x= 'size', y= pct, fill= cat)) + geom_bar(stat='identity') +
annotate('text', y= tmp[,cumsum] - 0.15, x= 1, label= as.character(tmp[,pct]))
But this is a poor decision graphically. Stacked bar charts, by definition sum to 100%. Rather than labeling the components with text, just let the graphic do this for you via the axis labels:
ggplot(tmp, aes(x= cat, y= pct, fill= cat)) + geom_bar(stat='identity') + coord_flip() +
scale_y_continuous(breaks= seq(0,1,.05))
I am creating a grouped boxplot with a scatterplot overlay using ggplot2. I would like to group each scatterplot datapoint with the grouped boxplot that it corresponds to.
However, I'd also like the scatterplot points to be different symbols. I seem to be able to get my scatterplot points to group with my grouped boxplots OR get my scatterplot points to be different symbols... but not both simultaneously. Below is some example code to illustrate what's happening:
library(scales)
library(ggplot2)
# Generates Data frame to plot
Gene <- c(rep("GeneA",24),rep("GeneB",24),rep("GeneC",24),rep("GeneD",24),rep("GeneE",24))
Clone <- c(rep(c("D1","D2","D3","D4","D5","D6"),20))
variable <- c(rep(c(rep("Day10",6),rep("Day20",6),rep("Day30",6),rep("Day40",6)),5))
value <- c(rnorm(24, mean = 0.5, sd = 0.5),rnorm(24, mean = 10, sd = 8),rnorm(24, mean = 1000, sd = 900),
rnorm(24, mean = 25000, sd = 9000), rnorm(24, mean = 8000, sd = 3000))
value <- sqrt(value*value)
Tdata <- cbind(Gene, Clone, variable)
Tdata <- data.frame(Tdata)
Tdata <- cbind(Tdata,value)
# Creates the Plot of All Data
# The below code groups the data exactly how I'd like but the scatter plot points are all the same shape
# and I'd like them to each have different shapes.
ln_clr <- "black"
bk_clr <- "white"
point_shapes <- c(0,15,1,16,2,17)
blue_cols <- c("#EFF2FB","#81BEF7","#0174DF","#0000FF","#0404B4")
lp1 <- ggplot(Tdata, aes(x=variable, y=value, fill=Gene)) +
stat_boxplot(geom ='errorbar', position = position_dodge(width = .83), width = 0.25,
size = 0.7, coef = 4) +
geom_boxplot( coef=1, outlier.shape = NA, position = position_dodge(width = .83), lwd = 0.3,
alpha = 1, colour = ln_clr) +
geom_point(position = position_jitterdodge(dodge.width = 0.83), size = 1.8, alpha = 0.7,
pch=15)
lp1 + scale_fill_manual(values = blue_cols) + labs(y = "Fold Change") +
expand_limits(y=c(0.01,10^5)) +
scale_y_log10(expand = c(0, 0), breaks = c(0.01,1,100,10000,100000),
labels = trans_format("log10", math_format(10^.x)))
ggsave("Scatter Grouped-Wrong Symbols.png")
#*************************************************************************************************************************************
# The below code doesn't group the scatterplot data how I'd like but the points each have different shapes
lp2 <- ggplot(Tdata, aes(x=variable, y=value, fill=Gene)) +
stat_boxplot(geom ='errorbar', position = position_dodge(width = .83), width = 0.25,
size = 0.7, coef = 4) +
geom_boxplot( coef=1, outlier.shape = NA, position = position_dodge(width = .83), lwd = 0.3,
alpha = 1, colour = ln_clr) +
geom_point(position = position_jitterdodge(dodge.width = 0.83), size = 1.8, alpha = 0.7,
aes(shape=Clone))
lp2 + scale_fill_manual(values = blue_cols) + labs(y = "Fold Change") +
expand_limits(y=c(0.01,10^5)) +
scale_y_log10(expand = c(0, 0), breaks = c(0.01,1,100,10000,100000),
labels = trans_format("log10", math_format(10^.x)))
ggsave("Scatter Ungrouped-Right Symbols.png")
If anyone has any suggestions I'd really appreciate it.
Thank you
Nathan
To get the boxplots to appear, the shape aesthetic needs to be inside geom_point, rather than in the main call to ggplot. The reason for this is that when the shape aesthetic is in the main ggplot call, it applies to all the geoms, including geom_boxplot. However, applying a shape=Clone aesthetic causes geom_boxplot to create a separate boxplot for each level of Clone. Since there's only one row of data for each combination of variable and Clone, no boxplot is produced.
That the shape aesthetic affects geom_boxplot seems counterintuitive to me, but maybe there's a reason for it that I'm not aware of. In any case, moving the shape aesthetic into geom_point solves the problem by applying the shape aesthetic only to geom_point.
Then, to get the points to appear with the correct boxplot, we need to group by Gene. I also added theme_classic to make it easier to see the plot (although it's still very busy):
ggplot(Tdata, aes(x=variable, y=value, fill=Gene)) +
stat_boxplot(geom ='errorbar', width=0.25, size=0.7, coef=4, position=position_dodge(0.85)) +
geom_boxplot(coef=1, outlier.shape=NA, lwd=0.3, alpha=1, colour=ln_clr, position=position_dodge(0.85)) +
geom_point(position=position_jitterdodge(dodge.width=0.85), size=1.8, alpha=0.7,
aes(shape=Clone, group=Gene)) +
scale_fill_manual(values=blue_cols) + labs(y="Fold Change") +
expand_limits(y=c(0.01,10^5)) +
scale_y_log10(expand=c(0, 0), breaks=10^(-2:5),
labels=trans_format("log10", math_format(10^.x))) +
theme_classic()
I think the plot would be easier to understand if you use faceting for Gene and the x-axis for variable. Putting time on the x-axis seems more intuitive, while using facetting frees up the color aesthetic for the points. With six different clones, it's still difficult (for me at least) to differentiate the point markers, but this looks cleaner to me than the previous version.
library(dplyr)
ggplot(Tdata %>% mutate(Gene=gsub("Gene","Gene ", Gene)),
aes(x=gsub("Day","",variable), y=value)) +
stat_boxplot(geom='errorbar', width=0.25, size=0.7, coef=4) +
geom_boxplot(coef=1, outlier.shape=NA, lwd=0.3, alpha=1, colour=ln_clr, width=0.5) +
geom_point(aes(fill=Clone), position=position_jitter(0.2), size=1.5, alpha=0.7, shape=21) +
theme_classic() +
facet_grid(. ~ Gene) +
labs(y = "Fold Change", x="Day") +
expand_limits(y=c(0.01,10^5)) +
scale_y_log10(expand=c(0, 0), breaks=10^(-2:5),
labels=trans_format("log10", math_format(10^.x)))
If you really need to keep the points, maybe it would be better to separate the boxplots and points with some manual dodging:
set.seed(10)
ggplot(Tdata %>% mutate(Day=as.numeric(substr(variable,4,5)),
Gene = gsub("Gene","Gene ", Gene)),
aes(x=Day - 2, y=value, group=Day)) +
stat_boxplot(geom ='errorbar', width=0.5, size=0.5, coef=4) +
geom_boxplot(coef=1, outlier.shape=NA, lwd=0.3, alpha=1, width=4) +
geom_point(aes(x=Day + 2, fill=Clone), size=1.5, alpha=0.7, shape=21,
position=position_jitter(width=1, height=0)) +
theme_classic() +
facet_grid(. ~ Gene) +
labs(y="Fold Change", x="Day") +
expand_limits(y=c(0.01,10^5)) +
scale_y_log10(expand=c(0, 0), breaks=10^(-2:5),
labels=trans_format("log10", math_format(10^.x)))
One more thing: For future reference, you can simplify your data creation code:
Gene = rep(paste0("Gene",LETTERS[1:5]), each=24)
Clone = rep(paste0("D",1:6), 20)
variable = rep(rep(paste0("Day", seq(10,40,10)), each=6), 5)
value = rnorm(24*5, mean=rep(c(0.5,10,1000,25000,8000), each=24),
sd=rep(c(0.5,8,900,9000,3000), each=24))
Tdata = data.frame(Gene, Clone, variable, value)