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

library(zeallot)

## Getting Started
The *zeallot* package defines an operator for *unpacking assignment*, sometimes
called *parallel assignment* or *destructuring assignment* in other programming
languages. The operator is written as `%<-%` and used like this.

```r
c(lat, lng) %<-% list(38.061944, -122.643889)

The result is that the list is unpacked into its elements, and the elements are assigned to lat and lng.

lat
lng

You can also unpack the elements of a vector.

c(lat, lng) %<-% c(38.061944, -122.643889)
lat
lng

You can unpack much longer structures, too, of course, such as the 6-part summary of a vector.

c(min_wt, q1_wt, med_wt, mean_wt, q3_wt, max_wt) %<-% summary(mtcars$wt)
min_wt
q1_wt
med_wt
mean_wt
q3_wt
max_wt

If the left-hand side and right-hand sides do not match, an error is raised. This guards against missing or unexpected values.

c(stg1, stg2, stg3) %<-% list("Moe", "Donald")
c(stg1, stg2, stg3) %<-% list("Moe", "Larry", "Curley", "Donald")

Unpacking a returned value

A common use-case is when a function returns a list of values and you want to extract the individual values. In this example, the list of values returned by coords_list() is unpacked into the variables lat and lng.

#
# A function which returns a list of 2 numeric values.
# 
coords_list <- function() {
  list(38.061944, -122.643889)
}

c(lat, lng) %<-% coords_list()
lat
lng

In this next example, we call a function that returns a vector.

#
# Convert cartesian coordinates to polar
#
to_polar = function(x, y) {
  c(sqrt(x^2 + y^2), atan(y / x))
}

c(radius, angle) %<-% to_polar(12, 5)
radius
angle

Example: Intercept and slope of regression

You can directly unpack the coefficients of a simple linear regression into the intercept and slope.

c(inter, slope) %<-% coef(lm(mpg ~ cyl, data = mtcars))
inter
slope

Example: Unpacking the result of safely

The purrr package includes the safely function. It wraps a given function to create a new, "safe" version of the original function.

```{R, eval = require("purrr")} safe_log <- purrr::safely(log)

The safe version returns a list of two items. The first item is the result of 
calling the original function, assuming no error occurred; or `NULL` if an error
did occur. The second item is the error, if an error occurred; or `NULL` if no 
error occurred. Whether or not the original function would have thrown an error,
the safe version will never throw an error.

