| 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 |
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 |> data() + point + x(gdp) + y(life)“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:
| 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)“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)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)`.
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)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)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)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:
| year | sales |
|---|---|
| 2019 | 120 |
| 2020 | 135 |
| 2021 | 128 |
| 2022 | 152 |
| 2023 | 168 |
| 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") + pointplot(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)“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, bigrqueryYou 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-clipkgload::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/gogThe 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.