R: assignment by index with a list of lists - r

Suppose I create an empty list of 3 elements, then I want to fill the first and third element with the same copy of another list. Can't seem to figure out how to do this.
z = vector('list', 3)
z[c(1, 3)] = 'a'
z # works fine
[[1]]
[1] "a"
[[2]]
NULL
[[3]]
[1] "a"
z[c(1, 3)] = list('a', 'b')
z # doesn't work
[[1]]
[1] "a"
[[2]]
NULL
[[3]]
[1] "b"
Desired result is that both [[1]] and [[3]] contain the list('a','b'). Saw some other posts, but those have to do with retrieving values, not assigning them.

Related

Using lapply to apply function to each row in a tibble

This is my code that attempts apply a function to each row in a tibble , mytib :
> mytib
# A tibble: 3 x 1
value
<chr>
1 1
2 2
3 3
Here is my code where I'm attempting to apply a function to each line in the tibble :
mytib = as_tibble(c("1" , "2" ,"3"))
procLine <- function(f) {
print('here')
print(f)
}
lapply(mytib , procLine)
Using lapply :
> lapply(mytib , procLine)
[1] "here"
[1] "1" "2" "3"
$value
[1] "1" "2" "3"
This output suggests the function is not invoked once per line as I expect the output to be :
here
1
here
2
here
3
How to apply function to each row in tibble ?
Update : I appreciate the supplied answers that allow my expected result but what have I done incorrectly with my implementation ? lapply should apply a function to each element ?
invisible is used to avoid displaying the output. Also you have to loop through elements of the column named 'value', instead of the column as a whole.
invisible( lapply(mytib$value , procLine) )
# [1] "here"
# [1] "1"
# [1] "here"
# [1] "2"
# [1] "here"
# [1] "3"
lapply loops through columns of a data frame by default. See the example below. The values of two columns are printed as a whole in each iteration.
mydf <- data.frame(a = letters[1:3], b = 1:3, stringsAsFactors = FALSE )
invisible(lapply( mydf, print))
# [1] "a" "b" "c"
# [1] 1 2 3
To iterate through each element of a column in a data frame, you have to loop twice like below.
invisible(lapply( mydf, function(x) lapply(x, print)))
# [1] "a"
# [1] "b"
# [1] "c"
# [1] 1
# [1] 2
# [1] 3

Iterating over the named and unnamed elements separately in an R list

