Is it possible to make R function satisfy the following:
Return a list and only print the first element. (I know how to do it)
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.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