Using ggplot, you can change the width of a bar of a bar graph by modifying width:
geom_bar(stat="identity",position=position_dodge(),width = .9)
You can uniformly change the distance of the bars using position_dodge():
geom_bar(stat="identity",position=position_dodge(1),width = .9)
How do I customize the distance between bars so they are varied in a ununiform manner?
It's not clear exactly what you mean. I'm assuming you mean you have a discrete x axis variable and you wish to specify custom spacing between each bar. It's possible to use position_jitter to get random spacing, though this also affects bar width and I'm guessing is not what you want.
I would probably handle this by using a numeric x scale and relabelling the axis with my factor levels:
library(ggplot2)
ggplot(data = data.frame(x = 1:10 + rep(c(0.1, -0.1), 5), y = sample(11:20))) +
geom_bar(aes(x, y, fill = factor(x)), color = "black", stat = "identity") +
scale_x_continuous(breaks = 1:10 + rep(c(0.1, -0.1), 5),
labels = LETTERS[1:10]) +
guides(fill = guide_none())
Of course, we can only guess at what you really want since you didn't provide a motivating example.
Related
I am trying to plot overlaying violin plots by condition within the same variable.
Var <- rnorm(100,50)
Cond <- rbinom(100, 1, 0.5)
df2 <- data.frame(Var,Cond)
ggplot(df2)+
aes(x=factor(Cond),y=Var, colour = Cond)+
geom_violin(alpha=0.3,position="identity")+
coord_flip()
So, where do I specify that I want them to overlap? Preferably, I want them to become more lighter when overlapping and darker colour when not so that their differences are clear. Any clues?
If you don't want them to have different (flipped) x-values, set x to a constant instead of x = factor(Cond). And if you want them filled in, set a fill aesthetic.
ggplot(df2)+
aes(x=0,y=Var, colour = Cond, fill = Cond)+
geom_violin(alpha=0.3,position="identity")+
coord_flip()
coord_flip isn't often needed anymore--since version 3.3.0 (released in early 2020) all geoms can point in either direction. I'd recommend simplifying as below for a similar result.
df2$Cond = factor(df2$Cond)
ggplot(df2) +
aes(y = 0, x = Var, colour = Cond, fill = Cond) +
geom_violin(alpha = 0.3, position = "identity")
I'm plotting 2 densities and trying to add a few annotations that align horizontally while text is rotated 90 degrees but I can't seem to get them to line up when the annotations are of different character lengths.
library(ggplot2)
n <- 10000
mu_a <- .089
mu_b <- .099
s_a <- .0092
s_b <- .004
df <- data.frame(
variant = factor(c(rep("A", n),rep("B", n))),
p = c(rnorm(n = n, mean = mu_a, sd = s_a), rnorm(n = n, mean = mu_b, sd = s_b)))
ggplot(df, aes(x = p, fill = variant)) +
geom_density() +
scale_x_continuous(labels = scales::percent) +
scale_y_continuous(expand = expansion(mult = c(0, .1))) +
annotate("text",
x = c(mu_a,mu_b),
y = Inf,
vjust = "center",
hjust = 6,
label = c("5char","06char"),
angle = 90
)
Created on 2021-05-12 by the reprex package (v0.3.0)
Plot image at https://i.imgur.com/CxW1pjP.png
I've tried changing the y axis to a 0:1 scale with scale_y_continuous(y = ..scaled..) and then setting the annotation y values to fixed positions like y = 0.2 but then the densities aren't sized appropriately. Have tried all manner of combinations of hjust and vjust. I thought that these were supposed to work like percentages of the plot. So vjust = 0.2 means 20% up the plot, but it's not working like that for me. I was not expecting that by rotating the text 90 degrees, that hjust and vjust would swap, but that seems to be what happened.
I'm not sure I'm 100% following, but try this:
ggplot(df, aes(x = p, ..scaled.., fill = variant)) +
geom_density(alpha = 0.8, adjust = 0.2) +
scale_x_continuous(labels = scales::percent) +
scale_y_continuous(expand = expansion(mult = c(0, .1))) +
annotate("text",
x = c(mu_a,mu_b),
y = 0.5,
hjust = 0.5,
label = c("5char","20charxxxxxxxxxxxxxx"),
angle = 90
)
The main callouts:
Set y to just be 0.5. It's a density plot, so it can be scaled so that the max y is always 1 using ..scaled.. in the mapping (see geom_density y-axis goes above 1)
Removed the vjust
Changed the hjust to be 0.5. While values >1 are accepted, it's easiest to just think of 0 as left-justified, 1 as right-justified, and 0 as center-justified. The reason it's hjust rather than vjust is because the justification is from the perspective of the text—not the orientation of the plot (this makes some sense—consider an angle that is anything other than 0 or 90).
I threw an alpha value into the`geom_density() function so that the full curve would show for both (which wasn't part of the question at all, but I couldn't help myself)
This should return the following:
Plot using the code above
The best answers I've found here are:
Use annotation_custom() with the grid package and the textGrob() function. This does allow positioning of annotations by % of x and y axis. Problem is you can't mix methods like setting x to point on the scale and y to % of scale like I'm trying to do.
Calculate the upper end of the range of values in the plot. You can get this from a ggplot object like so ggplot_build(.)$layout$panel_scales_y[[1]]$range$range[[2]] or you can get it from a density function like so d <- density(.) then d$y[which.max(d$y)]. Once you have the upper end of the range, you can continue to build the plot by using a proportion of that upper end for the y placement.
Setting the y scale to ..scaled.. does indeed set the y scale to max 1, however, when plotting multiple densities, it sets both to their own scale rather than scaling accurately to each other. So wide and narrow densities will have the same height.
I'm working with some grid data and I'm having problems with working with discrete diverging scales. Specifically, how to set the midpoint so it's not at the center of the range. This is a reproducible example to get what i mean:
library(ggplot2)
grid <- expand.grid(lon = seq(0, 360, by = 2), lat = seq(-90, 0, by = 2))
grid$z <- with(grid, cos(lat*pi/180) - .7)
ggplot(grid, aes(lon, lat)) +
geom_raster(aes(fill = cut_width(z, .1))) +
scale_fill_brewer(palette = "RdBu")
Here, the center of the scale is not a the divide between positive and negative values. I know I could use a continuous scale, but I find that having fewer colours help with what I'm trying to show.
Is there a way to shift the midpoint in a discrete scale? Other alternatives that achieve the same result are welcome too.
The issue is that your cut points are not falling symmetrically around 0, and are mapping directly to your colors. One approach is to manually set your cut points so that they center around 0. Then, just make sure to not drop unused levels in the legend:
zCuts <-
seq(-.7, 0.7, length.out = 10)
ggplot(grid, aes(lon, lat)) +
geom_raster(aes(fill = cut(z, zCuts))) +
scale_fill_brewer(palette = "RdBu"
, drop = FALSE)
If you are willing to go with a gradient instead of such discrete colors, you can use scale_fill_gradient2 which by default centers at 0 and ranges between two colors:
ggplot(grid, aes(lon, lat)) +
geom_raster(aes(fill = z)) +
scale_fill_gradient2()
Or, if you really want the interpolation from Color Brewer, you can set the limits argument in scale_fill_distiller and get a gradient that way instead. Here, I set them at + and - the range around 0 (max(abs(grid$z)) is getting the largest deviation from 0, whether it is the min or the max, to ensure that the range is symetrical). If you are using more than the 11 available values, that is probably the best way to go:
ggplot(grid, aes(lon, lat)) +
geom_raster(aes(fill = z)) +
scale_fill_distiller(palette = "RdBu"
, limits = c(-1,1)*max(abs(grid$z))
)
If you want more colors, without doing a gradient, you are probably going to need to construct your own palette manually with more colors. The more you add, the less the distinction between the colors you will find. Here is one example stitching together two palettes to ensure that you are working from colors that are distinct.
zCuts <-
seq(-.7, 0.7, length.out = 20)
myPallette <-
c(rev(brewer.pal(9, "YlOrRd"))
, "white"
, brewer.pal(9, "Blues"))
ggplot(grid, aes(lon, lat)) +
geom_raster(aes(fill = cut(z, zCuts))) +
scale_fill_manual(values = myPallette
, drop = FALSE)
geom_bar seems to work best when it has fixed width bars - even the spaces between bars seem to be determined by width, according to the documentation. When you have variable widths, however, it does not respond as I would expect, leading to overlaps or gaps between the different bars (as shown here).
To see what I mean, please try this very simple reproducible example:
x <- c("a","b","c")
w <- c(1.2, 1.3, 4) # variable widths
y <- c(9, 10, 6) # variable heights
ggplot() +
geom_bar(aes(x = x, y = y, width = w, fill=x),
stat="identity", position= "stack")
What I really want is for the different bars to be just touching, but not overlapping, like in a histogram.
I've tried adding position= "stack", "dodge", and "fill, but none work. Does the solution lie in geom_histogram or am I just not using geom_bar correctly?
P.s. to see the issue with gaps, try replacing 4 with 0.5 in the above code and see the outcome.
Seems that there isn't any straightforward solution, so we should treat x-axis as continuous in terms of w and manually compute required positions for ticks and bar centers (this is useful):
# pos is an explicit formula for bar centers that we are interested in:
# last + half(previous_width) + half(current_width)
pos <- 0.5 * (cumsum(w) + cumsum(c(0, w[-length(w)])))
ggplot() +
geom_bar(aes(x = pos, width = w, y = y, fill = x), stat = "identity") +
scale_x_continuous(labels = x, breaks = pos)
You can now do this with the mekko package: https://cran.r-project.org/web/packages/mekko/vignettes/mekko-vignette.html
I am trying to make a labeled bubble plot with ggplot2 in R. Here is the simplified scenario:
I have a data frame with 4 variables: 3 quantitative variables, x, y, and z, and another variable that labels the points, lab.
I want to make a scatter plot, where the position is determined by x and y, and the size of the points is determined by z. I then want to place text labels beside the points (say, to the right of the point) without overlapping the text on top of the point.
If the points did not vary in size, I could try to simply modify the aesthetic of the geom_text layer by adding a scaling constant (e.g. aes(x=x+1, y=y+1)). However, even in this simple case, I am having a problem with positioning the text correctly because the points do not scale with the output dimensions of the plot. In other words, the size of the points remains constant in a 500x500 plot and a 1000x1000 plot - they do not scale up with the dimensions of the outputted plot.
Therefore, I think I have to scale the position of the label by the size (e.g. dimensions) of the output plot, or I have to get the radius of the points from ggplot somehow and shift my text labels. Is there a way to do this in ggplot2?
Here is some code:
# Stupid data
df <- data.frame(x=c(1,2,3),
y=c(1,2,3),
z=c(1,2,1),
lab=c("a","b","c"), stringsAsFactors=FALSE)
# Plot with bad label placement
ggplot(aes(x=x, y=y), data=df) +
geom_point(aes(size=z)) +
geom_text(aes(label=lab),
colour="red") +
scale_size_continuous(range=c(5, 50), guide="none")
EDIT: I should mention, I tried hjust and vjust inside of geom_text, but it does not produce the desired effect.
# Trying hjust and vjust, but it doesn't look nice
ggplot(aes(x=x, y=y), data=df) +
geom_point(aes(size=z)) +
geom_text(aes(label=lab), hjust=0, vjust=0.5,
colour="red") +
scale_size_continuous(range=c(5, 50), guide="none")
EDIT: I managed to get something that works for now, thanks to Henrik and shujaa. I will leave the question open just in case someone shares a more general solution.
Just a blurb of what I am using this for: I am plotting a map, and indicating the amount of precipitation at certain stations with a point that is sized proportionally to the amount of precipitation observed. I wanted to add a station label beside each point in an aesthetically pleasing manner. I will be making more of these plots for different regions, and my output plot may have a different resolution or scale (e.g. due to different projections) for each plot, so a general solution is desired. I might try my hand at creating a custom position_jitter, like baptiste suggested, if I have time during the weekend.
It appears that position_*** don't have access to the scales used by other layers, so it's a no go. You could make a clone of GeomText that shifts the labels according to the size mapped,
but it's a lot of effort for a very kludgy and fragile solution,
geom_shiftedtext <- function (mapping = NULL, data = NULL, stat = "identity",
position = "identity",
parse = FALSE, ...) {
GeomShiftedtext$new(mapping = mapping, data = data, stat = stat, position = position,
parse = parse, ...)
}
require(proto)
GeomShiftedtext <- proto(ggplot2:::GeomText, {
objname <- "shiftedtext"
draw <- function(., data, scales, coordinates, ..., parse = FALSE, na.rm = FALSE) {
data <- remove_missing(data, na.rm,
c("x", "y", "label"), name = "geom_shiftedtext")
lab <- data$label
if (parse) {
lab <- parse(text = lab)
}
with(coord_transform(coordinates, data, scales),
textGrob(lab, unit(x, "native") + unit(0.375* size, "mm"),
unit(y, "native"),
hjust=hjust, vjust=vjust, rot=angle,
gp = gpar(col = alpha(colour, alpha),
fontfamily = family, fontface = fontface, lineheight = lineheight))
)
}
})
df <- data.frame(x=c(1,2,3),
y=c(1,2,3),
z=c(1.2,2,1),
lab=c("a","b","c"), stringsAsFactors=FALSE)
ggplot(aes(x=x, y=y), data=df) +
geom_point(aes(size=z), shape=1) +
geom_shiftedtext(aes(label=lab, size=z),
hjust=0, colour="red") +
scale_size_continuous(range=c(5, 100), guide="none")
This isn't a very general solution, because you'll need to tweak it every time, but you should be able to add to the x value for the text some value that's linear depending on z.
I had luck with
ggplot(aes(x=x, y=y), data=df) +
geom_point(aes(size=z)) +
geom_text(aes(label=lab, x = x + .06 + .14 * (z - min(z))),
colour="red") +
scale_size_continuous(range=c(5, 50), guide="none")
but, as the font size depends on your window size, you would need to decide on your output size and tweak accordingly. I started with x = x + .05 + 0 * (z-min(z)) and calibrated the intercept based on the smallest point, then when I was happy with that I adjusted the linear term for the biggest point.
Another alternative. Looks OK with your test data, but you need to check how general it is.
dodge <- abs(scale(df$z))/4
ggplot(data = df, aes(x = x, y = y)) +
geom_point(aes(size = z)) +
geom_text(aes(x = x + dodge), label = df$lab, colour = "red") +
scale_size_continuous(range = c(5, 50), guide = "none")
Update
Just tried position_jitter, but the width argument only takes one value, so right now I am not sure how useful that function would be. But I would be happy to find that I am wrong. Example with another small data set:
df3 <- mtcars[1:10, ]
ggplot(data = df3, aes(x = wt, y = mpg)) +
geom_point(aes(size = qsec), alpha = 0.1) +
geom_text(label = df3$carb, position = position_jitter(width = 0.1, height = 0)) +
scale_size_continuous(range = c(5, 50), guide = "none")