My sticky note filled up quickly after I only added setNames()
on it, with related functions for dealing with object names, in base R and beyond!
(Un)Setting object names: stats::setNames()
, unname()
and rlang::set_names()
I noticed a function ending with something like this:
It struck me as simplifiable by:
blop <- function() {
# code creating the df data.frame
# ...
stats::setNames(df, c("col1", "col2"))
}
Interestingly the docs for setNames()
sound as if it were created just for this use case!
“This is a convenience function that sets the names on an object and returns the object. It is most useful at the end of a function definition where one is creating the object to be returned and would prefer not to store it under a name just so the names can be assigned.”
For the opposite operation, removing the names of an object, we can use unname()
.
Unsurprisingly, the rlang package has its own special rlang::set_names()
, that is more or less a stats::setNames()
+ unname()
+ generic janitor::clean_names()
combo!
(df <- data.frame(c(1, 2), c(5, 7)))
#> c.1..2. c.5..7.
#> 1 1 5
#> 2 2 7
rlang::set_names(df, c("a", "b")) # sets names!
#> a b
#> 1 1 5
#> 2 2 7
rlang::set_names(df, NULL) # removes names
#>
#> 1 1 5
#> 2 2 7
df2 <- rlang::set_names(df, c("SHOUTING", "LOUDLY"))
df2
#> SHOUTING LOUDLY
#> 1 1 5
#> 2 2 7
rlang::set_names(df2, tolower) # using a function to transform names
#> shouting loudly
#> 1 1 5
#> 2 2 7
Of note,
“
set_names()
is stable and exported in purrr”.
So if you prefer, you can use purrr::set_names()
instead.
Last but not least, despite the examples above being on a data.frame, these functions can be used on lists, vectors…
Extracting names: names()
, rlang::names2()
names()
probably doesn’t need any introduction, but it’s interesting to mention it in contrast to rlang::names2()
that " always returns a character vector, even when an object does not have a names attribute.”.
rlang also has a replacement variant, rlang::names2<-
, that “never adds NA names and instead fills unnamed vectors with ""
.”
I didn’t know all this before skimming the list of rlang functions dealing with object attributes.
Checking names
Is the object named: rlang::is_named()
, rlang::is_named2()
, rlang::have_name()
rlang::have_name()
is the vectorized version of rlang::is_name()
, not to be confused with rlang::has_name()
. 😅
rlang::is_named(c(a = 1))
#> [1] TRUE
rlang::is_named(NULL)
#> [1] FALSE
rlang::is_named2(NULL)
#> [1] TRUE
rlang::have_name(c(a = 1, 2)) # the example from the manual page
#> [1] TRUE FALSE
I do not know of a base R equivalent, I mean I’d find a way to express it, but not as directly. But maybe I am missing yet another base R gem!
Does the object have an element called X, does the object have elements called X, Y: utils::hasName()
, rlang::has_name()
If you want to check that an object contains elements with certain names, you can use utils::hasName()
expected_names <- c("pof", "blop")
vector1 <- c(pof = 1, bla = 2)
vector2 <- c(pof = 1, bla = 2, blop = 3)
utils::hasName(vector1, expected_names)
#> [1] TRUE FALSE
utils::hasName(vector2, expected_names)
#> [1] TRUE TRUE
The rlang package has a similar function, rlang::has_name()
.
expected_names <- c("pof", "blop")
vector1 <- c(pof = 1, bla = 2)
vector2 <- c(pof = 1, bla = 2, blop = 3)
rlang::has_name(vector1, expected_names)
#> [1] TRUE FALSE
rlang::has_name(vector2, expected_names)
#> [1] TRUE TRUE
I’m honestly not sure whether there’s any difference between the utils and rlang versions, apart from the name!
In package tests: testthat::expect_named()
testthat::expect_named()
allows you to check that an object returned by your code has the names you expect, or simply has names. You can ignore case and order based on the arguments ignore.case
and ignore.order
.
(object <- tibble::tibble(a = c(1, 2), b = c(3, 4)))
#> # A tibble: 2 × 2
#> a b
#> <dbl> <dbl>
#> 1 1 3
#> 2 2 4
testthat::expect_named(object)
unnamed_object <- unname(object)
testthat::expect_named(unnamed_object)
#> Error: `unnamed_object` does not have names.
testthat::expect_named(object, c("b", "a"))
#> Error: Names of `object` ('a', 'b') don't match 'b', 'a'
testthat::expect_named(object, c("b", "a"), ignore.order = TRUE)
testthat::expect_named(object, c("A", "B"))
#> Error: Names of `object` ('a', 'b') don't match 'A', 'B'
testthat::expect_named(object, c("A", "B"), ignore.case = TRUE)
If you’re not used to using this expectation yet, lintr can help you a bit.
Conclusion
In this post I went through functions that deals with names: stats::setNames()
, unname()
and rlang::set_names()
for (un)setting names (my initial motivation for this post!); names()
, rlang::names2()
for extracting names; rlang::is_named()
, rlang::is_named2()
and rlang::have_name()
to find out whether an object is named; utils::hasName()
and rlang::has_name()
to find out whether an object contains elements of a given name; testthat::expect_named()
for testing whether an object has names, or specific names. Any other related function that comes to mind?