```r
pair <- safe_log(10)
pair$result
pair$error
pair <- safe_log("donald")
pair$result
pair$error

You can tighten and clarify calls to the safe function by using %<-%.

c(res, err) %<-% safe_log(10)
res
err

Unpacking a data frame

A data frame is simply a list of columns, so the zeallot assignment does what you expect. It unpacks the data frame into individual columns.

c(mpg, cyl, disp, hp) %<-% mtcars[, 1:4]

head(mpg)

head(cyl)

head(disp)

head(hp)

Example: List of data frames

Bear in mind that a list of data frames is still just a list. The assignment will extract the list elements (which are data frames) but not unpack the data frames themselves.

quartet <- lapply(1:4, function(i) anscombe[, c(i, i + 4)])

c(an1, an2, an3, an4) %<-% lapply(quartet, head, n = 3)

an1

an2

an3

an4

The %<-% operator assigned four data frames to four variables, leaving the data frames intact.

Unpacking nested values

In addition to unpacking flat lists, you can unpack lists of lists.

c(a, c(b, d), e) %<-% list("begin", list("middle1", "middle2"), "end")
a
b
d
e

Not only does this simplify extracting individual elements, it also adds a level of checking. If the described list structure does not match the actual list structure, an error is raised.

c(a, c(b, d, e), f) %<-% list("begin", list("middle1", "middle2"), "end")

Splitting a value into its parts

The previous examples dealt with unpacking a list or vector into its elements. You can also split certain kinds of individual values into subvalues.

Character vectors

You can assign individual characters of a string to variables.

c(ch1, ch2, ch3) %<-% "abc"
ch1
ch2
ch3

Dates

You can split a Date into its year, month, and day, and assign the parts to variables.

c(y, m, d) %<-% Sys.Date()
y
m
d

Class objects

zeallot includes implementations of destructure for character strings, complex numbers, data frames, date objects, and linear model summaries. However, because destructure is a generic function, you can define new implementations for custom classes. When defining a new implementation keep in mind the implementation needs to return a list so that values are properly unpacked.

Trailing values: the "everything else" clause

In some cases, you want the first few elements of a list or vector but do not care about the trailing elements. The summary function of lm, for example, returns a list of 11 values, and you might want only the first few. Fortunately, there is a way to capture those first few and say "don't worry about everything else".

f <- lm(mpg ~ cyl, data = mtcars)

c(fcall, fterms, resids, ...rest) %<-% summary(f)

fcall

fterms

head(resids)

Here, rest will capture everything else.

str(rest)

The assignment operator noticed that ...rest is prefixed with ..., and it created a variable called rest for the trailing values of the list. If you omitted the "everything else" prefix, there would be an error because the lengths of the left- and right-hand sides of the assignment would be mismatched.

c(fcall, fterms, resids, rest) %<-% summary(f)

If multiple collector variables are specified at a particular depth it is ambiguous which values to assign to which collector and an error will be raised.

Leading values and middle values

In addition to collecting trailing values, you can also collect initial values and assign specific remaining values.

c(...skip, e, f) %<-% list(1, 2, 3, 4, 5)
skip
e
f

Or you can assign the first value, skip values, and then assign the last value.

c(begin, ...middle, end) %<-% list(1, 2, 3, 4, 5)
begin
middle
end

Skipped values: anonymous elements

You can skip one or more values without raising an error by using a period (.) instead of a variable name. For example, you might care only about the min, mean, and max values of a vector's summary.

c(min_wt, ., ., mean_wt, ., max_wt) %<-% summary(mtcars$wt)
min_wt
mean_wt
max_wt

By combining an anonymous element (.) with the collector prefix, (...), you can ignore whole sublists.

c(begin, ..., end) %<-% list("hello", "blah", list("blah"), "blah", "world!")
begin
end

You can mix periods and collectors together to selectively keep and discard elements.

c(begin, ., ...middle, end) %<-% as.list(1:5)
begin
middle
end

It is important to note that although value(s) are skipped they are still expected. The next section touches on how to handle missing values.

Default values: handle missing values

You can specify a default value for a left-hand side variable using =, similar to specifying the default value of a function argument. This comes in handy when the number of elements returned by a function cannot be guaranteed. tail for example may return fewer elements than asked for.

nums <- 1:2
c(x, y) %<-% tail(nums, 2)
x
y

However, if we tried to get 3 elements and assign them an error would be raised because tail(nums, 3) still returns only 2 values.

c(x, y, z) %<-% tail(nums, 3)

We can fix the problem and resolve the error by specifying a default value for z.

c(x, y, z = NULL) %<-% tail(nums, 3)
x
y
z

Swapping values

A handy trick is swapping values without the use of a temporary variable.

c(first, last) %<-% c("Ai", "Genly")
first
last

c(first, last) %<-% c(last, first)
first
last

or

cat <- "meow"
dog <- "bark"

c(cat, dog, fish) %<-% c(dog, cat, dog)
cat
dog
fish

Right operator

The magrittr package provides a pipe operator %>% which allows functions to be called in succession instead of nested. The left operator %<-% does not work well with these function chains. Instead, the right operator %->% is recommended. The below example is adapted from the magrittr readme.

library(magrittr)

mtcars %>%
  subset(hp > 100) %>%
  aggregate(. ~ cyl, data = ., FUN = . %>% mean() %>% round(2)) %>%
  transform(kpl = mpg %>% multiply_by(0.4251)) %->% 
  c(cyl, mpg, ...rest)

cyl
mpg
rest


nteetor/zeallot documentation built on March 17, 2020, 11:22 a.m.