Working with values in lists - r

I have a list containing 3 vectors.
mylist <- list( a = c(1,2),
b = c(3,4),
c = c(5,6) )
Is there any simple way to, for instance, perform computations on the first values of the three objects with the sum() function?
I tried many things like:
sum(mylist[c(a, b, c)][1])
This line of code does not work, but it gives insight into what I am trying to do.
Thanks for your help.

use sapply
> sum(sapply(mylist, "[", 1))
[1] 9
Bonus fun fact: You can use c( ) inside of [[ ]]:
sum( sapply(seq(mylist), function(i) mylist[[ c(i, 1) ]]) )

Not very efficient solution:
sum(unlist(lapply(mylist,'[',1)))
[1] 9

Related

Change first input based on second input, applying a function, using purrrĀ“s map2

I'm wondering if it's possible to change, let's say .x based on .y applying map2() on the fly, applying a function.
Let's say I have two vectors with different lengths and want to fill shortest one with NA, in order to have same length in both vectors:
vec1 <- seq(1, 3, by = 2)
vec2 <- seq(2, 3, by = 2)
Both solutions are valid:
length(vec2) <- length(vec1)
`<-`(length(vec2), length(vec1))
But, what if I have vectors in lists, and want to apply purrr's map2?
l1 <- list(c(1,2), c(1,2))
l2 <- list(3, 3)
I have tried:
library(purrr)
map2(l1, l2, ~ `<-`(length(.y), length(.x)))
but does not work. Any ideas how to assign a value inside map2? Thank you, help much appreciated.
PS: I'm trying to avoid using loops!
You should call `length<-` rather than `<-`.
purrr::map2(l1, l2, ~ `length<-`(.y, length(.x)))
# [[1]]
# [1] 3 NA
#
# [[2]]
# [1] 3 NA
which is equivalent to
purrr::map2(l1, l2, ~ { length(.y) <- length(.x); .y })
Using a simple for loop
for(i in seq_along(l2)) length(l2[[i]]) <- length(l1[[i]])

R: mapply with vector argument

