Search code examples
rcbind

Combine table with different elements


I have items in different lists and I want to count the item in each list and output it to a table. However, I ran into difficulty when there are different items in the list. Too illustrate my problem:

item_1 <- c("A","A","B")
item_2 <- c("A","B","B","B","C")
item_3 <- c("C","A")
item_4 <- c("D","A", "A")
item_5 <- c("B","D")


list_1 <- list(item_1, item_2, item_3)
list_2 <- list(item_4, item_5)

table_1 <- table(unlist(list_1))
table_2 <- table(unlist(list_2))

> table_1

A B C 
4 4 2 
> table_2

A B D 
2 1 2 

What I get from cbind is :

> cbind(table_1, table_2)

  table_1 table_2
A       4       2
B       4       1
C       2       2

which is clearly wrong. What I need is:

  table_1 table_2
A       4       2
B       4       1
C       2       0
D       0       2

Thanks in advance


Solution

  • It would probably be better to use factors at the start if possible, something like:

    L <- list(list_1 = list_1, 
              list_2 = list_2)
    RN <- unique(unlist(L))
    do.call(cbind, 
            lapply(L, function(x)
              table(factor(unlist(x), RN))))
    #   list_1 list_2
    # A      4      2
    # B      4      1
    # C      2      0
    # D      0      2
    

    However, going with what you have, a function like the following might be useful. I've added comments to help explain what's happening in each step.

    myFun <- function(..., fill = 0) {
      ## Get the names of the ...s. These will be our column names
      CN <- sapply(substitute(list(...))[-1], deparse)
      ## Put the ...s into a list
      Lst <- setNames(list(...), CN)
      ## Get the relevant row names
      RN <- unique(unlist(lapply(Lst, names), use.names = FALSE))
      ## Create an empty matrix. `fill` can be anything--it's set to 0
      M <- matrix(fill, length(RN), length(CN),
                  dimnames = list(RN, CN))
      ## Use match to identify the correct row to fill in
      Row <- lapply(Lst, function(x) match(names(x), RN))
      ## use matrix indexing to fill in the unlisted values of Lst
      M[cbind(unlist(Row), 
              rep(seq_along(Lst), vapply(Row, length, 1L)))] <-
        unlist(Lst, use.names = FALSE)
      ## Return your matrix
      M
    }
    

    Applied to your two tables, the outcome is like this:

    myFun(table_1, table_2)
    #   table_1 table_2
    # A       4       2
    # B       4       1
    # C       2       0
    # D       0       2
    

    Here's an example with adding another table to the problem. It also demonstrates use of NA as a fill value.

    set.seed(1) ## So you can get the same results as me
    table_3 <- table(sample(LETTERS[3:6], 20, TRUE) )
    table_3
    # 
    # C D E F 
    # 2 7 9 2
    
    myFun(table_1, table_2, table_3, fill = NA)
    #   table_1 table_2 table_3
    # A       4       2      NA
    # B       4       1      NA
    # C       2      NA       2
    # D      NA       2       7
    # E      NA      NA       9
    # F      NA      NA       2