Way to capture functions names inside a list

I have the following program that calculates bootstrap results for a set of different values and produces plots of the results. It does the trick, but it requires that I name each of the functions in the list model_names <- list(test_fun = test_fun, next_fun = next_fun) , which seems like double work. Is there a way to extract the name of the function called from the list instead?

# Trying to name list to help with eventual file naming of graph
library(tidyverse)
#> Registered S3 method overwritten by 'rvest':
#>   method            from
#>   read_xml.response xml2
library(rsample)

# Define functions to loop over
test_fun <- function(df, cut_off) {
  mean((df$carb < cut_off))
}

next_fun <- function(df, cut_off) {
  mean(df[df$carb > cut_off, ]$mpg)
}

# All runs use same data
set.seed(2)
df_bt <- bootstraps(mtcars, times = 2000, apparent = TRUE)

# List of functions to loop over
model_names <- list(test_fun = test_fun, next_fun = next_fun)

mtcars_bt <- map(model_names, function(f) {
    map_df(1:7, function(z) {
      df_bt %>% 
        mutate(ratio = map(splits, ~ {
          df <- analysis(.x)
          tibble(
            term = "ratio",
            estimate  = f(df, z),
            std.error = NA_real_
          )
        }
        )) %>% 
        int_pctl(ratio) %>% 
        mutate(cut_off = z) 
    })
  }
)

names(mtcars_bt) <- names(model_names)

# graph production
map(names(model_names), function(x) {
  ggplot(data = mtcars_bt[[x]], aes(x = cut_off, y = .estimate)) +
    geom_line() +
    geom_ribbon(aes(ymin = .lower, ymax = .upper), alpha =  0.1)
  ggsave(paste0(x, ".pdf"))
})
#> Saving 7 x 5 in image
#> Saving 7 x 5 in image
#> [[1]]
#> NULL
#> 
#> [[2]]
#> NULL

Created on 2020-03-12 by the reprex package (v0.2.1)

I'm not exactly sure I understand what you mean, but is the following modification of your code closer to what you're asking about?

# Trying to name list to help with eventual file naming of graph
library(tidyverse)
library(rsample)

# Define functions to loop over
test_fun <- function(df, cut_off) {
  mean((df$carb < cut_off))
}

next_fun <- function(df, cut_off) {
  mean(df[df$carb > cut_off, ]$mpg)
}

# All runs use same data
set.seed(2)
df_bt <- bootstraps(mtcars, times = 2000, apparent = TRUE)

# List of functions to loop over
model_names <- list(test_fun = test_fun, next_fun = next_fun)

mtcars_bt <- map(model_names, function(f) {
    map_df(1:7, function(z) {
      df_bt %>% 
        mutate(ratio = map(splits, ~ {
          df <- analysis(.x)
          tibble(
            term = "ratio",
            estimate  = f(df, z),
            std.error = NA_real_
          )
        }
        )) %>% 
        int_pctl(ratio) %>% 
        mutate(cut_off = z) 
    })
  }
)

# graph production
map(mtcars_bt, function(x) {
  p <-
  ggplot(data = x, aes(x = cut_off, y = .estimate)) +
    geom_line() +
    geom_ribbon(aes(ymin = .lower, ymax = .upper), alpha =  0.1)
  # ggsave(paste0(x, ".pdf"))
  print(p)
})

Thank you. Re-reading my own question, I can see that it was not very clear what I was trying to achieve, so let me try with one that is boiled down as much as possible.

If I name each element in my list, I can get the name of each list element using, for example, lmap, as follows:

library(tidyverse)
#> Registered S3 method overwritten by 'rvest':
#>   method            from
#>   read_xml.response xml2
dfs <- list(mtcars = mtcars, iris = iris)
lmap(dfs, ~{message(names(.x));return(list(NULL))})
#> mtcars
#> iris
#> [[1]]
#> NULL
#> 
#> [[2]]
#> NULL

Created on 2020-03-15 by the reprex package (v0.2.1)

What I am looking for is a way of getting the "called" element of the list without having to name each of them. This, for example, just gives my blanks (obviously because I have not named each element):

# Returns blanks
library(tidyverse)
#> Registered S3 method overwritten by 'rvest':
#>   method            from
#>   read_xml.response xml2
dfs <- list(mtcars, iris)
lmap(dfs, ~{message(names(.x));return(list(NULL))})
#> 
#> 
#> [[1]]
#> NULL
#> 
#> [[2]]
#> NULL

Created on 2020-03-15 by the reprex package (v0.2.1)

Is there a way of getting "mtcars" and "iris" from the list dfs <- list(mtcars, iris)? I know I have used data frames here, but I assume the same process would work for both data frames and functions

