• Stars
    star
    209
  • Rank 186,881 (Top 4 %)
  • Language
    R
  • License
    Apache License 2.0
  • Created over 1 year ago
  • Updated about 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Lightweight extension of the base R graphics system

plot2

R-universe status badge R-CMD-check

A lightweight extension of the base R plot system, with support for automatic grouping and legend handling, and several other enhancements.

Installation

plot2 is not yet on CRAN, but can be installed from R-universe.

install.packages("plot2", repos = "https://grantmcdermott.r-universe.dev")

Our goal is to submit to CRAN by the end of July/August 2023, once we have settled on some remaining design choices and features support. You can take a look at the open issues to see what’s currently under consideration. Please feel free to weigh on these if you have opinions. We want end users to have a say in determining the final product.

Motivation

R users are spoiled for choice when it comes to visualization frameworks. The options of course include ggplot2 (arguably the most important graphics system of the last decade) and lattice, not to mention a bewildering array of extensions built around, on top of, and in between these amazing packages.

It is perhaps not surprising, then, that the base R graphics system sometimes gets short shrift. This is unfortunate, because base R offers very powerful and flexible plotting facilities. Just type demo(graphics) or demo(persp) into your R console to get an idea. Or, take a look at these two excellent tutorials. The downside of this power and flexibility is that base R plotting can require a fair bit of manual tinkering. A case in point is plotting grouped data with an appropriate legend. Doing so with the generic plot() function can require several function calls or a loop, fiddling with your plot regions, and then generating the legend manually.

The plot2 package aims to remove this overhead. It provides a lightweight extension of the base R plot system with various convenience features, particularly for creating (2D) scatter and line plots using grouped data. For example, the core plot2() function makes it easy to plot different categories of a dataset in a single function call and highlight these categories (groups) using modern colour palettes. Coincident with this grouping support, plot2() also produces automatic legends with scope for further customization. While the package offers several other enhancements, it tries as far as possible to be a drop-in replacement for the equivalent base plot function. Users should generally be able to swap a valid plot() call with plot2() without any changes to the expected output.

Examples

Let’s load the package then walk through some examples.

library(plot2)

Similarity to plot()

As far as possible, plot2 tries to be a drop-in replacement for regular plot calls.

par(mfrow = c(1, 2))

plot(0:10, main = "plot")
plot2(0:10, main = "plot2")

Similarly, we can plot elements from a data frame using either the atomic or formula methods. Here’s a simple example using a slightly modified version of the airquality dataset that comes bundled with base R.

aq = airquality
aq$Month = factor(month.abb[aq$Month], levels = month.abb[5:9])

# with(aq,  plot2(Day, Temp)) # atomic method (same as below)
plot2(Temp ~ Day, data = aq) # formula method

For the remaining examples below, we’ll continue with this modified aq dataset to demonstrate some of additional features that plot2 enables.

Grouped data

Where plot2 starts to diverge from its base counterpart is with respect to grouped data. In particular, plot2 allows you to characterize groups using the by argument.1

# plot2(aq$Day, aq$Temp, by = aq$Month) # same as below
with(aq, plot2(Day, Temp, by = Month))

An arguably more convenient approach is to use the equivalent formula syntax. Just place the grouping variable after a vertical bar (i.e., |).

plot2(Temp ~ Day | Month, data = aq)

You can use standard base plotting arguments to adjust features of your plot. For example, change pch (plot character) to get filled points.

plot2(
  Temp ~ Day | Month, data = aq,
  pch = 16
)

Similarly, converting to a grouped line plot is a simple matter of adjusting the type argument.

plot2(
  Temp ~ Day | Month, data = aq,
  type = "l"
)

The default behaviour of plot2 is to represent groups through colour. However, note that we can automatically adjust pch and lty by groups too by passing the "by" convenience keyword. This can be used in conjunction with the default group colouring. Or, as a replacement for group colouring—an option that may be particularly useful for contexts where colour is expensive or prohibited (e.g., certain academic journals).

plot2(
  Temp ~ Day | Month, data = aq,
  type = "l",
  col = "black", # override automatic group colours
  lty = "by"     # change line type by group instead
)

Colours

