2

I'm getting an unexpected result when piping a matrix into as.data.frame and could use some wisdom on why it's happening. When I run the below code:

library(magrittr)
matrix(c(1,0,0,1), nrow = 2)*5 %>% as.data.frame()

I'm expecting to get

  V1 V2
1  5  0
2  0  5

but instead I get

  .
1 5

This is confusing because it works fine when I wrap the as.data.frame call around the matrix construction:

> as.data.frame(matrix(c(1,0,0,1), nrow = 2)*5)
  V1 V2
1  5  0
2  0  5

and when I pipe the multiplication function as well:

> matrix(c(1,0,0,1), nrow = 2) %>% `*`(5) %>% as.data.frame()
  V1 V2
1  5  0
2  0  5

and when I save the intermediate result and pipe that into the next function:

> v <- matrix(c(1,0,0,1), nrow = 2)*5
> v %>% as.data.frame()
  V1 V2
1  5  0
2  0  5

This made me pretty sure it's due to the arithmetic operation being performed before sending it into the pipe, which we can confirm with other arithmetic and other functions to ensure it's not particular to the * operator or the as.data.frame function:

# Plus instead of times
> matrix(c(1,0,0,1), nrow = 2)+1 %>% as.data.frame()
  .
1 2

# rowSums instead of as.data.frame()
> matrix(c(1,0,0,1), nrow = 2)*5 %>% rowSums()
Error in rowSums(.) : 'x' must be an array of at least two dimensions

I'm clearly not understanding how pipes work with arithmetic operators or what the arithmetic functions return(?) so I could use some advice for what's happening here. Is this a known quirk of which I haven't been able to find documentation? Hadley's book doesn't mention this being an issue in the chapter on pipes and the Technical Notes section of help("%>%") seems to only caution against functions that capture their calling environment. The final note there mentions arithmetic operators but makes it seem like it's only operator precedence that's affected when the results are different from switching the as.data.frame call and the multiplication step:

matrix(c(1,0,0,1), nrow = 2) %>% as.data.frame() %>% `*`(5)
  V1 V2
1  5  0
2  0  5

Why do I get different results when piping a arithmetically-modified matrix into another function?

Dubukay
  • 1,764
  • 1
  • 8
  • 13
  • 1
    You may need to wrap `(matrix(c(1,0,0,1), nrow = 2)*5 ) %>% as.data.frame` – akrun May 23 '22 at 18:08
  • @akrun Thanks for the tip! That's an elegant workaround - but why the heck is it happening in the first place? It's making me nervous about using pipes since I don't understand what's happening here or what it'll be limited to. – Dubukay May 23 '22 at 18:10
  • 1
    It can be a precedence effect of operator – akrun May 23 '22 at 18:11
  • I wondered about that too - but switching the order of the functions still gives the expected output, and I can't figure out at all what functions are being applied to get the outputs I'm seeing. No combination of multiplication and as.data.frame seems to return the single-row single-column "5" with a name of ".". – Dubukay May 23 '22 at 18:15
  • 1
    In the help page of `%>%` - `Another note is that special attention is advised when using non-magrittr operators in a pipe-chain (⁠+, -, $,⁠ etc.), as operator precedence will impact how the chain is evaluate. In general it is advised to use the aliases provided by magrittr.` – akrun May 23 '22 at 18:17
  • Also, `if more than a single expression is needed one encloses the body in a pair of braces, { rhs }. However, note that within braces there are no "first-argument rule": it will be exactly like writing a unary function where the argument name is "."` – akrun May 23 '22 at 18:18
  • I think what is going on is with the evaluation is based on the documentation`Often you will want lhs to the rhs call at another position than the first. For this purpose you can use the dot (.) as placeholder.`. Now, if you consider the `y` as `5` `nm1 <- setNames(5, '.');as.data.frame(nm1)# nm1 . 5`. where as the in the pipe, the first expression is the `matrix` call which would be the dot placeholder – akrun May 23 '22 at 18:37

1 Answers1

3

As specified by the ?Syntax help page, the %>% operator has a higher precedence than the * operator. This means that

matrix(c(1,0,0,1), nrow = 2) * 5 %>% as.data.frame()

is the same as

matrix(c(1,0,0,1), nrow = 2) * (5 %>% as.data.frame())

So the 5 is passed to as.data.frame before the multiplication with the matrix happens. When combining operators of different precedence, you can be more explicity with () or {} to make sure the order happens in which you expect. These both will work

(matrix(c(1,0,0,1), nrow = 2) * 5) %>% as.data.frame()
{matrix(c(1,0,0,1), nrow = 2) * 5} %>% as.data.frame()
MrFlick
  • 195,160
  • 17
  • 277
  • 295