I'd like to label a horizontal line on a ggplot with multiple series, without associating the line with a series. R ggplot2: Labelling a horizontal line on the y axis with a numeric value asks about the single-series case, for which geom_text solves. However, geom_text associates the label with one of the series via color and legend.
Consider the same example from that question, with another color column:
library(ggplot2)
df <- data.frame(y=1:10, x=1:10, col=c("a", "b")) # Added col
h <- 7.1
plot1 <- ggplot(df, aes(x=x, y=y, color=col)) + geom_point()
plot2 <- plot1 + geom_hline(aes(yintercept=h))
# Applying top answer https://stackoverflow.com/a/12876602/1840471
plot2 + geom_text(aes(0, h, label=h, vjust=-1))
How can I label the line without associating the label to one of the series?
Is this what you had in mind?
library(ggplot2)
df <- data.frame(y=1:10, x=1:10, col=c("a", "b")) # Added col
h <- 7.1
ggplot(df, aes(x=x,y=y)) +
geom_point(aes(color=col)) +
geom_hline(yintercept=h) +
geom_text(data=data.frame(x=0,y=h), aes(x, y), label=h, vjust=-1)
First, you can make the color mapping local to the points layer. Second, you do not have to put all the aesthetics into calls to aes(...) - only those you want mapped to columns of the dataset. Three, you can have layer-specific datasets using data=... in the calls to a specific geom_*.
You can use annotate instead:
plot2 + annotate(geom="text", label=h, x=1, y=h, vjust=-1)
Edit: Removed drawback that x is required, since that's also true of geom_text.
Related
I have a data frame with three continuous variables (x,y,z). I want a column plot in which x defines the x-axis position of the columns, y defines the length of the columns, and the column colors (function of y) are defined by z. The test code below shows the set up.
`require(ggplot2)
require(viridis)
# Create a dummy data frame
x <- c(rep(0.0, 5),rep(0.5,10),rep(1.0,15))
y <- c(seq(0.0,-5,length.out=5),
seq(0.0,-10,length.out=10),
seq(0.0,-15,length.out=15))
z <- c(seq(10,0,length.out=5),
seq(8,0,length.out=10),
seq(6,0,length.out=15))
df <- data.frame(x=x, y=y, z=z)
pbase <- ggplot(df, aes(x=x, y=y, fill=z))
ptest <- pbase + geom_col(width=0.5, position="identity") +
scale_fill_viridis(option="turbo",
limits = c(0,10),
breaks=seq(0,10,2.5),
labels=c("0","2.5","5.0","7.5","10.0"))
print(ptest)`
The legend has the correct colors but the columns do not. Perhaps this is not the correct way to do this type of plot. I tried using geom_bar() which creates a bars with the correct colors but the y-values are incorrect.
It looks like you have 3 X values that each appear 5, 10, or 15 times. Do you want the bars to be overlaid on top of one another, as they are now? If you add an alpha = 0.5 to the geom_col call you'll see the overlapping bars.
Alternatively, you might use dodging to show the bars next to one another instead of on top of one another.
ggplot(df, aes(x=x, y=y, fill=z, group = z)) +
geom_col(width=0.5, position=position_dodge()) +
scale_fill_viridis_c(option="turbo", # added with ggplot 3.x in 2018
limits = c(0,10),
breaks=seq(0,10,2.5),
labels=c("0","2.5","5.0","7.5","10.0"))
Or you might plot the data in order of y so that the smaller bars appear on top, visibly:
ggplot(dplyr::arrange(df,y), aes(x=x, y=y, fill=z))+
geom_col(width=0.5, position="identity") +
scale_fill_viridis_c(option="turbo",
limits = c(0,10),
breaks=seq(0,10,2.5),
labels=c("0","2.5","5.0","7.5","10.0"))
I solved this by using geom_tile() in place of geom_col().
I want to make a line chart in plotly so that it does not have the same color on its whole length. The color is given continuous scale. It is easy in ggplot2 but when I translate it to plotly using ggplotly function the variable determining color behaves like categorical variable.
require(dplyr)
require(ggplot2)
require(plotly)
df <- data_frame(
x = 1:15,
group = rep(c(1,2,1), each = 5),
y = 1:15 + group
)
gg <- ggplot(df) +
aes(x, y, col = group) +
geom_line()
gg # ggplot2
ggplotly(gg) # plotly
ggplot2 (desired):
plotly:
I found one work-around that, on the other hand, behaves oddly in ggplot2.
df2 <- df %>%
tidyr::crossing(col = unique(.$group)) %>%
mutate(y = ifelse(group == col, y, NA)) %>%
arrange(col)
gg2 <- ggplot(df2) +
aes(x, y, col = col) +
geom_line()
gg2
ggplotly(gg2)
I also did not find a way how to do this in plotly directly. Maybe there is no solution at all. Any ideas?
It looks like ggplotly is treating group as a factor, even though it's numeric. You could use geom_segment as a workaround to ensure that segments are drawn between each pair of points:
gg2 = ggplot(df, aes(x,y,colour=group)) +
geom_segment(aes(x=x, xend=lead(x), y=y, yend=lead(y)))
gg2
ggplotly(gg2)
Regarding #rawr's (now deleted) comment, I think it would make sense to have group be continuous if you want to map line color to a continuous variable. Below is an extension of the OP's example to a group column that's continuous, rather than having just two discrete categories.
set.seed(49)
df3 <- data_frame(
x = 1:50,
group = cumsum(rnorm(50)),
y = 1:50 + group
)
Plot gg3 below uses geom_line, but I've also included geom_point. You can see that ggplotly is plotting the points. However, there are no lines, because no two points have the same value of group. If we hadn't included geom_point, the graph would be blank.
gg3 <- ggplot(df3, aes(x, y, colour = group)) +
geom_point() + geom_line() +
scale_colour_gradient2(low="red",mid="yellow",high="blue")
gg3
ggplotly(gg3)
Switching to geom_segment gives us the lines we want with ggplotly. Note, however, that line color will be based on the value of group at the first point in the segment (whether using geom_line or geom_segment), so there might be cases where you want to interpolate the value of group between each (x,y) pair in order to get smoother color gradations:
gg4 <- ggplot(df3, aes(x, y, colour = group)) +
geom_segment(aes(x=x, xend=lead(x), y=y, yend=lead(y))) +
scale_colour_gradient2(low="red",mid="yellow",high="blue")
ggplotly(gg4)
I'd like to label a horizontal line on a ggplot with multiple series, without associating the line with a series. R ggplot2: Labelling a horizontal line on the y axis with a numeric value asks about the single-series case, for which geom_text solves. However, geom_text associates the label with one of the series via color and legend.
Consider the same example from that question, with another color column:
library(ggplot2)
df <- data.frame(y=1:10, x=1:10, col=c("a", "b")) # Added col
h <- 7.1
plot1 <- ggplot(df, aes(x=x, y=y, color=col)) + geom_point()
plot2 <- plot1 + geom_hline(aes(yintercept=h))
# Applying top answer https://stackoverflow.com/a/12876602/1840471
plot2 + geom_text(aes(0, h, label=h, vjust=-1))
How can I label the line without associating the label to one of the series?
Is this what you had in mind?
library(ggplot2)
df <- data.frame(y=1:10, x=1:10, col=c("a", "b")) # Added col
h <- 7.1
ggplot(df, aes(x=x,y=y)) +
geom_point(aes(color=col)) +
geom_hline(yintercept=h) +
geom_text(data=data.frame(x=0,y=h), aes(x, y), label=h, vjust=-1)
First, you can make the color mapping local to the points layer. Second, you do not have to put all the aesthetics into calls to aes(...) - only those you want mapped to columns of the dataset. Three, you can have layer-specific datasets using data=... in the calls to a specific geom_*.
You can use annotate instead:
plot2 + annotate(geom="text", label=h, x=1, y=h, vjust=-1)
Edit: Removed drawback that x is required, since that's also true of geom_text.
I have data that plots over time with four different variables. I would like to combine them in one plot using facet_grid, where each variable gets its own sub-plot. The following code resembles my data and the way I'm presenting it:
require(ggplot2)
require(reshape2)
subm <- melt(economics, id='date', c('psavert','uempmed','unemploy'))
mcsm <- melt(data.frame(date=economics$date, q=quarters(economics$date)), id='date')
mcsm$value <- factor(mcsm$value)
ggplot(subm, aes(date, value, col=variable, group=1)) + geom_line() +
facet_grid(variable~., scale='free_y') +
geom_step(data=mcsm, aes(date, value)) +
scale_y_discrete(breaks=levels(mcsm$value))
If I leave out scale_y_discrete, R complains that I'm trying to combine discrete value with continuous scale. If I include scale_y_discreate my continuous series miss their scale.
Is there any neat way of solving this issue ie. getting all scales correct ? I also see that the legend is alphabetically sorted, can I change that so the legend is ordered in the same order as the sub-plots ?
Problem with your data is that that for data frame subm value is numeric (continuous) but for the mcsm value is factor (discrete). You can't use the same scale for numeric and continuous values and you get y values only for the last facet (discrete). Also it is not possible to use two scale_y...() functions in one plot.
My approach would be to make mcsm value as numeric (saved as value2) and then use them - it will plot quarters as 1,2,3 and 4. To solve the problem with legend, use scale_color_discrete() and provide breaks= in order you need.
mcsm$value2<-as.numeric(mcsm$value)
ggplot(subm, aes(date, value, col=variable, group=1)) + geom_line()+
facet_grid(variable~., scale='free_y') + geom_step(data=mcsm, aes(date, value2)) +
scale_color_discrete(breaks=c('psavert','uempmed','unemploy','q'))
UPDATE - solution using grobs
Another approach is to use grobs and library gridExtra to plot your data as separate plots.
First, save plot with all legends and data (code as above) as object p. Then with functions ggplot_build() and ggplot_gtable() save plot as grob object gp. Extract from gp only part that plots legend (saved as object gp.leg) - in this case is list element number 17.
library(gridExtra)
p<-ggplot(subm, aes(date, value, col=variable, group=1)) + geom_line()+
facet_grid(variable~., scale='free_y') + geom_step(data=mcsm, aes(date, value2)) +
scale_color_discrete(breaks=c('psavert','uempmed','unemploy','q'))
gp<-ggplot_gtable(ggplot_build(p))
gp.leg<-gp$grobs[[17]]
Make two new plot p1 and p2 - first plots data of subm and second only data of mcsm. Use scale_color_manual() to set colors the same as used for plot p. For the first plot remove x axis title, texts and ticks and with plot.margin= set lower margin to negative number. For the second plot change upper margin to negative number. faced_grid() should be used for both plots to get faceted look.
p1 <- ggplot(subm, aes(date, value, col=variable, group=1)) + geom_line()+
facet_grid(variable~., scale='free_y')+
theme(plot.margin = unit(c(0.5,0.5,-0.25,0.5), "lines"),
axis.text.x=element_blank(),
axis.title.x=element_blank(),
axis.ticks.x=element_blank())+
scale_color_manual(values=c("#F8766D","#00BFC4","#C77CFF"),guide="none")
p2 <- ggplot(data=mcsm, aes(date, value,group=1,col=variable)) + geom_step() +
facet_grid(variable~., scale='free_y')+
theme(plot.margin = unit(c(-0.25,0.5,0.5,0.5), "lines"))+ylab("")+
scale_color_manual(values="#7CAE00",guide="none")
Save both plots p1 and p2 as grob objects and then set for both plots the same widths.
gp1 <- ggplot_gtable(ggplot_build(p1))
gp2 <- ggplot_gtable(ggplot_build(p2))
maxWidth = grid::unit.pmax(gp1$widths[2:3],gp2$widths[2:3])
gp1$widths[2:3] <- as.list(maxWidth)
gp2$widths[2:3] <- as.list(maxWidth)
With functions grid.arrange() and arrangeGrob() arrange both plots and legend in one plot.
grid.arrange(arrangeGrob(arrangeGrob(gp1,gp2,heights=c(3/4,1/4),ncol=1),
gp.leg,widths=c(7/8,1/8),ncol=2))
First, a quick example to set the stage:
set.seed(123)
dat <- data.frame(
x=rep( c(1, 2, 4, 7), times=25 ),
y=rnorm(100),
gp=rep(1:2, each=50)
)
p <- ggplot(dat, aes(x=factor(x), y=y))
p + geom_boxplot(aes(fill = factor(gp)))
I would like to produce a similar plot, except with control over the x position of each set of boxplots. My first guess was using a non-factor x aesthetic that controls the position along the x-axis of these box plots. However, once I try to do this it seems like geom_boxplot doesn't interpret the aesthetics as I would hope.
p + geom_boxplot( aes(x=x, y=y, fill=factor(gp)) )
In particular, geom_boxplot seems to collapse over all x values in some way when they're non-factors.
Is there a way to control the x position of boxplots with ggplot2? Either through specifying a distance between each level of a factor aesthetic, some more clever use of non-factor aesthetics, or otherwise?
You can use scale_x_discrete() to set positions (ticks) for the x axis.
p <- ggplot(dat, aes(x=factor(x), y=y))
p + geom_boxplot(aes(fill = factor(gp))) +
scale_x_discrete(limits=1:7)
You can also do this with the group aesthetic. However, I'm not sure why you cannot just pass x to the group. This doesn't work:
ggplot() +
geom_boxplot(data=dat, aes(x=x, y=y, fill=factor(gp), group=x))
But this does:
ggplot() +
geom_boxplot(data=dat, aes(x=x, y=y, fill=factor(gp), group=paste(x, gp)))