moodymudskipper / nakedpipe

Pipe Into a Sequence of Calls Without Repeating the Pipe Symbol.
69 stars 7 forks source link

special syntax for `with()` or `%$%` ? to access directly elements of a list or data frame ? #8

Closed sfd99 closed 4 years ago

sfd99 commented 4 years ago

Hi Antoine,

Your nakedpipe pkg. is really GREAT!. Love it.

Question:

When I try:

cars %.% { head(2)
mean(dist) }

I get:

[1] NA Warning message: In mean.default(., dist) : argument is not numeric or logical: returning NA

What am I missing?. I really, really want to use the %.% pipe!. :-)

Merci / Thanks! sfd99 San Francisco Rstudio/Ubuntu Linux

bransonf commented 4 years ago

@sfd99 I can answer this one.

The pipe currently interprets dist as a separate argument to mean, and the object does not exist. What you want to do is subset the object to get only that column. Mean accepts only a numeric vector as an argument, so you must coerce the data to match.

Here are two solutions, both being sort of messy:

# Option 1
cars %.% {
  head(2)
  subset(select = 'dist')
  unlist()
  mean()
}

# Option 2
cars %.% {
  head(2)
  `[[`("dist")
  mean()
}
sfd99 commented 4 years ago

Thanks Branson for the quick reply.

Yes, you explain the problem very clearly.

As I understand it, (in my limited knowledge), some R BASE functions like mean(), plot(), cor(), only accept a vector of values as input. Fine.

But any %>% pipe, before mean() will "feed" mean() with a dataframe structure. mean() will gasp!.

So, this will fail:

cars %>%
    head(2) %>%  
    mean(dist)
[1] NA

Warning message: In mean.default(., dist) : argument is not numeric or logical: returning NA

But...this will work: YAY!

cars %>%
    head(2) %$%  
    mean(dist)
[1] 6 

Seems the special %$% pipe, (part of the standard pipes family, originally f/ magrittr pkg), “explodes out" all the variables in a data frame so that you can refer to them explicitly.

So, the %$% pipe is Super-useful when working with many base R functions! ...like mean(), plot(), cor(), etc... which need a vector of values not a DF, to work properly.

Hope Antoine can add this %$% functionality to his great %.% pipe in the nakedpipe pkg!. This way, we'll be able to use the %.% pipe w/ any base-R functions, too!.

That would be fantastic!!!...

moodymudskipper commented 4 years ago

I'm excited to have issues to solve here :), and just as much to have other people interacting, thank you both so much.

@bransonf is right, I'd personally probably use his 2nd solution, or use dplyr::pull() instead of [[ (or purrr::pluck() with lists), or use with() as shown below. Note that you can write .[["dist"]] rather than `[[`("dist")

I'll add a nuance, @bransonf said dist didn't exist but it does, it's a function from the package stats, the error would be different if it didn't.

mean(dist) is changed internally to mean(., dist), that's why you see the dot in the error message, the dot is the result of the previous call, but dist is fed to the second argument of mean.default, which is trim, trim receives a function where it expected a numeric, hence the error.

magrittr indeed has %$% to solve the problem, but %$% is really just with() in infix form :

library(magrittr)
cars %>% head(2) %$% mean(dist)
#> [1] 6
cars %>% head(2) %>% with(mean(dist))
#> [1] 6

# let's make our own
`%$$%` <- with
cars %>% head(2) %$$% mean(dist)
#> [1] 6

Using nakedpipe we can use with() in the same way :

library(nakedpipe)
cars %.% {
  head(2)
  with(mean(dist))
}
#> [1] 6

Or use {} as in magrittr to force dots to be explicit (not insert them in first position by default)

cars %.% {
  head(2)
  {mean(.$dist)}
}
#> [1] 6

Or use %..% so all dots need to be explicit, then no need for {} but we need to use the dot in the head() call too.

cars %..% {
  head(.,2)
  mean(.$dist)
}
#> [1] 6

So I am not sure if a new syntax is needed here (and I don't know what it would be), but I'll add your example to the doc.

moodymudskipper commented 4 years ago

Another idea :

`%with%` <- with
cars %.% {
  head(2)
  . %with% mean(dist)
}
#> [1] 6
sfd99 commented 4 years ago

All good alternative ideas, Antoine!.

As a user, I like your solution:

cars  %.%  { 
    head(2)
    with(mean(dist))
                 } 

[1] 6

because it's cleaner and simpler than the other valid solutions.

It follows +better the "spirit" of your nakedpipe pkg.: = write less code and do more!...

The ideal would be if you could "incorporate" the functionality of the %$% pipe, (as needed by certain R-base functions like: mean, plot, cor...).

This way, the end user does not need to remember extra special syntax.

Even if one R command inside the { ... } is R-base and expects a vector of values, nakedpipe would detect the situation and handle it!.

That's the beauty of the syntax and easy flow of nakedpipe, simple and elegant!:

DF %.% { R command 1 (R-base or not) R command 2 (R-base or not) .... }

to make the life of the end user simpler.

So, Antoine, maybe when you are at sea, on your ship, the idea will suddenly come to you how to do this... :-)

github-actions[bot] commented 2 years ago

This old thread has been automatically locked. If you think you have found something related to this, please open a new issue and link to this old issue if necessary.