applying a function to elements of a list matched by values in a dataframe

Viewed 46

I have a list (mylist) of data frames named a, b, and c. I want to apply a simple addition to every value of the first variable (var1) of each data frame, corresponding to (matched) values from a separate data frame (shift_df), which contains the variables "id" (b, a, c) and "shift" (2.1, 4.5, 6.6). In other words I want to get a list that has every value in mylist$b$var1 added by 2.1 etc.

mylist <- list("a" = data.frame("var1" = c(1,2,3), "var2" = c(9,3,5)), "b" = data.frame("var1"= c(2,4,7), "var2" = c(5,1,5)), "c" = data.frame("var1" = c(8,5,6), "var2" = c(5,3,3)))

shift_df <- data.frame("id"=c("b", "a", "c"), "shift"=c(2.1, 4.2, 6.6))

My idea was to use lapply for this, but I'm not able to apply the matching with shift_df$id properly. Grateful for any help!

lapply(mylist,function(x) {x[names(mylist)==shift_df[1]] + shift_df$shift[names(mylist)==shift_df[1]]})
3 Answers

a data.table approach.

First, join the list together, using the names of the list's elements as id.
Then perform an update join, adding the shoft_df value for each id to val1 and val2.
Finally, split the data.table back to a list, by id.

library(data.table)
split( rbindlist(mylist, idcol = "id")[setDT(shift_df), 
                                       `:=`(var1 = var1 + i.shift,
                                            var2 = var2 + i.shift),
                                       on = .(id)],
       by = "id", keep.by = FALSE)

# $a
#    var1 var2
# 1:  5.2 13.2
# 2:  6.2  7.2
# 3:  7.2  9.2
# 
# $b
#    var1 var2
# 1:  4.1  7.1
# 2:  6.1  3.1
# 3:  9.1  7.1
# 
# $c
#    var1 var2
# 1: 14.6 11.6
# 2: 11.6  9.6
# 3: 12.6  9.6

You can use Map in base R -

Map(`+`, mylist, shift_df$shift[match(names(mylist), shift_df$id)])

#$a
#  var1 var2
#1  5.2 13.2
#2  6.2  7.2
#3  7.2  9.2

#$b
#  var1 var2
#1  4.1  7.1
#2  6.1  3.1
#3  9.1  7.1

#$c
#  var1 var2
#1 14.6 11.6
#2 11.6  9.6
#3 12.6  9.6

where match returns the shift value that needs to be added to each dataframe in the list.

shift_df$shift[match(names(mylist), shift_df$id)]
#[1] 4.2 2.1 6.6

Using imap

library(purrr)
imap(mylist, ~ .x + with(shift_df, shift[id == .y]))
$a
  var1 var2
1  5.2 13.2
2  6.2  7.2
3  7.2  9.2

$b
  var1 var2
1  4.1  7.1
2  6.1  3.1
3  9.1  7.1

$c
  var1 var2
1 14.6 11.6
2 11.6  9.6
3 12.6  9.6

or use map2 with split

map2(mylist, split(shift_df$shift, shift_df$id), `+`)

Or with pmap

pmap(shift_df, ~ mylist[[..1]] + ..2)
Related