Search code examples
rmatrixpurrr

How to use the map() function from the tidyverse package correctly in adding layers of matrix calculations?


I am new to the map() function and have hit a stumbling block. In the code below denoted as "working code", it works fine. Working code expands the elements in the matList list into a little group of matrices for each series ("mat_One", etc.) embedded in matList and calculates the "Inflows" column of each matrix as the product of the respective series vector values embedded in the allocate list and the value in the flexVector vector.

In the below "Failed code" I am trying to insert into the "Due" columns of the rendered matrices the products of the balVector (no series designation) and the ratesVector (for the applicable series) with the line of code Due = balVector * alc * rates[[names(allocate)]][[.y]], but I get an error message.

What am I doing wrong with my use of the map() function?

More generally, I need to know how to perform calculations by moving through various matrices and vectors using map() and reduce(). I have more calculation columns to add, some of which don't move in parallel through 2 vectors the way that map2() does.

In the image below I show what the output of running createBucketMap() should look like.

enter image description here

Working code:

library(tidyverse)

seriesVector <- function() {c("mat_One", "mat_Two")}
matList <- list(mat_One = c("Boy", "Cat"),mat_Two = c("Boy", "Bat"))
allocate <- list(mat_One = c(0.6,0.5,0.4),mat_Two = c(0.4,0.5,0.6))
flowVector <- c(6,5,4)
balVector <- c(1000,900,800)
rates <- list(mat_One = 0.10,mat_Two = 0.20)

createBucketMap <- function(){
  map(allocate, \(alc) alc*flowVector) |>
  map2(matList, \(alc, matL) expand.grid(
    V1 = matL, 
    Inflow = alc,
    Due = 0,
    Cover_due = 0,
    Outflow = 0)
     ) |>
  map(\(dfs) group_split(dfs, V1, .keep = FALSE) |>
        map(as.matrix))  |>
  map2(matList, ~set_names(.x, .y))
}
createBucketMap()

Failed code:

library(tidyverse)
createBucketMap <- function() {
  map(allocate, \(alc) alc * flowVector) |>
    map2(matList, \(alc, matL) expand.grid(
      V1 = matL,
      Inflow = alc,
      Due = balVector * alc * rates[[names(allocate)]][[.y]],
      Cover_due = 0,
      Outflow = 0)
    ) |>
    map(\(dfs) group_split(dfs, V1, .keep = FALSE) |> 
          map(as.matrix)) |>
    map2(matList, ~ set_names(.x, .y))
}
createBucketMap()

Edit OP: add code from the accepted answer but without using an intermediate dataframe, running all calculations on only a matrix:

createBucketMap <- function() {
  imap(matList, \(matL, matL_name) {
    map(matL, \(x) {
      alc <- allocate[[matL_name]]
      rate <- rates[[matL_name]]
      matrix(
        c(flowVector * alc, balVector * alc * rate, rep(0, length(flowVector)), rep(0, length(flowVector))),
        ncol = 4,
        dimnames = list(NULL, c("Inflow", "Due", "Cover_due", "Outflow"))
      )
    }) |>
      set_names(matL)
  })
}
createBucketMap()

Solution

  • A bit unsure about your final result but IMHO you could simplify your code considerably. In the code below I first loop over your matList which already reflects the nested structure of your desired final list. To this end I use imap which loops over both the list and the list names. For each list element I use another map to loop over the inner elements and to create the matrices:

    library(tidyverse)
    
    createBucketMap <- function() {
      imap(matList, \(matL, matL_name) {
        map(matL, \(x) {
          alc <- allocate[[matL_name]]
          rate <- rates[[matL_name]]
          data.frame(
            Inflow = flowVector * alc,
            Due = balVector * alc * rate,
            Cover_due = 0,
            Outflow = 0
          ) |>
            as.matrix()
        }) |>
          set_names(matL)
      })
    }
    
    createBucketMap()
    #> $mat_One
    #> $mat_One$Boy
    #>      Inflow Due Cover_due Outflow
    #> [1,]    3.6  60         0       0
    #> [2,]    2.5  45         0       0
    #> [3,]    1.6  32         0       0
    #> 
    #> $mat_One$Cat
    #>      Inflow Due Cover_due Outflow
    #> [1,]    3.6  60         0       0
    #> [2,]    2.5  45         0       0
    #> [3,]    1.6  32         0       0
    #> 
    #> 
    #> $mat_Two
    #> $mat_Two$Boy
    #>      Inflow Due Cover_due Outflow
    #> [1,]    2.4  80         0       0
    #> [2,]    2.5  90         0       0
    #> [3,]    2.4  96         0       0
    #> 
    #> $mat_Two$Bat
    #>      Inflow Due Cover_due Outflow
    #> [1,]    2.4  80         0       0
    #> [2,]    2.5  90         0       0
    #> [3,]    2.4  96         0       0