49  R

Every sentence in this book so far was written first in R, and each tab for another language shows a translation of it. So this chapter adds nothing to the grammar. It answers the seven questions R raises. The first two are how the two pipes reach data() and how R groups the operators. Then come where a plot appears, which of gog’s words R’s own packages already use, which two kinds of R value gog refuses, how a database table reaches a plot, and how the package installs. Data covers what a table is and how you pass one. A table is an ordinary R data frame, and needs no package of its own.

Every R block on this page ran when the page was built, except the database and install commands, which are shown but not run. Every plot came from the same engine every other chapter uses.

49.1 Pipes

Much R code prepares a table in a chain of steps joined by a pipe, and you will often want the last step to be a plot. R has two pipes in common use, and which one you write decides whether gog learns the table’s name (Data explains why the name matters). Write the native pipe, |>: it keeps the name, and draws exactly the plot that data(gapminder_2007) + point + x(gdp) + y(life) draws.

gapminder_2007: first 5 of 142 rows
country continent year life population gdp
Afghanistan Asia 2007 43.828 31889923 974.5803
Albania Europe 2007 76.423 3600523 5937.0295
Algeria Africa 2007 72.301 33333216 6223.3675
Angola Africa 2007 42.731 12420476 4797.2313
Argentina Americas 2007 75.320 40301927 12779.3796
gapminder_2007 |> data() + point + x(gdp) + y(life)
0 10K 20K 30K 40K 50K 40 50 60 70 80 Life Gdp

“Given gapminder 2007: points, x is gdp, y is life.”

gog has no code for the pipe. Before anything runs, R rewrites the piped form as the unpiped one, so the two are the same expression. quote() shows the expression R built rather than what you typed:

quote(gapminder_2007 |> data() + point + x(gdp) + y(life))
data(gapminder_2007) + point + x(gdp) + y(life)

Two facts about R make that work, and both are needed.

The first is precedence, the order in which a language groups its operators (Operators). |> groups before +, so gapminder_2007 |> data() is complete before + joins anything to it. * groups after |> and before +, so a transform still joins its mark first:

gm_all: first 5 of 1704 rows
country continent year life population gdp
Afghanistan Asia 1952 28.801 8425333 779.4453
Afghanistan Asia 1957 30.332 9240934 820.8530
Afghanistan Asia 1962 31.997 10267083 853.1007
Afghanistan Asia 1967 34.020 11537966 836.1971
Afghanistan Asia 1972 36.088 13079460 739.9811
gm_all |> data() + bar * count + x(continent)
Asia Europe Africa Americas Oceania 0 200 400 600 Count Continent

“Given all the gapminder years: bars derived by count, x is continent.”

The second is when it happens. The native pipe is a rule of R’s parser, the part of R that reads your code before running it: writing df |> data() produces the code data(df) before any of it runs. That timing matters because data() reads the table’s name from the expression you wrote, and the native pipe leaves that expression a bare name. data() keeps the name as well as the rows (Data), and a later layer looks up its columns in the table with that name.

The piped table fills the first argument, so any argument you write yourself follows it:

gapminder_2007 |> data(name = "gapminder") + point + x(gdp) + y(life)
0 10K 20K 30K 40K 50K 40 50 60 70 80 Life Gdp

49.2 If your code uses %>%

The magrittr pipe, %>%, is the one most R code still uses, and it does not keep the table’s name. It is an ordinary function rather than a rule of the parser, so it evaluates the left side and gives data() the placeholder . instead of the table’s name:

library(magrittr)
gapminder_2007 %>% data() + point + x(gdp) + y(life)
Warning: gog: magrittr's `%>%` replaced this table with its placeholder `.`
before `data()` could read the name, so gog calls the table `data`, or `data2`
and on when that name is taken. Either name it — `data(name = "...")` — or use
R's native pipe, which keeps the name: `df |> data()` reads as `data(df)`.
0 10K 20K 30K 40K 50K 40 50 60 70 80 Life Gdp

The plot is correct. With one table, the missing name changes nothing in the plot, but gog warns you anyway and says how to supply it. Either use the native pipe, or name the table yourself:

