Error bar sizing skewed when using plotly - r

I have a chart which has an error bar on it:
However, when I put the chart inside a plotly wrapper, the error bar sizing gets messed up, as shown below:
Does anyone have a solution for keeping the error bar width the same size as the bar, as shown in plot 1, but while keeping the plot rendering with plotly?
library(tidyverse)
library(plotly)
dat <- data.frame(peeps= c("Bill", "Bob", "Becky"),
vals = c(10, 15, 12),
goals = c(8, 13, 10),
grp = c("Bears", "Bears", "Mongoose") %>% as.factor)
p1 <- dat %>%
ggplot(aes(x = peeps, y = vals, fill = grp)) +
geom_bar(stat = "identity") +
geom_errorbar(data = dat,
aes(ymin = goals, ymax = goals),
color = "blue",
size = 1,
linetype = 1) +
scale_y_continuous(expand = c(0, 0)) +
coord_flip()
p1
ggplotly(p1) %>%
layout(legend = list(orientation = "h",
xanchor = "center",
y = -0.15,
x = 0.5))

Using geom_segment() instead of geom_errorbar() is a work-around for this problem.
dat <- data.frame(peeps= c("Bill", "Bob", "Becky") %>% as.factor,
vals = c(10, 15, 12),
goals = c(8, 13, 10),
grp = c("Bears", "Bears", "Mongoose"),
rowid = 1:3)
p1 <- ggplot(data = dat, aes(x = peeps, y = vals, fill = grp, order = rowid)) +
geom_col() +
geom_segment(aes(
x = as.numeric(peeps)-0.45,
xend = as.numeric(peeps)+0.45,
y = goals, yend = goals),
color = "blue",
size = 1) +
scale_y_continuous(expand = c(0, 0)) +
coord_flip()
ggplotly(p1) %>%
layout(legend = list(orientation = "h",
xanchor = "center",
y = -0.15,
x = 0.5))

Related

Legend for a plot with sec.axis (geom bar + geom line)