On the subject of group colours, these are easily customized via the palette argument. The default group colours are inherited from the user’s default palette. (Most likely the “R4” set of colors; see ?palette). However, all of the various palettes listed by palette.pals() and hcl.pals() are supported as convenience strings.2 Note that case-insensitive, partial matching for these convenience string is allowed. For example:

plot2(
  Temp ~ Day | Month, data = aq,
  type = "l",
  palette = "tableau" # or "ggplot2", "okabe-ito", "set2", "harmonic", etc.
)

Beyond these convenience strings, users can also supply a valid palette-generating function for finer control over transparency, colour order, and so forth. We’ll see a demonstration of this further below.

To underscore what we said earlier, colours are inherited from the user’s default palette. So these can also be set globally, just as they can for the base plot function. The next code chunk will set a new default palette for the remainder of the plots that follow.

# Set the default palette globally via the generic palette function
palette("tableau")

Legend

In all of the preceding plots, you will have noticed that we get an automatic legend. The legend position and look can be customized with the legend argument. At a minimum, you can pass the familiar legend position keywords as a convenience string (“topright”, “left”, etc.). Moreover, a key feature of plot2 is that we can easily and elegantly place the legend outside the plot area by adding a trailing “!” to these keywords. (As you may have realised, the default legend position is “right!”.) Let’s demonstrate by moving the legend to the left of the plot:

plot2(
  Temp ~ Day | Month, data = aq,
  type = "l",
  legend = "left!"
)

Beyond the convenience of these positional keywords, the legend argument also permits additional customization by passing an appropriate function (or, a list of arguments that will be passed on to the standard legend() function internally.) So you can change or turn off the legend title, remove the bounding box, switch the direction of the legend text to horizontal, etc. Here’s a grouped density plot example, where we also add some shading by specifying that the background colour should vary by groups too.

with(
  aq,
  plot2(
    density(Temp),
    by = Month,
    fill = "by",                         # add fill by groups
    grid = TRUE,                         # add background grid
    legend = list("topright", bty = "o") # change legend features
  )
)

Interval plots

plot2 adds supports for interval plots via the "pointrange", "errorbar", "ribbon" type arguments. An obvious use-case is for regression analysis and prediction.

mod = lm(Temp ~ 0 + Month / Day, data = aq)
aq = cbind(aq, predict(mod, interval = "confidence"))

with(
  aq,
  plot2(
    x = Day, y = fit, by = Month,
    ymin = lwr, ymax = upr,
    type = "ribbon",
    grid = TRUE,
    main = "Model predictions"
  )
)

Similarly, we can grab the model estimates to produce nice coefficient plots.

coeftab = data.frame(
  gsub("Month", "", names(coef(mod))),
  coef(mod),
  confint(mod)
) |>
  setNames(c("term", "estimate", "ci_low", "ci_high"))

with(
  subset(coeftab, !grepl("Day", term)),
  plot2(
    x = term, y = estimate,
    ymin = ci_low, ymax = ci_high,
    type = "pointrange", # or: "errobar", "ribbon"
    pch = 19, col = "dodgerblue",
    grid = TRUE,
    main = "Average Monthly Effect on Temperature"
  )
)

Customization

Customizing your plots further is straightforward, whether that is done by changing global parameters (via par) or invoking plot2 arguments. Here’s a quick penultimate example, where we change our point character, tick labels, and font family globally, before adding some transparency to our colour palette, and use Tufte-style floating axes with a background panel grid.

par(
  pch    = 16,           # Filled points as default
  las    = 1,            # Horizontal axis tick labels
  family = "HersheySans" # Use a (built-in) Hershey font instead of Arial default
)

plot2(
  Temp ~ Day | Month, data = aq,
  type = "b",
  palette = palette.colors(palette = "tableau", alpha = 0.5),
  frame = FALSE, grid = TRUE,
  main = "Daily temperatures by month"
)

At the risk of repeating ourselves, the use of par in the previous example again underscores the correspondence with the base graphics system. Because plot2 is effectively a convenience wrapper around base plot, any global elements that you have set for the latter should carry over to the former. For nice out-of-the-box themes, we recommend the basetheme package.

par(pch = 15, las = 0, family = "") # change/revert global changes from above

library(basetheme)
basetheme("royal") # or "clean", "dark", "ink", "brutal", etc.