gapminder_2007 %>% data(name = "gapminder_2007") + point + x(gdp) + y(life)
0 10K 20K 30K 40K 50K 40 50 60 70 80 Life Gdp

Only the placeholder . raises that warning. The native pipe raises none, even when what you pipe in is an expression rather than a bare name. Its most useful case is filtering the table before data(). Unlike ., that expression is different for every table, so gog uses the whole expression as the table’s name.

gm_all |> subset(year == 2007 & continent == "Europe") |> data() +
  point + x(gdp) + y(life)
10K 20K 30K 40K 50K 72 74 76 78 80 82 Life Gdp

gog drops the quotation marks, so the table there is called subset(gm_all, year == 2007 & continent == Europe). That name is long, and it still works. name = gives the table a shorter name:

gm_all |> subset(year == 2007 & continent == "Europe") |> data(name = "europe") +
  point + x(gdp) + y(life)
10K 20K 30K 40K 50K 72 74 76 78 80 82 Life Gdp

49.3 Two tables with one name

The name matters once a plot has two tables. A second data() gives a layer its own table, and the “nearest table wins” rule finds each table by name. So gog refuses two different tables that you gave the same name:

actuals: all 5 rows
year sales
2019 120
2020 135
2021 128
2022 152
2023 168
forecast: all 3 rows
year sales
2024 180
2025 195
2026 210
data(actuals, name = "series") + x(year) + y(sales) + line +
  data(forecast, name = "series") + point
(data(actuals, name = "series") + x(col.year) + y(col.sales) + line +
  data(forecast, name = "series") + point)
data(actuals, name = "series") + x(:year) + y(:sales) + line +
  data(forecast, name = "series") + point
plot(data(actuals, { name: "series" }), x(col.year), y(col.sales), line,
  data(forecast, { name: "series" }), point)
Error:
! gog: two different tables are both called `series` — a layer resolves its bare columns against the nearest table by name, so one of these can never be reached. Give them distinct names: `data(name = "...")`.

Without that refusal the plot would draw the first table twice and report the second table’s columns as misspellings. You would have spelled the columns correctly, and gog would have checked them against the wrong table. Two %>% tables do not cause this refusal. gog calls them data and data2, warns once for each, and draws the plot. Naming the tables yourself removes both warnings.

49.4 What the pipe does not change

The pipe supplies the table that data() names. It does not replace +, and there is no piped form of the rest of the sentence. Marks, channels, and transforms compose with + and * as they do everywhere else in this book. gog has no pipe of its own, because R already has two.

The pipe question is R’s alone. Julia has a |>, but Julia’s data() never reads a table’s name from your code, so its pipe changes nothing. Neither Python nor JavaScript has a pipe operator. The other three chapters do not raise it.

49.5 The operators, and their precedence

That was the first question. The second is how R groups the four operators of a sentence. The pipe section used one case of precedence: |> groups before * and +. Among gog’s operators, R groups * and / first, then +, then |. That order is R’s, not gog’s, so it applies to every sentence you write (What groups first).

In that order, one plot needs no parentheses, even with a facet. An arrangement, two plots joined by | or /, is different, because R groups / before + and | after it. gog draws an arrangement written with | and no parentheses, because | groups last, after both plots are complete. It refuses the same arrangement written with /, because / joins the first plot’s last atom to the second plot’s data(). Inside an arrangement, / also groups before |, so a | b / c is read as a | (b / c). And a theme() added after an arrangement with no parentheses around it goes to the second plot, not to the arrangement, because + groups before |.

So in an arrangement, write each plot inside its own parentheses, whichever operator joins them. When one arrangement holds another, put the inner one in parentheses too. Put the whole arrangement in parentheses before a theme() for it. The arrangement then means the same thing in all three languages, including Julia, which groups | together with + (Parentheses in an arrangement).

49.6 Showing the plot

