Skip to content

Using the pipe, |>, syntax with "names<-"

21 messages · Rui Barradas, Iris Simmons, Duncan Murdoch +7 more

#
This post is likely pretty useless;  it is motivated by a recent post
from "Val" that was elegantly answered using Tidyverse constructs, but
I wondered how to do it using base R only. Along the way, I ran into
the following question to which I think my answer (below) is pretty
awful. I would be interested in more elegant base R approaches. So...

z <- data.frame(a = 1:3, b = letters[1:3])
a h
1 1 a
2 2 b
3 3 c

Suppose I want to change the name of the second column of z from 'b'
to 'foo' . This is very easy using nested function syntax by:

names(z)[2] <- "foo"
a foo
1 1   a
2 2   b
3 3   c

Now suppose I wanted to do this using |> syntax, along the lines of:

z |> names()[2] <- "foo"  ## throws an error

Slightly fancier is:

z |> (\(x)names(x)[2] <- "b")()
## does nothing, but does not throw an error.

However, the following, which resulted from a more careful read of
?names works (after changing the name of the second column back to "b"
of course):

z |>(\(x) "names<-"(x,value = "[<-"(names(x),2,'foo')))()
a foo
1 1   a
2 2   b
3 3   c

This qualifies to me as "pretty awful." I'm sure there are better ways
to do this using pipe syntax, so I would appreciate any better
approaches.

