Mark Miller
Mark Miller

Reputation: 13113

Convert object name to string in function

I have a list of data.frames. I want to send each data.frame to a function using lapply. Inside the function I want to check whether the name of a data.frame includes a particular string. If the string in question is present I want to perform one series of operations. Otherwise I want to perform a different series of operations. I cannot figure out how to check whether the string in question is present from within the function.

I wish to use base R. This seems to be a possible solution but I cannot get it to work:

In R, how to get an object's name after it is sent to a function?

Here is an example list followed by an example function further below.

matrix.apple1 <- read.table(text = '
     X3   X4   X5
      1    1    1
      1    1    1
', header = TRUE)
matrix.apple2 <- read.table(text = '
     X3   X4   X5
      1    1    1
      2    2    2
', header = TRUE)
matrix.orange1 <- read.table(text = '
     X3   X4   X5
     10   10   10
     20   20   20
', header = TRUE)
my.list <- list(matrix.apple1  = matrix.apple1,
                matrix.orange1 = matrix.orange1,
                matrix.apple2  = matrix.apple2)

This operation can check whether each object name contains the string apples but I am not sure how to use this information inside the function further below.

grepl('apple', names(my.list), fixed = TRUE)
#[1]  TRUE FALSE  TRUE

Here is an example function. Based on hours of searching and trial-and-error I perhaps am supposed to use deparse(substitute(x)) but so far it only returns x or something similar.

table.function <- function(x) {

     # The three object names are:
     # 'matrix.apple1', 'matrix.orange1' and 'matrix.apple2'
     myObjectName <- deparse(substitute(x))
     print(myObjectName)

     # perform a trivial example operation on a data.frame
     my.table <- table(as.matrix(x))

     # Test whether an object name contains the string 'apple'
     contains.apple <- grep('apple', myObjectName, fixed = TRUE)

     # Use the result of the above test to perform a trivial example operation.
     # With my code 'my.binomial' is always given the value of 0 even though
     # 'apple' appears in the name of two of the data.frames.
     my.binomial <- ifelse(contains.apple == 1, 1, 0)

     return(list(my.table = my.table, my.binomial = my.binomial))
        
}

table.function.output <- lapply(my.list, function(x) table.function(x))

These are the results of print(myObjectName):

#[1] "x"
#[1] "x"
#[1] "x"

table.function.output

Here are the rest of the results of table.function showing that my.binomial is always 0. The first and third value of my.binomial should be 1 because the names of the first and third data.frames contain the string apple.

# $matrix.apple1
# $matrix.apple1$my.table
# 1 
# 6 
# $matrix.apple1$my.binomial
# logical(0)
# 
# $matrix.orange1
# $matrix.orange1$my.table
# 10 20 
#  3  3 
# $matrix.orange1$my.binomial
# logical(0)
# 
# $matrix.apple2
# $matrix.apple2$my.table
# 1 2 
# 3 3 
# $matrix.apple2$my.binomial
# logical(0)

Upvotes: 1

Views: 78

Answers (2)

Ronak Shah
Ronak Shah

Reputation: 388982

Use Map and pass both list data and it's name to the function. Change your function to accept two arguments.

table.function <- function(data, name) {
  
  # The three object names are:
  # 'matrix.apple1', 'matrix.orange1' and 'matrix.apple2'
  print(name)
  
  # perform a trivial example operation on a data.frame
  my.table <- table(as.matrix(data))
  
  # Test whether an object name contains the string 'apple'
  contains.apple <- grep('apple', name, fixed = TRUE)
  
  # Use the result of the above test to perform a trivial example operation.
  # With my code 'my.binomial' is always given the value of 0 even though
  # 'apple' appears in the name of two of the data.frames.
  my.binomial <- as.integer(contains.apple == 1)
  
  return(list(my.table = my.table, my.binomial = my.binomial))
}

Map(table.function, my.list, names(my.list))

#[1] "matrix.apple1"
#[1] "matrix.orange1"
#[1] "matrix.apple2"
#$matrix.apple1
#$matrix.apple1$my.table

#1 
#6 

#$matrix.apple1$my.binomial
#[1] 1


#$matrix.orange1
#$matrix.orange1$my.table

#10 20 
# 3  3 

#$matrix.orange1$my.binomial
#integer(0)
#...
#...

The same functionality is provided by imap in purrr where you don't need to explicitly pass the names.

purrr::imap(my.list, table.function)

Upvotes: 1

Martin Gal
Martin Gal

Reputation: 16978

You could redesign your function to use the list names instead:

table_function <- function(myObjectName) {
  # The three object names are:
  # 'matrix.apple1', 'matrix.orange1' and 'matrix.apple2'
  myObject <- get(myObjectName)
  
  print(myObjectName)
  
  # perform a trivial example operation on a data.frame
  my.table <- table(as.matrix(myObject))
  
  # Test whether an object name contains the string 'apple'
  contains.apple <- grep('apple', myObjectName, fixed = TRUE)
  
  # Use the result of the above test to perform a trivial example operation.
  # With my code 'my.binomial' is always given the value of 0 even though
  # 'apple' appears in the name of two of the data.frames.
  my.binomial <- +(contains.apple == 1)
  
  return(list(my.table = my.table, my.binomial = my.binomial))
  
}


lapply(names(my.list), table_function)

This returns

[[1]]
[[1]]$my.table

1 
6 

[[1]]$my.binomial
[1] 1


[[2]]
[[2]]$my.table

10 20 
 3  3 

[[2]]$my.binomial
integer(0)


[[3]]
[[3]]$my.table

1 2 
3 3 

[[3]]$my.binomial
[1] 1

If you want to keep the list names, you could use

sapply(names(my.list), table_function, simplify = FALSE, USE.NAMES = TRUE)

instead of lapply.

Upvotes: 2

Related Questions