I want to vary the size of points created with stat_summary_2d, at present they are all the same size which isn't informative. I would normally use geom_point() but because I want the points central to their correct bins it isn't appropriate, they are inconsistent in their placement relative to the bin centres so an offset also doesn't work.
The code relevant for creating the points is:
stat_summary_2d(data = filter(ni_eff, fishyear %in% c('2013', '2014', '2015', '2016') & coverage),
mapping = aes(x = start_long, y = start_lat, z=event_key),
binwidth = c(1, 1),
geom = 'point',
colour = 'red',
shape = 1,
# size = event_key,
fun = n_distinct) +
Stat_summary_2d doesn't like size in the aesthetics, and also doesn't recognise event_key if it is entered to determine size. I think it should be possible but haven't worked out how, so any help would be appreciated.
Ultimately, I want points centred on 1*1 degree lat-long bins that vary in size given a column event_key within the dataframe with their co-ordinates.
Similar question with an applicable answer:
Map with geom_bin2d overlay with additional stat info
Applying my code:
point_df <- ggplot(filter(ni_eff, fishyear %in% c('2013', '2014', '2015', '2016') & coverage),
aes(x = start_long, y = start_lat)) +
stat_summary_2d(aes(z = event_key), binwidth = c(1, 1), fun = length)
df <- ggplot_build(point_df)$data[[1]]
And the size varying points are plotted using geom_point() and at the correct lat-long bin centres (x, y) generated from ggplot_build()
geom_point(data = df, aes(x = x, y = y, size = value),
colour = "red", shape = 1)
You could add the points manually without stat_summary2d() and then use position_nudge to adjust the position so the dots are centered. For example:
df = data.frame(a = 1:10, b = 1:10, c = 1:10)
d <- ggplot(df, aes(a, b, z = c))
d + stat_summary_2d() +
geom_point(aes(x = a, y = b, size = c),
position = position_nudge(-.1, -.1))
Related
I'm trying to create a set of small multiple line charts, where in each chart all lines are visible as light grey/background but in each individual chart one line is one highlighted.
As an example I want to go from this chart: facet_wrap without previous geom to a facet_wrap that would have two plots, each with all grey dashed lines, and one coloured line per plot.
I can facet_wrap so each highlighted line has it's own chart (but no other background lines show)(facet_fail below) and I can create individual charts which have all lines in grey and a highlighted single line (ind_chart below), but I can't get both at the same time.
I'm sure I'm missing a simple setting here but can't see it sorry.
Here's a simplified example of the data and charts I've made so far.
(the lines have been converted to radar/polar coordinates but I don't think that should matter?)
library(tidyverse)
#variables
food <- rep(c("apple", "pear", "orange"),3)
sense <- as.factor(rep(c("taste", "texture", "appearance"),each = 3))
score <- sample(0:10, 9)
#dataframe
df <- data.frame(food,sense,score) %>%
mutate(theta = (as.integer(sense)-1)*(360/n_distinct(y)*pi/180)+pi/2) %>% #transrom data to polar with cartesian coords
mutate(x = cos(theta)*score,
y = sin(theta)*score)%>%
rbind(filter(., sense == "taste")) #repeat first row so that geom_path completes circuit
# Plot
p <- ggplot(df, aes(x = x, y = y, group = food))+
geom_point(colour = "gray", size = 3)+
geom_path(colour = "gray", size = 1, linetype = "longdash")+
geom_spoke(data = data.frame( x = 0,
y = 0,
angle =seq(from = 0, to = 2*pi, length.out = 4)+pi/2,
radius = 10),
aes(x = x, y = y, angle = angle, radius = radius, group = NULL),
colour = "darkgray", size = 0.8)+
theme_void()
p
# individual chart is correct
ind_chart <- p + geom_path(data = filter(df, food == "apple" | food == "orange"), aes(color = food), size = 2)
# facet wrap by 'food' creates multiples without backgroud geom_path's from p
fltr <- filter(df, food == "apple" | food == "orange")
facet_fail <- p +
geom_path(data = fltr , aes(color = food))+
facet_wrap(~food)+
coord_equal()
I'm wondering if in a geom_line you can make it so the colors of, say, the dashes within a single line alternate (rather than the colors differing between lines). For example, if I wanted this singular line to alternate red, green, and blue rather than being just red.
library(tidyverse)
ggplot(tibble(x = 1:10, y = 1:10), aes(x, y)) +
geom_line(linetype = "dashed", color = "red") # i'd like to say something like, color = c("red", "green", "blue") instead
While a little inefficient, a little-known thing about R's par(lty=) (that geom_line(linetype=) shares) is that it can be specified as on/off stretches. From ?par under Line Type Specification:
Line types can either be specified by giving an index into a small
built-in table of line types (1 = solid, 2 = dashed, etc, see
'lty' above) ...
(which is what most tutorials/howtos/plots tend to use)
... or directly as the lengths of on/off stretches of
line. This is done with a string of an even number (up to eight)
of characters, namely _non-zero_ (hexadecimal) digits which give
the lengths in consecutive positions in the string. For example,
the string '"33"' specifies three units on followed by three off
and '"3313"' specifies three units on followed by three off
followed by one on and finally three off. The 'units' here are
(on most devices) proportional to 'lwd', and with 'lwd = 1' are in
pixels or points or 1/96 inch.
So with your dat, one could do
dat <- tibble(x = 1:10, y = 1:10)
ggplot(dat, aes(x,y)) +
geom_line(linetype="1741", color="red", size=3) +
geom_line(linetype="1345", color="blue", size=3) +
geom_line(linetype="49", color="green", size=3)
to get
I could not get it to work without one blank space: the on/off stretches must always start with an "on", and end with an "off"; as such I could not find a pattern that didn't (at least once) end on an "on" without an imposed gap.
For further explanation, since we always must start with an "on", I start all three with at least a single pixel of "on"; the trick is to make the "long" stretch for the beginning to be the last line plotted, so it over-plots the others.
red: R.......RRRR.
1 -4--
---7--- 1
grn: G...GGGG.....
1 -4--
-3- --5--
blu: BBBB.........
-4--
----9----
This has some advantages: regardless of size=, it scales the same. For instance, omitting size=,
Using approx:
# number of points at which interpolation takes place
# increase if line takes sharp turns
n = 100
# number of segments along line, according to taste
n_seg = 20
# segment colors
cols = c("red", "green", "blue")
# interpolate
d = approx(dat$x, dat$y, n = n)
# create start and end points for segments
d2 = data.frame(x = head(d$x, -1), xend = d$x[-1],
y = head(d$y, -1), yend = d$y[-1])
# create vector of segment colors
d2$col = rep(cols, each = ceiling((n - 1) / n_seg), length.out = n - 1)
ggplot(d2, aes(x = x, xend = xend, y = y, yend = yend, color = col)) +
geom_segment() + scale_color_identity(guide = "none")
This is an implementation of a new Stat based on GeomSegment which creates alternating segments of different colors. This works by passing the alternating colors to the data frame created in Stat$compute_group. GeomSegment uses StatIdentity, so no need to specifically map xend, yend and color.
BIG THANKS to Henrik for showing a very neat way of creating the segments. (my own way was very convoluted, and I'll leave it in this thread for posterity). The only remaining "problem" is that the segments might have different lengths in changing slopes - on the other hand, it might be visually desirable to have different segment lengths in this case.
library(ggplot2)
## attaching just for demonstration purpose
library(patchwork)
# geom_colorpath
# #description lines with alternating color "just for the effect".
# #name colorpath
# #examples
# #export
StatColorPath <- ggproto("StatColorPath", Stat,
compute_group = function(data, scales, params,
n_seg = 20, n = 100, cols = c("black", "white")) {
# interpolate
d <- approx(data$x, data$y, n = n)
# create start and end points for segments
d2 <- data.frame(
x = head(d$x, -1), xend = d$x[-1],
y = head(d$y, -1), yend = d$y[-1]
)
# create vector of segment colors
d2$color <- rep(cols, each = ceiling((n - 1) / n_seg), length.out = n - 1)
d2
},
required_aes = c("x", "y")
)
# #rdname colorpath
# #import ggplot2
# #inheritParams ggplot2::layer
# #inheritParams ggplot2::geom_segment
# #param n_seg number of segments along line, according to taste
# #param n number of points at which interpolation takes place
# increase if line takes sharp turns
# #param cols vector of alternating colors
# #export
geom_colorpath <- function(mapping = NULL, data = NULL, geom = "segment",
position = "identity", na.rm = FALSE, show.legend = NA,
inherit.aes = TRUE, cols = c("black", "white"),
n_seg = 20, n = 100, ...) {
layer(
stat = StatColorPath, data = data, mapping = mapping, geom = geom,
position = position, show.legend = show.legend, inherit.aes = inherit.aes,
params = list(na.rm = na.rm, cols = cols, n = n, n_seg = n_seg,...)
)
}
## examples
dat <- data.frame(x = seq(2,10, 2), y = seq(4,20, 4))
p1 <- ggplot(dat, aes(x = x, y = y)) +
geom_colorpath()+
ggtitle("Default colors")
p2 <- ggplot(dat, aes(x, y)) +
geom_colorpath(cols = c("red", "blue"))+
ggtitle("Two colors")
p3 <- ggplot(dat, aes(x, y)) +
geom_colorpath(cols = c("red", "blue", "green"))+
ggtitle("Three colors")
p4 <- ggplot(dat, aes(x, y)) +
geom_colorpath(cols = c("red", "blue", "green", "white"))+
ggtitle("Four colors")
wrap_plots(mget(ls(pattern = "p[1-9]")))
air_df <- data.frame(x = 1: length(AirPassengers), y = c(AirPassengers))
a1 <- ggplot(air_df, aes(x, y)) +
geom_colorpath(cols = c("red", "blue", "green"))+
ggtitle("Works also with more complex curves")
a2 <- ggplot(air_df, aes(x, y)) +
geom_colorpath(cols = c("red", "blue", "green"), n_seg = 150)+
ggtitle("... more color segments")
a1 / a2
Created on 2022-06-22 by the reprex package (v2.0.1)
Here's a ggplot hack that is simple, but works for two colors only (your question is the top result when searching for "alternating colored dashed line" and I wanted to put this option out there). It results in two lines being overlayed, one a solid line, the other a dashed line.
library(dplyr)
library(ggplot2)
library(reshape2)
# Create df
x_value <- 1:10
group1 <- c(0,1,2,3,4,5,6,7,8,9)
group2 <- c(0,2,4,6,8,10,12,14,16,18)
dat <- data.frame(x_value, group1, group2) %>%
mutate(group2_2 = group2) %>% # Duplicate the column that you want to be alternating colors
melt(id.vars = "x_value", variable.name = "group", value.name ="y_value") # Long format
# Put in your selected order
dat$group <- factor(dat$group, levels=c("group1", "group2", "group2_2"))
# Plot
ggplot(dat, aes(x=x_value, y=y_value)) +
geom_line(aes(color=group, linetype=group), size=1) +
scale_color_manual(values=c("black", "red", "black")) +
scale_linetype_manual(values=c("solid", "solid", "dashed"))
Unfortunately the legend still needs to be edited by hand. Here's the example plot.
I wonder if there is the possibility to change the fill main colour according to a categorical variable
Here is a reproducible example
df = data.frame(x = c(rnorm(10, mean = 0),
rnorm(10, mean = 3)),
y = c(rnorm(10, mean = 0),
rnorm(10, mean = 3)),
grp = c(rep('a', times = 10),
rep('b', times = 10)),
val = rep(1:10, times = 2))
ggplot(data = df,
aes(x = x,
y = y)) +
geom_point(pch = 21,
aes(color = grp,
fill = val,
size = val))
Of course it is easy to change the circle colour/shape, according to the variable grp, but I'd like to have the a group in shades of red and the b group in shades of blue.
I also thought about using facets, but don't know if the fill gradient can be changed for the two panels.
Anyone knows if that can be done, without gridExtra?
Thanks!
I think there are two ways to do this. The first is using the alpha aesthetic for your val column. This is a quick and easy way to accomplish your goal but may not be exactly what you want:
ggplot(data = df,
aes(x = x,
y = y)) +
geom_point(pch = 21,
aes(alpha=val,
fill = grp,
size = val)) + theme_minimal()
The second way would be to do something similar to this post: Vary the color gradient on a scatter plot created with ggplot2. I edited the code slightly so its not a range from white to your color of interest but from a lighter color to a darker color. This requires a little bit of work and using the scale_fill_identity function which basically takes a variable that has the colors you want and maps them directly to each point (so it doesn't do any scaling).
This code is:
#Rescale val to [0,1]
df$scaled_val <- rescale(df$val)
low_cols <- c("firebrick1","deepskyblue")
high_cols <- c("darkred","deepskyblue4")
df$col <- ddply(df, .(grp), function(x)
data.frame(col=apply(colorRamp(c(low_cols[as.numeric(x$grp)[1]], high_cols[as.numeric(x$grp)[1]]))(x$scaled_val),
1,function(x)rgb(x[1],x[2],x[3], max=255)))
)$col
df
ggplot(data = df,
aes(x = x,
y = y)) +
geom_point(pch = 21,
aes(
fill = col,
size = val)) + theme_minimal() +scale_fill_identity()
Thanks to this other post I found a way to visualize the fill bar in the legend, even though that wasn't what I meant to do.
Here's the ouptup
And the code
df = data.frame(x = c(rnorm(10, mean = 0),
rnorm(10, mean = 3)),
y = c(rnorm(10, mean = 0),
rnorm(10, mean = 3)),
grp = factor(c(rep('a', times = 10),
rep('b', times = 10)),
levels = c('a', 'b')),
val = rep(1:10, times = 2)) %>%
group_by(grp) %>%
mutate(scaledVal = rescale(val)) %>%
ungroup %>%
mutate(scaledValOffSet = scaledVal + 100*(as.integer(grp) - 1))
scalerange <- range(df$scaledVal)
gradientends <- scalerange + rep(c(0,100,200), each=2)
ggplot(data = df,
aes(x = x,
y = y)) +
geom_point(pch = 21,
aes(fill = scaledValOffSet,
size = val)) +
scale_fill_gradientn(colours = c('white',
'darkred',
'white',
'deepskyblue4'),
values = rescale(gradientends))
Basically one should rescale fill values (e.g. between 0 and 1) and separate them using another order of magnitude, provided by the categorical variable grp.
This is not what I wanted though: the snippet can be improved, of course, to make the whole thing less manual, but still lacks the simple usual discrete fill legend.
I'm trying to plot 2 sets of data points and a single line in R using ggplot.
The issue I'm having is with the legend.
As can be seen in the attached image, the legend applies the lines to all 3 data sets even though only one of them is plotted with a line.
I have melted the data into one long frame, but this still requires me to filter the data sets for each individual call to geom_line() and geom_path().
I want to graph the melted data, plotting a line based on one data set, and points on the remaining two, with a complete legend.
Here is the sample script I wrote to produce the plot:
xseq <- 1:100
x <- rnorm(n = 100, mean = 0.5, sd = 2)
x2 <- rnorm(n = 100, mean = 1, sd = 0.5)
x.lm <- lm(formula = x ~ xseq)
x.fit <- predict(x.lm, newdata = data.frame(xseq = 1:100), type = "response", se.fit = TRUE)
my_data <- data.frame(x = xseq, ypoints = x, ylines = x.fit$fit, ypoints2 = x2)
## Now try and plot it
melted_data <- melt(data = my_data, id.vars = "x")
p <- ggplot(data = melted_data, aes(x = x, y = value, color = variable, shape = variable, linetype = variable)) +
geom_point(data = filter(melted_data, variable == "ypoints")) +
geom_point(data = filter(melted_data, variable == "ypoints2")) +
geom_path(data = filter(melted_data, variable == "ylines"))
pushViewport(viewport(layout = grid.layout(1, 1))) # One on top of the other
print(p, vp = viewport(layout.pos.row = 1, layout.pos.col = 1))
You can set them manually like this:
We set linetype = "solid" for the first item and "blank" for others (no line).
Similarly for first item we set no shape (NA) and for others we will set whatever shape we need (I just put 7 and 8 there for an example). See e.g. http://www.r-bloggers.com/how-to-remember-point-shape-codes-in-r/ to help you to choose correct shapes for your needs.
If you are happy with dots then you can use my_shapes = c(NA,16,16) and scale_shape_manual(...) is not needed.
my_shapes = c(NA,7,8)
ggplot(data = melted_data, aes(x = x, y = value, color=variable, shape=variable )) +
geom_path(data = filter(melted_data, variable == "ylines") ) +
geom_point(data = filter(melted_data, variable %in% c("ypoints", "ypoints2"))) +
scale_colour_manual(values = c("red", "green", "blue"),
guide = guide_legend(override.aes = list(
linetype = c("solid", "blank","blank"),
shape = my_shapes))) +
scale_shape_manual(values = my_shapes)
But I am very curious if there is some more automated way. Hopefully someone can post better answer.
This post relied quite heavily on this answer: ggplot2: Different legend symbols for points and lines
Following up on a recent question of mine, this one is a bit different and illustrates the problem more fully using simpler examples. Below are two data sets and three functions. The first one draws some points and a circle as expected:
library("ggplot2")
library("grid")
td1 <- data.frame(x = rnorm(10), y = rnorm(10))
tf1 <- function(df) { # works as expected
p <- ggplot(aes(x = x, y = y), data = df)
p <- p + geom_point(color = "red")
p <- p + annotation_custom(circleGrob())
print(p)
}
tf1(td1)
This next one seems to ask for the exact sample plot but the code is slightly different. It does not give an error but does not draw the circle:
tf2 <- function(df) { # circle isn't draw, but no error either
p <- ggplot()
p <- p + geom_point(data = df, aes(x = x, y = y), color = "red")
p <- p + annotation_custom(circleGrob())
print(p)
}
tf2(td1)
Finally, this one involves a more complex aesthetic and gives an empty layer when you try to create the circle:
td3 <- data.frame(r = c(rnorm(5, 5, 1.5), rnorm(5, 8, 2)),
f1 = c(rep("L", 5), rep("H", 5)), f2 = rep(c("A", "B"), 5))
tf3 <- function(df) {
p <- ggplot()
p <- p + geom_point(data = df,
aes(x = f1, y = r, color = f2, group = f2))
# p <- p + annotation_custom(circleGrob()) # comment out and it works
print(p)
}
tf3(td3)
Now, I suspect the problem here is not the code but my failure to grasp the inner workings of ggplot2. I could sure use an explanation of why the circle is not drawn in the 2nd case and why the layer is empty in the third case. I looked at the code for annotation_custom and it has a hard-wired inherit.aes = TRUE which I think is the problem. I don't see why this function needs any aesthetic at all (see the docs on it). I did try several ways to override it and set inherit.aes = FALSE but I was unable to fully penetrate the namespace and make it stick. I tried to example the objects created by ggplot2 but these proto objects are nested very deeply and hard to decipher.
To answer this :
"I don't see why this function needs any aesthetic at all".
In fact annotation_custom need x and y aes to scale its grob, and to use after the native units.
Basically it did this :
x_rng <- range(df$x, na.rm = TRUE) ## ranges of x :aes x
y_rng <- range(df$y, na.rm = TRUE) ## ranges of y :aes y
vp <- viewport(x = mean(x_rng), y = mean(y_rng), ## create a viewport
width = diff(x_rng), height = diff(y_rng),
just = c("center","center"))
dd <- editGrob(grod =circleGrob(), vp = vp) ##plot the grob in this vp
To illustrate this I add a grob to a dummy plot used as a scale for my grob. The first is a big scale and the second is a small one.
base.big <- ggplot(aes(x = x1, y = y1), data = data.frame(x1=1:100,y1=1:100))
base.small <- ggplot(aes(x = x1, y = y1), data = data.frame(x1=1:20,y1=1:1))
I define my grob, see I use the native scales for xmin,xmax,ymin,ymax
annot <- annotation_custom(grob = circleGrob(), xmin = 0,
xmax = 20,
ymin = 0,
ymax = 1)
Now see the scales difference(small point / big circle) between (base.big +annot) and (base.small + annot).
library(gridExtra)
grid.arrange(base.big+annot,
base.small+annot)