Search code examples
rfunctiondataframeloopsmapply

Appropriate use of `mapply()` to obtain matrix output


I was wondering how to modify my mapply call to avoid getting: Error: $ operator is invalid for atomic vectors after my last line of code?

Note: the use of matrix(c(1, .1, 2, .2), nrow = 2) is just for simplicity.

My expected output is:

$Trus 
     [,1] [,2]
[1,]  1.0  2.0
[2,]  0.1  0.2

$Yu_Chen
     [,1] [,2]
[1,]  1.0  2.0
[2,]  0.1  0.2

My reproducible code is:

ctlist <- function(mm, cont=FALSE, pos=1, outcom=1){
  
  mm <- mm[[1]]
  mm[mm$control==cont & mm$post == pos & mm$outcome == outcom, , drop = FALSE]
}

#=========
dinter <- function(m, pos, outcom){
  
  clist <- ctlist(mm=m, cont=TRUE, pos = pos, outcom = outcom)
  tlist <- ctlist(mm=m, cont=FALSE, pos = pos, outcom = outcom)
  
  matrix(c(1, .1, 2, .2), nrow = 2) # For simplicity
}
#=========
L1 = list(Trus = data.frame(control=c(T,F), post=c(1,1), outcome=c(1,1), time=c(2,2)),
          Yu_Chen = data.frame(control=c(F,F,T,T), post=c(1,2,1,2), outcome=c(1,1,1,1), time=c(1,2,1,2)) )

#=========

G <- function(m){
  
input <- rev(expand.grid(outcom = seq_len(max(m$outcom, na.rm = TRUE)), pos = seq_len(max(m$post, na.rm = TRUE))))
 
mapply(dinter, m=m, input$pos, input$outcom)
  
}
#=========

setNames(lapply(L1, G), names(L1))

#Error: $ operator is invalid for atomic vectors
#Called from: ctlist(mm = m, cont = TRUE, pos = pos, outcom = outcom)

Solution

  • There are couple of issues in the function ctlist

    mm <- mm[[1]]
    

    updates the 'mm' to a vector by extracting the first column. Now, it doesn't have any dim attributes. So, if we do the next line, it won't work because those columns are not present as it is a vector

    mm[mm$control==cont & mm$post == pos & mm$outcome == outcom, , drop = FALSE]
    

    Now, checking the dinter, it is unclear, why the OP created the clist and tlist and then a matrix out of the blue as expected. It can be simplified with

    ctlist <- function(mm, cont=FALSE, pos=1, outcom=1){ 
      mm[mm$control==cont & mm$post == pos & mm$outcome == outcom, , drop = FALSE]
     }
    
    
    lapply(L1, function(m) {
    
     input <- rev(expand.grid(outcom = seq_len(max(m$outcom,
          na.rm = TRUE)), pos = seq_len(max(m$post, na.rm = TRUE))))     
          dinter(m, pos = 1, outcom = 1)
    
     })
    #$Trus
    #     [,1] [,2]
    #[1,]  1.0  2.0
    #[2,]  0.1  0.2
    
    #$Yu_Chen
    #     [,1] [,2]
    #[1,]  1.0  2.0
    #[2,]  0.1  0.2
    

    If we are applying the dinter on each element of 'input$pos', 'input$outcom', then the m should be the same. In the OP's code, 'm' is also an input to mapply/Map. As 'm' is a data.frame, the unit is a column. So, it loops over the columns, while the loop for input$pos and input$outcom is a single element (creating an issue in the 'ctlist' function which expects 'mm' as a data.frame along with the fact that length of input$pos and the number of columns of 'mm' differ)

    lapply(L1, function(m) {
    
         input <- rev(expand.grid(outcom = seq_len(max(m$outcom, na.rm = TRUE)), pos = seq_len(max(m$post, na.rm = TRUE))))
         
         Map(dinter, MoreArgs = list(m = m), pos = input$pos, outcom = input$outcom)
        
    
    })
    #$Trus
    #$Trus[[1]]
    #     [,1] [,2]
    #[1,]  1.0  2.0
    #[2,]  0.1  0.2
    
    
    #$Yu_Chen
    #$Yu_Chen[[1]]
    #     [,1] [,2]
    #[1,]  1.0  2.0
    #[2,]  0.1  0.2
    
    #$Yu_Chen[[2]]
    #     [,1] [,2]
    #[1,]  1.0  2.0
    #[2,]  0.1  0.2
    

    If we want to flatten the list, use c with do.call

    do.call(c, lapply(L1, function(m) {
    
     input <- rev(expand.grid(outcom = seq_len(max(m$outcom, 
         na.rm = TRUE)), pos = seq_len(max(m$post, na.rm = TRUE))))
     Map(dinter, MoreArgs = list(m = m), pos = input$pos, 
             outcom = input$outcom)
       }))