'R ggplot: Modifying aesthetics of individual lines without recreating entire color palette

In ggplot, is there any simple way of overriding the line attributes of a single group(s) without having to specify the entirety of the color/line pallet via scale_*_manual()?

In the example below, I basically want to make all the boot_* lines gray and skinny, while I want all other lines to retain the default colors/widths otherwise being used.

I know there's a lot of brute ways of doing this by some combo of a) creating some auxiliary variables in the data-frame based on the string-pattern that will server as my color/size group, then b) generating the plot below, extracting all the color-layer info, and then filling out an entire scale_color_manual() and scale_size_manual() map, and c)replacing the 'boot_*' values with "grey."

Are there any versatile shortcuts here?

library(dplyr)
library(ggplot)

set.seed(231)
df=tibble(time=c(1:5), actual=2*time+3, estimate = actual+rnorm(length(actual)))
for(i in 1:8){
  df[paste('boot_', i, sep='')] = df$estimate + rnorm(nrow(df))
}


> head(df) %>% data.frame
# time actual  estimate    boot_1    boot_2    boot_3    boot_4    boot_5    boot_6    boot_7
# 1    1      5  4.466898  4.684295  4.240585  4.786520  5.904332  4.862498  2.092772  4.595850
# 2    2      7  4.688336  4.751258  6.074914  5.694181  3.445036  4.639329  4.548511  5.453597
# 3    3      9  8.045802  7.167972  6.858666  7.519752  7.721405  7.801243 10.156436  9.521482
# 4    4     11 11.262516 11.826206 10.682760 11.137814 11.252465 11.452442 11.925339 11.754248
# 5    5     13 12.526643 12.492315 13.927974 14.176896 11.924183 12.950479 11.257865 13.430229
# boot_8
# 1  3.987001
# 2  3.813539
# 3  7.549984
# 4 11.482360
# 5 11.645106


# Melt for ggplot compatibility
df_long = df %>% 
  pivot_longer(cols=(-time))


head(df_long) %>% data.frame
# time     name    value
# 1    1   actual 5.000000
# 2    1 estimate 4.466898
# 3    1   boot_1 4.684295
# 4    1   boot_2 4.240585
# 5    1   boot_3 4.786520
# 6    1   boot_4 5.904332

## The basic ggplot
df_long %>% 
  ggplot(aes(x=time, y=value, color=name)) + geom_line()



Solution 1:[1]

You could just use the first four characters of name for the colour aesthetic (using substr), and the full name as a group aesthetic. It's a bit hacky but it's short, effective, and all gets done in the plotting code without extra data wrangling, post-hoc changes or a long vector of colour mappings.

df_long %>% 
  ggplot(aes(x = time, y = value, color = substr(name, 1, 4), group = name)) + 
  geom_line() + 
  scale_color_manual(labels = c("actual", "boot", "estimate"), 
                     values = c("orange", "gray", "blue3"), name = "name")

enter image description here

An alternative is using filtering to have two sets of lines: one coloured, and one merely grouped. This has the benefit that you don't need to add any scale calls at all:

df_long %>% 
  filter(!grepl("boot", name)) %>%
  ggplot(aes(x = time, y = value, color = name)) +
  geom_line(data = filter(df_long, grepl("boot", name)), 
            aes(group = name), color = "gray", size = 0.3) +
  geom_line() 

enter image description here


EDIT

It's pretty difficult to only specify an aesthetic mapping for a single (multiple) group, while leaving the others at default values. However, it is possible using ggnewscale. Here we only have to specify the color of the boot group:

library(ggnewscale)

df_long %>% 
  filter(!grepl("boot", name)) %>%
  ggplot(aes(x = time, y = value)) +
  new_scale_color() +
  geom_line(aes(color = name)) +
  scale_color_discrete(name = "Variable") +
  new_scale_color() +
  geom_line(data = filter(df_long, grepl("boot", name)), 
            aes(group = name, color = "boot"), size = 0.3) +
  scale_color_manual(values = "gray", name = "") +
  theme(legend.margin = margin(-28, 10, 0, 0))

enter image description here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1