I have a ggplot with two y-axes by the sec.axis command, and I've been struggling with handling legends in these situations.
The code:
library(ggplot2)
library(ggrepel)
df <- data.frame(day = as.character(seq(from = 1, to = 100, by = 1)),
total = rbinom(n=100,30,0.5),
prop = runif(100))
df <- df %>% arrange(df, by = day)
df$`percentage` <- label_percent(accuracy = 0.01)(df$prop)
ggplot(data = df,
aes(x = day, y = total)) +
geom_bar(aes(x = day, y = total), stat = "identity", fill = "lightgreen", width = 0.35) +
geom_line(data = df,
aes(x = day, y = (prop)*15, group = 1, color = prop),
color = "red", size = 1,inherit.aes = TRUE) +
scale_y_continuous(
labels = function(x) format(x, scientific = FALSE),
#breaks = seq(from = 0, to = 10000000,by = 100000),
sec.axis = sec_axis(trans = ~./15,
name = "Secondary axis",
breaks = seq(from = 0, to = 10, by = 0.1),
scales::percent))+
theme(axis.text.x = element_text(angle = 90, vjust = 0.5))+
geom_label_repel(data=df[nrow(df),],
aes(x = day,
y = prop*15,
label = round(prop*100,2)),
color = 'red',
nudge_x = 2,
segment.alpha = 0.5) +
scale_x_discrete(expand = expansion(add = c(0, 7)))
And I wanted to simply have the legend, instead of having the axis description, like this:
I know it seems reasonably easy to obtain, but given the fact that I don’t have any groups, I either: can't plot any legend what so ever; or I get plotted two squares (when I wanted the legend to consist of a line, with the geom_line color and a square with the geom_bar color).
With the code #divibisan provided, I get the following output:
Final update:
I finally found the solution. I still have no idea how I got a different output from what #divibisan posted, but here goes what worked for me:
library(dplyr)
library(ggplot2)
library(ggrepel)
df <- data.frame(day = as.character(seq(from = 1, to = 100, by = 1)),
total = rbinom(n=100,30,0.5),
prop = runif(100))
df <- df %>% arrange(df, by = day)
df$`percentage` <- scales::label_percent(accuracy = 0.01)(df$prop)
ggplot(data = df,
aes(x = day, y = total)) +
geom_bar(aes(x = day, y = total, fill = "Total"), stat = "identity", width = 0.35) +
geom_line(data = df,
aes(x = day, y = (prop)*15, group = 1, color = 'Percentage'), size = 1,inherit.aes = TRUE) +
scale_y_continuous(
labels = function(x) format(x, scientific = FALSE),
#breaks = seq(from = 0, to = 10000000,by = 100000),
sec.axis = sec_axis(trans = ~./15,
name = "Secondary axis",
breaks = seq(from = 0, to = 10, by = 0.1),
scales::percent))+
theme(axis.text.x = element_text(angle = 90, vjust = 0.5))+
geom_label_repel(data=df[nrow(df),],
aes(x = day,
y = prop*15,
label = round(prop*100,2)),
color = 'red',
nudge_x = 2,
segment.alpha = 0.5) +
scale_x_discrete(expand = expansion(add = c(0, 7))) +
scale_fill_manual(values=c('Total' = 'lightgreen'), drop=TRUE, name='') +
scale_color_manual(values=c('Percentage' = "red"), drop=TRUE, name='') +
theme(legend.title = element_blank())
You just need to set the color/fill with a value in the aes, then use a scale function to set the color and create a legend. Here, we move the color= and fill= values from the bar and line into the aes. Then we add scale_fill/color_manual functions that set the color based on those names:
library(dplyr)
library(ggplot2)
library(ggrepel)
df <- data.frame(day = as.character(seq(from = 1, to = 100, by = 1)),
total = rbinom(n=100,30,0.5),
prop = runif(100))
df <- df %>% arrange(df, by = day)
df$`percentage` <- scales::label_percent(accuracy = 0.01)(df$prop)
ggplot(data = df,
aes(x = day, y = total)) +
geom_bar(aes(x = day, y = total, fill = "Total"), stat = "identity", width = 0.35) +
geom_line(data = df,
aes(x = day, y = (prop)*15, group = 1, color = 'Percentage'), size = 1,inherit.aes = TRUE) +
scale_y_continuous(
labels = function(x) format(x, scientific = FALSE),
#breaks = seq(from = 0, to = 10000000,by = 100000),
sec.axis = sec_axis(trans = ~./15,
name = "Secondary axis",
breaks = seq(from = 0, to = 10, by = 0.1),
scales::percent))+
theme(axis.text.x = element_text(angle = 90, vjust = 0.5))+
geom_label_repel(data=df[nrow(df),],
aes(x = day,
y = prop*15,
label = round(prop*100,2)),
color = 'red',
nudge_x = 2,
segment.alpha = 0.5) +
scale_x_discrete(expand = expansion(add = c(0, 7))) +
scale_fill_manual(values=c('Total' = 'lightgreen', 'Percentage'='red'), drop=TRUE, name='') +
scale_color_manual(values=c('Total' = 'lightgreen', 'Percentage'='red'), drop=TRUE, name='')
If, for some reason, the drop argument isn't working and both colors show up in both scales, there's really no reason to include them in the scale if they're not expected to be there. Just only include the colors in the scale that are desired:
scale_fill_manual(values=c('Total' = 'lightgreen'), drop=TRUE, name='') +
scale_color_manual(values=c('Percentage'='red'), drop=TRUE, name='')

Geom_label_repel not properly referencing to the sec.axis

