Home > Net >  Use R's Reduce to sum up identically named elements for each element in a list
Use R's Reduce to sum up identically named elements for each element in a list

Time:12-20

I have this list in R

> test
[[1]]
[[1]]$right
[1] FALSE

[[1]]$left
[1] FALSE


[[2]]
[[2]]$right
[1] TRUE

[[2]]$left
[1] FALSE

which can quickly be created with this dput

list(list(right = FALSE, left = FALSE), list(right = TRUE, left = FALSE))

Now I want to sum up the right and the left elements in each element of the test-list, so that I end up with a list of two elements like this:

> res
$right
[1] 1

$left
[1] 0

I thought R's Reduce would be a good option for that (although I'm open for any advice), but I could not figure out the code. I tried the following, yet it did not work...

Reduce(function(x){
     r = sum(x[["right"]]) 
     l = sum(x[["left"]]) 
     v = list(r, v)
 }, test)

I get this error

Error in f(init, x[[i]]) : unused argument (x[[i]])

And I think I am having some misconception in my head...

CodePudding user response:

You could unlist the lists and coerce rowSums as.list, which might be more efficient.

as.list(rowSums(sapply(test, unlist)))
# $right
# [1] 1
# 
# $left
# [1] 0

Data:

test <- list(list(right=FALSE, left=FALSE), list(right=TRUE, left=FALSE))

CodePudding user response:

Using purrr

library(purrr)
sides <- c("left", "right")

setNames(sides, sides) |>
    map(~(map_int(test, chuck, .x))) |>
    map(reduce, ` `)

##> $left
##> [1] 0
##> 
##> $right
##> [1] 1

CodePudding user response:

Reduce expects a function that accepts two arguments. It will get the result of the previous value of Reduce and a new element from the vector you pass to Reduce

Reduce(\(x,y) list(left = x$left   y$left, right = x$right   y$right), test)

Personally, I would use sapply (or Map if you want) to first get the left/right elements and then pass those to sum:

sapply(test, \(x) x$left) |> sum()

A version with Map and Reduce would be:

Map(\(x) x$left, test) |> Reduce(f = \(x, y) x   y)

or

Map(\(x) x$left, test) |> Reduce(f = ` `)

CodePudding user response:

Since you need to aggregate per names, you can unlist and use tapply to sum by names:

ll <- list(list(right = FALSE, left = FALSE), list(right = TRUE, left = FALSE))
un <- unlist(ll)
tapply(un, names(un), FUN = sum)
# left right 
#    0     1 

You can transpose first:

purrr::transpose(ll) |>
  lapply(Reduce, f = sum)

# $right
# [1] 1
# 
# $left
# [1] 0
  • Related