5

I am trying to remove a named component from a list, using within and rm. This works for a single component, but not for two or more. I am completely befuddled.

For example - this works

aa = list(a = 1:3, b = 2:5, cc = 1:5)
within(aa, {rm(a)})

the output from within will have just the non-removed components.

However, this does not:

aa = list(a = 1:3, b = 2:5, cc = 1:5)
within(aa, {rm(a); rm(b)})

Neither does this:

within(aa, {rm(a, b)})

The output from within will have all the components, with the ones I am trying to remove, set to NULL. Why?

smci
  • 32,567
  • 20
  • 113
  • 146
martin
  • 51
  • 1
  • Well, yes. But I am trying to do it inside within – martin Nov 25 '15 at 17:57
  • This is weird behaviour! you can delete one, but after that they stay on as NULL - even if there are more than 3 columns. Works similarly with `within(aa, {"b"<-NULL; "a"<-NULL})` – jeremycg Nov 25 '15 at 18:03

1 Answers1

4

First, note the following behavior:

> aa = list(a = 1:3, b = 2:5, cc = 1:5)
>
> aa[c('a', 'b')] <- NULL
>
> aa
# $cc
# [1] 1 2 3 4 5

> aa = list(a = 1:3, b = 2:5, cc = 1:5)
>
> aa[c('a', 'b')] <- list(NULL, NULL)
>
> aa
# $a
# NULL
#
# $b
# NULL
#
# $cc
# [1] 1 2 3 4 5

Now let's look at the code for within.list:

within.list <- function (data, expr, ...) 
{
    parent <- parent.frame()
    e <- evalq(environment(), data, parent)
    eval(substitute(expr), e)
    l <- as.list(e)
    l <- l[!sapply(l, is.null)]
    nD <- length(del <- setdiff(names(data), (nl <- names(l))))
    data[nl] <- l
    if (nD) 
        data[del] <- if (nD == 1) NULL else vector("list", nD)
    data
}

Look in particular at the second to last line of the function. If the number of deleted items in the list is greater than one, the function is essentially calling aa[c('a', 'b')] <- list(NULL, NULL), because vector("list", 2) creates a two item list where each item is NULL. We can create our own version of within where we remove the else statement from the second to last line of the function:

mywithin <- function (data, expr, ...) 
{
    parent <- parent.frame()
    e <- evalq(environment(), data, parent)
    eval(substitute(expr), e)
    l <- as.list(e)
    l <- l[!sapply(l, is.null)]
    nD <- length(del <- setdiff(names(data), (nl <- names(l))))
    data[nl] <- l
    if (nD) data[del] <- NULL
    data
}

Now let's test it:

> aa = list(a = 1:3, b = 2:5, cc = 1:5)
>
> mywithin(aa, rm(a, b))
# $cc
# [1] 1 2 3 4 5

Now it works as expected!

Cameron Bieganek
  • 7,208
  • 1
  • 23
  • 40
  • Ah, yes. Should have thought to look at the source. Much appreciate! – martin Dec 03 '15 at 15:23
  • This is very neat, but why do you need to remove the else clause from `within.list`? and assuming it's a bug, please file an R bug on `base::within` – smci Apr 03 '17 at 01:59