---
title: "ggblanket"
author: "David Hodge"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{ggblanket}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  message = FALSE,
  warning = FALSE,
  fig.width = 6,
  fig.asp = 0.618,
  out.width = "70%",
  dpi = 300
)
```

## How ggblanket works

To simplify ggplot2 visualisation, ggblanket provides:

1.  Over thirty gg\_\* wrapper functions
2.  A single col argument to colour and fill by a variable
3.  A pal argument to customise colours
4.  A facet argument to facet by a variable
5.  An additional facet2 argument to facet by a 2nd variable
6.  Prefixed arguments to customise titles, scales, guides, and faceting
7.  Unspecified x/y/col titles converted to sentence case
8.  Ability to set the theme, including to provided themes
9.  Pretty defaults for symmetry
10. Access to other geom\_\* arguments via ...
11. Ability to add multiple geom\_\* layers
12. Other key differences

```{r setup}
library(dplyr)
library(ggplot2)
library(ggblanket)
library(patchwork)

penguins <- palmerpenguins::penguins |>
  mutate(sex = stringr::str_to_sentence(sex)) |>
  tidyr::drop_na(sex)
```

### 1. Over thirty gg\_\* wrapper functions

Each `gg_*` function wraps a ggplot2 `ggplot(aes(...))` function with the applicable ggplot2 `geom_*()` function. All aesthetics are placed directly in the `gg_*` function: they are not within a `ggplot2::aes` function.  

```{r, fig.asp=0.6}
# ggplot2
penguins |> 
  ggplot() + 
  geom_point(aes(x = flipper_length_mm, 
                 y = body_mass_g))
```

```{r, fig.asp=0.6}
# ggblanket
penguins |>
  gg_point(
    x = flipper_length_mm,
    y = body_mass_g)
```

### 2. A single col argument to colour and fill by a variable

The colour and fill aesthetics of ggplot2 are merged into a single concept represented by the `col` argument. This argument means that everything should be coloured according to it, i.e. all points, lines and polygon interiors. 

```{r}
# ggplot2
p1 <- penguins |> 
  ggplot() + 
  geom_point(aes(x = flipper_length_mm, 
                 y = body_mass_g,
                 colour = species)) + 
  theme(legend.position = "bottom") +
  guides(colour = guide_legend(title.position = "top")) +
  labs(colour = "Species") +
  scale_x_continuous(breaks = scales::breaks_pretty(n = 3))

p2 <- penguins |>
  ggplot() +
  geom_density(aes(x = flipper_length_mm, 
                   fill = species)) +
  theme(legend.position = "bottom") +
  guides(fill = guide_legend(title.position = "top")) +
  labs(fill = "Species")

p1 + p2
```

```{r}
# ggblanket
p1 <- penguins |>
  gg_point(
    x = flipper_length_mm,
    y = body_mass_g, 
    col = species, 
    x_breaks = scales::breaks_pretty(n = 3), 
    col_legend_ncol = 2)

p2 <- penguins |>
  gg_density(
    x = flipper_length_mm, 
    col = species,
    x_breaks = scales::breaks_pretty(n = 3), 
    col_legend_ncol = 2)

p1 + p2
```

### 3. A pal argument to customise colours

The `pal` argument is used to customise the colours of the geom. A user can provide a vector of colours to this argument. It can be named or not. It works in a consistent way - regardless of whether a `col` argument is added or not. A named palette can be used to make individual colours stick to particular values. 

```{r, fig.asp=0.5}
# ggplot2
p1 <- penguins |>
  ggplot() +
  geom_histogram(aes(x = body_mass_g),
                 fill = "#1B9E77") +
  scale_x_continuous(breaks = scales::breaks_pretty(n = 3)) 

p2 <- penguins |>
  ggplot() +
  geom_jitter(aes(x = species, 
                  y = body_mass_g, 
                  colour = sex)) +
  scale_colour_manual(values = c("#2596be", "#fc7c24"))

