Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@
^cran-comments\.md$
^CRAN-SUBMISSION$
^revdep$
^doc$
^Meta$
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
*.html
docs
inst/doc
/doc/
/Meta/
226 changes: 226 additions & 0 deletions vignettes/percent.Rmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
---
title: "Using percent vectors"
output: rmarkdown::html_vignette
author: Nick Christofides
vignette: >
%\VignetteIndexEntry{Using percent vectors}
%\VignetteEncoding{UTF-8}
%\VignetteEngine{knitr::rmarkdown}
editor_options:
chunk_output_type: console
---

```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>"
)
```

Let's start by loading the phsmethods package.

```{r setup}
library(phsmethods)
```

## Motivation

Working with percentages in R can be frustrating, to say the least. A typical workflow for generating percentages might look like this:

- Generate proportions
- Scale up proportions to percentages (multiplying by 100)
- Round percentages
- Convert percentages to characters
- Append a percentage symbol
- Use proportions for math operations and rounding
- Use formatted percentage strings for outputs

With `<percent>` vectors, this workflow is reduced to:

- Generate proportions
- Convert to percentages

This is clearly much simpler and allows for cleaner and more reproducible code.

## Creating percentages

The primary function for converting to percentages is `as_percent()`. This converts a `<numeric>` vector to a `<percent>` vector, handling the formatting and rounding when needed, i.e printing or converting to a character vector.

```{r}
(p <- as_percent(0.055))
```

Internally, the numeric vector is left as-is, which can be confirmed by examining the vector via `unclass(x)`.

```{r}
unclass(p)
```

The only time percentage formatting actually happens is when the `<percent>` vector is printed or converted to a character vector (via `as.character` or `format`).

```{r}
print(p)
as.character(p)
format(p)
```

## Rounding

Percentages are rounded using a round-halves-up approach. The rationale for straying away from R's round-to-even is that readers generally expect percentages to be rounded this way in formatted outputs such as papers, reports, etc. We are less concerned with statistical bias and more concerned with formatting.

There are two main ways to control how percent vectors are rounded:

## Rounding via `as_percent` + `digits`

`as_percent` does two things:

1. It creates a `<percent>` vector
2. It sets the ".digits" attribute, which controls how the percent vector is printed downstream.

```{r}
p2 <- as_percent(p, digits = 0)

# Prints and formats to 0 decimal places
print(p2)
as.character(p2)

# Underlying data has not been rounded!
unclass(p2)
```

## Rounding via `round()`

This method will 'physically' round the numbers.

```{r}
p3 <- round(p, digits = 0)

p3

# Underlying data has been rounded
unclass(p3)
```

In practice, this means that rounding with `as_percent` is more flexible as it reduces downstream errors that can accumulate from premature rounding.

## Math with percent vectors

A strong feature of `<percent>` vectors is the ability to use them in mathematical contexts without extra unnecessary work.

```{r}
# Helper to create literal percentages
percent <- function(x) {
as_percent(x / 100)
}
```

Addition, subtraction, multiplication and division.

```{r}
percent(50) + percent(25) # = 50% + 25% = 75%
percent(50) - percent(25) # = 50% - 25% = 25%
percent(50) * percent(25) # = 50% * (1/4) = 12.5%
percent(50) / percent(25) # = 50% / (1/4) = 200%
```

More rounding functions.

```{r}
percentages <- percent(seq(-0.1, 0.1, by = 0.05))
floor(percentages)
ceiling(percentages)
trunc(percentages)
round(percentages)
round(percentages, 1)
round(percentages, 2)
```

## percent vectors and tidyverse

`<percent>` vectors can be used in tibbles just like regular vectors.

```{r}
library(dplyr)

species <- starwars |>
count(species, sort = TRUE) |>
mutate(perc = as_percent(n / sum(n), digits = 1))

# Prints nicely
species |>
slice_head(n = 5)
```

We can also do statistical summaries.

```{r}
perc_summary <- species |>
summarise(
min = min(perc),
max = max(perc),
median = median(perc),
avg = mean(perc),
sum = sum(perc)
)
perc_summary
```

## `<percent>` vectors and formatted tables

They can also be easily and nicely formatted into tables (e.g. via `kable()`)

```{r}
library(knitr)
kable(perc_summary)
```

And flextables.

```{r}
library(flextable)
qflextable(perc_summary)
```

## percent vectors and `ggplot2`

```{r}
library(ggplot2)

gg_data <- iris |>
as_tibble() |>
count(Species) |>
mutate(
prop = n / sum(n),
perc = as_percent(
prop,
digits = 1 # To control formatting in ggplot + elsewhere
)
)

gg_data

species_gg <- gg_data |>
ggplot(aes(Species)) +
geom_col(aes(y = prop, fill = Species), width = 0.25)
```

Use `as_percent` for formatting percentage axes.

```{r, fig.width = 7, fig.height = 6}
species_gg +
scale_y_continuous(name = "Percentage", labels = as_percent)
```

We can also use `<percent>` vectors as ggplot aesthetics[^1]

[^1]: Using `<percent>` vectors in ggplot2 is currently limited and you are likely to face issues with using them as plot aesthetics. This can include messages surrounding unsupported scales and axes. With the new release of ggplot2 4.0.0, there is potential for embedding `<percent>` vectors more formally and programmatically into `ggplot2`. This is currently being worked on.

```{r, fig.width = 7, fig.height = 6}
gg_data |>
ggplot(aes(x = "", y = perc, fill = Species)) +
geom_bar(stat = "identity", width = 1, color = "white") +
coord_polar("y", start = 0) +
theme_void() +
geom_text(aes(label = perc), position = position_stack(vjust = 0.5)) +
scale_fill_brewer(palette = "Set1")
```
Loading