How to coerce NESTED list into double without losing the structure (R)

201 views Asked by At

there seem to many answered questions about how to convert a list into a double, but nothing that seems to keep the structure of the list valid.

I have a nested list like that (R output):

my_list

[[1]] 
[1]  1  3  4  8 11 13 17

[[2]] 
 [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20

[[3]]  
[1]  2 14

Then I pass this list to JAGS in order to iterate over its elements. In the JAGS loop I want to iterate over the first, second and third element and then retrieve the numbers stored in there to be iterated over again in another loop. That means I want to access "1 3 4 8 11 13 17" of the first element and so on.


The code in the JAGS script that I use to iterate is like this:

    for (j in 1:subjects) {  # Subject-loop
          for (i in my_list[j]) {  # Trial loop
   .... 

     }
    }

The background for this is that not all subjects have valid trials. so I need to pass JAGS the valid trials for each subject.

I intended to do that with a nested list like above. However, it turns out JAGS cannot deal with list - so now I try to convert it into something that JAGS can iterate over without losing the information which trials belong to which subject.

I need to keep the structure functioning so everything that just converts the list into a single vector does not help as I cannot iterate over that anymore.

Any suggestions? I have tried to just pass the list, but JAGS cannot deal with that and needs a "double". I also tried to convert it into a matrix, but failed to keep structure.

Thank you!


Update: I tried this, which works somewhat as matrixes seem to work with JAGS

list_as_matrix <- do.call(rbind, my_list)

But the nested lists have different lengths, so the empty columns in the matrix are just filled with the same values over an over again.


The JAGS error code is:

4 nodes produced errors; first error: 'list' object cannot be coerced to type "double"  

Update with the answer thanks to the input from DaveArmstrong:

# a list of 6 subjects and their valid trials
subjects_and_their_valid_trials <- list(
  c(1,3,4,8, 11,13,17), 
  c(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20), 
  c(2, 14),
  c(1,3,4,8, 11,13,17), 
  c(1,2,3,4,5,6,8,9,10,11,12,13,14,15,17,18,19,20), 
  c(6, 12))

l <- subjects_and_their_valid_trials # make it easier to read

maxlen <- max(sapply(l, length))
mat <- t(sapply(l, function(x)c(x, rep(NA, maxlen-length(x)))))
minvals <- 1
maxvals <- apply(mat, 1, function(x)max(which(!is.na(x))))


# ---- How the JAGS Loop is structured
nSubj_Condition1 <- 6

# Loop for condition 
for (j in 1:nSubj_Condition1) {  #Subject-loop
  
  print( "///---------------------------------- ///")
  print(paste("Subject number", j))
  
  #-------------------------
  # This loop restricts the following loop to valid cases in the matrix
  for (k in 1:maxvals[j]) { 
    
    print(paste("Iterating through trial matrix... step", k))
    
    # This loop loops through the matrix of valid trials for each subjects 
    for (i in mat[j,k]) {
      
      print(paste("A valid trial number is:", i))
    }
  }
}
2

There are 2 answers

5
DaveArmstrong On BEST ANSWER

What about something like this:

l <- list(
c(1,3,4,8, 11,13,17), 
c(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20), 
c(2, 14))

Turn your list into a matrix that is padded out with NA values.

maxlen <- max(sapply(l, length))
mat <- t(sapply(l, function(x)c(x, rep(NA, maxlen-length(x)))))

# > mat
# [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12] [,13] [,14] [,15] [,16]
# [1,]    1    3    4    8   11   13   17   NA   NA    NA    NA    NA    NA    NA    NA    NA
# [2,]    1    2    3    4    5    6    7    8    9    10    11    12    13    14    15    16
# [3,]    2   14   NA   NA   NA   NA   NA   NA   NA    NA    NA    NA    NA    NA    NA    NA
# [,17] [,18] [,19] [,20]
# [1,]    NA    NA    NA    NA
# [2,]    17    18    19    20
# [3,]    NA    NA    NA    NA

Then, for each subject, identify the first and last columns with valid data.

minvals <- 1
maxvals <- apply(mat, 1, function(x)max(which(!is.na(x))))

Then, in your JAGS model, you could pull the appropriate values out of mat to identify the trial numbers.

for (j in 1:subjects) {  # Subject-loop
  for (i in minvals[j]:maxvals[j]) {  # Trial loop
     mat[j,i] ## gives you the trial number for each subject 
    
  }
}
8
AudioBubble On

You can map lists which returns the initial list after an arbitrary function has been applied to each list member (recursively, too, if desired). In base R, you'd do this with lapply() or Map(). Package {purrr} extends this concept with a variety of convenient functions.

Example:

## base R
lapply(your_list, function(list_element) {some_JAGS_call(list_element)})

## purrr (plus pipe and formula notation)
your_list %>% map(~ some_JAGS_call(.x))

This online ressource on R and JAGS might be helpful.

Edit 1 If you, e.g., need to map my_list from a list of vectors to a list of matrices fit for JAGS, you lapply (list-apply) the function as.matrix():

my_list_of_matrices <- lapply(my_list, function(el) as.matrix(el))

(Supply further arguments to as.matrix() as required by your JAGS call.)

Edit 2 Gotcha: when you index a list with single brackets like in your loop: for (i in my_list[j]) { # Trial loop you will still obtain the list element as a list. Use double brackets: for (i in my_list[[j]]) to pick the list element (e.g. a matrix, if so) without the sticky list part.