p1 + p2
```

```{r}
# ggblanket
p1 <- penguins |>
  gg_histogram(
    x = body_mass_g, 
    pal = "#1B9E77", 
    x_breaks = scales::breaks_pretty(n = 3))

p2 <- penguins |>
  gg_jitter(
    x = species, 
    y = body_mass_g, 
    col = sex, 
    pal = c("#2596be", "#fc7c24")
    )

p1 + p2
```

### 4. A facet argument to facet by a variable

Faceting is treated as if it were an aesthetic. Users just provide an unquoted variable to facet by. If a single facet (or facet2) variable is provided, it'll default to a "wrap" layout. But users can change this with a `facet_layout = "grid"` argument.

```{r}
# ggplot2
penguins |>
  ggplot() +
  geom_violin(aes(x = sex, 
                  y = body_mass_g)) +
  facet_wrap(~species) 
```

```{r}
# ggblanket
penguins |>
  gg_violin(
    x = sex,
    y = body_mass_g,
    facet = species)
```

### 5. An additional facet2 argument to facet by a 2nd variable

A `facet2` argument is also provided for extra functionality and flexibility. If both `facet`and `facet2` variables are provided, then it'll default to a "grid" layout of `facet` by `facet2`. But users can change this with a `facet_layout = "wrap"` argument.

```{r, fig.asp=0.75}
# ggplot2
penguins |>
  ggplot() +
  geom_histogram(aes(x = flipper_length_mm)) +
  facet_grid(sex ~ species)
```

```{r, fig.asp=0.75}
# ggblanket
penguins |>
  gg_histogram(
    x = flipper_length_mm,
    facet = species,
    facet2 = sex)
```

### 6. Prefixed arguments to customise titles, scales, guides, and faceting

Prefixed arguments are available to customise titles, scales, guides, and faceting. These are designed to work with the Rstudio auto-complete to help users remember and find the adjustment they need. Users should first determine whether they want to change something that relates to x, y, col or facet. Then they should type this prefix and press the tab key to access the list of options from the Rstudio auto-complete. Then they can use the arrow keys, and press tab again to select what they want.

```{r, echo = FALSE,   fig.width = 3, fig.asp = 2}
knitr::include_graphics("screenshot_autotab_y.png", dpi = 300)
```

```{r}
# ggplot2
penguins |>
  ggplot() +
  geom_jitter(aes(x = species, 
                  y = body_mass_g, 
                  colour = sex)) +
  expand_limits(y = 0) +
  scale_x_discrete(labels = \(x) stringr::str_sub(x, 1, 1)) +
  scale_y_continuous(breaks = scales::breaks_width(1500),
                     labels = scales::label_number(big.mark = " "),
                     trans = "sqrt") +
  labs(x = "Species", y = "Body mass (g)", col = "Sex") +
  theme(legend.position = "top") +
  theme(legend.justification = "left") +
  scale_colour_manual(values = scales::hue_pal()(2), 
                      guide = ggplot2::guide_legend(title.position = "top"))
```

```{r}
# ggblanket
penguins |>
  gg_jitter(
    x = species,
    y = body_mass_g,
    col = sex,
    x_labels = \(x) stringr::str_sub(x, 1, 1),
    y_include = 0,
    y_breaks = scales::breaks_width(1500), 
    y_labels = scales::label_number(big.mark = " "), 
    y_trans = "sqrt",
    y_title = "Body mass (g)", 
    col_legend_place = "t")
```

### 7. Unspecified x/y/col titles converted to sentence case

Unspecified x/y/col titles are converted to sentence case with snakecase::to_sentence. All titles can be manually changed using the `*_title` arguments. The default conversion is intended to make titles sometimes able to be left as is.

```{r, fig.asp=0.5}
# ggplot2
penguins |>
  ggplot() +
  geom_point(aes(x = flipper_length_mm, 
                 y = body_mass_g, 
                 colour = sex)) +
  facet_wrap(~species) +
  scale_x_continuous(breaks = scales::breaks_pretty(n = 3)) 
