Find max value in nested list in R - r

I have a nested list as follows:
results <- list()
c <- 1
outcomes <- c("Value1", "Value2", "Value3")
values <- c(2, 11, 20)
for(i in outcomes){
for(p in 1:length(values)){
results[[c]] <- c(outcomes[p], values[p])
c <- c + 1
}
}
results <- results[1:3]
>results
[[1]]
[1] "Value1" "2"
[[2]]
[1] "Value2" "11"
[[3]]
[1] "Value3" "20"
I want to find a way to return the pair that has the highest value as follows: "Value3" "20"
How can I do this only using base r?

Maybe this can be helpful:
#Code
results[which(unlist(lapply(results,function(x) as.numeric(x[2])))==max(unlist(lapply(results,function(x) as.numeric(x[2])))))]
Output:
[[1]]
[1] "Value3" "20"

We can extract the second element in the list, unlist, convert it to numeric find the index of the max element with which.max to extract the list
results[which.max(as.numeric(unlist(sapply(results, `[`, 2))))]
#[[1]]
#[1] "Value3" "20"
Or slightly compact with
results[which.max(as.numeric(do.call(rbind, results)[,2]))]
#[[1]]
#[1] "Value3" "20"

Related

Combining lists, possibly with mapply

I have a list of lists - a simple example is given below:
my_list <- vector(mode = "list", length = 4)
my_list[[1]] <- c(1, 2, 3)
my_list[[2]] <- c(1, 2, 6)
my_list[[3]] <- c("A")
my_list[[4]] <- c("A", "B")
I would like to combine a subset of these lists based on their indices in a vector. For example if
my_indices <- c(1,2,3), I would like to combine the first three lists and eliminates duplicates to get
c(1, 2, 3, 6, "A")
I can do this manually as follows:
c(my_list[[1]], my_list[[2]], my_list[[3]]) %>%
unique()
[1] "1" "2" "3" "6" "A"
but when i try and simplify / generalize this to
my_indices <- c(1, 2, 3)
c(my_list[[my_indices ]]) %>%
unique()
I get an error message:
error in my_list[[my_indices]] : recursive indexing failed at level 2
How can i combine lists in this setting. I do want a general solution, as my list of lists is large, and I want to be able to extract any subset of it. I have seen posts that use mapply in a related setting, but have not successfully got it to work.
Many thanks in advance for your help
Thomas Philips
c(1, 2, 3, 6, "A") is not what you think, it will be converted to c("1", "2", "3", "6", "A"). If you want mixed class, you cannot unlist, it must stay a list.
Some thoughts:
my_list[my_indices]
# [[1]]
# [1] 1 2 3
# [[2]]
# [1] 1 2 6
# [[3]]
# [1] "A"
unlist(my_list[my_indices])
# [1] "1" "2" "3" "1" "2" "6" "A"
unique(unlist(my_list[my_indices]))
# [1] "1" "2" "3" "6" "A"
To preserve class and ensure uniqueness, you can do
func <- function(a, b) {
a_chrs <- as.character(a)
b_chrs <- as.character(b)
b[ match(setdiff(b_chrs, a_chrs), b_chrs) ]
}
Reduce(func, my_list[my_indices], accumulate = TRUE)
# [[1]]
# [1] 1 2 3
# [[2]]
# [1] 6
# [[3]]
# [1] "A"
The _chrs fancy footwork is because setdiff by itself will not reduce correctly:
out <- Reduce(setdiff, my_list[my_indices], accumulate = TRUE)
out
# [[1]]
# [1] 1 2 3
# [[2]]
# [1] 3
# [[3]]
# [1] 3
If you need that with individually-indexable values, then
unlist(lapply(out, as.list), recursive = FALSE)
# [[1]]
# [1] 1
# [[2]]
# [1] 2
# [[3]]
# [1] 3
# [[4]]
# [1] 6
# [[5]]
# [1] "A"
Here's a tidyverse solution using reduce.
library(tidyverse)
my_list <- vector(mode = "list", length = 4)
my_list[[1]] <- c(1, 2, 3)
my_list[[2]] <- c(1, 2, 6)
my_list[[3]] <- c("A")
my_list[[4]] <- c("A", "B")
to_merge <- c(1,2,3)
unique(reduce(my_list[to_merge], c))
#> [1] "1" "2" "3" "6" "A"
Created on 2021-01-08 by the reprex package (v0.3.0)

Indexing named list with vector in R

How would you index the second element of a vector which is stored as a value in a named list?
I start with this:
hi <- list("1" = c("a","b"),
"2" = c("dog","cat"),
"3" = c("sister","brother")
)
and would like to end up with a named list with the key plus the 2nd element of the vector i.e:
list("1" = "b",
"2" = "cat",
"3" = "brother"
)
You can do:
lapply(hi, `[`, 2)
$`1`
[1] "b"
$`2`
[1] "cat"
$`3`
[1] "brother"
We can use map
library(purrr)
map(hi, pluck, 2)
#$`1`
#[1] "b"
#$`2`
#[1] "cat"
#$`3`
#[1] "brother"

Sorting lists by purrr::transpose but some value is missing