I have a function of this form:
foo<-function(x,y){
if(length(y)==1){
return(x*y)
}
else{
return(x-y[1]*y[2])
}
}
and for the y argument I pass either a number or a vector of numbers:
> #test function:
> foo(1,2)
[1] 2
> foo(1,c(1,2))
[1] -1
Now I wish to use mapply to this function, but I run into problems when I wish to pass a vector for the y argument:
df<-data.frame(
"a"<-floor(runif(6, 1,10)),
"b"<-floor(runif(6, 18,80)),
"c"<-floor(runif(6, 1,80)),
"d"<-floor(runif(6, 100,800)),
"e"<-floor(runif(6, 1000,4000)),
"f"<-floor(runif(6, 1,10)),
"g"<-floor(runif(6, 5,80))
)
names(df)=c("a","b","c","d","e","f","g")
The following works fine:
> mapply(FUN=foo,df["a"],df["b"])
,but I run into trouble when I try to do the following:
> mapply(FUN=foo,df["a"],cbind(df["b"],df["c"]))
I'm very grateful for tips on how to better use an argument that have verying length, or how to pass the argument to mapply!
There are a lot of possible fixes here. Fundamentally, you need to turn 2nd input into mapply into a list with two elements in each list. One way to achieve that is to do something like:
tmp <- as.data.frame(t(df[c('b', 'c')]))
result <- mapply(FUN=foo,df["a"], tmp)
since a data frame is a list. This is going to run the function on all combinations of df["a"] and tmp. The elements you want will be along the diagonal (1st element of df['a'] with the first element of tmp, so the final answer is
diag(result)
BTW, when you are inside a function such as data.frame, use = for assignment instead of <-. You also do not need the quotes around the letters (they are being ignored). so you're call to data.frame should look like
df<-data.frame(
a = floor(runif(6, 1,10)),
b = floor(runif(6, 18,80)),
c = floor(runif(6, 1,80)),
d = floor(runif(6, 100,800)),
e = floor(runif(6, 1000,4000)),
f = floor(runif(6, 1,10)),
g = floor(runif(6, 5,80))
)
Which allows you to avoid having to name the data frame after you define it.
Update without diagonal call
f1 <- function(x) {
if(length(x) ==2 ) x[1] * x[2]
else x[1] - x[2]*x[3]
}
apply(df[,c("a","b", "c")], 1, f1)

using expand.grid with objects

I have the following vectors and a combined data frame which are objects feed to the expresion below.
x <- c(1,2,3,4)
y <- c(5,6,7,8)
z <- c(9,10,11,12)
h <- data.frame(x,y,z)
D <- print (( rep ( paste ( "h[,3]" ) , nrow(h) )) , quote=FALSE )
# [1] h[,3] h[,3] h[,3] h[,3]
DD <- c ( print ( paste ( (D) , collapse=",")))
# "[1] h[,3],h[,3],h[,3],h[,3]"
DDD <- print ( DD, quote = FALSE )
# However when I place DDD in expand.grid it does not work
is(DDD)
[1] "character" "vector" "data.frameRowLabels" "SuperClassMethod"
Thus the expresion expand.grid(DDD) does not work. How could I get a process where I repeat n times a character element which represents an object as to obtain a vector of the number of repeated character elements which when placed in expand.grid works.
It looks like you are trying to generate some R code then execute it. For your case, this will work:
# From your question
DDD
# [1] "h[,3],h[,3],h[,3],h[,3]"
# The code that you wish to execute, as a string
my_code <- paste("expand.grid(", DDD, ")")
# [1] "expand.grid( h[,3],h[,3],h[,3],h[,3] )"
# Execute the code
eval(parse(text = my_code))
I really recommend against doing this. See here for some good reasons why eval(parse(text = ...)) is a bad idea.
A more "R" solution to accomplish your task:
# Generate the data.frame, h
x <- c(1,2,3,4)
y <- c(5,6,7,8)
z <- c(9,10,11,12)
h <- data.frame(x,y,z)
# Repeat the 3rd column 3 times, then call expand.grid
expand.grid(rep(list(h[,3]), times = 3))
# Alternatively, access the column by name
expand.grid(rep(list(h$z), times = 3))
By the way, I recommend looking at the help files for expand.grid - they helped me reach a solution to your problem quite quickly after understanding the arguments for expand.grid.

Apply family of functions for functions with multiple arguments

I would like to use a function from the apply family (in R) to apply a function of two arguments to two matrices. I assume this is possible. Am I correct? Otherwise, it would seem that I have to put the two matrices into one, and redefine my function in terms of the new matrix.
Here's an example of what I'd like to do:
a <- matrix(1:6,nrow = 3,ncol = 2)
b <- matrix(7:12,nrow = 3,ncol = 2)
foo <- function(vec1,vec2){
d <- sample(vec1,1)
f <- sample(vec2,1)
result <- c(d,f)
return(result)
}
I would like to apply foo to a and b.
(Strictly answering the question, not pointing you to a better approach for you particular use here....)
mapply is the function from the *apply family of functions for applying a function while looping through multiple arguments.
So what you want to do here is turn each of your matrices into a list of vectors that hold its rows or columns (you did not specify). There are many ways to do that, I like to use the following function:
split.array.along <- function(X, MARGIN) {
require(abind)
lapply(seq_len(dim(X)[MARGIN]), asub, x = X, dims = MARGIN)
}
Then all you have to do is run:
mapply(foo, split.array.along(a, 1),
split.array.along(b, 1))
Like sapply, mapply tries to put your output into an array if possible. If instead you prefer the output to be a list, add SIMPLIFY = FALSE to the mapply call, or equivalently, use the Map function:
Map(foo, split.array.along(a, 1),
split.array.along(b, 1))
You could adjust foo to take one argument (a single matrix), and use apply in the function body.
Then you can use lapply on foo to sample from each column of each matrix.
> a <- matrix(1:6,nrow = 3,ncol = 2)
> b <- matrix(7:12,nrow = 3,ncol = 2)
> foo <- function(x){
apply(x, 2, function(z) sample(z, 1))
}
> lapply(list(a, b), foo)
## [[1]]
## [1] 1 6
## [[2]]
## [1] 8 12

Is it possible to modify list elements?

I have a list of records:
z <- list(list(a=1),list(a=4),list(a=2))
and I try to add fields to each of them.
Alas, neither
lapply(z,function(l) l$b <- 1+l$a)
nor
for(l in z) l$b <- 1+l$a
modifies z.
In this simple case I can, of course, do
z <- lapply(z,function(l) c(list(b= 1+l$a),l))
but this quickly gets out of hand when the lists have more nesting:
z <- list(list(a=list(b=1)),list(a=list(b=4)),list(a=list(b=2)))
How do I turn it into
list(list(a=list(b=1,c=2)),list(a=list(b=4,c=5)),list(a=list(b=2,c=3)))
without repeating the definition of the whole structure?
Each element of z has many fields, not just a; and z[[10]]$a has many subfields, not just b.
Your first code example doesn't modify the list because you need to return the list in your call to lapply:
z <- list(list(a=1),list(a=4),list(a=2))
expected <- list(list(a=1, b=2), list(a=4, b=5), list(a=2, b=3))
outcome <- lapply(z,function(l) {l$b <- 1+l$a ; l})
all.equal(expected, outcome)
# [1] TRUE
In the doubly nested example, you could use lapply within lapply, again making sure to return the list in the inner lapply:
z <- list(list(a=list(b=1)),list(a=list(b=4)),list(a=list(b=2)))
expected <- list(list(a=list(b=1, c=2)), list(a=list(b=4, c=5)), list(a=list(b=2, c=3)))
obtained <- lapply(z, function(l1) { lapply(l1, function(l2) {l2$c = l2$b+1 ; l2 } )})
all.equal(expected, obtained)
# [1] TRUE
Another, somewhat convoluted, option:
z <- list(list(a=1),list(a=4),list(a=2))
res <- list(list(a=list(b=1,c=2)),list(a=list(b=4,c=5)),list(a=list(b=2,c=3)))
res1 <- rapply(z,function(x) list(b = x,c = x+1),how = "replace")
> all.equal(res,res1)
[1] TRUE
I only say convoluted because rapply can be tricky to use at times (for me at least).

Resources