The third question is where a plot appears once you have written its sentence. You do not call anything to show it: R shows a plot as soon as you run its sentence, and where it appears depends on where you work:

  • At the console, it opens in the viewer pane of RStudio or Positron. Without a viewer pane, it opens in your web browser.
  • In a Quarto or R Markdown document, it is drawn under the chunk, as every plot in this book is.
  • In a Jupyter notebook with the R kernel, it appears under the cell. print() shows it too, so a loop can show several plots one after another.

To keep the drawing itself, render_svg() returns it as text.

49.7 Twenty names R already uses

The fourth question is which of gog’s words are already R’s. Twenty of the names gog provides are also names in the packages R loads when it starts, and library(gog) lists eleven of them. Here are all twenty:

from names
stats density line median quantile smooth step
graphics box layout text title
grDevices palette
utils data stack
base jitter max mean min order range sum

Nine of the twenty cannot affect an ordinary call, and library(gog) leaves those nine out of its message. A rule in R itself is the reason. A mark that takes no argument is an object rather than a function, and so is a transform that takes no argument. When R evaluates a call, it looks for a function of that name and skips any object of that name that is not a function. So a call still reaches base R’s mean, even though library(gog) attached its own:

is.function(mean)   # gog's `mean` is an object, so a call cannot reach it
[1] FALSE
mean(1:10)          # and base R's function answers anyway
[1] 5.5

The same is usually true when you pass mean to another function, because most functions look the name up the same way; the exceptions come later in this chapter:

sapply(gapminder_2007["life"], mean)
    life 
67.00742 

The other eleven are functions, and a call does reach gog’s: box, data, density, jitter, layout, order, palette, quantile, range, stack and title. To choose one, give it a prefix: the package’s name and ::, as in base::order. Both remain available:

you write you get for R’s own, write
order(population) the plot’s sort order base::order(df$population)
data(gapminder_2007) the plot’s table utils::data(mtcars)
density(2) the density transform stats::density(x)
jitter(0.5) the offset transform base::jitter(x)
layout(exporter, importer) the layout transform graphics::layout(mat)
quantile(0.9) the 90th percentile transform stats::quantile(df$gdp)
range(0.25, 0.75) the band transform base::range(df$gdp)
stack(share = TRUE) the piling transform utils::stack(df)
palette("soft") the plot’s colors grDevices::palette()
title("Life expectancy") the plot’s title graphics::title(main = "…")
box() the box-and-whisker mark graphics::box()

gog’s range takes two numbers, so it is a function rather than an object, and R does not skip past it. Writing range(0.25, 0.75) therefore reaches gog’s range and never base R’s. gog then reads the argument you passed. If you pass a vector where gog expects two numbers between 0 and 1, you meant base R’s range, and gog’s refusal says so:

range(c(3, 1, 4, 1, 5))
Error:
! gog: `range()` takes the band's two ends, each one number between 0 and 1, e.g. `range(0.25, 0.75)`. It was given 5 numbers. For the smallest and largest of a vector, gog masks that name: use `base::range()`.