I am working with a ggplot that has two axis: one for the geom_bar component, and the other for the geom_linecomponent. And for this, I am using the sec.axis() command.
I wanted to insert a box to provide the last value of the geom_line component, but I am struggling because I believe that while using the commmand geom_label_repel, the aesthetic being used, is referent to the geom_barcomponent.
I'll provide a similar data to illustrate what I am saying.
df <- data.frame(day = as.character(seq(from = 1, to = 100, by = 1)),
total = rbinom(n=100,30,0.5),
prop = runif(100))
df <- df %>% arrange(df, by = day)
df$`percentage` <- label_percent(accuracy = 0.01)(df$prop)
ggplot(data = df,
aes(x = day, y = total)) +
geom_bar(stat = "identity", fill = "lightgreen", width = 0.35) +
geom_line(data = df,
aes(x = day, y = (prop)*15, group = 1),
color = "red", size = 1,inherit.aes = TRUE) +
scale_y_continuous(
labels = function(x) format(x, scientific = FALSE),
#breaks = seq(from = 0, to = 10000000,by = 100000),
sec.axis = sec_axis(trans = ~./15,
name = "Secondary axis",
breaks = seq(from = 0, to = 10, by = 0.1),
scales::percent))+
theme(axis.text.x = element_text(angle = 90, vjust = 0.5))+
geom_label_repel(data=df[nrow(df),],
aes(x = day,
y = prop*1,
label = round(prop*100,2)),
color = 'red',
segment.alpha = 0.5) +
scale_x_discrete(expand = expansion(add = c(0, 7)))
Which outputs the following image:
As you can tell, it works well in regards to obtaining the last number of the prop column, which is intended, but it is not automatically placed beside the geom_line.
I have tried messing with the nudge_xand nudge_y commands but it didn't lead me to anywhere, given the fact that I want to have this "number placement" automatic.
Can anyone help?
The sec.axis is in some ways just decorative. ggplot is plotting everything by the main axis. To make the label follow the line, make the same transform as in your geom_line call (y = prop*15):
library(tidyverse)
library(ggrepel)
df <- data.frame(day = as.character(seq(from = 1, to = 100, by = 1)),
total = rbinom(n=100,30,0.5),
prop = runif(100))
df <- df %>% arrange(df, by = day)
df$`percentage` <- scales::label_percent(accuracy = 0.01)(df$prop)
ggplot(data = df,
aes(x = day, y = total)) +
geom_bar(stat = "identity", fill = "lightgreen", width = 0.35) +
geom_line(data = df,
aes(x = day, y = (prop)*15, group = 1),
color = "red", size = 1,inherit.aes = TRUE) +
scale_y_continuous(
labels = function(x) format(x, scientific = FALSE),
#breaks = seq(from = 0, to = 10000000,by = 100000),
sec.axis = sec_axis(trans = ~./15,
name = "Secondary axis",
breaks = seq(from = 0, to = 10, by = 0.1),
scales::percent))+
theme(axis.text.x = element_text(angle = 90, vjust = 0.5))+
geom_label_repel(data=df[nrow(df),],
aes(x = day,
y = prop*15,
label = round(prop*100,2)),
color = 'red',
segment.alpha = 0.5) +
scale_x_discrete(expand = expansion(add = c(0, 7)))
#> Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
#> ℹ Please use `linewidth` instead.

How to arrange data visualization in geom_segment() in a decreasing order?