```

```{r}
# ggblanket
penguins |>
  gg_point(
      x = flipper_length_mm,
      y = body_mass_g, 
      col = sex,
      facet = species)
```

### 8. Ability to set the theme, including to provided themes

The `ggplot2::theme_set` function can be used to set the theme globally, such that all subsequent plots will use this where the `theme` argument of a plot is left as NULL. Alternatively, users can use the `theme` argument in a `gg_*` function.

ggblanket provides two complete ggplot2 theme functions called `light_mode` (the default) and `dark_mode`. The first argument is the `base_size`. This changes the size of all the text to this, except the title is 10% higher and the caption is 10% lower. In quarto, it is likely that users will want to set the `*_mode` theme to have a larger `base_size` (e.g. `ggplot2::theme_set(light_mode(11))`). 

Note that the `gg_*` function will control what gridlines are present and the placement of the legend - despite the theme provided. Therefore, if you are providing a theme other than `light_mode` or `dark_mode`, ggblanket works well  if this theme has both vertical and horizontal gridlines. If users want everything adjusted as per the theme, then they can `+` their theme onto the plot instead.

Note that `theme_set(theme_grey())` resets the set theme for ggplot2 code to theme_grey and for ggblanket `gg_*` functions to `light_mode()`. If you want ggblanket `gg_*` functions to default to using `theme_grey()`, then you must modify the base_size slightly (e.g. `theme_set(theme_grey(11.01))`).

```{r, fig.asp=0.7}
# ggblanket
theme_set(dark_mode())

penguins |>
  gg_point(
    x = flipper_length_mm,
    y = body_mass_g,
    col = sex,
    title = "Penguins body mass by flipper length",
    subtitle = "Palmer Archipelago, Antarctica",
    caption = "Source: Gorman, 2020",
    pal = c("#2596be", "#fc7c24")
    )
```

```{r, fig.asp=0.8}
# ggblanket
theme_set(light_mode(base_size = 12))

penguins |>
  gg_point(
    x = flipper_length_mm,
    y = body_mass_g,
    col = sex,
    title = "Penguins body mass by flipper length",
    subtitle = "Palmer Archipelago, Antarctica",
    caption = "Source: Gorman, 2020",
    pal = c("#2596be", "#fc7c24")
    )
theme_set(theme_grey()) #unset the theme
```

### 9. Pretty defaults for symmetry

Where the orientation is normal (i.e. vertical):

-   Default x scale limits and expanding are as per ggplot2 defaults
-   Default numeric y scale limits (that are non-transformed) start and end on a break with zero expanding
-   Default categorical (or numeric transformed) y scale limits and expanding are as per ggplot2 defaults
-   Default removal of vertical gridlines

It does the opposite where the orientation is horizontal. 

Note this symmetry approach does _not_ apply:
* if a transformation other than identity or reverse is applied to x or y scales.
* for `gg_raster`, `gg_contour_filled` or `gg_density_2d_filled`

```{r, fig.asp=0.6}
# ggplot2
penguins |>
  group_by(species, sex) |> 
  summarise(body_mass_g = mean(body_mass_g)) |> 
  ggplot() +
  geom_col(aes(x = body_mass_g, 
               y = species, 
               fill = sex),
           position = "dodge",
           width = 0.75)
```

```{r}
# ggblanket
penguins |>
  group_by(species, sex) |> 
  summarise(body_mass_g = mean(body_mass_g)) |> 
  gg_col(
    x = body_mass_g,
    y = species,
    col = sex,
    position = "dodge",
    width = 0.75)
```

### 10. Access to other geom\_\* arguments via `...`

The `...` argument is placed in the `gg_*` function within the wrapped `ggplot2::geom_*` function. This means all other arguments in the `geom_*` function are available to users. Common arguments from `...` to add are `size`, `linewidth` and `width`.

```{r}
# ggblanket
penguins |>
  gg_smooth(
    x = flipper_length_mm,
    y = body_mass_g,
    col = sex,
    linewidth = 0.5, #accessed via geom_smooth
    level = 0.99) #accessed via geom_smooth
