Title: | Elegant Data Manipulation with Lenses |
---|---|
Description: | Provides tools for creating and using lenses to simplify data manipulation. Lenses are composable getter/setter pairs for working with data in a purely functional way. Inspired by the 'Haskell' library 'lens' (Kmett, 2012) <https://hackage.haskell.org/package/lens>. For a fairly comprehensive (and highly technical) history of lenses please see the 'lens' wiki <https://github.com/ekmett/lens/wiki/History-of-Lenses>. |
Authors: | Chris Hammill [aut, cre, trl], Ben Darwin [aut, trl] |
Maintainer: | Chris Hammill <[email protected]> |
License: | MIT + file LICENSE |
Version: | 0.0.3 |
Built: | 2024-10-24 03:43:17 UTC |
Source: | https://github.com/cfhammill/lenses |
Compose two lenses to produce a new lens which represents
focussing first with the first lens, then with the second.
A view
using the resulting composite lens will first view
using
the first, then the second, while an set
will view
via the first lens,
set
into the resulting piece with the second, and then replace the
updated structure in the first with set
. Lens composition
is analogous to the .
syntax of object-oriented programming or to
a flipped version of function composition.
l %.% m
l %.% m
l |
|
m |
the second lens |
lst <- list(b = c(3,4,5)) lns <- index_l("b") %.% index_l(2) lst %>% view(lns) # returns 4 lst %>% set(lns, 1) # returns list(b = c(3,2,5)) lst # returns list(b = c(3,4,5))
lst <- list(b = c(3,4,5)) lns <- index_l("b") %.% index_l(2) lst %>% view(lns) # returns 4 lst %>% set(lns, 1) # returns list(b = c(3,2,5)) lst # returns list(b = c(3,4,5))
The lens version of attr
and attr<-
attr_l(attrib)
attr_l(attrib)
attrib |
A length one character vector indicating the attribute to lens into. |
(x <- structure(1:10, important = "attribute")) view(x, attr_l("important")) set(x, attr_l("important"), "feature")
(x <- structure(1:10, important = "attribute")) view(x, attr_l("important")) set(x, attr_l("important"), "feature")
The lens equivalent of attributes and attributes<-
attributes_l
attributes_l
An object of class lens
of length 2.
(x <- structure(1:10, important = "attribute")) view(x, attributes_l) set(x, attributes_l, list(important = "feature"))
(x <- structure(1:10, important = "attribute")) view(x, attributes_l) set(x, attributes_l, list(important = "feature"))
A lens into the body of a function. The lens equivalent of body and body<-. You probably shouldn't use this.
body_l
body_l
An object of class lens
of length 2.
inc2 <- function(x) x + 2 view(inc2, body_l) inc4 <- set(inc2, body_l, quote(x + 4)) inc4(10)
inc2 <- function(x) x + 2 view(inc2, body_l) inc4 <- set(inc2, body_l, quote(x + 4)) inc4(10)
A lens version of purrr::pluck. Takes a series element indicators and creates a composite lens.
c_l(...)
c_l(...)
... |
index vectors or lenses |
length one vectors are converted to index_l,
length one logical vectors and numeric vectors that are negative are converted to indexes_l,
larger vectors are converted to indexes_l,
lenses are composed as is.
See examples for more
view(iris, c_l("Petal.Length", 10:20, 3)) sepal_l <- index("Sepal.Length") view(iris, c_l(sepal_l, id_l, 3))
view(iris, c_l("Petal.Length", 10:20, 3)) sepal_l <- index("Sepal.Length") view(iris, c_l(sepal_l, id_l, 3))
A lens into the class of an object. Lens equivalent of class and class<-.
class_l
class_l
An object of class lens
of length 2.
x <- 1:10 view(x, class_l) set(x, class_l, "super_integer")
x <- 1:10 view(x, class_l) set(x, class_l, "super_integer")
The lens version of colnames
and colnames<-
colnames_l
colnames_l
An object of class lens
of length 2.
x <- matrix(1:4, ncol = 2) colnames(x) <- c("first", "second") x view(x, colnames_l) set(x, colnames_l, c("premiere", "deuxieme"))
x <- matrix(1:4, ncol = 2) colnames(x) <- c("first", "second") x view(x, colnames_l) set(x, colnames_l, c("premiere", "deuxieme"))
Create a lens into a set of columns
cols_l(cols, drop = FALSE)
cols_l(cols, drop = FALSE)
cols |
the columns to focus on |
drop |
whether or not to drop dimensions with length 1 |
x <- matrix(1:4, ncol = 2) colnames(x) <- c("first", "second") x view(x, cols_l(1)) view(x, cols_l("second")) set(x, cols_l(1), c(20,40))
x <- matrix(1:4, ncol = 2) colnames(x) <- c("first", "second") x view(x, cols_l(1)) view(x, cols_l("second")) set(x, cols_l(1), c(20,40))
view is equivalent to Filter(f,d)
,
set replaces elements that satisfy f
with elements
of x.
cond_il(f)
cond_il(f)
f |
the predicate (logical) function |
This lens is illegal because set-view
is not satisfied,
multiple runs of the same lens will reference potentially
different elements.
A lens into a matrix's diagonal elements
diag_l
diag_l
An object of class lens
of length 2.
A lens into an objects dimensions
dim_l
dim_l
An object of class lens
of length 2.
x <- 1:10 (y <- set(x, dim_l, c(2,5))) view(y, dim_l)
x <- 1:10 (y <- set(x, dim_l, c(2,5))) view(y, dim_l)
A lens into the dimnames of an object. Lens equivalent of dimnames and dimnames<-.
dimnames_l
dimnames_l
An object of class lens
of length 2.
x <- matrix(1:4, ncol = 2) colnames(x) <- c("first", "second") x view(x, dimnames_l) set(x, dimnames_l, list(NULL, c("premiere", "deuxieme")))
x <- matrix(1:4, ncol = 2) colnames(x) <- c("first", "second") x view(x, dimnames_l) set(x, dimnames_l, list(NULL, c("premiere", "deuxieme")))
A lens into all elements starting from the first element that doesn't satisfy a predicate. Essentially the complement of take_while_il
drop_while_il(f)
drop_while_il(f)
f |
the predicate (logical) function |
A lens into the environment of an object. This is the lens version of environment and environment<-
env_l
env_l
An object of class lens
of length 2.
x <- 10 f <- (function(){x <- 2; function() x + 1})() f f() view(f, env_l)$x g <- over(f, env_l, parent.env) g()
x <- 10 f <- (function(){x <- 2; function() x + 1})() f f() view(f, env_l)$x g <- over(f, env_l, parent.env) g()
Create an illegal lens into the result of a filter. Arguments are interpreted with non-standard evaluation as in dplyr::filter
filter_il(...)
filter_il(...)
... |
unquoted NSE filter arguments |
head(view(iris, filter_il(Species == "setosa"))) head(over(iris, filter_il(Species == "setosa") %.% select_l(-Species), function(x) x + 10))
head(view(iris, filter_il(Species == "setosa"))) head(over(iris, filter_il(Species == "setosa") %.% select_l(-Species), function(x) x + 10))
Create a lawful lens into the result of a filter. This focuses only columns not involved in the filter condition.
filter_l(...)
filter_l(...)
... |
unquoted NSE filter arguments |
head(view(iris, filter_l(Species == "setosa"))) # Note Species is not seen head(over(iris, filter_l(Species == "setosa"), function(x) x + 10))
head(view(iris, filter_l(Species == "setosa"))) # Note Species is not seen head(over(iris, filter_l(Species == "setosa"), function(x) x + 10))
Lens version of x[[1]]
and x[[1]] <- val
x <- 1:10
view(x, first_l)
set(x, first_l, 50)
[[1]: R:[1 [[1]: R:[1
first_l
first_l
An object of class lens
of length 2.
A lens equivalent of formals and formals<-, allowing you to change the formal arguments of a function. As with body_l you probably shouldn't use this.
formals_l
formals_l
An object of class lens
of length 2.
f <- function(x) x + y + 7 view(f, formals_l) g <- set(f, formals_l, list(x = 1, y = 2)) g()
f <- function(x) x + y + 7 view(f, formals_l) g <- set(f, formals_l, list(x = 1, y = 2)) g()
This lens focuses on the whole object
id_l
id_l
An object of class lens
of length 2.
x <- 1:10 view(x, id_l) head(set(x, id_l, iris))
x <- 1:10 view(x, id_l) head(set(x, id_l, iris))
This is the lens version of [[
index_l(el) index(el)
index_l(el) index(el)
el |
The element the lens should point to
can be an |
index
: shorthand
x <- 1:10 view(x, index_l(1)) set(x, index(5), 50) head(view(iris, index(2)))
x <- 1:10 view(x, index_l(1)) set(x, index(5), 50) head(view(iris, index(2)))
This is the lens version of [
indexes_l(els) indexes(els)
indexes_l(els) indexes(els)
els |
a subset vector, can be |
indexes
: shorthand
x <- 1:10 view(x, indexes_l(3:5)) set(x, indexes_l(c(1,10)), NA) head(view(iris, indexes_l(c("Sepal.Length", "Species"))))
x <- 1:10 view(x, indexes_l(3:5)) set(x, indexes_l(c(1,10)), NA) head(view(iris, indexes_l(c("Sepal.Length", "Species"))))
Lens version of x[[length(x)]]
and x[[length(x)]] <- val
[[length(x)]: R:[length(x) [[length(x)]: R:[length(x)
last_l
last_l
An object of class lens
of length 2.
x <- 1:10 view(x, last_l) set(x, last_l, 50)
x <- 1:10 view(x, last_l) set(x, last_l, 50)
A lens
represents the process of focusing on a specific part of a data structure.
We represent this via a view
function and
an set
function, roughly corresponding to object-oriented
"getters" and "setters" respectively.
Lenses can be composed to access or modify deeply nested
structures.
lens(view, set, getter = FALSE)
lens(view, set, getter = FALSE)
view |
A function that takes a data structure of a certain type and returns a subpart of that structure |
set |
A function that takes a data structure of a certain type
and a value and returns a new data structure with the given subpart
replaced with the given value. Note that |
getter |
Default is |
Lenses are popular in functional programming because they allow you to build pure, compositional, and re-usable "getters" and "setters".
As noted in the README, using lens
directly incurs the following obligations
(the "Lens laws"):
Get-Put: If you get (view) some data with a lens, and then modify (set) the data with that value, you get the input data back.
Put-Get: If you put (set) a value into some data with a lens, then get that value with the lens, you get back what you put in.
Put-Put: If you put a value into some data with a lens, and then put another value with the same lens, it's the same as only doing the second put.
"Lenses" which do not satisfy these properties should be documented accordingly. By convention, such objects present in this library are suffixed by "_il" ("illegal lens").
third_l <- lens(view = function(d) d[[3]], set = function(d, x){ d[[3]] <- x; d }) view(1:10, third_l) # returns 3 set(1:10, third_l, 10) # returns c(1:2, 10, 4:10)
third_l <- lens(view = function(d) d[[3]], set = function(d, x){ d[[3]] <- x; d }) view(1:10, third_l) # returns 3 set(1:10, third_l, 10) # returns c(1:2, 10, 4:10)
A lens into the levels of an object. Usually this is factor levels. Lens equivalent of levels and levels<-.
levels_l
levels_l
An object of class lens
of length 2.
x <- factor(c("a", "b")) view(x, levels_l) set(x, levels_l, c("A", "B"))
x <- factor(c("a", "b")) view(x, levels_l) set(x, levels_l, c("A", "B"))
Create a lens into the lower diagonal elements of a matrix
lower_tri_l(diag = FALSE)
lower_tri_l(diag = FALSE)
diag |
whether or not to include the diagonal |
(x <- matrix(1:9, ncol = 3)) view(x, lower_tri_l()) view(x, lower_tri_l(diag = TRUE)) set(x, lower_tri_l(), c(100, 200, 300))
(x <- matrix(1:9, ncol = 3)) view(x, lower_tri_l()) view(x, lower_tri_l(diag = TRUE)) set(x, lower_tri_l(), c(100, 200, 300))
Create a new lens that views and sets each element of the list.
map_l(l)
map_l(l)
l |
the lens to promote |
Uses lapply under the hood for view and mapply under the hood for set. This means that set can be given a list of values to set, one for each element. If the input or update are lists this lens always returns a list. If the input and update are vectors this lens will return a vector.
(ex <- replicate(10, sample(1:5), simplify = FALSE)) view(ex, map_l(index(1))) set(ex, map_l(index(1)), 11:20)
(ex <- replicate(10, sample(1:5), simplify = FALSE)) view(ex, map_l(index(1))) set(ex, map_l(index(1)), 11:20)
The lens versions of names
and names<-
.
names_l
names_l
An object of class lens
of length 2.
view(iris, names_l) head(set(iris, names_l, LETTERS[1:5]))
view(iris, names_l) head(set(iris, names_l, LETTERS[1:5]))
To flatten lens composition, you can prespecify the data the lens with be applied to by constructing an objectoscope. These can be integrated easily with normal data pipelines.
oscope(d, l = id_l)
oscope(d, l = id_l)
d |
The data for interest |
l |
The lens to bind the data to. Defaults to the identity lens |
list(a = 5, b = 1:3, c = 8) %>% oscope() %.% index_l("b") %.% index_l(1) %>% set(10)
list(a = 5, b = 1:3, c = 8) %>% oscope() %.% index_l("b") %.% index_l(1) %>% set(10)
Get the data pointed to by a lens, apply a function and replace it with the result.
over(d, l, f)
over(d, l, f)
d |
the data (or an oscope) |
l |
the lens (or the function if |
f |
the function (or nothing if |
third_l <- index(3) over(1:5, third_l, function(x) x + 2) # returns c(1:2, 5, 4:5)
third_l <- index(3) over(1:5, third_l, function(x) x + 2) # returns c(1:2, 5, 4:5)
Apply the specified function to each element of the subobject.
over_map(d, l, f)
over_map(d, l, f)
d |
the data |
l |
the lens |
f |
the function to use, potentially a |
Apply the specified function with named elements of the viewed data in scope. Similar to dplyr::mutate
over_with(d, l, f)
over_with(d, l, f)
d |
the data |
l |
the lens |
f |
the function to use, potentially a |
iris %>% over_with(id_l, ~ Sepal.Length)
iris %>% over_with(id_l, ~ Sepal.Length)
Construct a lens that is a view of the data with a new set of dimensions. Both view and set check that the new dimensions match the number of elements of the data.
reshape_l(dims)
reshape_l(dims)
dims |
a vector with the new dimensions |
x <- 1:9 view(x, reshape_l(c(3,3))) set(x, reshape_l(c(3,3)) %.% diag_l, 100)
x <- 1:9 view(x, reshape_l(c(3,3))) set(x, reshape_l(c(3,3)) %.% diag_l, 100)
Lens into the reverse of an object.
rev_l
rev_l
An object of class lens
of length 2.
x <- 1:10 view(x, rev_l) set(x, rev_l, 11:20)
x <- 1:10 view(x, rev_l) set(x, rev_l, 11:20)
The lens version of rownames
and rownames<-
rownames_l
rownames_l
An object of class lens
of length 2.
x <- matrix(1:4, ncol = 2) rownames(x) <- c("first", "second") x view(x, rownames_l) set(x, rownames_l, c("premiere", "deuxieme"))
x <- matrix(1:4, ncol = 2) rownames(x) <- c("first", "second") x view(x, rownames_l) set(x, rownames_l, c("premiere", "deuxieme"))
Create a lens into a set of rows
rows_l(rows, drop = FALSE)
rows_l(rows, drop = FALSE)
rows |
the rows to focus on |
drop |
whether or not to drop dimensions with length 1 |
x <- matrix(1:4, ncol = 2) rownames(x) <- c("first", "second") x view(x, rows_l(1)) view(x, rows_l("second")) set(x, rows_l(1), c(20,40))
x <- matrix(1:4, ncol = 2) rownames(x) <- c("first", "second") x view(x, rows_l(1)) view(x, rows_l("second")) set(x, rows_l(1), c(20,40))
Create a lens into a named collection. On set names of the input are not changed. This generalizes dplyr::select to arbitrary named collections and allows updating.
select_l(...)
select_l(...)
... |
An expression to be interpreted by tidyselect::vars_select which is the same interpreter as dplyr::select |
lets <- setNames(seq_along(LETTERS), LETTERS) set(lets, select_l(G:F, A, B), 1:4) # A and B are 3,4 for a quick check
lets <- setNames(seq_along(LETTERS), LETTERS) set(lets, select_l(G:F, A, B), 1:4) # A and B are 3,4 for a quick check
Set one lens to the view of another
send(d, l, m)
send(d, l, m)
d |
the data |
l |
the lens to view through |
m |
the lens to set into |
Set one lens to the view of another (transformed)
send_over(d, l, m, f)
send_over(d, l, m, f)
d |
the data |
l |
the lens to view through |
m |
the lens to set into |
f |
the function to apply to the viewed data |
Set the subcomponent of the data referred to by a lens
with a new value. See lens for details. Merely dispatches
to the set
component of the lens.
set(d, l, x)
set(d, l, x)
d |
the data, or an oscope |
l |
the lens, or in the case of an |
x |
the replacement value, or nothing in the case of an |
Create a lens into a chunk of an array (hyperslab). Uses
the same syntactic rules as [
.
slab_l(..., drop = FALSE)
slab_l(..., drop = FALSE)
... |
arguments as they would be passed to |
drop |
whether or not to drop dimensions with length 1. Only
applies to |
(x <- matrix(1:4, ncol = 2)) view(x, slab_l(2,)) # x[2,, drop = FALSE] view(x, slab_l(2, 2)) # x[2,2, drop = FALSE] set(x, slab_l(1,1:2), c(10,20))
(x <- matrix(1:4, ncol = 2)) view(x, slab_l(2,)) # x[2,, drop = FALSE] view(x, slab_l(2, 2)) # x[2,2, drop = FALSE] set(x, slab_l(1,1:2), c(10,20))
Create a lens into a specific slice of a specific dimension of a multidimensional object. Not to be confused with dplyr slice.
slice_l(dimension, slice, drop = FALSE)
slice_l(dimension, slice, drop = FALSE)
dimension |
the dimension to slice |
slice |
the slice index |
drop |
whether or not to drop dimensions with length 1. Only applies to view. |
(x <- matrix(1:4, ncol = 2)) view(x, slice_l(1, 2)) # x[2,, drop = FALSE] view(x, slice_l(2, 2)) # x[,2, drop = FALSE] set(x, slice_l(1,1), c(10,20))
(x <- matrix(1:4, ncol = 2)) view(x, slice_l(1, 2)) # x[2,, drop = FALSE] view(x, slice_l(2, 2)) # x[,2, drop = FALSE] set(x, slice_l(1,1), c(10,20))
The lens equivalent of @
and @<-
for getting and setting S4 object slots.
slot_l(slot)
slot_l(slot)
slot |
the name of the slot |
new_class <- setClass("new_class", slots = c(x = "numeric")) (x <- new_class()) view(x, slot_l("x")) set(x, slot_l("x"), 1:10)
new_class <- setClass("new_class", slots = c(x = "numeric")) (x <- new_class()) view(x, slot_l("x")) set(x, slot_l("x"), 1:10)
Lens into the transpose of a matrix
t_l
t_l
An object of class lens
of length 2.
(x <- matrix(1:4, ncol = 2)) view(x, t_l) set(x, t_l, matrix(11:14, ncol = 2))
(x <- matrix(1:4, ncol = 2)) view(x, t_l) set(x, t_l, matrix(11:14, ncol = 2))
This constructs a lens into the first n
elements
of an object or the if negative indexing is used,
as many as length(x) - n
.
take_l(n)
take_l(n)
n |
number of elements to take, or if negative the number of elements at the end to not take. |
x <- 1:10 view(x, take_l(3)) view(x, take_l(-7)) set(x, take_l(2), c(100,200)) set(x, take_l(-8), c(100,200))
x <- 1:10 view(x, take_l(3)) view(x, take_l(-7)) set(x, take_l(2), c(100,200)) set(x, take_l(-8), c(100,200))
A lens into the elements from the beginning of a structure until the last element that satisfies a predicate.
take_while_il(f)
take_while_il(f)
f |
the predicate (logical) function |
This lens is illegal because set-view
is not satisfied,
multiple runs of the same lens will reference potentially
different elements.
getter
lensCreate a getter
lens from a function.
to_l(f)
to_l(f)
f |
The function to promote. |
# This wouldn't make sense as a general legal lens, but fine as a `getter` sqrt_l <- to_l(sqrt) iris_root <- index(1) %.% index(1) %.% sqrt_l sqrt(iris[[1]][[1]]) iris %>% view(iris_root) tryCatch(iris %>% set(iris_root, 2) , error = function(e) "See, can't do that")
# This wouldn't make sense as a general legal lens, but fine as a `getter` sqrt_l <- to_l(sqrt) iris_root <- index(1) %.% index(1) %.% sqrt_l sqrt(iris[[1]][[1]]) iris %>% view(iris_root) tryCatch(iris %>% set(iris_root, 2) , error = function(e) "See, can't do that")
A lens that creates a list-of-rows view of a data.frame
transpose_l
transpose_l
An object of class lens
of length 2.
A lens between a list and an unrecursively unlisted object.
unlist_l
unlist_l
An object of class lens
of length 2.
(x <- list(x = list(y = 1:10))) view(x, unlist_l) set(x, unlist_l %.% unlist_l, rep("hello", 10))
(x <- list(x = list(y = 1:10))) view(x, unlist_l) set(x, unlist_l %.% unlist_l, rep("hello", 10))
Create a lens into the upper diagonal elements of a matrix
upper_tri_l(diag = FALSE)
upper_tri_l(diag = FALSE)
diag |
whether or not to include the diagonal (x <- matrix(1:9, ncol = 3)) view(x, upper_tri_l()) view(x, upper_tri_l(diag = TRUE)) set(x, upper_tri_l(), c(100, 200, 300)) |
Get the subcomponent of the data referred to by a lens. This function
merely dispatches to the view
component of the lens.
view(d, l)
view(d, l)
d |
the data |
l |
the lens |