Best,
Bert
#
Nope, I still got it wrong: None of my approaches work.  :(

So my query remains: how to do it via piping with |> ?

Bert
On Sat, Jul 20, 2024 at 1:06?PM Bert Gunter <bgunter.4567 at gmail.com> wrote:
#
With further fooling around, I realized that explicitly assigning my
last "solution" 'works'; i.e.

names(z)[2] <- "foo"

can be piped as:

 z <- z |>(\(x) "names<-"(x,value = "[<-"(names(x),2,'foo')))()
a foo
1 1   a
2 2   b
3 3   c

This is even awfuller than before. So my query still stands.

-- Bert
On Sat, Jul 20, 2024 at 1:14?PM Bert Gunter <bgunter.4567 at gmail.com> wrote:
#
?s 21:46 de 20/07/2024, Bert Gunter escreveu:
Hello,

This is not exactly the same but in one of your attempts all you have to 
do is to return x.
The following works and does something.


z |> (\(x){names(x)[2] <- "foo";x})()
#   a foo
# 1 1   a
# 2 2   b
# 3 3   c


Hope this helps,

Rui Barradas
#
I suspect that you would want to define a function which was aware of 
the limitations of piping to handle this.  For example:

rename <- function(x, col, newname) {
   names(x)[col] <- newname
   x
}

Then

z |> rename(2, "foo")

would be fine.

Duncan Murdoch
On 2024-07-20 4:46 p.m., Bert Gunter wrote:
#
It should be written more like this:

```R
z <- data.frame(a = 1:3, b = letters[1:3])
z |> names() |> _[2] <- "foo"
z
```

Regards,
    Iris
On Sat, Jul 20, 2024 at 4:47?PM Bert Gunter <bgunter.4567 at gmail.com> wrote:
#
Bert,

You need to consider LHS vs RHS functionality.

Before I start, I would have done your example setup lie this:

trio <- 1:3
z <- data.frame(a = trio, b = letters[trio])

Just kidding!

Syntactic sugar means you are calling this function:
function (x, value)  .Primitive("names<-")

Not particularly documented is a gimmick I just tried of supplying a second argument to the more routine function version:
one two
1   1   a
2   2   b
3   3   c

The above does not change z, but returns a new DF with new names.

In a pipeline, try this:

z <-
  z |>
  `names<-`( c("one","two"))
a b
1 1 a
2 2 b
3 3 c
+   z |>
+   `names<-`( c("one","two"))
one two
1   1   a
2   2   b
3   3   c





-----Original Message-----
From: R-help <r-help-bounces at r-project.org> On Behalf Of Bert Gunter
Sent: Saturday, July 20, 2024 4:47 PM
To: R-help <R-help at r-project.org>
Subject: Re: [R] Using the pipe, |>, syntax with "names<-"

With further fooling around, I realized that explicitly assigning my
last "solution" 'works'; i.e.

names(z)[2] <- "foo"

can be piped as:

 z <- z |>(\(x) "names<-"(x,value = "[<-"(names(x),2,'foo')))()
a foo
1 1   a
2 2   b
3 3   c

This is even awfuller than before. So my query still stands.

-- Bert
On Sat, Jul 20, 2024 at 1:14?PM Bert Gunter <bgunter.4567 at gmail.com> wrote:
______________________________________________
R-help at r-project.org mailing list -- To UNSUBSCRIBE and more, see
https://stat.ethz.ch/mailman/listinfo/r-help
PLEASE do read the posting guide http://www.R-project.org/posting-guide.html
and provide commented, minimal, self-contained, reproducible code.
#
Iris's reply is what I was looking for.  Many thanks -- I can now sleep tonight!

Both Rui's and Duncan's responses merely hid what I wanted to avoid. I
hope that I did not occupy much of your times on my useless question
and rather pathetic attempts at an answer.

Cheers,
Bert
On Sat, Jul 20, 2024 at 3:02?PM Iris Simmons <ikwsimmo at gmail.com> wrote:
#
On 2024-07-20 6:02 p.m., Iris Simmons wrote:
That's a great suggestion!

Duncan Murdoch
#
I think Iris's solution should be added to the help file: ?|>
there are no examples there now that show assignment or replacement using the "_"
#
I second Rich's excellent suggestion.

As with all elegant solutions, Iris's clicked on the wee light bulb in
my brain, and I realized that a slightly more verbose, but perhaps
more enlightening, alternative may be:

z |>  attr("names") |> _[2] <- "foo"

However, I would add this as an example *only with* Iris's solution.
Hers should be shown whether or not the above is.

Cheers,
Bert
On Sat, Jul 20, 2024 at 3:35?PM Richard M. Heiberger <rmh at temple.edu> wrote:
#
The main challenge in Bert's original problem is that `[` and `[<-` cannot
be called in a pipeline. The obvious solution is to define named versions,
e.g.:

elt <- `[`
`elt<-` <- `[<-`

Then,
[1] "b"
a foo
1 1   a
2 2   b
3 3   c

You could actually also do (using a similar function already defined in
methods)

z |> names() |> el(2) <- "bar"

Iris's _ trick is of course a nice alternative; and this example in ?pipeOp
already covers it:

# using the placeholder as the head of an extraction chain:
mtcars |> subset(cyl == 4) |> lm(formula = mpg ~ disp) |> _$coef[[2]]

While the replacement question is a nice exercise, I am not sure about the
value of emphasizing that you can use pipes to do complex assignments.
Doesn't that defeat the whole purpose of piping? For one thing, it will
necessarily terminate the pipe. Also, it will not work if the starting
value is not a variable. E.g.,
Error in names(data.frame(a = 1:3, b = letters[1:3]))[2] <- "bar" :
  target of assignment expands to non-language object

Duncan's rename() approach, which will just change the column name and
return the modified object, seems more useful as part of a pipeline.

Best,
-Deepayan
On Sun, 21 Jul 2024 at 04:46, Bert Gunter <bgunter.4567 at gmail.com> wrote:

            

  
  
#
I think that the simplicity of setNames is hard to beat:

z |> setNames( c( "a", "foo" ) )

and if you are determined not to load dplyr then

column_rename <- function( DF, map ) {
  on <- names( DF )
  on[ match( map, on ) ] <- names( map )
  names( DF ) <- on
  DF
}

is more robust to column reorganization than replace():

z |> column_rename( c( foo = "b" ) )
On July 20, 2024 10:07:57 PM PDT, Deepayan Sarkar <deepayan.sarkar at gmail.com> wrote:

  
    
#
As an intellectual exercise it can be reasonable to discuss these ways to use a pipe even in places where it may not have been seen as something anyone would even try to use it.

In actual code, it is often better to not make overly cute constructions that others (or yourself a month later) will not understand.

Pipes were not part of R originally and the versions created over the years have included many kinds of functionality that is not in the new official pipe and that might allow functionality such as this. In some ways, the tidyverse evolved so that they did not require this game as their normal method of changing the names of columns works inline by using verbs such as rename(). Other things you can often do in-line is to reorder the columns or apply changes selectively to columns whose names or contents match your patterns. 

If you now wanted to use base R to make similar changes in a pipeline, the result may be that you end up reinventing extensions such as functions that do what you want in a pipeline, OR you realize that many things done in just base R should continue being done in more discrete lumps rather than one huge pipeline.

There may well already be one or more packages outside the tidyverse that provide such extensions but I am not so sure that using them will be as easy or convenient or readable until and unless they are as well-known. I note that even in the tidyverse, many things are often better done, especially while testing the code, without really long pipes so that it is easier to modify and rearrange things or see intermediate values. Similar arguments apply to something like using ggplot() with its own sort-of piping where it may make sense to use repeated invocations of "p <- p + function(args)" so each can clearly be documented with comments and sometimes steps can selectively be commented out or moved later in the "pipeline" if it seems the changes by one step are interfering with a later step by re-setting internal variables. Of course, if the code is completely done and not expected to change, you can always switch to piping if that is what you want.

Programming languages can have many purposes including things like efficiency or compact representations or making it harder to make mistakes but a major advantage of some is that the programs be READ easily without having to consult gurus or try to debug. Pipes can both be helpful in this regard or be absolutely mysterious. Using "_" in any way imaginable as a placeholder is convenient and allowing a default of it being a replacement for a first argument without specifying it is nice. But you can imagine an implementation where you constantly put in ".placeHolder." as clearer.


-----Original Message-----
From: R-help <r-help-bounces at r-project.org> On Behalf Of Deepayan Sarkar
Sent: Sunday, July 21, 2024 1:08 AM
To: Bert Gunter <bgunter.4567 at gmail.com>
Cc: R-help <R-help at r-project.org>
Subject: Re: [R] [External] Using the pipe, |>, syntax with "names<-"

The main challenge in Bert's original problem is that `[` and `[<-` cannot
be called in a pipeline. The obvious solution is to define named versions,
e.g.:

elt <- `[`
`elt<-` <- `[<-`

Then,
[1] "b"
a foo
1 1   a
2 2   b
3 3   c

You could actually also do (using a similar function already defined in
methods)

z |> names() |> el(2) <- "bar"

Iris's _ trick is of course a nice alternative; and this example in ?pipeOp
already covers it:

# using the placeholder as the head of an extraction chain:
mtcars |> subset(cyl == 4) |> lm(formula = mpg ~ disp) |> _$coef[[2]]

While the replacement question is a nice exercise, I am not sure about the
value of emphasizing that you can use pipes to do complex assignments.
Doesn't that defeat the whole purpose of piping? For one thing, it will
necessarily terminate the pipe. Also, it will not work if the starting
value is not a variable. E.g.,
Error in names(data.frame(a = 1:3, b = letters[1:3]))[2] <- "bar" :
  target of assignment expands to non-language object

Duncan's rename() approach, which will just change the column name and
return the modified object, seems more useful as part of a pipeline.

Best,
-Deepayan
On Sun, 21 Jul 2024 at 04:46, Bert Gunter <bgunter.4567 at gmail.com> wrote:

            
______________________________________________
R-help at r-project.org mailing list -- To UNSUBSCRIBE and more, see
https://stat.ethz.ch/mailman/listinfo/r-help
PLEASE do read the posting guide http://www.R-project.org/posting-guide.html
and provide commented, minimal, self-contained, reproducible code.
#
This
- is non-destructive (does not change z)
- passes the renamed z onto further pipe legs
- does not use \(x)...

It works by boxing z, operating on the boxed version and then unboxing it.

  z <- data.frame(a = 1:3, b = letters[1:3])
  z |> list(x = _) |> within(names(x)[2] <- "foo") |> _$x
  ##   a foo
  ## 1 1   a
  ## 2 2   b
  ## 3 3   c
On Sat, Jul 20, 2024 at 4:07?PM Bert Gunter <bgunter.4567 at gmail.com> wrote:

  
    
#
Wow!
Yes, this is very clever -- way too clever for me -- and meets my
criteria for a solution.

I think it's also another piece of evidence of why piping in base R is
not suited for complex/nested assignments, as discussed in Deepayan's
response.

Maybe someone could offer a better Tidydata piping solution just for
completeness?

Best,
Bert

On Sun, Jul 21, 2024 at 7:48?AM Gabor Grothendieck
<ggrothendieck at gmail.com> wrote:
#
hmmm...
But note that you still used the nested assignment, names()[2] <-
"foo", to circumvent R's pipe limitations, which is exactly what
Iris's solution avoids. So I think I was overawed by your cleverness
;-)