Could you give a boiled down example of what your ideal use-case is for having access to the names? That might help folks (and me!) understand where you're coming from, since the literal answer to your question

is 'no' --- applying str() to dfs shows that only the content of the objects is retained.

That is kind of what I expected, but thank you for your help.

The end goal is to do some calculations and then save a graph with the name of the data frame as the first part of the file name, say mtcars_my_graph.pdf and iris_my_graph.pdf. Naming each item is fine for smaller lists and I already have code that do what I want when names are available. I was looking at whether I could do without the naming in case I run into larger lists.

In that case, if you're creating graphs through functions, there are definitely ways of capturing object names. Here's an example with objects in the parent environment of the function (where strings short-circuit passing the objects themselves):

library(tidyverse)
my_function <- 
  function(one, two) {
    # convert strings to names
    sym.one <- as.symbol(one)
    sym.two <- as.symbol(two)
    # make list of objects with corresponding names
    my_list <- list(eval(sym.one), eval(sym.two)) 
    list_names <- c(one, two)
    names(my_list) <- list_names
    my_list
  }
my_function('mtcars', 'iris') %>% str()
#> List of 2
#>  $ mtcars:'data.frame':  32 obs. of  11 variables:
#>   ..$ mpg : num [1:32] 21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ...
#>   ..$ cyl : num [1:32] 6 6 4 6 8 6 8 4 4 6 ...
#>   ..$ disp: num [1:32] 160 160 108 258 360 ...
#>   ..$ hp  : num [1:32] 110 110 93 110 175 105 245 62 95 123 ...
#>   ..$ drat: num [1:32] 3.9 3.9 3.85 3.08 3.15 2.76 3.21 3.69 3.92 3.92 ...
#>   ..$ wt  : num [1:32] 2.62 2.88 2.32 3.21 3.44 ...
#>   ..$ qsec: num [1:32] 16.5 17 18.6 19.4 17 ...
#>   ..$ vs  : num [1:32] 0 0 1 1 0 1 0 1 1 1 ...
#>   ..$ am  : num [1:32] 1 1 1 0 0 0 0 0 0 0 ...
#>   ..$ gear: num [1:32] 4 4 4 3 3 3 3 4 4 4 ...
#>   ..$ carb: num [1:32] 4 4 1 1 2 1 4 2 2 4 ...
#>  $ iris  :'data.frame':  150 obs. of  5 variables:
#>   ..$ Sepal.Length: num [1:150] 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>   ..$ Sepal.Width : num [1:150] 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>   ..$ Petal.Length: num [1:150] 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>   ..$ Petal.Width : num [1:150] 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#>   ..$ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...

Created on 2020-03-15 by the reprex package (v0.3.0)

and here's an example with an object not in the parent environment of the function (so a string argument isn't needed):

library(rlang)
my_function2 <- 
  function(one) {
    # convert name-like argument to expression
    one <- enexpr(one)
    # create expression extracting name from ggplot2
    object_call <- expr(`::`(ggplot2, !!one))
    # extract object from ggplot2
    object <- eval(object_call)
    my_list <- list(object)
    # convert name expression to string, pass as name in list
    names(my_list) <- as_string(one)
    my_list
  }
str(my_function2(mpg))
#> List of 1
#>  $ mpg:Classes 'tbl_df', 'tbl' and 'data.frame': 234 obs. of  11 variables:
#>   ..$ manufacturer: chr [1:234] "audi" "audi" "audi" "audi" ...
#>   ..$ model       : chr [1:234] "a4" "a4" "a4" "a4" ...
#>   ..$ displ       : num [1:234] 1.8 1.8 2 2 2.8 2.8 3.1 1.8 1.8 2 ...
#>   ..$ year        : int [1:234] 1999 1999 2008 2008 1999 1999 2008 1999 1999 2008 ...
#>   ..$ cyl         : int [1:234] 4 4 4 4 6 6 6 4 4 4 ...
#>   ..$ trans       : chr [1:234] "auto(l5)" "manual(m5)" "manual(m6)" "auto(av)" ...
#>   ..$ drv         : chr [1:234] "f" "f" "f" "f" ...
#>   ..$ cty         : int [1:234] 18 21 20 21 16 18 18 18 16 20 ...
#>   ..$ hwy         : int [1:234] 29 29 31 30 26 26 27 26 25 28 ...
#>   ..$ fl          : chr [1:234] "p" "p" "p" "p" ...
#>   ..$ class       : chr [1:234] "compact" "compact" "compact" "compact" ...

Created on 2020-03-15 by the reprex package (v0.3.0)

Are these examples in the spirit of what you were asking about?

3 Likes

Oh, this is perfect! Thank you very much.

I was so focused on going from data frames/functions themselves to names that I completely forgot that it could make more sense to go from strings to data frames/functions.

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.