gkmagic
gkmagic

Reputation: 187

How to remove elements from a list?

Although the title looks easy, I have not found an answer here on StackOverflow. My problem is: I have a list of some elements. And each elemnt of the list also contains a list filled with some strings. That list inside list contains values that are empty (''), not null. I would like to remove the rows inside that list that are empty. How one can do it? I guess lapply would do the trick but I am not sure how to use it there.

Would be grateful for an advice! Thanks a lot!

EDIT: So my output looks like this

$'1'
1 text1
2 text2
3
4 text3
5 text4

$'2'
1 text1
2
3 text2
4
5 text3

My goal is to delete those rows that are empty so that it looked like this:

$'1'
1 text1
2 text2
3 text3
4 text4

$'2'
1 text1
2 text2
3 text3

Upvotes: 2

Views: 2077

Answers (2)

bgoldst
bgoldst

Reputation: 35314

This is a surprisingly tough problem. First I synthesized some test data:

l <- list(list('a','b','','c','','d'),list('','e',''));
l;
## [[1]]
## [[1]][[1]]
## [1] "a"
##
## [[1]][[2]]
## [1] "b"
##
## [[1]][[3]]
## [1] ""
##
## [[1]][[4]]
## [1] "c"
##
## [[1]][[5]]
## [1] ""
##
## [[1]][[6]]
## [1] "d"
##
##
## [[2]]
## [[2]][[1]]
## [1] ""
##
## [[2]][[2]]
## [1] "e"
##
## [[2]][[3]]
## [1] ""
##
##

Here's my solution:

matches <- do.call(rbind,lapply(seq_along(l),function(li) cbind(li,which(do.call(c,l[[li]])==''))));
matches;
##      li
## [1,]  1 3
## [2,]  1 5
## [3,]  2 1
## [4,]  2 3
invisible(apply(matches[rev(seq_len(nrow(matches))),],1,function(lri) l[[lri]] <<- NULL));
l;
## [[1]]
## [[1]][[1]]
## [1] "a"
##
## [[1]][[2]]
## [1] "b"
##
## [[1]][[3]]
## [1] "c"
##
## [[1]][[4]]
## [1] "d"
##
##
## [[2]]
## [[2]][[1]]
## [1] "e"
##
##

This uses recursive list indexing. This type of indexing is documented in Extract or Replace Parts of an Object:

[[ can be applied recursively to lists, so that if the single index i is a vector of length p, alist[[i]] is equivalent to alist[[i1]]...[[ip]] providing all but the final indexing results in a list.

This solution also leverages the fact that assigning NULL to a list component deletes that component. From the same documentation page:

Note that in all three kinds of replacement, a value of NULL deletes the corresponding item of the list. To set entries to NULL, you need x[i] <- list(NULL).

It was also important to delete components with higher indexes first; otherwise the precomputed higher indexes would be invalidated by the deletion of lower indexes. Hence I ran apply() over matches[rev(seq_len(nrow(matches))),] instead of matches.


Actually there's another easier way which involves rebuilding the list, omitting undesired elements:

lapply(l,function(l2) l2[do.call(c,l2)!=''])
## [[1]]
## [[1]][[1]]
## [1] "a"
##
## [[1]][[2]]
## [1] "b"
##
## [[1]][[3]]
## [1] "c"
##
## [[1]][[4]]
## [1] "d"
##
##
## [[2]]
## [[2]][[1]]
## [1] "e"
##
##

Upvotes: 0

Austin Taylor
Austin Taylor

Reputation: 162

You're correct in that you can use lapply here.

test <- list(c("text1", "text2", "", "text3", "text4"), 
             c("text1", "", "text2", "", "text3"))

lapply(1:length(test), function(x) test[[x]][test[[x]] != ""])

[[1]]
[1] "text1" "text2" "text3" "text4"

[[2]]
[1] "text1" "text2" "text3"

Upvotes: 7

Related Questions