Home > Mobile >  R function print and assign the first element of returned list and then be able to use pipe
R function print and assign the first element of returned list and then be able to use pipe

Time:12-05

Is it possible to make R function satisfy the following:

  1. Return a list and only print the first element. (I know how to do it)

  2. Assign step 1 result to a new object with class of the first element, then I can use pipe function and other dplyr functions directly.

  3. The new object still contain the original list elements.

For example,

my_tbl <- function(x) {
  dat <- tibble(num = x)
  words <- paste("input is", x)
  res <- list(dat = dat, words = words)
  class(res) <- c("myclass", "list")
  return(res)
}

print.myclass <- function(x) {
  print(x[[1]])
  invisible(x)
}

a <- my_tbl(1:2) 

a

# A tibble: 2 × 1
    num
  <int>
1     1
2     2

> a$words
[1] "input is 1" "input is 2"

a$dat %>%
  mutate(b = 3)

# A tibble: 2 × 2
    num     b
  <int> <dbl>
1     1     3
2     2     3

Is it possible to use dplyr functions, eg. mutate, directly without adding any intermediate steps, eg.a$dat, to have to same result? like this:

a %>%
  mutate(b = 3)

# A tibble: 2 × 2
    num     b
  <int> <dbl>
1     1     3
2     2     3

CodePudding user response:

I'm not sure if this is a valid solution for you, but an easy way would be to define a custom mutate method:

mutate.myclass <- function(x, ...) {
  x$dat <- mutate(x$dat, ...)
  x
}

Which allows you to do:

a <- my_tbl(1:2) 

a %>%
  mutate(b = 3)
#> # A tibble: 2 x 2
#>     num     b
#>   <int> <dbl>
#> 1     1     3
#> 2     2     3

While still keeping the whole object a:

str(a)
#> List of 2
#>  $ dat  : tibble [2 × 1] (S3: tbl_df/tbl/data.frame)
#>   ..$ num: int [1:2] 1 2
#>  $ words: chr [1:2] "input is 1" "input is 2"
#>  - attr(*, "class")= chr [1:2] "myclass" "list"

Another solution would be to change the structure of your output object. Just create a tibble and add the words as attribute. When doing operations with {dplyr} this should work. However, there are function which will drop the attributes making this a somewhat dodgy approach (depending on what kind of functions you are using this new object with).

library(dplyr)

my_tbl <- function(x) {
  dat <- tibble(num = x)
  words <- paste("input is", x)
  attr(dat, "words") <- words
  return(dat)
}

a <- my_tbl(1:2) 

a %>%
  mutate(b = 3)
#> # A tibble: 2 x 2
#>     num     b
#>   <int> <dbl>
#> 1     1     3
#> 2     2     3

str(a)
#> tibble [2 × 1] (S3: tbl_df/tbl/data.frame)
#>  $ num: int [1:2] 1 2
#>  - attr(*, "words")= chr [1:2] "input is 1" "input is 2"

Created on 2021-12-04 by the reprex package (v0.3.0)

CodePudding user response:

Use purrr::list_merge. The 2nd argument must have the same name as the list member to merge with.

library(dplyr)
library(purrr)

a %>%
  list_merge(dat = list(b = 3)) %>%
  print()
## A tibble: 2 x 2
#    num     b
#  <int> <dbl>
#1     1     3
#2     2     3

Edit

A mutate method for objects of class "myclass", like in TimTeaFan's answer, would have an extra argument, the list member to be mutated. This preserves the list structure.

mutate.myclass <- function(x, y, ...) {
  nm <- deparse(substitute(y))
  tmp <- x[[nm]] %>% mutate(...)
  x[[nm]] <- tmp
  x
}

a %>%
  mutate(dat, b = 3)
## A tibble: 2 x 2
#    num     b
#  <int> <dbl>
#1     1     3
#2     2     3

a %>%
  mutate(dat, b = 3) %>%
  str()
#List of 2
# $ dat  : tibble [2 × 2] (S3: tbl_df/tbl/data.frame)
#  ..$ num: int [1:2] 1 2
#  ..$ b  : num [1:2] 3 3
# $ words: chr [1:2] "input is 1" "input is 2"
# - attr(*, "class")= chr [1:2] "myclass" "list"

Without the pipe the extra argument is more obvious:

mutate(a, dat, b = 3)
## A tibble: 2 x 2
#    num     b
#  <int> <dbl>
#1     1     3
#2     2     3
  • Related