plot2(
  Temp ~ Day | Month, data = aq,
  type = "b",
  pch = "by",
  palette = "tropic",
  main = "Daily temperatures by month"
)

basetheme(NULL)  # back to default theme
dev.off()
#> null device 
#>           1

Conclusion

In summary, consider the plot2 package if you are looking for base R plot functionality with some added convenience features. You can use pretty much the same syntax and all of your theming elements should carry over too. It has no dependencies other than base R itself and this makes it an attractive option for situations where dependency management is expensive (e.g., an R application running in a browser via WebAssembly).

Footnotes

  1. At this point, experienced base plot users might protest that you can colour by groups using the col argument, e.g. with(aq, plot(Day, Temp, col = Month)). This is true, but there are several limitations. First, you don’t get an automatic legend. Second, the base plot.formula method doesn’t specify the grouping within the formula itself (not a deal-breaker, but not particularly consistent either). Third, and perhaps most importantly, this grouping doesn’t carry over to line plots (i.e., type=“l”). Instead, you have to transpose your data and use matplot. See this old StackOverflow thread for a longer discussion. ↩

  2. See the accompanying help pages of those two functions for more details on the available palettes, or read the article by Achim Zeileis and Paul Murrell. ↩

More Repositories

1

quarto-revealjs-clean

A minimalist and elegant presentation theme for Quarto Reveal.js
SCSS
198
star
2

lecturenotes

My lecture notes Rmd template
TeX
121
star
3

parttree

R package for plotting simple decision tree partitions
R
91
star
4

etwfe

Extended two-way fixed effects
R
50
star
5

ggfixest

Dedicated ggplot2 methods for fixest model objects
R
40
star
6

R-intro

Introduction to R
HTML
33
star
7

rstudio-compute-engine

RStudio Server on Google Compute Engine
32
star
8

renv-rspm

Testing renv and RStudio Package Manager
R
31
star
9

ds4e

Data Science for Economists and Other Animals
R
28
star
10

arch-tips

Customization log for my Arch setup
20
star
11

skeptic-priors

Code and data for "Skeptic priors and climate consensus" (McDermott, 2021)
TeX
19
star
12

codespaces-r2u

Minimal(ish) R codespaces environment
R
16
star
13

two-col-test

Trying sectioned two column format for both PDF and HTML output
HTML
15
star
14

duckdb-polars

Introduction to DuckDB and Polars
CSS
12
star
15

tikzexamples

Some TikZ and PGFPlots examples
TeX
11
star
16

quarto-revealjs-clean-demo

Demo slides for the clean Quarto revealjs theme
HTML
11
star
17

example

HTML
11
star
18

grantmcdermott.github.io

Repo used for my website.
HTML
9
star
19

ritest

Randomization Inference on R model objects
R
8
star
20

covote2020

Comparing the (county-level) 2020 US election results with COVID-19 outcomes
8
star
21

ec510

Course material for Big Data in Economics (EC 510)
TeX
7
star
22

bycatch

Data and R code for reproducing "Protecting marine mammals, turtles, and birds by rebuilding global fisheries".
R
6
star
23

blueparadox

Code and data for "The blue paradox: Preemptive overfishing in marine reserves" (McDermott, Meng, et al., 2018; PNAS)
Stata
6
star
24

lfe2fixest

Convert lfe scripts to their fixest equivalents.
R
5
star
25

sfg-bayes-intro

A very high-level introduction to Bayesian data analysis (using R)
HTML
4
star
26

covgit

Analysis of COVID-19's effect on GitHub activity
R
3
star
27

containerit-demo

Simple demo that builds a Docker image from a repo's contents using the containerit package
R
3
star
28

assa2020

Presentation at the ASSA 2020 meetings
HTML
2
star
29

hydro-slides

Slides for "Hydro power. Market might."
HTML
1
star
30

SAIL

CSS
1
star
31

grantmcdermott.r-universe.dev

R-universe subdomain
1
star
32

tree2020

Presentation at TREE
HTML
1
star
33

testpkg

Experimental repo for offloading an R package's test suite to a Git submodule
R
1
star
34

vita

My CV
TeX
1
star
35

presentations

Repo containing my public presentations
HTML
1
star