Of the eleven, order causes the most errors, because you often sort a table while you are making plots. gog’s order returns an atom, not the row positions that [ expects. Without a refusal, R would stop with invalid subscript type 'list', which names neither order nor gog. So whenever the argument is not a bare name, the refusal says which function you reached and how to reach the other one:

gapminder_2007[order(gapminder_2007$population), ]
Error:
! gog: `order(gapminder_2007$population)` is not a column name. gog's `order()` takes a bare column, as in `order(population, desc = TRUE)`. To sort a vector, the base function is still there as `base::order(gapminder_2007$population)`.

With the prefix, it sorts:

ranked <- gapminder_2007[base::order(-gapminder_2007$population), ]
head(ranked[c("country", "population")], 3)
          country population
25          China 1318683096
59          India 1110396331
135 United States  301139947

Several other chapters write base::order for exactly this reason.

Three of the eleven refuse nothing, because gog cannot tell from the argument which function you meant. data(mtcars) is well formed for both. It starts a plot and raises no complaint, so write utils::data() when you mean to load a dataset. title("...") is the same case: a string is what both want. So is box(), which both accept with no argument at all. Six of the others report the mistake, because what R’s function usually takes, a vector, a matrix or a table, is an argument gog refuses. The last two catch only part of it. order reports order(df$population) but not order(pop), where pop is a bare name holding a vector. palette reports palette() but not palette("R4"), because a palette name is what both want.

The names stay as they are, because renaming order to sort_by would replace a word every reader knows with one that only gog uses. The grammar chapter gives the full reason and compares how each language handles a name it already uses. R’s own packages are not the only source of a collision. Another package may define a name gog also defines. Load gog with library(gog) after that package, so R finds gog’s name first, or write the name with the prefix gog::.

49.8 Five calls that still need a prefix

The nine words that are objects in gog, such as mean, are safe in ordinary code, and sapply() was only one example: lapply(), tapply(), mapply(), Map(), Reduce() and apply() are all safe for the same reason.

Five ways of writing a call still fail, for two different reasons. The first is a name that one function passes to a second function before R looks it up:

aggregate(life ~ continent, gapminder_2007, mean)
Error in `get()`:
! object 'FUN' of mode 'function' was not found

When R finally looks the name up, it is looking for FUN, the name aggregate() gave the function you passed, and not for mean. So the message names neither mean nor gog. by() and ave(..., FUN = mean) fail the same way. Writing base::mean fixes all three. The data frame form of aggregate() looks the name up one step earlier, so aggregate(gapminder_2007["life"], gapminder_2007["continent"], mean) works unchanged.

The second reason is a name used as a value rather than called. do.call(sum, list(x)) wants a function and receives an object, so write do.call("sum", list(x)) or do.call(base::sum, list(x)). And f <- mean copies the object, so f(x) reports that it could not find a function called f.

That is the whole cost, and one habit avoids all five failures: when you pass one of those nine words to another function, or store it in a variable, write it with its prefix, as in base::mean. The functions listed above work without the prefix, but the prefixed name works everywhere. Only four of the nine are base R’s own: max, mean, min and sum. line, median, smooth and step belong to stats, and text belongs to graphics, so write stats::median; base::median is itself an error.

49.9 Two kinds of value gog refuses

The fifth question is which values in a data frame gog cannot draw. R has two kinds of value that are stored as numbers, and no scale can place either of them.

The first is an infinite number. log10(0) is -Inf, and 1 / 0 is Inf. An infinite value is not a missing one, so gog does not drop its row. It refuses the plot and names the column:

growth <- data.frame(day = c(1, 2, 3, 4), cells = c(0, 12, 150, 1900))
growth$log_cells <- log10(growth$cells)
data(growth) + point + x(day) + y(log_cells)
Error:
! gog: column `log_cells` of `growth` holds -Inf, which is not a number a scale can place. Use `NA` for a missing value.

If a row with an infinite value should be left out, write NA in its place: growth$log_cells[is.infinite(growth$log_cells)] <- NA. gog then drops that row and says so, as Missing values shows.

The second is a length of time. Subtracting one date or date-time from another gives a difftime, R’s type for a duration, and so does as.difftime(). gog has no scale for a duration, and one duration can be shown in minutes, hours or days. gog does not choose the unit for you. travel holds how many minutes the trip to work took on five days. gog refuses the plot and names the column, and the message gives the code that makes it a number:

travel <- data.frame(
  day  = as.Date("2024-05-06") + 0:4,
  time = as.difftime(c(42, 38, 55, 40, 47), units = "mins")
)
data(travel) + line + point + x(day) + y(time)
Error:
! gog: column `time` of `travel` holds lengths of time, and gog has no scale for a duration. Make it a number in the unit you mean, such as hours: `as.numeric(travel$time, units = "hours")`.

Choose the unit you want to read on the axis. Here it is minutes:

travel$minutes <- as.numeric(travel$time, units = "mins")
data(travel) + line + point + x(day) + y(minutes)
May 6 May 7 May 8 May 9 May 10 40 45 50 55 Minutes Day

“Given the travel table: a line and also points, x is day, y is minutes.”

gog checks for both kinds of value only in the columns the sentence names. travel still holds time, and this plot draws, because it never reads that column. A missing value in a column the sentence never names changes nothing either.

49.10 Reading from a database

The sixth question is how a table in a database reaches a plot. query() runs a query on a database, and it takes a DBI connection. DBI is R’s database standard, so the same call reaches SQLite, Postgres, MariaDB, BigQuery, and anything with an ODBC driver.

con <- DBI::dbConnect(RSQLite::SQLite(), "sales.db")

query(con, "SELECT status, revenue FROM orders") + bar + x(status) + y(revenue)

DBI and the drivers are optional: gog’s DESCRIPTION file lists DBI and RSQLite under Suggests, R’s field for optional packages, so installing gog does not install them. If you never write SQL, you need neither. If you do, install DBI and the one driver your database needs:

install.packages(c("DBI", "RSQLite"))    # or RPostgres, RMariaDB, odbc, bigrquery

You open the connection with DBI yourself, as the example above does, and pass it to query(). gog never opens one and never stores a password. Data covers what query() sends and how much data comes back.

49.11 Installing gog

The last question is how you install gog. Every plot is drawn by the engine, a separate program written in Rust, so the R package has to be able to find it. An installed copy of the package carries its own engine. The package’s configure script, which runs during the install, puts the engine inside the package, and it stops the install rather than leave you a package that cannot draw.

install.packages("gog", repos = c("https://psychometrician.r-universe.dev",
                                  "https://cloud.r-project.org"))

gog is published on r-universe. For the current version of R and the one before it, r-universe builds a ready-to-install copy of the package for macOS and Windows, with the engine already inside it, so installing needs no Rust toolchain. With an older version of R, the command installs from source, as it does on Linux.

On Linux the same command installs from source, and the source build compiles the engine. That needs Rust 1.75 or newer and a network connection. If the machine has no Rust, or only an older version, install the latest version first:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"

Then install gog with the install.packages() call at the start of this section. The build runs once, while the package installs.

The build also needs memory. On a machine with little memory, such as some Posit Cloud projects, the compiler may need more memory than the machine has. The operating system then stops the compiler, and the install stops with a message that says so. The message also gives the R code that downloads the engine already built, so nothing has to be compiled. Run that code, then install gog again in the same session.

The repos argument names two addresses because gog is not on CRAN. The first address is the r-universe repository that holds gog. The second is an ordinary CRAN mirror. It is there for jsonlite, the one package gog needs that r-universe does not hold.

You can also work from a checkout, a copy of gog’s source repository on your own computer; that is how you change gog itself. Besides the packages that come with R, gog needs one package, jsonlite, which turns the sentence you write into the JSON that the engine reads. install.packages() installs it along with gog. Loading from a checkout needs pkgload as well, which loads a package from a folder instead of from a library. Working from a checkout installs neither, so install both if you do not have them:

install.packages(c("jsonlite", "pkgload"))

If one is missing, the step that needs it stops immediately, and the message names the missing package.

Then, from the checkout’s top folder, build the engine once (this needs Rust) and load the package from its folder:

cargo build --release -p gog-cli
pkgload::load_all("r-pkg/gog")

If you change gog’s source, installing from the checkout checks more than loading it does, because the install runs configure, which puts the engine inside the installed package:

R CMD INSTALL r-pkg/gog

The R package looks for the engine in five places. In order: the GOG_CLI_PATH environment variable, a target/ build in a surrounding checkout, the copy inside the installed package, gog-cli on your PATH, and last a search upward from the working directory. A checkout is searched before the bundled copy on purpose. Inside a checkout the bundled engine is a copy an earlier install left behind. Using it would draw with an old engine while you are changing the engine’s source. If GOG_CLI_PATH names no file, gog refuses to draw and says which path it was given, and R CMD INSTALL stops for the same reason. Neither moves on to the next place, because a mistyped path would then draw with an engine you did not choose.

49.12 What comes next

R’s sentence is the one every chapter before this has written. Python writes the same sentence with col.gdp where R writes a bare name, and draws the same picture, byte for byte.