diff --git a/NEWS.md b/NEWS.md index fd72ef74c..bd8ae786e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -111,6 +111,8 @@ - Method `$over()` gains an argument `mapping_strategy` (#984, #988). - New method `$item()` for `DataFrame` and `Series` (#992). - New active binding `$struct$fields` (#1002). +- New methods `$select_seq()` and `$with_columns_seq()` for `DataFrame` and + `LazyFrame` (#1003). ### Bug fixes diff --git a/R/dataframe__frame.R b/R/dataframe__frame.R index b31ca251b..ebac337f8 100644 --- a/R/dataframe__frame.R +++ b/R/dataframe__frame.R @@ -714,6 +714,26 @@ DataFrame_select = function(...) { unwrap("in $select()") } +#' @inherit DataFrame_select title params return +#' +#' @description +#' Similar to `dplyr::mutate()`. However, it discards unmentioned columns (like +#' `.()` in `data.table`). +#' +#' This will run all expression sequentially instead of in parallel. Use this +#' when the work per expression is cheap. Otherwise, `$select()` should be +#' preferred. +#' +#' @examples +#' pl$DataFrame(iris)$select_seq( +#' pl$col("Sepal.Length")$abs()$alias("abs_SL"), +#' (pl$col("Sepal.Length") + 2)$alias("add_2_SL") +#' ) +DataFrame_select_seq = function(...) { + .pr$DataFrame$select_seq(self, unpack_list(..., .context = "in $select_seq()")) |> + unwrap("in $select_seq()") +} + #' Drop in place #' @name DataFrame_drop_in_place #' @description Drop a single column in-place and return the dropped column. @@ -821,6 +841,39 @@ DataFrame_with_columns = function(...) { unwrap("in $with_columns()") } +#' @inherit DataFrame_with_columns title params return +#' +#' @description +#' Add columns or modify existing ones with expressions. This is +#' the equivalent of `dplyr::mutate()` as it keeps unmentioned columns (unlike +#' `$select()`). +#' +#' This will run all expression sequentially instead of in parallel. Use this +#' when the work per expression is cheap. Otherwise, `$with_columns()` should be +#' preferred. +#' +#' @examples +#' pl$DataFrame(iris)$with_columns_seq( +#' pl$col("Sepal.Length")$abs()$alias("abs_SL"), +#' (pl$col("Sepal.Length") + 2)$alias("add_2_SL") +#' ) +#' +#' # same query +#' l_expr = list( +#' pl$col("Sepal.Length")$abs()$alias("abs_SL"), +#' (pl$col("Sepal.Length") + 2)$alias("add_2_SL") +#' ) +#' pl$DataFrame(iris)$with_columns_seq(l_expr) +#' +#' pl$DataFrame(iris)$with_columns_seq( +#' pl$col("Sepal.Length")$abs(), # not named expr will keep name "Sepal.Length" +#' SW_add_2 = (pl$col("Sepal.Width") + 2) +#' ) +DataFrame_with_columns_seq = function(...) { + .pr$DataFrame$with_columns_seq(self, unpack_list(..., .context = "in $with_columns_seq()")) |> + unwrap("in $with_columns_seq()") +} + #' @inherit LazyFrame_head title details #' @param n Number of rows to return. If a negative value is passed, diff --git a/R/extendr-wrappers.R b/R/extendr-wrappers.R index aa3becf7d..a826b8ed3 100644 --- a/R/extendr-wrappers.R +++ b/R/extendr-wrappers.R @@ -194,8 +194,12 @@ RPolarsDataFrame$drop_in_place <- function(names) .Call(wrap__RPolarsDataFrame__ RPolarsDataFrame$select <- function(exprs) .Call(wrap__RPolarsDataFrame__select, self, exprs) +RPolarsDataFrame$select_seq <- function(exprs) .Call(wrap__RPolarsDataFrame__select_seq, self, exprs) + RPolarsDataFrame$with_columns <- function(exprs) .Call(wrap__RPolarsDataFrame__with_columns, self, exprs) +RPolarsDataFrame$with_columns_seq <- function(exprs) .Call(wrap__RPolarsDataFrame__with_columns_seq, self, exprs) + RPolarsDataFrame$to_struct <- function(name) .Call(wrap__RPolarsDataFrame__to_struct, self, name) RPolarsDataFrame$unnest <- function(names) .Call(wrap__RPolarsDataFrame__unnest, self, names) @@ -1144,11 +1148,13 @@ RPolarsLazyFrame$slice <- function(offset, length) .Call(wrap__RPolarsLazyFrame_ RPolarsLazyFrame$with_columns <- function(exprs) .Call(wrap__RPolarsLazyFrame__with_columns, self, exprs) +RPolarsLazyFrame$with_columns_seq <- function(exprs) .Call(wrap__RPolarsLazyFrame__with_columns_seq, self, exprs) + RPolarsLazyFrame$unnest <- function(names) .Call(wrap__RPolarsLazyFrame__unnest, self, names) RPolarsLazyFrame$select <- function(exprs) .Call(wrap__RPolarsLazyFrame__select, self, exprs) -RPolarsLazyFrame$select_str_as_lit <- function(exprs) .Call(wrap__RPolarsLazyFrame__select_str_as_lit, self, exprs) +RPolarsLazyFrame$select_seq <- function(exprs) .Call(wrap__RPolarsLazyFrame__select_seq, self, exprs) RPolarsLazyFrame$tail <- function(n) .Call(wrap__RPolarsLazyFrame__tail, self, n) diff --git a/R/lazyframe__lazy.R b/R/lazyframe__lazy.R index 8ff424397..50abdeb2a 100644 --- a/R/lazyframe__lazy.R +++ b/R/lazyframe__lazy.R @@ -272,9 +272,23 @@ LazyFrame_select = function(...) { unwrap("in $select()") } -#' @title Select and modify columns of a LazyFrame +#' @inherit LazyFrame_select title +#' @inherit DataFrame_select_seq description params +#' @return A LazyFrame +#' @examples +#' pl$LazyFrame(iris)$select_seq( +#' pl$col("Sepal.Length")$abs()$alias("abs_SL"), +#' (pl$col("Sepal.Length") + 2)$alias("add_2_SL") +#' ) +LazyFrame_select_seq = function(...) { + .pr$LazyFrame$select_seq(self, unpack_list(..., .context = "in $select_seq()")) |> + unwrap("in $select_seq()") +} + +#' Select and modify columns of a LazyFrame +#' #' @inherit DataFrame_with_columns description params -#' @keywords LazyFrame +#' #' @return A LazyFrame #' @examples #' pl$LazyFrame(iris)$with_columns( @@ -298,6 +312,32 @@ LazyFrame_with_columns = function(...) { unwrap("in $with_columns()") } +#' @inherit LazyFrame_with_columns title +#' @inherit DataFrame_with_columns_seq description params +#' +#' @return A LazyFrame +#' @examples +#' pl$LazyFrame(iris)$with_columns_seq( +#' pl$col("Sepal.Length")$abs()$alias("abs_SL"), +#' (pl$col("Sepal.Length") + 2)$alias("add_2_SL") +#' ) +#' +#' # same query +#' l_expr = list( +#' pl$col("Sepal.Length")$abs()$alias("abs_SL"), +#' (pl$col("Sepal.Length") + 2)$alias("add_2_SL") +#' ) +#' pl$LazyFrame(iris)$with_columns_seq(l_expr) +#' +#' pl$LazyFrame(iris)$with_columns_seq( +#' pl$col("Sepal.Length")$abs(), # not named expr will keep name "Sepal.Length" +#' SW_add_2 = (pl$col("Sepal.Width") + 2) +#' ) +LazyFrame_with_columns_seq = function(...) { + .pr$LazyFrame$with_columns_seq(self, unpack_list(..., .context = "in $with_columns_seq()")) |> + unwrap("in $with_columns_seq()") +} + #' @inherit DataFrame_with_row_index title description params #' @return A new LazyFrame with a counter column in front diff --git a/man/DataFrame_select_seq.Rd b/man/DataFrame_select_seq.Rd new file mode 100644 index 000000000..d37b75c9c --- /dev/null +++ b/man/DataFrame_select_seq.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/dataframe__frame.R +\name{DataFrame_select_seq} +\alias{DataFrame_select_seq} +\title{Select and modify columns of a DataFrame} +\usage{ +DataFrame_select_seq(...) +} +\arguments{ +\item{...}{Columns to keep. Those can be expressions (e.g \code{pl$col("a")}), +column names (e.g \code{"a"}), or list containing expressions or column names +(e.g \code{list(pl$col("a"))}).} +} +\value{ +DataFrame +} +\description{ +Similar to \code{dplyr::mutate()}. However, it discards unmentioned columns (like +\code{.()} in \code{data.table}). + +This will run all expression sequentially instead of in parallel. Use this +when the work per expression is cheap. Otherwise, \verb{$select()} should be +preferred. +} +\examples{ +pl$DataFrame(iris)$select_seq( + pl$col("Sepal.Length")$abs()$alias("abs_SL"), + (pl$col("Sepal.Length") + 2)$alias("add_2_SL") +) +} diff --git a/man/DataFrame_with_columns_seq.Rd b/man/DataFrame_with_columns_seq.Rd new file mode 100644 index 000000000..2cce8b035 --- /dev/null +++ b/man/DataFrame_with_columns_seq.Rd @@ -0,0 +1,42 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/dataframe__frame.R +\name{DataFrame_with_columns_seq} +\alias{DataFrame_with_columns_seq} +\title{Modify/append column(s)} +\usage{ +DataFrame_with_columns_seq(...) +} +\arguments{ +\item{...}{Any expressions or string column name, or same wrapped in a list. +If first and only element is a list, it is unwrapped as a list of args.} +} +\value{ +A DataFrame +} +\description{ +Add columns or modify existing ones with expressions. This is +the equivalent of \code{dplyr::mutate()} as it keeps unmentioned columns (unlike +\verb{$select()}). + +This will run all expression sequentially instead of in parallel. Use this +when the work per expression is cheap. Otherwise, \verb{$with_columns()} should be +preferred. +} +\examples{ +pl$DataFrame(iris)$with_columns_seq( + pl$col("Sepal.Length")$abs()$alias("abs_SL"), + (pl$col("Sepal.Length") + 2)$alias("add_2_SL") +) + +# same query +l_expr = list( + pl$col("Sepal.Length")$abs()$alias("abs_SL"), + (pl$col("Sepal.Length") + 2)$alias("add_2_SL") +) +pl$DataFrame(iris)$with_columns_seq(l_expr) + +pl$DataFrame(iris)$with_columns_seq( + pl$col("Sepal.Length")$abs(), # not named expr will keep name "Sepal.Length" + SW_add_2 = (pl$col("Sepal.Width") + 2) +) +} diff --git a/man/LazyFrame_select_seq.Rd b/man/LazyFrame_select_seq.Rd new file mode 100644 index 000000000..ff07e0a82 --- /dev/null +++ b/man/LazyFrame_select_seq.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/lazyframe__lazy.R +\name{LazyFrame_select_seq} +\alias{LazyFrame_select_seq} +\title{Select and modify columns of a LazyFrame} +\usage{ +LazyFrame_select_seq(...) +} +\arguments{ +\item{...}{Columns to keep. Those can be expressions (e.g \code{pl$col("a")}), +column names (e.g \code{"a"}), or list containing expressions or column names +(e.g \code{list(pl$col("a"))}).} +} +\value{ +A LazyFrame +} +\description{ +Similar to \code{dplyr::mutate()}. However, it discards unmentioned columns (like +\code{.()} in \code{data.table}). + +This will run all expression sequentially instead of in parallel. Use this +when the work per expression is cheap. Otherwise, \verb{$select()} should be +preferred. +} +\examples{ +pl$LazyFrame(iris)$select_seq( + pl$col("Sepal.Length")$abs()$alias("abs_SL"), + (pl$col("Sepal.Length") + 2)$alias("add_2_SL") +) +} diff --git a/man/LazyFrame_with_columns.Rd b/man/LazyFrame_with_columns.Rd index 791a3aabd..4b655241e 100644 --- a/man/LazyFrame_with_columns.Rd +++ b/man/LazyFrame_with_columns.Rd @@ -36,4 +36,3 @@ pl$LazyFrame(iris)$with_columns( SW_add_2 = (pl$col("Sepal.Width") + 2) ) } -\keyword{LazyFrame} diff --git a/man/LazyFrame_with_columns_seq.Rd b/man/LazyFrame_with_columns_seq.Rd new file mode 100644 index 000000000..c0314a3de --- /dev/null +++ b/man/LazyFrame_with_columns_seq.Rd @@ -0,0 +1,42 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/lazyframe__lazy.R +\name{LazyFrame_with_columns_seq} +\alias{LazyFrame_with_columns_seq} +\title{Select and modify columns of a LazyFrame} +\usage{ +LazyFrame_with_columns_seq(...) +} +\arguments{ +\item{...}{Any expressions or string column name, or same wrapped in a list. +If first and only element is a list, it is unwrapped as a list of args.} +} +\value{ +A LazyFrame +} +\description{ +Add columns or modify existing ones with expressions. This is +the equivalent of \code{dplyr::mutate()} as it keeps unmentioned columns (unlike +\verb{$select()}). + +This will run all expression sequentially instead of in parallel. Use this +when the work per expression is cheap. Otherwise, \verb{$with_columns()} should be +preferred. +} +\examples{ +pl$LazyFrame(iris)$with_columns_seq( + pl$col("Sepal.Length")$abs()$alias("abs_SL"), + (pl$col("Sepal.Length") + 2)$alias("add_2_SL") +) + +# same query +l_expr = list( + pl$col("Sepal.Length")$abs()$alias("abs_SL"), + (pl$col("Sepal.Length") + 2)$alias("add_2_SL") +) +pl$LazyFrame(iris)$with_columns_seq(l_expr) + +pl$LazyFrame(iris)$with_columns_seq( + pl$col("Sepal.Length")$abs(), # not named expr will keep name "Sepal.Length" + SW_add_2 = (pl$col("Sepal.Width") + 2) +) +} diff --git a/src/rust/src/lazy/dataframe.rs b/src/rust/src/lazy/dataframe.rs index 3f82785a1..e9fbeb490 100644 --- a/src/rust/src/lazy/dataframe.rs +++ b/src/rust/src/lazy/dataframe.rs @@ -8,7 +8,7 @@ use crate::lazy::dsl::*; use crate::rdataframe::RPolarsDataFrame as RDF; use crate::rdatatype::{new_ipc_compression, new_parquet_compression, RPolarsDataType}; use crate::robj_to; -use crate::rpolarserr::{polars_to_rpolars_err, RPolarsErr, RResult, WithRctx}; +use crate::rpolarserr::{polars_to_rpolars_err, RPolarsErr, RResult}; use crate::utils::{r_result_list, try_f64_into_usize}; use extendr_api::prelude::*; use pl::{AsOfOptions, Duration, RollingGroupOptions}; @@ -291,24 +291,27 @@ impl RPolarsLazyFrame { } pub fn with_columns(&self, exprs: Robj) -> RResult { - let exprs = - robj_to!(VecPLExprColNamed, exprs).when("preparing expressions for $with_columns()")?; + let exprs = robj_to!(VecPLExprColNamed, exprs)?; Ok(RPolarsLazyFrame(self.clone().0.with_columns(exprs))) } + pub fn with_columns_seq(&self, exprs: Robj) -> RResult { + let exprs = robj_to!(VecPLExprColNamed, exprs)?; + Ok(RPolarsLazyFrame(self.clone().0.with_columns_seq(exprs))) + } + pub fn unnest(&self, names: Vec) -> RResult { Ok(RPolarsLazyFrame(self.clone().0.unnest(names))) } pub fn select(&self, exprs: Robj) -> RResult { - let exprs = - robj_to!(VecPLExprColNamed, exprs).when("preparing expressions for $select()")?; + let exprs = robj_to!(VecPLExprColNamed, exprs)?; Ok(RPolarsLazyFrame(self.clone().0.select(exprs))) } - pub fn select_str_as_lit(&self, exprs: Robj) -> RResult { - let exprs = robj_to!(VecPLExprNamed, exprs).when("preparing columns for DataFrame")?; - Ok(RPolarsLazyFrame(self.clone().0.select(exprs))) + pub fn select_seq(&self, exprs: Robj) -> RResult { + let exprs = robj_to!(VecPLExprColNamed, exprs)?; + Ok(RPolarsLazyFrame(self.clone().0.select_seq(exprs))) } fn tail(&self, n: Robj) -> Result { diff --git a/src/rust/src/rdataframe/mod.rs b/src/rust/src/rdataframe/mod.rs index 921f0bbf0..333e61de3 100644 --- a/src/rust/src/rdataframe/mod.rs +++ b/src/rust/src/rdataframe/mod.rs @@ -308,10 +308,18 @@ impl RPolarsDataFrame { self.lazy().select(exprs)?.collect() } + pub fn select_seq(&self, exprs: Robj) -> RResult { + self.lazy().select_seq(exprs)?.collect() + } + pub fn with_columns(&self, exprs: Robj) -> RResult { self.lazy().with_columns(exprs)?.collect() } + pub fn with_columns_seq(&self, exprs: Robj) -> RResult { + self.lazy().with_columns_seq(exprs)?.collect() + } + pub fn to_struct(&self, name: Robj) -> RResult { use pl::IntoSeries; let name = robj_to!(Option, str, name)?.unwrap_or(""); diff --git a/tests/testthat/_snaps/after-wrappers.md b/tests/testthat/_snaps/after-wrappers.md index 28a51d60f..419f35015 100644 --- a/tests/testthat/_snaps/after-wrappers.md +++ b/tests/testthat/_snaps/after-wrappers.md @@ -86,13 +86,13 @@ [33] "melt" "min" "n_chunks" "null_count" [37] "partition_by" "pivot" "print" "quantile" [41] "rechunk" "rename" "reverse" "rolling" - [45] "sample" "schema" "select" "shape" - [49] "shift" "shift_and_fill" "slice" "sort" - [53] "std" "sum" "tail" "to_data_frame" - [57] "to_list" "to_series" "to_struct" "transpose" - [61] "unique" "unnest" "var" "width" - [65] "with_columns" "with_row_index" "write_csv" "write_json" - [69] "write_ndjson" "write_parquet" + [45] "sample" "schema" "select" "select_seq" + [49] "shape" "shift" "shift_and_fill" "slice" + [53] "sort" "std" "sum" "tail" + [57] "to_data_frame" "to_list" "to_series" "to_struct" + [61] "transpose" "unique" "unnest" "var" + [65] "width" "with_columns" "with_columns_seq" "with_row_index" + [69] "write_csv" "write_json" "write_ndjson" "write_parquet" --- @@ -112,15 +112,16 @@ [21] "print" "rechunk" [23] "sample_frac" "sample_n" [25] "schema" "select" - [27] "select_at_idx" "set_column_from_robj" - [29] "set_column_from_series" "set_column_names_mut" - [31] "shape" "to_list" - [33] "to_list_tag_structs" "to_list_unwind" - [35] "to_struct" "transpose" - [37] "unnest" "with_columns" - [39] "with_row_index" "write_csv" - [41] "write_json" "write_ndjson" - [43] "write_parquet" + [27] "select_at_idx" "select_seq" + [29] "set_column_from_robj" "set_column_from_series" + [31] "set_column_names_mut" "shape" + [33] "to_list" "to_list_tag_structs" + [35] "to_list_unwind" "to_struct" + [37] "transpose" "unnest" + [39] "with_columns" "with_columns_seq" + [41] "with_row_index" "write_csv" + [43] "write_json" "write_ndjson" + [45] "write_parquet" # public and private methods of each class GroupBy @@ -155,49 +156,17 @@ [31] "quantile" "rename" [33] "reverse" "rolling" [35] "schema" "select" - [37] "set_optimization_toggle" "shift" - [39] "shift_and_fill" "sink_csv" - [41] "sink_ipc" "sink_ndjson" - [43] "sink_parquet" "slice" - [45] "sort" "std" - [47] "sum" "tail" - [49] "to_dot" "unique" - [51] "unnest" "var" - [53] "width" "with_columns" - [55] "with_context" "with_row_index" - ---- - - Code - ls(.pr[[private_key]]) - Output - [1] "clone_in_rust" "collect" - [3] "collect_in_background" "debug_plan" - [5] "describe_optimized_plan" "describe_plan" - [7] "drop" "drop_nulls" - [9] "explode" "fetch" - [11] "fill_nan" "fill_null" - [13] "filter" "first" - [15] "get_optimization_toggle" "group_by" - [17] "group_by_dynamic" "join" - [19] "join_asof" "last" - [21] "max" "mean" - [23] "median" "melt" - [25] "min" "print" - [27] "profile" "quantile" - [29] "rename" "reverse" - [31] "rolling" "schema" - [33] "select" "select_str_as_lit" - [35] "set_optimization_toggle" "shift" - [37] "shift_and_fill" "sink_csv" - [39] "sink_ipc" "sink_json" - [41] "sink_parquet" "slice" - [43] "sort_by_exprs" "std" - [45] "sum" "tail" - [47] "to_dot" "unique" - [49] "unnest" "var" - [51] "with_columns" "with_context" - [53] "with_row_index" + [37] "select_seq" "set_optimization_toggle" + [39] "shift" "shift_and_fill" + [41] "sink_csv" "sink_ipc" + [43] "sink_ndjson" "sink_parquet" + [45] "slice" "sort" + [47] "std" "sum" + [49] "tail" "to_dot" + [51] "unique" "unnest" + [53] "var" "width" + [55] "with_columns" "with_columns_seq" + [57] "with_context" "with_row_index" # public and private methods of each class Expr diff --git a/tests/testthat/test-dataframe.R b/tests/testthat/test-dataframe.R index fcf31e1b0..b13037974 100644 --- a/tests/testthat/test-dataframe.R +++ b/tests/testthat/test-dataframe.R @@ -239,6 +239,27 @@ patrick::with_parameters_test_that("select with list of exprs", { .test_name = type ) +patrick::with_parameters_test_that("select_seq with list of exprs", { + expect_equal( + pl$DataFrame(mtcars)$select_seq(expr)$columns, + c("mpg", "hp") + ) +}, +expr = list( + list(pl$col("mpg"), pl$col("hp")), + list(pl$col("mpg", "hp")), + list(c("mpg", "hp")), + list("mpg", "hp") +), +type = c( + "list of exprs", + "expr", + "character", + "list of character" +), +.test_name = type +) + test_that("select: create a list variable", { test = pl$DataFrame(x = 1:2) @@ -482,6 +503,22 @@ test_that("with_columns lazy/eager", { ) }) +test_that("with_columns_seq", { + test = pl$DataFrame(x = 1:2) + + # create one column + expect_identical( + test$with_columns_seq(y = list(1:2, 3:4))$to_list(), + list(x = 1:2, y = list(1:2, 3:4)) + ) + + # create several column + expect_identical( + test$with_columns_seq(y = list(1:2, 3:4), z = list(c("a", "b"), c("c", "d")))$to_list(), + list(x = 1:2, y = list(1:2, 3:4), z = list(c("a", "b"), c("c", "d"))) + ) +}) + test_that("head lazy/eager", { l = list( diff --git a/tests/testthat/test-lazy.R b/tests/testthat/test-lazy.R index 754a831ce..c026eae9f 100644 --- a/tests/testthat/test-lazy.R +++ b/tests/testthat/test-lazy.R @@ -1039,3 +1039,45 @@ test_that("rolling for LazyFrame: can be ungrouped", { to_data_frame() expect_equal(actual, df$collect()$to_data_frame()) }) + + +patrick::with_parameters_test_that("select_seq with list of exprs", + { + expect_equal( + pl$LazyFrame(mtcars)$select_seq(expr)$collect()$columns, + c("mpg", "hp") + ) + }, + expr = list( + list(pl$col("mpg"), pl$col("hp")), + list(pl$col("mpg", "hp")), + list(c("mpg", "hp")), + list("mpg", "hp") + ), + type = c( + "list of exprs", + "expr", + "character", + "list of character" + ), + .test_name = type +) + +test_that("with_columns_seq", { + test = pl$LazyFrame(x = 1:2) + + # create one column + expect_identical( + test$with_columns_seq(y = list(1:2, 3:4))$collect()$to_list(), + list(x = 1:2, y = list(1:2, 3:4)) + ) + + # create several column + expect_identical( + test$ + with_columns_seq(y = list(1:2, 3:4), z = list(c("a", "b"), c("c", "d")))$ + collect()$ + to_list(), + list(x = 1:2, y = list(1:2, 3:4), z = list(c("a", "b"), c("c", "d"))) + ) +})