In R, I have a list that contains both named elements and unnamed elements. I want to iterate over the whole list and apply a function that only takes the element for unnamed elements and apply a different function to both the name and the attached elements for named elements.
In pseudo code, the function would look something like if(named) f(name, list[[name]]) else g(element), where list[[name]] is the element stored with that name.
For example, if the list element is unnamed then the element is returned. If the list element is named, then I want to return the name repeated a number of times equal to the element (assume that the element is an integer).
If I had ll = list(a = 5, b = 3, 9, 18) then the function should return a a list with elements
[[1]]
[1] "aaaaa"
[[2]]
[1] "bbb"
[[3]]
[1] 9
[[4]]
[1] 18
I thought about iterating over names(ll) but this cannot access the unnamed elements. Both names[[3]] and names[[4]] are equal to "" and ll[[""]] = NULL.
The process doesn't need to be very efficient so I could use
for(i in 1:length(names(ll)))
if(names(ll)[[i]] == "")
g(ll[[i]])
else
f(names(ll)[[i]], ll[[names(ll)[[i]])
but I was looking for a neater solution.
ll = list(a = 5, b = 3, 9, 18)
lapply(seq_along(ll), function(x){
if(names(ll[x]) == ""){
ll[[x]]
}else{
paste0(rep(names(ll[x]), times =ll[[x]]), collapse = '')
}
}
)
[[1]]
[1] "aaaaa"
[[2]]
[1] "bbb"
[[3]]
[1] 9
[[4]]
[1] 18
You could make a little named function
named<-function(x) names(x)!=''
and then use it on your list:
ll <- list(a = 5, b = 3, 9, 18)
ll[named(ll)]
# $a
# [1] 5
#
# $b
# [1] 3
Or the other way:
ll[!named(ll)]
# [[1]]
# [1] 9
#
# [[2]]
# [1] 18
But this will fail if you have no names on the list at all, so you could modify named to handle that case:
named<-function(x) if (is.null(names(x))) rep(FALSE,length(x)) else names(x)!=''

How to check the existence of elements of list in r

How can I check if an element of list exists or not. This is a list of lists so for example I want to check whether the third element l1[[3]] exists or not. I have tried is.null(l1[["3"]])
but it returns false no matter whether it exists or not and if I use is.null(l1[[3]]) it will give the error of subscript out of bind in case it does not exists but not TRUE.
How should I che
tl;dr:
If you want to check if element n exists, even if checking at end of a list or an empty list, use:
length(mylist) >= n # TRUE indicates exists. FALSE indicates DNE
For nested lists, make sure to check the correct list. eg:
length(outerlist[[innerlist]]) >= n
NULL in lists in R has some differences from what one is used to in other languages. For example, if we replace the element of a list by NULL, all subsequent elements are shifted over, and the list is left with a length of one less.
# SAMPLE DATA
mylist <- as.list(LETTERS[1:5])
[[1]]
[1] "A"
[[2]]
[1] "B"
[[3]]
[1] "C"
[[4]]
[1] "D"
[[5]]
[1] "E"
Testing for NULL in elements 3 & 6. Not quite the information we are looking for.
is.null(mylist[[3]])
# FALSE
is.null(mylist[[6]])
# Error in mylist[[6]] : subscript out of bounds
Instead we check the length of the list:
length(mylist) >= 3 # TRUE
length(mylist) >= 5 # TRUE
length(mylist) >= 6 # FALSE
Removing the 3rd element. Notice that the "empty slot" is not preserved. (ie, element 4, becomes element 3, etc..)
mylist[[3]] <- NULL
[[1]]
[1] "A"
[[2]]
[1] "B"
[[3]]
[1] "D"
[[4]]
[1] "E"
length(mylist) >= 3 # TRUE
length(mylist) >= 5 # FALSE
length(mylist) >= 6 # FALSE
An empty list will have length of 0
emptyList <- list()
length(emptyList) # 0
nestedList <- list( letters=list("A", "B", "C"), empty=list(), words=list("Hello", "World"))
length(nestedList)
# [1] 3
lapply(nestedList, length)
# $letters
# [1] 3
#
# $empty
# [1] 0
#
# $words
# [1] 2
Note that you can incoporate NULL into a list. Which is when testing for NULL is applicable. For example:
myListWithNull <- list("A", "B", NULL, "D")
is.null(myListWithNull[[3]])
# TRUE
length(myListWithNull) >= 3
# TRUE
As I posted here, rather than checking the length of the whole list, it's possible to check the length of the element itself to check for NULL values. As far as I can tell, all values except NULL have a length greater than 0.
x <- list(4, -1, NULL, NA, Inf, -Inf, NaN, T, x = 0, y = "", z = c(1,2,3))
lapply(x, function(el) print(length(el)))
[1] 1
[1] 1
[1] 0
[1] 1
[1] 1
[1] 1
[1] 1
[1] 1
[1] 1
[1] 1
[1] 3
Thus we could make a simple function that works with both named and numbered indices:
element.exists <- function(var, element)
{
tryCatch({
if(length(var[[element]]) > -1)
return(T)
}, error = function(e) {
return(F)
})
}
If the element doesn't exist, it causes an out-of-bounds condition caught by the tryCatch block.
As evaluation of statements in if stops as soon as a condition is false, you can use the following to reliably check if an element is empty:
if(length(l1)<n || is.null(l1[[n]]){ # TRUE only if not NULL
}
Alternatively you can use named lists
is.null(l1[["a"]])
behaves independently from the length of the list.

How do I combine lists of identical lengths into one?

Assuming I have two lists:
xx <- as.list(1:3)
yy <- as.list(LETTERS[1:3])
How do I combine the two such that each element of the new list is a list of the corresponding elements of each component list. So if I combined the two above, I should get:
> combined_list
[[1]]
[[1]][[1]]
[1] 1
[[1]][[2]]
[1] "a"
[[2]]
[[2]][[1]]
[1] 2
[[2]][[2]]
[1] "b"
[[3]]
[[3]][[1]]
[1] 3
[[3]][[2]]
[1] "c"
If you can suggest a solution, I'd like to scale this to 3 or more.
This should do the trick. Nicely, mapply() will take an arbitrary number of lists as arguments.
xx <- as.list(1:3)
yy <- as.list(LETTERS[1:3])
zz <- rnorm(3)
mapply(list, xx, yy, zz, SIMPLIFY=FALSE)

How to subset from a list in R

I have a rather simple task but haven't find a good solution.
> mylist
[[1]]
[1] 1 2 3 4 5 6 7 8 9 10
[[2]]
[1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"
[[3]]
[1] 25 26 27 28 29 30 31 32
y <- c(3,5,9)
I would like to extract from mylist the sub-elements 3,5, and 9 of each component in the list.
I have tried, sapply[mylist,"[[",y] but not luck!, and others like vapply, lapply, etc..
You could use sapply(mylist, "[", y):
mylist <- list(1:5, 6:10, 11:15)
sapply(mylist, "[", c(2,3))
Try using [ instead of [[ (and depending on what you're after you light actually want lapply).
From ?'[[':
The most important distinction between [, [[ and $ is that the [ can
select more than one element whereas the other two select a single
element.
Using lapply:
# create mylist
list1<-1:10
list2<-letters[1:26]
list3<-25:32
mylist<-list(list1,list2,list3)
# select 3,5,9th element from each list
list.2 <- lapply(mylist, function(x) {x[c(3,5,9)]})
purrr provides another solution for solving these kinds of list manipulations within the tidyverse
library(purrr)
library(dplyr)
desired_values <- c(1,3)
mylist <- list(1:5, letters[1:6], 11:15) %>%
purrr::map(`[`,desired_values)
mylist
An easy way to subset repeated named elements of a list, similar to other answers here.
(so I can find it next time I look this question up)
E.g., subset the "b" elements from a repeating list where each element includes an "a" and "b" sub-element:
mylist <- list(
list(
"a" = runif(3),
"b" = runif(1)
),
list(
"a" = runif(3),
"b" = runif(1)
)
)
mylist
#> [[1]]
#> [[1]]$a
#> [1] 0.7547490 0.6528348 0.2339767
#>
#> [[1]]$b
#> [1] 0.8815888
#>
#>
#> [[2]]
#> [[2]]$a
#> [1] 0.51352909 0.09637425 0.99291650
#>
#> [[2]]$b
#> [1] 0.8407162
blist <- lapply(
X = mylist,
FUN = function(x){x[["b"]]}
)
blist
#> [[1]]
#> [1] 0.8815888
#>
#> [[2]]
#> [1] 0.8407162
Created on 2019-11-06 by the reprex package (v0.3.0)
I don't think sgibb's answer gives what you would want. I suggest making a new function:
subsetList <- function(myList, elementNames) {
lapply(elementNames, FUN=function(x) myList[[x]])
}
Then you can use it like this:
x <- list(a=3, b="hello", c=4.5, d="world")
subsetList(x, c("d", "a"))
subsetList(x, c(4, 1))
These both give
[[1]]
[1] "world"
[[2]]
[1] 3
which is what you would want, I think.
There are better ways of doing this, but here's a quick solution.
# your values
list1<-1:10
list2<-letters[1:26]
list3<-25:32
# put 'em together in a list
mylist<-list(list1,list2,list3)
# function
foo<-function(x){x[c(3,5,9)]}
# apply function to each of the element in the list
foo(mylist[[1]])
foo(mylist[[2]])
foo(mylist[[3]])
# check the output
> foo(mylist[[1]])
[1] 3 5 9
> foo(mylist[[2]])
[1] "c" "e" "i"
> foo(mylist[[3]])
[1] 27 29 NA

Resources