I was trying to plot tweets' sources/devices in a decreasing order using ggplot/geom_segment in R.
Here is the code I ran:
ggplot(data = device, mapping = aes(x = fct_reorder(as.factor(source), n), y = n)) +
geom_segment(aes(x = source, xend = source, y = 0, yend = n)) +
theme(axis.text.x = element_text(angle = 90, hjust = 1, family = "sans")) +
labs(title = "Tweets by device/source", x = "device/source", y = "frequency") +
geom_point(size = 4, color = "red", fill = alpha("yellow", 0.3), alpha = 0.7, shape = 21, stroke = 2)
Here is the plot it returned, which is not in decreasing pattern as I wanted to be.
So, I was wondering how could I plot the geom_segment in decreasing order?
You used the correct approach, but at the wrong spot. Try to do the factor rearrangement on your data before the ggplot call. In your case you did the reordering, but then used the original "source" data and not the reordered one in geom_segment. Doing the reordering before the ggplot call fixes that.
Here is an example using the mtcars dataset:
mtcars %>%
rownames_to_column("model") %>%
as_tibble() %>%
mutate(model = fct_reorder(model, -mpg)) %>%
ggplot() +
geom_segment(aes(x = model, xend = model, y = 0, yend = mpg)) +
theme(axis.text.x = element_text(angle = 90, hjust = 1, family = "sans")) +
labs(title = "Tweets by device/source", x = "device/source", y = "frequency") +
geom_point(aes(x = model, y = mpg), size = 4, color = "red", fill = alpha("yellow", 0.3), alpha = 0.7, shape = 21, stroke = 2)
The new plot looks like this:
The improved code:
device %>%
as_tibble() %>%
mutate(source = fct_reorder(source, -n)) %>%
ggplot() +
geom_segment(aes(x = source, xend = source, y = 0, yend = n)) +
theme(axis.text.x = element_text(angle = 90, hjust = 1, family = "sans", size = 10)) +
labs(title = "Tweets by device/source", x = "device/source", y = "frequency") +
geom_point(aes(x = source, y = n), size = 3, color = "red", fill =
alpha("yellow", 0.3), alpha = 0.7, shape = 21, stroke = 2)

Adding a legend entry for geom_segment

In the chart below, I would like to include an item in the legend for the blue reference line, which for the sake of this example, we can call "Arbitrary Line". Can anyone provide me a solution for getting that into the legend? Note that the final plot must be rendered in plotly.
library(tidyverse)
library(plotly)
dat <- data.frame(peeps= c("Bill", "Bob", "Becky"),
vals = c(10, 15, 12),
label = c("8% Fake", "12% Pizza", "45% Becky"),
grp = c("Bears", "Bears", "Mongoose") %>% as.factor)
p1 <- dat %>%
ggplot(aes(x = peeps, y = vals, fill = grp)) +
geom_bar(stat = "identity") +
geom_segment(aes(x = 0.55, xend = 3.45, y = 5, yend = 5), color = "blue") +
scale_y_continuous(expand = c(0, 0)) +
coord_flip()
ggplotly(p1) %>%
layout(legend = list(orientation = "h",
xanchor = "center",
y = -0.15,
x = 0.5))
Try to add:
scale_fill_manual(name = "", values="blue", label="Arbitrary Line")

R - (ggplot) Make geom_step jumps dashed

I'm plotting a discrete CDF. I have a few questions regarding geom_step which I'm not finding by using Google.
Is it possible to make the line segment representing the jump dashed
rather than solid to better show whats going on?
Is it possible to add geom_point more efficiently than I do? (less
c/p).
Below is my current solution:
library(tidyverse)
library(ggthemes)
theme_set(theme_few())
x0 <- seq(-0.5, -0.01, by = 0.01)
x1 <- seq(0, 0.99, by = 0.02)
x2 <- seq(1, 1.99, by = 0.02)
x3 <- seq(2, 2.99, by = 0.02)
x35 <- seq(3, 3.49, by = 0.01)
x4 <- seq(3.5, 3.99, by = 0.01)
tibble_ex <- tibble(
x0 = x0,
x1 = x1,
x2 = x2,
x3 = x3,
x35 = x35,
x4 = x4
)
tibble_ex %>%
gather(x, xax, x0:x4) %>%
mutate(cdf = case_when(x == 'x0' ~ 0,
x == 'x1' ~ 1/2,
x == 'x2' ~ 3/5,
x == 'x3' ~ 4/5,
x == 'x35' ~ 9/10,
x == 'x4' ~ 1)) %>%
ggplot(aes(x = xax, y = cdf)) +
geom_step() +
geom_point(aes(x = 0, y = 0), size = 3, shape = 21, fill = 'white') +
geom_point(aes(x = 1, y = 0.5), size = 3, shape = 21, fill = 'white') +
geom_point(aes(x = 2, y = 3/5), size = 3, shape = 21, fill = 'white') +
geom_point(aes(x = 3, y = 4/5), size = 3, shape = 21, fill = 'white') +
geom_point(aes(x = 3.5, y = 9/10), size = 3, shape = 21, fill = 'white') +
geom_point(aes(x = 0, y = 0.5), size = 3, shape = 21, fill = 'black') +
geom_point(aes(x = 1, y = 3/5), size = 3, shape = 21, fill = 'black') +
geom_point(aes(x = 2, y = 4/5), size = 3, shape = 21, fill = 'black') +
geom_point(aes(x = 3, y = 9/10), size = 3, shape = 21, fill = 'black') +
geom_point(aes(x = 3.5, y = 1), size = 3, shape = 21, fill = 'black') +
labs(x = 'x', y = 'F(x)')
ggplot will be more powerful to use if you can put your data into a data frame and structure it so that the characteristics of your data can be mapped directly.
Here's a way to take your data and augment it with additional rows that represent the connecting points, by matching each x with the prior cdf value. I added a column, type, to keep track of which is which. I also arrange df so that geom_segment plots the points in the right order.
new_steps <-
tibble(x = c(0:3, 3.5, 4),
cdf = c(0, .5, .6, .8, .9, 1))
df <- new_steps %>%
mutate(type = "cdf") %>%
bind_rows(new_steps %>%
mutate(type = "prior",
cdf = lag(cdf))) %>%
drop_na() %>%
arrange(x, desc(type))
Then we can map the points' fill and the geom_segments' linetype to type.
ggplot(df) +
geom_point(aes(x, cdf, fill = type),
shape = 21) +
scale_fill_manual(values = c("black", "white")) +
geom_segment(aes(x = lag(x), y = lag(cdf),
xend = x, yend = cdf,
lty = type)) +
scale_linetype_manual(values = c("dashed", "solid"))
(1) No, there is not a built-in way to make the geom_step half-dashed. But if you post this as a separate question, perhaps someone will help create a new geom for this.
(2) The answer is to put the points you want plotted in a data frame, like anything else you might want to plot:
point_data = data.frame(x = rep(c(0, 1, 2, 3, 3.5), 2),
y = c(0, rep(c(.5, .6, .8, .9), 2), 1),
z = rep(c("a", "b"), each = 5))
# calling your gathered/mutated version of tibble_ex df
ggplot(df, aes(x = xax, y = cdf)) +
geom_step() +
geom_point(data = point_data, aes(x = x, y = y, fill = z), shape = 21) +
scale_fill_manual(values = c("white", "black"), guide = FALSE) +
labs(x = 'x', y = 'F(x)')
For the second part of your question, you can put all the coordinates in a separate data frame and call geom_point only once:
ddf <- data.frame(xax = rep(c(0:3, 3.5), 2),
cdf = c(0, .5, .6, .8, .9, .5, .6, .8, .9, 1),
col = rep(c("white", "black"), each = 5))
dev.new()
tibble_ex %>%
gather(x, xax, x0:x4) %>%
mutate(cdf = case_when(x == 'x0' ~ 0,
x == 'x1' ~ 1/2,
x == 'x2' ~ 3/5,
x == 'x3' ~ 4/5,
x == 'x35' ~ 9/10,
x == 'x4' ~ 1)) %>%
ggplot(aes(x = xax, y = cdf)) +
geom_step() +
geom_point(data = ddf, aes(fill = I(col)), size = 3, shape = 21) +
labs(x = 'x', y = 'F(x)')

Resources