```

### 11. Ability to add multiple geom\_\* layers

Users can make plots with multiple layers with ggblanket by adding on `ggplot2::geom_*` layers. The `gg_*` function puts the aesthetic variables (i.e. x, y, col) within the wrapped `ggplot` function. Therefore, these aesthetics will inherit to any subsequent layers added. Geoms will plot in order. The `gg_*` function will plot the associated geom as the first layer, and then other geoms will be plotted on top of it.

Users need to ensure that the scales built by their `gg_*` function are appropriate for subsequent layers. This is because the plot scales are constructed without knowledge of subsequent layers. They are built based on the `data`, `x`, `y`, `*_limits`, `*_include`, `facet_scales` and the underlying `stat` and `position` in the `gg_*`  function. The `stat` and `position` is unique to each `geom_*` layer - it does not inherit. 

Note that it is recommended that ggblanket users use the term `colour` within `geom_*` functions to help keep the distinction that the ggblanket `col` is the merged colour and fill. 

```{r, fig.asp=0.6}
# ggblanket + ggplot2
penguins |>
  gg_boxplot(x = species,
             y = body_mass_g,
             width = 0.5,
             outlier.colour = NA) +
  geom_jitter(colour = pal_blue)
```

If some geoms have a `col` aesthetic and some do not, then it is recommended to have the `col` variable in the `gg_*` function. This enables ggblanket legend placement, as well as the ability to adjust the col scale via the `gg_*` function.

If you do not want a `col` aesthetic in your bottom plot layer, but require it in subsequent layers - then you should use the `gg_blank` function. _Subsequent_ `geom_*` layers where the col aesthetic is not required can have `colour = NA` and/or `fill = NA` arguments - or use the `inherit.aes = FALSE` argument. 

Note it is a useful to convention to refer to the 2 dimensional `col`/`colour` aesthetic in subsequent ggplot2 layers as `colour` to help avoid confusion with the merged ggblanket `col` aesthetic.

```{r, fig.asp=0.75}
# ggblanket + ggplot2
d <- penguins |>
  group_by(species) |>
  summarise(body_mass_g = mean(body_mass_g)) |>
  mutate(lower = body_mass_g * 0.95) |> 
  mutate(upper = body_mass_g * 1.2)

p1 <- d |>
  gg_blank(    
    y = species,
    x = body_mass_g,
    col = species,
    xmin = lower,
    xmax = upper,
    x_include = 0, 
    x_labels = \(x) x / 1000, 
    x_title = "Body mass kg",
    col_legend_place = "r") +
  geom_col(alpha = 0.9, width = 0.75) +
  geom_errorbar(colour = "black", width = 0.1)

p2 <- d |>
  gg_blank(
    y = species,
    x = body_mass_g,
    xmin = lower, 
    xmax = upper, 
    col = species,
    x_include = 0, 
    x_labels = \(x) x / 1000, 
    x_title = "Body mass kg", 
    col_legend_place = "r") +
  geom_col(colour = NA, fill = "#d3d3d3", width = 0.75) +
  geom_errorbar(width = 0.1)

p1 / p2
```

```{r}
# ggblanket + ggplot2
d_wide <- gapminder::gapminder |>
  filter(year %in% c(1967, 2007)) |>
  select(country, year, lifeExp) |>
  tidyr::pivot_wider(names_from = year, values_from = lifeExp) |>
  mutate(gap = `2007` - `1967`) |>
  slice_max(gap, n = 10) |>
  mutate(country = forcats::fct_inorder(forcats::fct_drop(country))) 

d_long <- d_wide |>
  select(-gap) |> 
  tidyr::pivot_longer(-country, 
                      names_to = "year", 
                      values_to = "life_expectancy")

d_long |> 
  gg_blank(x = life_expectancy,
           y = country,
           col = year,
           pal = pal_discrete[c(2, 1)],
           x_include = 0,
           col_legend_place = "r",
           title = "We're living longer",
           subtitle = "Biggest life expectancy rise, 1967\u20132007",
           x_title = "Life expectancy", 
           y_title = "") +
  geom_segment(aes(x = `1967`, xend = `2007`, 
                   y = country, yend = country), 
               data = d_wide, inherit.aes = FALSE, 
               colour = "#dddddd", linewidth = 2) +
  geom_point(size = 2) 
```

### 12. Other key differences

##### Unquoted variables only

ggblanket requires unquoted variables only for `x`, `y`, `col`, `facet` and `facet2`. You cannot wrap these in a function. Instead you need to apply the function to the relevant variable in the data prior to plotting. For example, reordering or reversing a factor or dropping NAs. 

```{r, fig.asp=0.75}
p1 <- diamonds |>
  count(color) |>
  gg_col(
    x = n,
    y = color,
    width = 0.75,
    x_labels = \(x) x / 1000,
    x_title = "Count (thousands)",
    title = "Default y",
    theme = light_mode(title_face = "plain")
  )


p2 <- diamonds |>
  count(color) |>
  mutate(color = forcats::fct_rev(color)) |>
  gg_col(
    x = n,
    y = color,
    width = 0.75,
    x_labels = \(x) x / 1000,
    x_title = "Count (thousands)",
    title = "Reverse y",
    theme = light_mode(title_face = "plain")
  )

p3 <- diamonds |>
  count(color) |>
  mutate(color = forcats::fct_reorder(color, n)) |>
  gg_col(
    x = n,
    y = color,
    width = 0.75,
    x_labels = \(x) x / 1000,
    x_title = "Count (thousands)",
    title = "Reordered y ascending by x",
    theme = light_mode(title_face = "plain")
  )

p4 <- diamonds |>
  count(color) |>
  mutate(color = color |>
           forcats::fct_reorder(n) |>
           forcats::fct_rev()) |>
  gg_col(
    x = n,
    y = color,
    width = 0.75,
    x_labels = \(x) x / 1000,
    x_title = "Count (thousands)",
    title = "Reordered y decending by x",
    theme = light_mode(title_face = "plain")
  )

(p1 + p2) / (p3 + p4)
```

##### Unused factor levels kept

ggblanket keeps unused factor levels in the plot. If users wish to drop unused levels they should likewise do it in the data prior to plotting. 

```{r, fig.asp=0.4}
p1 <- diamonds |> 
  count(color) |> 
  filter(color %in% c("E", "G", "I")) |>
  gg_col(
    x = n,
    y = color,
    x_labels = \(x) x / 1000,
    x_title = "Count (thousands)",
    title = "A factor filtered",
    theme = light_mode(title_face = "plain")
  )

p2 <- diamonds |> 
  count(color) |>
  filter(color %in% c("E", "G", "I")) |>
  mutate(color = forcats::fct_drop(color)) |> 
  gg_col(
    x = n,
    y = color,
    width = 0.75,
    x_labels = \(x) x / 1000,
    x_title = "Count (thousands)",
    title = "A factor filtered & unused levels dropped",
    theme = light_mode(title_face = "plain")
  )

p1 + p2
```

##### Different oob & clip defaults

By default, ggblanket keeps values outside of the limits (`*_oob = scales::oob_keep`) in calculating the geoms and scales to plot. It also does _not_ clip anything outside the cartesian coordinate space by default (`coord = ggplot2::coord_cartesian(clip = "off"`)). 
ggplot2 by default drops values outside of the limits in calculating the geoms and scales to plot (`scales::oob_censor`). It also clips anything outside the cartesian coordinate space by default (`coord = ggplot2::coord_cartesian(clip = "on"`)).

Users should be particularly careful when setting limits for stats other than `identity`. 

```{r, fig.asp=1}
p1 <- economics |> 
  gg_smooth(
    x = date, 
    y = unemploy, 
    y_labels = str_keep_seq,
    title = "No x_limits set", 
    theme = light_mode(title_face = "plain")) +
  geom_vline(xintercept = c(lubridate::ymd("1985-01-01", "1995-01-01")),
             col = pal_blue, 
             linetype = 3) +
  geom_point(col = pal_blue, alpha = 0.05)

p2 <- economics |> 
  gg_smooth(
    x = date, 
    y = unemploy, 
    x_limits = c(lubridate::ymd("1985-01-01", "1995-01-01")),
    x_labels = \(x) stringr::str_sub(x, 3, 4),
    y_labels = str_keep_seq,
    title = "x_limits set", 
    theme = light_mode(title_face = "plain")) +
  geom_point(col = pal_blue, alpha = 0.1)

p3 <- economics |> 
  gg_smooth(
    x = date, 
    y = unemploy, 
    x_limits = c(lubridate::ymd("1985-01-01", "1995-01-01")),
    x_labels = \(x) stringr::str_sub(x, 3, 4),
    y_labels = str_keep_seq,
    coord = coord_cartesian(clip = "on"), 
    title = "x_limits set & cartesian space clipped", 
    theme = light_mode(title_face = "plain")) +
  geom_point(col = pal_blue, alpha = 0.1)

p4 <- economics |> 
  gg_smooth(
    x = date, 
    y = unemploy, 
    x_limits = c(lubridate::ymd("1985-01-01", "1995-01-01")),
    x_labels = \(x) stringr::str_sub(x, 3, 4),
    x_oob = scales::oob_censor,
    y_labels = str_keep_seq,
    title = "x_limits set & x_oob censored", 
    theme = light_mode(title_face = "plain")) +
  geom_point(col = pal_blue, alpha = 0.1)

p5 <- economics |> 
  filter(between(date, lubridate::ymd("1985-01-01"), lubridate::ymd("1995-01-01"))) |> 
  gg_smooth(
    x = date, 
    y = unemploy,
    x_labels = \(x) stringr::str_sub(x, 3, 4),
    y_labels = str_keep_seq,
    title = "x data filtered", 
    theme = light_mode(title_face = "plain")) +
  geom_point(col = pal_blue, alpha = 0.1)

p1 / (p2 + p3) / (p4 + p5) 
```

##### Different pal and alpha defaults

ggblanket uses different defaults for colouring. The default `pal` is:

* `ggblanket::pal_blue` for where there is no `col` variable
* `viridis::viridis` for a continuous `col` variable
* `ggblanket::pal_discrete` for a discrete `col` variable with 5 or less levels (or unique values if a character). Testing suggests this is safe for colour blindness. 
* `scales::hue_pal` for a discrete `col` variable with more than 5 levels (or unique values if not ordered).

ggblanket uses different `alpha` defaults for the different `gg_*` functions. Polygons that generally have no gap or overlap default to 1: `gg_bin_2d`, `gg_contour_filled`, `gg_density_2d_filled`, `gg_hex` - as well as `gg_sf` for polygons with a `col` aesthetic. Polygons that generally overlap default to 0.5: `gg_density`. Polygons that generally have key lines within them also default to 0.5: `gg_boxplot`, `gg_crossbar`, `gg_ribbon` and `gg_smooth`. `gg_label` defaults to 0.05. `gg_blank` has no alpha argument. Other polygons default to 0.9: `gg_area`, `gg_bar`, `gg_col`, `gg_histogram`, `gg_polygon`, `gg_rect`, `gg_tile` and `gg_violin`. For all other contexts, alpha defaults to 1. 

##### Other minor stuff

* `*_title` is equivalent to `ggplot2::labs(* = ...)` or `ggplot2::scale_*(name = ...)`.
* Logical variables are reversed, so that `TRUE` always comes before `FALSE`.
* `*_include` works in a similar way to `ggplot2::expand_limits(* = ...)`.

## Further information 

See the ggblanket [website](https://davidhodge931.github.io/ggblanket/index.html) for further information, including [articles](https://davidhodge931.github.io/ggblanket/articles/) and [function reference](https://davidhodge931.github.io/ggblanket/reference/index.html).
