---
title: "signs"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{signs}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>"
)
```
```{r setup, message = FALSE}
library(scales)
library(signs)
library(dplyr)
library(ggplot2)
library(ggrepel)
```
## The Basics
Using `signs` is simple, especially if you're familiar
with functions like `number()`, `number_format()`,
`comma()`, `comma_format()`, `percent()`, and `percent_format()`
from the [`scales`](https://scales.r-lib.org/reference/number_format.html)
package.
It simply provides two new functions to complement these:
`signs()` and `signs_format()`.
* Points in group 1 are labeled with a true Unicode minus glyph.
* Points in group 2 are labeled with the traditional ASCII hyphen-minus.
* Basic usage is identical.
```{r basics}
theme_set(theme_gray())
theme_update(
panel.grid.minor = element_blank(),
axis.text.y = element_blank(),
axis.ticks.y = element_blank()
)
p <-
ggplot(sleep) +
aes(group, extra) +
geom_point() +
xlab("Drug") +
ylab("Extra Sleep (hours)")
label_hours <- function(mapping) {
geom_text_repel(
mapping,
nudge_x = -.1,
direction = "y",
segment.size = .4,
segment.color = "grey75",
hjust = "right"
)
}
p +
label_hours(
aes(
label = case_when(
group == 1 ~ signs(extra, accuracy = .1),
group == 2 ~ number(extra, accuracy = .1)
)
)
)
```
## Other Number Formats
You can use any formatting function with `signs::signs()``r "\u2014"`not just
`scales::number()`.
Let's assume everyone gets 8 hours of sleep,
so we can label the points as __percentages__.
```{r percentages}
p +
ylab("Extra Sleep (% over 8 hours)") +
label_hours(
aes(
label = case_when(
group == 1 ~ signs(extra / 8, accuracy = .1, format = scales::percent),
group == 2 ~ percent(extra / 8, accuracy = .1)
)
)
)
```
Or we can muliply by days in a year and use `scales::comma()`.
```{r commas}
p +
ylab("Extra Sleep (hours / year)") +
label_hours(
aes(
label = case_when(
group == 1 ~ signs(extra * 365, format = scales::comma),
group == 2 ~ comma(extra * 365)
)
)
)
```
`format` can be any function that takes a numeric vector
and returns a character vector.
### Matching by Position
Note that `format` and all other options (see below) come __after__ the dots;
this way you can enjoy the same matching by position
you know from `scales`.
For example, if you prefer the simplicity of `number(x, 1)`,
you can simply use `signs(x, 1)`.
```{r matching-by-position}
x <- seq(-4, 4)
number(x, 1) # first argument is accuracy
signs(x, 1) # first argument is accuracy
```
## Other Arguments
`signs::signs()` offers 3 other arguments for convenience:
1. `add_plusses`
1. `trim_leading_zeros`
1. `label_at_zero`
(#3 is addressed below under _Axis Labels_.)
Sometimes, as with this dataset,
you want to show __change from a baseline__.
You might not only want to include a `r "\u2212"` in front of negative numbers,
but a + in front of positive numbers as well.
This is as simple as `add_plusses = TRUE`.
```{r add-plusses}
p +
label_hours(
aes(
label = case_when(
group == 1 ~ signs(extra, accuracy = .1, add_plusses = TRUE),
group == 2 ~ number(extra, accuracy = .1)
)
)
)
```
If all values are on the interval (`r "\u2212"`1, 1),
it can be more compact to remove leading zeros.
Do this with `trim_leading_zeros = TRUE`:
```{r trim-leading-zeros}
p +
ylim(-.8, .8) +
label_hours(
aes(
label = case_when(
group == 1 ~ signs(extra, accuracy = .1, trim_leading_zeros = TRUE),
group == 2 ~ number(extra, accuracy = .1)
)
)
) +
theme(
axis.text.y = element_blank(),
axis.ticks.y = element_blank()
)
```
## Axis Labels
You can also use Unicode minus signs on an entire axis.
This function is called `signs::signs_format()`,
by analogy to `scales::number_format()`, `scales::percent_format()`,
and the rest of the `_format()` functions.
Note that it accepts the same optional arguments as `signs::signs()` as well.
```{r axis-labels}
theme_update(
axis.text.y = element_text(hjust = 1)
)
p +
scale_y_continuous(
limits = c(-.8, .8),
breaks = seq(-.8, .8, by = .2),
labels = signs_format(
accuracy = .1,
add_plusses = TRUE,
trim_leading_zeros = TRUE
)
) +
label_hours(
aes(
label = case_when(
group == 1 ~ signs(
extra,
accuracy = .1,
add_plusses = TRUE,
trim_leading_zeros = TRUE
),
group == 2 ~ number(extra, accuracy = .1)
)
)
)
```
You may want to treat zero itself differently,
particularly when every other value has either a plus or a minus.
Maybe you'll be extra pedantic about it with `label_at_zero = "symbol"`
(notice the y-axis labels below, not the data point labels):
```{r plus-or-minus}
p +
scale_y_continuous(
limits = c(-4, 6),
breaks = seq(-4, 6, by = 1),
labels = signs_format(
add_plusses = TRUE,
label_at_zero = "symbol"
)
) +
label_hours(
aes(
label = case_when(
group == 1 ~ signs(
extra,
accuracy = .1,
add_plusses = TRUE
),
group == 2 ~ number(extra, accuracy = .1)
)
)
)
```
Or, especially if the location of zero is already obvious,
you might want to leave it blank
with `label_at_zero = "blank"`:
```{r zero-blank}
p +
scale_y_continuous(
limits = c(-4, 6),
breaks = seq(-4, 6, by = 1),
labels = signs_format(
add_plusses = TRUE,
label_at_zero = "blank"
)
) +
label_hours(
aes(
label = case_when(
group == 1 ~ signs(
extra,
accuracy = .1,
add_plusses = TRUE
),
group == 2 ~ number(extra, accuracy = .1)
)
)
)
```
## Setting Options Globally
You can set `format`, `add_plusses`, `trim_leading_zeros`, or `label_at_zero`
globally for a script with `options()`:
```{r setting-options-globally}
options(
signs.format = scales::number,
signs.add.plusses = TRUE,
signs.trim.leading.zeros = TRUE,
signs.label.at.zero = "none"
)
p +
scale_y_continuous(
limits = c(-.8, .8),
breaks = seq(-.8, .8, by = .2),
labels = signs_format(accuracy = .1, label_at_zero = "blank")
) +
label_hours(
aes(
label = case_when(
group == 1 ~ signs(extra, accuracy = .1),
group == 2 ~ number(extra, accuracy = .1)
)
)
)
```
The defaults are `scales::number`, `FALSE`, `FALSE`, and `"none"`, respectively.