Uli Keller
Uli Keller

Reputation: 41

Run an expression rowwise for its side-effect only

I often want to run an expression or call a function on each row of a tibble purely for the side-effect, without any interest in the return value. For example, I might have a tibble with a list column of data that I want to save using file names from a character column. There are of course a myriad ways to do this, but I would prefer to do it as elegantly as the other rowwise operations in dplyr (1.0). Essentially, I'm looking for an amalgam of rowwise mutate() and purrr::walk(). Here's an ugly approximation to what I want to do:

library(tidyverse)
dat <- 
  tibble(file = c("iris.csv", "mtcars.csv"),
         data = list(iris, mtcars))
dat %>% rowwise() %>% mutate(x = list(write_csv(data, file))) %>% invisible()

Is there a way I can do away with the x = list(…) stuff and explicit hiding of the return value while maintaining easy access to the "data-variables" in the function call (without ugly stuff like .x$data[[1]])? Suppose there was such a function (walk_rows()?) I would expect to use it something like this:

dat %>% walk_rows(write_csv(data, file)))

I know I can do this:

dat %>% pwalk(function(dat, file) write_csv(dat, file))

But having to write the names of the data-variables twice is inelegant.

Upvotes: 4

Views: 323

Answers (2)

Ken Arnold
Ken Arnold

Reputation: 2013

group_walk seems to be the current dplyr way to do this. In the example from the question:

dat %>%
  rowwise() %>%
  group_walk(~ write_csv(.$data[[1]], .$file)))

or, using an explicit function instead of the purrr-style formula:

dat %>%
  rowwise() %>%
  group_walk(function(row, key) { write_csv(row$data[[1]], row$file) })

or, if you don't like the $s,

dat %>%
  rowwise() %>%
  group_walk(function(row, key) {
    with(row, {
      write_csv(data[[1]], file)
    })
  })

Notes:

  • I'm not sure why we need the [[1]]; without it, data is a one-element list.
  • This feels way more verbose than the OP's mutate.
  • group_walk is lifecycle:experimental.
  • Thanks to this answer which outlined this approach.

Upvotes: 2

Allan Cameron
Allan Cameron

Reputation: 173803

I'm not clear if you're looking for something that's already within the tidyverse that allows what you want, or whether you're looking for an implementation of walk_rows. I'm not aware of anything in the tidyverse that does exactly what you want, but here's an implementation of walk_rows:

walk_rows <- function(dat, expr)
{
  `%>%` <- dplyr::`%>%`
  m <- as.list(match.call())[-(1:2)]
  dummy <- dat %>% 
    dplyr::rowwise() %>% 
    dplyr::summarize(x = list(eval(m$expr)), .groups = "drop")
}

So you can do:

dat %>% walk_rows(write.csv(data, file))

Which quietly writes your files. Or, for example:

dat %>% walk_rows(print(paste0(file, ": ", nrow(data))))
#> [1] "iris.csv: 150"
#> [1] "mtcars.csv: 32"

Upvotes: 2

Related Questions