Best,
Bert
On Sun, Jul 21, 2024 at 8:01?AM Bert Gunter <bgunter.4567 at gmail.com> wrote:
#
The tidy solution is rename

literally:

z |> rename(foo = 2)

Or you could do it with other functions

z |> select ( 1, foo = 2)

Or

z |> mutate( foo = 2 ) |> # untested (always worry that makes the whole
column 2)
select (-2)

But that's akin to

z$foo <- z[2]
z[2] <- null
On Sun, 21 Jul 2024, 16:01 Bert Gunter, <bgunter.4567 at gmail.com> wrote:

            

  
  
#
If you object to names(x)[2]<- ... then use replace:

  z |> list(x = _) |> within(replace(names(x), 2, "foo")) |> _$x
On Sun, Jul 21, 2024 at 11:10?AM Bert Gunter <bgunter.4567 at gmail.com> wrote:

  
    
#
Thanks, Calum.

That was exactly what Duncan Murdoch proposed earlier in this thread,
except, of course, he had to explicitly write the function first.

-- Bert
On Sun, Jul 21, 2024 at 8:12?AM CALUM POLWART <polc1410 at gmail.com> wrote:
#
That was supposed to be

  z |> list(x = _) |> within(names(x) <- replace(names(x), 2, "foo")) |> _$x

but I really see no advantage over

  z |> list(x = _) |> within(names(x)[2] <- "foo") |> _$x

Regarding the z |> names() |> _[2] <- "foo" idiom, while it is clever,
and well illustrates
what is possible with base R pipes that might not be initially expected,
I think it should be discouraged as not in the spirit of pipes which is a more
functional approach to programming.  Such an approach ought to be
non-destructive and should pass on the result to the next
step in the pipeline.  These criteria are not satisfied by it.

On Sun, Jul 21, 2024 at 11:17?AM Gabor Grothendieck
<ggrothendieck at gmail.com> wrote: