---
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.