I want to sort the lists depending on the number of "a" in each element.
library("purrr")
data1 <- c("apple","appreciate","available","account","adapt")
data2 <- c("tab","banana","cable","tatabox","aaaaaaa")
list1 <- list(data1,data2)
ca <- lapply(list1, function(x) str_count(x, "a"))
t2 <- Map(split, list1, ca)
t3 <- transpose(t2)
> t3
$`1`
$`1`[[1]]
[1] "apple" "account"
$`1`[[2]]
[1] "tab" "cable"
$`2`
$`2`[[1]]
[1] "appreciate" "adapt"
$`2`[[2]]
[1] "tatabox"
$`3`
$`3`[[1]]
[1] "available"
$`3`[[2]]
[1] "banana"
It lost the "aaaaaaa" which in data2. How can I fix this problem?
I had find a solution:
data1 <- c("apple","appreciate","available","account","adapt")
data2 <- c("tab","banana","cable","tatabox","aaaaaaa","aaaaaaaaaaa")
list1 <- list(data1,data2)
ca <- lapply(list1, function(x) str_count(x, "a"))
k11<- flatten(Map(split, list1, ca))
k1<-split(k11, as.integer(names(k11)))
Citing the words of lionel: "transpose() treats lists of lists as implicitly rectangular tables". It was not designed for many edge cases like the current one. However, you could get the requried put by place the longer one at the begining: transpose(t2[2:1]).
However, this workaround could not generalize. I prefer the following way --- combine the sublists into a single list and split again:
> t3 <- do.call(c, t2)
> split(t3, names(t3))
$`1`
$`1`$`1`
[1] "apple" "account"
$`1`$`1`
[1] "tab" "cable"
$`2`
$`2`$`2`
[1] "appreciate" "adapt"
$`2`$`2`
[1] "tatabox"
$`3`
$`3`$`3`
[1] "available"
$`3`$`3`
[1] "banana"
$`7`
$`7`$`7`
[1] "aaaaaaa"
Edit
A function for named and unnamed input:
data1 <- c("apple","appreciate","available","account","adapt")
data2 <- c("tab","banana","cable","tatabox","aaaaaaa","aaaaaaaaaaa")
list1 <- list(data1,data2)
names(list1) <- c("atf","bdfs")
f <- function(x){
if(is.null(names(x))){
names(x) <- make.names(seq_along(x))
}
dtf <- stack(x)
res <- split(dtf, str_count(dtf$values, 'a'))
lapply(res, function(y) split(y$values, y$ind, drop = TRUE) )
}
f(list1)
We can also do this using pipe with purrr functions
map(list1, str_count, "a") %>%
map2(list1, ., split) %>%
flatten %>%
split(names(.))
#$`1`
#$`1`$`1`
#[1] "apple" "account"
#$`1`$`1`
#[1] "tab" "cable"
#$`2`
#$`2`$`2`
#[1] "appreciate" "adapt"
#$`2`$`2`
#[1] "tatabox"
#$`3`
#$`3`$`3`
#[1] "available"
#$`3`$`3`
#[1] "banana"
#$`7`
#$`7`$`7`
#[1] "aaaaaaa"

Summing a dataframe with lapply

Here I'm attempting to sum the columns for c1,c2,c3 and add total to a new column in res dataframe :
res <- data.frame("ID" = c(1,2), "c1" = c(1,2), "c2" = c(3,4), "c3" = c(5,6))
res_subset <- data.frame(res$c1 , res$c2 , res$c3)
tr <- t(res_subset)
s1 <- lapply(tr , function(x){
sum(x)
})
s1 contains :
s1
[[1]]
[1] 1
[[2]]
[1] 3
[[3]]
[1] 5
[[4]]
[1] 2
[[5]]
[1] 4
[[6]]
[1] 6
I take the transpose of the columns to be summed ( tr <- t(res_subset) ) as lapply executes function against each column but I'm attempting to execute function against row.
Is there an issue with how I take transpose as this appears to work for simpler example :
res1 <- data.frame("c1" = c(1,2), "c2" = c(3,4), "c3" = c(5,6))
lapply(res1 , function(x){
sum(x)
})
returns :
$c1
[1] 3
$c2
[1] 7
$c3
[1] 11
If I understood right what you need, just use rowSums() function.
res$sum <- rowSums(res[,2:4])
The function sum returns a scalar, which is not what you want here. Instead, col1 + col2 + ... gives the desired result. So you can use Reduce in combination with +:
res$sum <- Reduce(`+`, res[, c('c1','c2','c3')])
The + operator must be quoted with backticks, since we are using it as a function. (I think quoting with normal quotation marks is OK too.)
rowSums also works, but my understanding is that it will create an intermediate matrix, which is not efficient.

Creating strings from dataframe

My dataframe
x1 <- data.frame(C1 = letters[1:4], C3=1:4, C3=letters[11:14])
I need something a list where each listelement are two values from a row
x2 <- list(c("a", "1"), c("b", "2"), c("c", "3"), c("d", "4"))
Basically each two values from a row need to be a listelement so that I can process them later on!
I tried
lapply(X = x2, MARGIN = 1, FUN = paste, collapse = "")
But that did not give me the desired output!
Is this what you want?
paste0(x1[,1], x1[,2])
# [1] "a1" "b2" "c3" "d4"
How about:
as.list(paste0(x1[,1], x1[,2]))
# [[1]]
# [1] "a1"
#
# [[2]]
# [1] "b2"
#
# [[3]]
# [1] "c3"
#
# [[4]]
# [1] "d4"
It doesn't matter how many rows you have. You just need to specify the columns you want pasted into a string.
Here is a method using lapply:
lapply(1:nrow(x1), function(i) c(x1[i,1], x1[i,2]))
The result is
[[1]]
[1] "a" "1"
[[2]]
[1] "b" "2"
[[3]]
[1] "c" "3"
[[4]]
[1] "d" "4"
data
x1 <- data.frame(C1 = letters[1:4], C3=1:4, C3=letters[11:14],
stringsAsFactors = F)
Note that I used the stringsAsFactors = F argument to construct the data. If I didn't do this, then C1 and C3 would be factors, so I'd have to wrap x[i, 1] in as.character.
If there are multiple columns, we can use do.call
as.list(do.call(paste0, x1[-3]))

Resources