Odd behavior with walk2 and pipe

library(purrr)
library(dplyr)

df <- data.frame(a = c('a','b','c'), b = c(1,2,3))
df %>% walk2(.$a, .$b , function(a, b) print(c(a, b)) )

gives me an error Error: all(lengths == 1L | lengths == n) is not TRUE

However, the following prints successfully

df <- data.frame(a = c('a','b','c'), b = c(1,2,3))
walk2(df$a, df$b , function(a, b) print(c(a, b)) )

and even if I pass in all the arguments explicitly I get another error

df %>% walk2(.x = .$a, .y = .$b , .f = function(a, b) print(c(a, b)) )

Where the error is

 Error in .f(1L, 1, list(a = 1:3, b = c(1, 2, 3))) : 
  unused argument (list(a = 1:3, b = c(1, 2, 3)))

Can anyone help me make sense of this?

I'm using purrr 0.24 and dplyr 0.7.4

The issue is that when you pipe df into walk2 df becomes the first argument of walk2, which has a length of 2, not the 4 as either of it's columns would. In the end walk2 sees an error because the lengths of the first two arguments are not the same.

Here are some example that do work though they may not be what you are looking for:

suppressPackageStartupMessages(library(purrr))
suppressPackageStartupMessages(library(dplyr))

df <- data.frame(a = c('a','b','c'), b = c(1,2,3))
 walk2(df$a, df$b , function(a, b) print(c(a, b)) )
#> [1] 1 1
#> [1] 2 2
#> [1] 3 3
 # both columns are numeric because column a is a factor
 
 df <- data.frame(a = c('a','b','c'), b = c(1,2,3), stringsAsFactors = FALSE)
 walk2(df$a, df$b , function(a, b) print(c(a, b)) )
#> [1] "a" "1"
#> [1] "b" "2"
#> [1] "c" "3"
# now the a column is a charactor because col a was
# stored in the data.frame as characters

 
# this will work but it probably isn't all
# that useful
df$a %>% walk2(df$b, ~ print(c(.x, .y)))
#> [1] "a" "1"
#> [1] "b" "2"
#> [1] "c" "3"

# this also sort of works but isn't all that 
# useful either, but it gets around the problem of the lengths
# not being equal. c(1,2) has a length of 2 which corresponds to
# the length of df
df %>% walk2( c(1,2), ~ print(c(.x, .y)))
#> [1] "a" "b" "c" "1"
#> [1] 1 2 3 2

Created on 2018-02-23 by the reprex package (v0.2.0).

2 Likes

Thanks for your response Dan. Although, I have a few clarifying questions

Normally when arguments are piped, using .x as a named argument overrides the behavior of df being the first argument. I try that explicitly here

df %>% walk2(.x = .$a, .y = .$b , .f = function(a, b) print(c(a, b)) )

Further, how did you know that walk2 accepts four arguments? According to the official map2 documentation (which should be equivalent to the walk2 documentation),

map2(.x, .y, .f, ...)

Throwing in some braces will change magrittr's behavior.

library(purrr)
library(dplyr)

df <- data.frame(a = c('a','b','c'), b = c(1,2,3))
df %>% { walk2(.$a, .$b , function(a, b) print(c(a, b)) ) }

The idea is that forces magrittr to treat the contents as an expression, and not mess around with any function arguments (other than populating "."). With functions magrittr performs the dot substitution and adds in the contents on the left as a new first argument (hence calling walk with 4 arguments instead of 3).

5 Likes