Looping over a list to extract value in R - r

I'm new to R programming, trying to write a loop to extract a number from a list containing dataframes. However, I can't seem to subset the list correctly. This is probably basic, but its driving me nuts by now!
df1 <- tibble("a"=c(1,2,3,4,5), "b"=c(22,23,24,25,26), c=c("alpha", "beta","alpha", "beta", "alpha"))
df2 <- tibble("a"=c(1,2,3,4,5), "b"=c(22,23,24,25,26), c=c("alpha", "beta","alpha", "beta", "alpha"))
df3 <- tibble("a"=c(1,2,3,4,5), "b"=c(22,23,24,25,26), c=c("alpha", "beta","alpha", "beta", "alpha"))
df4 <- tibble("a"=c(1,2,3,4,5), "b"=c(22,23,24,25,26), c=c("alpha", "beta","alpha", "beta", "alpha"))
list <- c(df1, df2, df3, df4)
res <- vector("numeric",4)
df2[[2,2]]
for (i in list){
res[i] <- i[[2,2]]
}
I get this (and similar) error; "Error in i[[2, 2]] : incorrect number of subscripts"
Thankful for any help.

I suppose you're trying to pick out the second row of the second column in each dataframe that is part of a list:
library(tidyverse)
res <- list(df1 = tibble("a"=c(1,2,3,4,5), "b"=c(22,23,24,25,26),
c=c("alpha", "beta","alpha", "beta", "alpha")),
df2 = tibble("a"=c(1,2,3,4,5), "b"=c(22,23,24,25,26),
c=c("alpha", "beta","alpha", "beta", "alpha")),
df3 = tibble("a"=c(1,2,3,4,5), "b"=c(22,23,24,25,26),
c=c("alpha", "beta","alpha", "beta", "alpha")),
df4 = tibble("a"=c(1,2,3,4,5), "b"=c(22,23,24,25,26),
c=c("alpha", "beta","alpha", "beta", "alpha"))) %>%
# if you want the answer in a dataframe
purrr::map_df(~ .x %>%
dplyr::select(2) %>% # Pick the second column
dplyr::slice(2)) %>% # Pick the second row's value
unlist() # if you want it as a vector
> res
b1 b2 b3 b4
23 23 23 23

(Up front, I'm going to use mylist instead of list.)
Your data is not a list of frames as you appear to be trying to use it as.
mylist <- c(df1, df2, df3, df4)
str(mylist)
# List of 12
# $ a: num [1:5] 1 2 3 4 5
# $ b: num [1:5] 22 23 24 25 26
# $ c: chr [1:5] "alpha" "beta" "alpha" "beta" ...
# $ a: num [1:5] 1 2 3 4 5
# $ b: num [1:5] 22 23 24 25 26
# $ c: chr [1:5] "alpha" "beta" "alpha" "beta" ...
# $ a: num [1:5] 1 2 3 4 5
# $ b: num [1:5] 22 23 24 25 26
# $ c: chr [1:5] "alpha" "beta" "alpha" "beta" ...
# $ a: num [1:5] 1 2 3 4 5
# $ b: num [1:5] 22 23 24 25 26
# $ c: chr [1:5] "alpha" "beta" "alpha" "beta" ...
Instead, use list:
mylist <- list(df1, df2, df3, df4)
str(mylist)
# List of 4
# $ :Classes 'tbl_df', 'tbl' and 'data.frame': 5 obs. of 3 variables:
# ..$ a: num [1:5] 1 2 3 4 5
# ..$ b: num [1:5] 22 23 24 25 26
# ..$ c: chr [1:5] "alpha" "beta" "alpha" "beta" ...
# $ :Classes 'tbl_df', 'tbl' and 'data.frame': 5 obs. of 3 variables:
# ..$ a: num [1:5] 1 2 3 4 5
# ..$ b: num [1:5] 22 23 24 25 26
# ..$ c: chr [1:5] "alpha" "beta" "alpha" "beta" ...
# $ :Classes 'tbl_df', 'tbl' and 'data.frame': 5 obs. of 3 variables:
# ..$ a: num [1:5] 1 2 3 4 5
# ..$ b: num [1:5] 22 23 24 25 26
# ..$ c: chr [1:5] "alpha" "beta" "alpha" "beta" ...
# $ :Classes 'tbl_df', 'tbl' and 'data.frame': 5 obs. of 3 variables:
# ..$ a: num [1:5] 1 2 3 4 5
# ..$ b: num [1:5] 22 23 24 25 26
# ..$ c: chr [1:5] "alpha" "beta" "alpha" "beta" ...
for (i in mylist) { print(i[[2,2]]); break; }
# [1] 23
(Notional for loop just to demonstrate that i[[2,2]] does work after all.)
And to your intended use, some working examples:
res <- sapply(mylist, function(x) x[[2,2]])
res
# [1] 23 23 23 23
### identical results, might be more obscure (and perhaps less flexible)
res <- sapply(mylist, `[[`, c(2, 2))
### identical results, less R-idiomatic
res <- vector("numeric", 4)
for (i in seq_along(mylist)) { res[[i]] <- mylist[[i]][[2,2]]; }
res

Related

how to covert column type of data frames that are inside a list?

I have the following two data frames that in a list called df.list
df1 <- data.frame(name=c("a","b","c"),total=c("1","2","3"),other=c("100","200","300"))
df2 <- data.frame(name=c("d","e","f"),total=c("4","5","6"),other=c("100","200","300"))
df.list <- list(df1,df2)
[[1]]
name total other
1 a 1 100
2 b 2 200
3 c 3 300
[[2]]
name total other
1 d 4 100
2 e 5 200
3 f 6 300
I want to be able to go through each data frame in the list and covert the total and other columns to be numeric, and assign it back to df.list
I tried the following but it does not seem to work
lapply(df.list, function(x) as.numeric(x[2:3]))
We may use type.convert directly on the list
df.list2 <- type.convert(df.list, as.is = TRUE)
-checking the structure
str(df.list2)
List of 2
$ :'data.frame': 3 obs. of 3 variables:
..$ name : chr [1:3] "a" "b" "c"
..$ total: int [1:3] 1 2 3
..$ other: int [1:3] 100 200 300
$ :'data.frame': 3 obs. of 3 variables:
..$ name : chr [1:3] "d" "e" "f"
..$ total: int [1:3] 4 5 6
..$ other: int [1:3] 100 200 300
If we want to loop, then as.integer/as.numeric works on vectors. So, we need to loop again
df.list2 <- lapply(df.list, function(x) {
x[2:3] <- lapply(x[2:3], as.integer)
x})
Or maybe this one:
library(purrr)
df.list %>%
map(., ~mutate(.x, across(c(other, total), ~as.numeric(.x)))) %>%
str()
List of 2
$ :'data.frame': 3 obs. of 3 variables:
..$ name : chr [1:3] "a" "b" "c"
..$ total: num [1:3] 1 2 3
..$ other: num [1:3] 100 200 300
$ :'data.frame': 3 obs. of 3 variables:
..$ name : chr [1:3] "d" "e" "f"
..$ total: num [1:3] 4 5 6
..$ other: num [1:3] 100 200 300
you can create a function that works for each data frame such as the following functional_as_numeric() and then apply to each element in the list with map() from {purrr}. Personally I find {purrr}'s interface more consistent and easier to follow than the traditional _apply() functions.
library(purrr)
functional_as_numeric <- function(df) {
df %>% mutate(
total = as.numeric(total),
other = as.numeric(other)
)
}
df.list.result <- df.list %>%
purrr::map(functional_as_numeric)
str(df.list.result)
List of 2
$ :'data.frame': 3 obs. of 3 variables:
..$ name : chr [1:3] "a" "b" "c"
..$ total: num [1:3] 1 2 3
..$ other: num [1:3] 100 200 300
$ :'data.frame': 3 obs. of 3 variables:
..$ name : chr [1:3] "d" "e" "f"
..$ total: num [1:3] 4 5 6
..$ other: num [1:3] 100 200 300

bind list of lists based on the lists location

I have some data similar to mainList below.
List of 2
$ :List of 3
..$ :List of 1
.. ..$ :'data.frame': 3 obs. of 2 variables:
.. .. ..$ col1: chr [1:3] "1" "2" "3"
.. .. ..$ col2: chr [1:3] "a" "b" "c"
..$ :List of 1
.. ..$ :'data.frame': 3 obs. of 2 variables:
.. .. ..$ col1: chr [1:3] "3" "7" "4"
.. .. ..$ col2: chr [1:3] "e" "d" "g"
..$ :List of 1
.. ..$ :'data.frame': 3 obs. of 2 variables:
.. .. ..$ col1: chr [1:3] "2" "7" "4"
.. .. ..$ col2: chr [1:3] "l" "o" "i"
$ :List of 3
..$ :List of 1
.. ..$ :'data.frame': 3 obs. of 2 variables:
.. .. ..$ col1: chr [1:3] "8" "3" "4"
.. .. ..$ col2: chr [1:3] "r" "t" "q"
..$ :List of 1
.. ..$ :'data.frame': 3 obs. of 2 variables:
.. .. ..$ col1: chr [1:3] "7" "5" "2"
.. .. ..$ col2: chr [1:3] "h" "w" "p"
..$ :List of 1
.. ..$ :'data.frame': 3 obs. of 2 variables:
.. .. ..$ col1: chr [1:3] "9" "3" "6"
.. .. ..$ col2: chr [1:3] "x" "y" "z"
I want to merge, or bind the lists based on the lists location in the list of lists.
That is, I want to merge splt1 with splt11, and then merge splt2 with splt22 and finally splt3 with splt33.
So it would take the first data frame from the first List of 3 and merge it with the first data frame from the second List of 3.
This does not get what I want
mainList %>%
map(., ~bind_rows(., .id = "split"))
Since all of the splits are merged into a single data frame (I want them kept separate).
Data:
splt1 <- list(
data.frame(
col1 = c("1", "2", "3"),
col2 = c("a", "b", "c")
)
)
splt2 <- list(
data.frame(
col1 = c("3", "7", "4"),
col2 = c("e", "d", "g")
)
)
splt3 <- list(
data.frame(
col1 = c("2", "7", "4"),
col2 = c("l", "o", "i")
)
)
nestList1 <- list(
splt1,
splt2,
splt3
)
splt11 <- list(
data.frame(
col1 = c("8", "3", "4"),
col2 = c("r", "t", "q")
)
)
splt22 <- list(
data.frame(
col1 = c("7", "5", "2"),
col2 = c("h", "w", "p")
)
)
splt33 <- list(
data.frame(
col1 = c("9", "3", "6"),
col2 = c("x", "y", "z")
)
)
nestList2 <- list(
splt11,
splt22,
splt33
)
mainList <- list(
nestList1,
nestList2
)
EDIT:
Screenshot of the lists:
I am trying to bind together all of the split's, i.e.
split1 will contain the results from 08001, 08003, 08005 ... 0801501 for each of the lists in catalunya_madrid.
split2 will contain the same results 08001, 08003, 08005 ... 0801501
and so on.
EDIT2:
# Function to invert the list structure
invertListStructure <- function(ll) {
nms <- unique(unlist(lapply(ll, function(X) names(X))))
ll <- lapply(ll, function(X) setNames(X[nms], nms))
ll <- apply(do.call(rbind, ll), 2, as.list)
lapply(ll, function(X) X[!sapply(X, is.null)])
}
invertedList <- map(analysis, ~invertListStructure(.) %>%
map(., ~bind_rows(.x, .id = "MITMA")))
You can use purrr::transpose() to group list elements with the same location (i.e. the first element in list 1 with the first element in list 2 and list 3 and so on) for any number of lists. In your case, transpose will convert 592 lists of 216 into 216 lists of 592, each properly titled. With transpose, l[[x]][[y]] becomes l[[y]][[x]].
library(tidyverse)
mainList %>% purrr::transpose() %>%
map(function(x) {
flatten(x) %>% bind_rows(.id = 'id')
})
# $splt1
# id col1 col2
# 1 1 1 a
# 2 1 2 b
# 3 1 3 c
# 4 2 8 r
# 5 2 3 t
# 6 2 4 q
#
# $splt2
# id col1 col2
# 1 1 3 e
# 2 1 7 d
# 3 1 4 g
# 4 2 7 h
# 5 2 5 w
# 6 2 2 p
#
# $splt3
# id col1 col2
# 1 1 2 l
# 2 1 7 o
# 3 1 4 i
# 4 2 9 x
# 5 2 3 y
# 6 2 6 z
Note that you only need to flatten if the data.frame is in a list of length 1, by itself. If you have a list of data.frames (as opposed to a list of lists, each of which contains one data.frame, as in your example data), you can ignore the flatten() command and just bind the rows.
Your example dataset doesn't quite match your actual data, but if you make a list of two mainLists, it's closer. These types of operations are heavily dependent on the structure of the data, though, so I can't be sure this is what you need. All you need to do here is add a subscript.
mainList2 <- list(mainList, mainList) # First is Madrid, second is Valencia
# Operations are done on Madrid only
mainList2[[1]] %>%
transpose() %>%
map(function(x) {
flatten(x) %>% bind_rows(.id = 'id')
})
If you want to do this for both elements in mainList2, you can wrap the whole thing in map.
mainList2 %>% map(function(x) {
transpose(x) %>%
map(function(x) {
flatten(x) %>% bind_rows(.id = 'id')
})
})
You can combine the pairs in following way :
Map(rbind, unlist(mainList[[1]], recursive = FALSE),
unlist(mainList[[2]], recursive = FALSE))
Or using purrr you can also add an id column easily.
library(purrr)
map2(mainList[[1]] %>% flatten,
mainList[[2]] %>% flatten, dplyr::bind_rows, .id = 'id')
#[[1]]
# id col1 col2
#1 1 1 a
#2 1 2 b
#3 1 3 c
#4 2 8 r
#5 2 3 t
#6 2 4 q
#[[2]]
# id col1 col2
#1 1 3 e
#2 1 7 d
#3 1 4 g
#4 2 7 h
#5 2 5 w
#6 2 2 p
#[[3]]
# id col1 col2
#1 1 2 l
#2 1 7 o
#3 1 4 i
#4 2 9 x
#5 2 3 y
#6 2 6 z

R - Appending multiple level 2 elements to each level 1 element of a list by name

I have a list called master, which contains three IDs:
master = list(p1 = list(id = 'abc'), p2 = list(id = 'def'), p3 = list(id = 'ghi'))
str(master)
List of 3
$ p1:List of 1
..$ id: chr "abc"
$ p2:List of 1
..$ id: chr "def"
$ p3:List of 1
..$ id: chr "ghi"
To each level 1 element of this list, I would like to append the corresponding value and radius elements from the val and rad lists:
val = list(p1 = list(value = 5), p3 = list(value = 8))
str(val)
List of 2
$ p1:List of 1
..$ value: num 5
$ p3:List of 1
..$ value: num 8
rad = list(p1 = list(radius = 2), p2 = list(radius = 10))
str(rad)
List of 2
$ p1:List of 1
..$ radius: num 2
$ p2:List of 1
..$ radius: num 10
I have to be careful to match the elements by name because val and rad do not have the same structure as master, i.e. val is missing a slot for p2 and rad is missing a slot for p3.
I can use the following to partially achieve the desired result:
master_final = lapply(X=names(master),function(x, master, val, rad) c(master[[x]], val[[x]], rad[[x]]), master, val, rad)
str(master_final)
List of 3
$ :List of 3
..$ id : chr "abc"
..$ value : num 5
..$ radius: num 2
$ :List of 2
..$ id : chr "def"
..$ radius: num 10
$ :List of 2
..$ id : chr "ghi"
..$ value: num 8
But I would like each element of the resulting list to have the same structure, i.e. an id, value and radius slot. I am not sure how to do this in a way that generalises to any number of lists? I don't like having to write [[x]] for each list in the lapply function: function(x, master, val, rad) c(master[[x]], val[[x]], rad[[x]]).
One way would be to convert the lists to dataframe and do a merge based on list name. We can then split the dataframe based on list_name.
df1 <- Reduce(function(x, y) merge(x, y, all = TRUE, by = "ind"),
list(stack(master), stack(val),stack(rad)))
names(df1) <- c("list_name", "id", "value", "radius")
lapply(split(df1[-1], df1$list_name), as.list)
#$p1
#$p1$id
#[1] "abc"
#$p1$value
#[1] 5
#$p1$radius
#[1] 2
#$p2
#$p2$id
#[1] "def"
#$p2$value
#[1] NA
#$p2$radius
#[1] 10
#$p3
#$p3$id
#[1] "ghi"
#$p3$value
#[1] 8
#$p3$radius
#[1] NA
This keeps NA values in the list as it is, if we want to remove them the code becomes a bit ugly.
lapply(split(df1[-1], df1$list_name), function(x)
{inds <- !is.na(x); as.list(setNames(x[inds], names(x)[inds]))})
You could first group all your lists in L and run
L = list(master,val,rad)
lapply(names(master),function(x) unlist(lapply(L,"[[",x)))
[[1]]
id value radius
"abc" "5" "2"
[[2]]
id radius
"def" "10"
[[3]]
id value
"ghi" "8"
Here is one way with tidyverse
library(dplyr)
library(purrr)
out <- list(master, rad, val) %>%
transpose %>%
map(flatten)
str(out)
#List of 3
# $ p1:List of 3
# ..$ id : chr "abc"
# ..$ radius: num 2
# ..$ value : num 5
# $ p2:List of 2
# ..$ id : chr "def"
# ..$ radius: num 10
# $ p3:List of 2
# ..$ id : chr "ghi"
# ..$ value: num 8

Name columns with Dataframe name in a list of Dataframes

Objective: Change colname of dataframes in a list of dataframes to the name of each dataframe.
I have some issues when dealing with list and dataframes regarding its name. I have prepared this example to clarify. Hope it is not a mess.
Data:
df1 <- data.frame(A = 1, B = 2, C = 3)
df2 <- data.frame(A = 3, B = 3, C = 2)
dfList <- list(df1,df2)
Output:
> str(dfList)
List of 2
$ :'data.frame': 1 obs. of 3 variables:
..$ A: num 1
..$ B: num 2
..$ C: num 3
$ :'data.frame': 1 obs. of 3 variables:
..$ A: num 3
..$ B: num 3
..$ C: num 2
> names(dfList)
NULL
> names(dfList$df1)
NULL
> names(dfList$df2)
NULL
Manually Input names:
names(dfList) <- c("df1", "df2")
dfList <- lapply(dfList, setNames, c("A", "B", "C"))
Which yields:
> str(dfList)
List of 2
$ df1:'data.frame': 1 obs. of 3 variables:
..$ A: num 1
..$ B: num 2
..$ C: num 3
$ df2:'data.frame': 1 obs. of 3 variables:
..$ A: num 3
..$ B: num 3
..$ C: num 2
> names(dfList)
[1] "df1" "df2"
> names(dfList$df1)
[1] "A" "B" "C"
> names(dfList$df2)
[1] "A" "B" "C"
Desired Solution:
WishedList <- dfList
WishedList[[1]] <- setNames(WishedList[[1]], c("A", "B", "df1"))
WishedList[[2]] <- setNames(WishedList[[2]], c("A", "B", "df2"))
Output solution:
> str(WishedList)
List of 2
$ df1:'data.frame': 1 obs. of 3 variables:
..$ A : num 1
..$ B : num 2
..$ df1: num 3
$ df2:'data.frame': 1 obs. of 3 variables:
..$ A : num 3
..$ B : num 3
..$ df2: num 2
> names(WishedList)
[1] "df1" "df2"
> names(WishedList$df1)
[1] "A" "B" "df1"
> names(WishedList$df2)
[1] "A" "B" "df2"
MyTry:
TryList1 <- lapply(dfList, function(x) setNames(x, c("A", "B", quote(x))))
str(TryList1)
List of 2
$ df1:'data.frame': 1 obs. of 3 variables:
..$ A: num 1
..$ B: num 2
..$ x: num 3
$ df2:'data.frame': 1 obs. of 3 variables:
..$ A: num 3
..$ B: num 3
..$ x: num 2
Doubts:
1) Why when creating the file the names both of the dataframes and of the cols of the dataframes are not included in the list?
2) quote(x) with a single dataframe works. Why not in the list?
> df1 <- data.frame(A = 1, B = 2, C = 3)
> df1 <- setNames(df1, c("A", "B", quote(df1)))
> names(df1)
[1] "A" "B" "df1"
Thank you very much!
Here's a slightly different approach:
df1 <- data.frame(A = 1, B = 2, C = 3)
df2 <- data.frame(A = 3, B = 3, C = 2)
dfList <- list(df1,df2)
names(dfList) <- c("df1", "df2")
Map(function(df, dfn) {names(df)[3] <- dfn; df}, dfList, names(dfList))
#$df1
# A B df1
#1 1 2 3
#
#$df2
# A B df2
#1 3 3 2
You could alternatively use setNames(df, c("A", "B", dfn)) inside the mapply function.
A note on OP's trial: The documentation for quote states:
quote simply returns its argument.
That's why when you use quote(x) inside lapply, it simply returns the character x.
We can lapply() over names(dfList) instead of dfList:
lapply(names(dfList), function(dfn) {
df <- dfList[[dfn]]
names(df)[3] <- dfn
df
})
# [[1]]
# A B df1
# 1 1 2 3
#
# [[2]]
# A B df2
# 1 3 3 2
There's a convenience function in purrr that maps over a list and its names simultaneously:
library(purrr)
imap(dfList, ~ {
names(.x)[3] <- .y
.x
})
# $df1
# A B df1
# 1 1 2 3
#
# $df2
# A B df2
# 1 3 3 2
Or if you're after a short one-liner and don't mind hard-coding "A" and "B":
imap(dfList, ~ setNames(.x, c("A", "B", .y)))
(NB: Essentially those are just variations around Docendo discimus' answer).
Also, not your expected output but maybe of interest for you:
dplyr::bind_rows(dfList, .id = "origin")
# origin A B C
# 1 df1 1 2 3
# 2 df2 3 3 2
Or:
bind_rows(map(dfList, select, -C), .id = "C")
# C A B
# 1 df1 1 2
# 2 df2 3 3

How to use tryCatch when the outcome is not distinct

I have a function that I have made which returns a dataframe with two variables. As a simple example lets have:
test <- function(x) {y <- matrix( 5 , nrow= x , ncol = 2)
z<- data.frame(y)
return(z) }
I want to find out on which x values this function gives an error. (on our example I think for negative values, but I just want to convey the concept.) So I try:
z <- rep(0)
testnumbers <- c(0,1,2,3,4,-1,5)
for (i in 1:length(testnumbers)) {
tempo <- tryCatch( testfun(testnumbers[i]) , error= function(e) return(0) )
if (tempo == 0 ) z[i] <- {testnumbers[i] next}
}
What is wrong with my process and how can I find where in my function does not work?
If you're looking to run all of the testnumbers regardless of any of them failing, I suggest a slightly different tact.
Base R
This borrows from Rui's use of inherits which is more robust and unambiguous. It goes one step further by preserving not just which one had the error, but the actual error text as well:
testfun <- function(x) {
y <- matrix(5, nrow = x, ncol = 2)
z <- as.data.frame(y)
z
}
testnumbers <- c(0, 1, 2, 3, 4, -1, 5)
rets <- setNames(
lapply(testnumbers, function(n) tryCatch(testfun(n), error=function(e) e)),
testnumbers
)
sapply(rets, inherits, "error")
# 0 1 2 3 4 -1 5
# FALSE FALSE FALSE FALSE FALSE TRUE FALSE
Filter(function(a) inherits(a, "error"), rets)
# $`-1`
# <simpleError in matrix(5, nrow = x, ncol = 2): invalid 'nrow' value (< 0)>
(The setNames(lapply(...), ...) is because the inputs are numbers so sapply(..., simplify=F) did not preserve the names, something I thought was important.)
All of this falls in line with what some consider good practice: if you're doing one function to a lot of "things", then do it in a list, and therefore in one of the *apply functions.
tidyverse
There is a function in purrr that formalizes this a little: safely, which returns a function wrapped around its argument. For instance:
library(purrr)
safely(testfun)
# function (...)
# capture_error(.f(...), otherwise, quiet)
# <environment: 0x0000000015151d90>
It is returning a function that can then be passed. A one-time call would look like one of the following:
safely(testfun)(0)
# $result
# [1] V1 V2
# <0 rows> (or 0-length row.names)
# $error
# NULL
testfun_safe <- safely(testfun)
testfun_safe(0)
# $result
# [1] V1 V2
# <0 rows> (or 0-length row.names)
# $error
# NULL
To use it here, you can do:
rets <- setNames(
lapply(testnumbers, safely(testfun)),
testnumbers
)
str(rets[5:6])
# List of 2
# $ 4 :List of 2
# ..$ result:'data.frame': 4 obs. of 2 variables:
# .. ..$ V1: num [1:4] 5 5 5 5
# .. ..$ V2: num [1:4] 5 5 5 5
# ..$ error : NULL
# $ -1:List of 2
# ..$ result: NULL
# ..$ error :List of 2
# .. ..$ message: chr "invalid 'nrow' value (< 0)"
# .. ..$ call : language matrix(5, nrow = x, ncol = 2)
# .. ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"
Filter(Negate(is.null), sapply(rets, `[[`, "error"))
# $`-1`
# <simpleError in matrix(5, nrow = x, ncol = 2): invalid 'nrow' value (< 0)>
and to get to the results of all runs (including the errant one):
str(sapply(rets, `[[`, "result"))
# List of 7
# $ 0 :'data.frame': 0 obs. of 2 variables:
# ..$ V1: num(0)
# ..$ V2: num(0)
# $ 1 :'data.frame': 1 obs. of 2 variables:
# ..$ V1: num 5
# ..$ V2: num 5
# $ 2 :'data.frame': 2 obs. of 2 variables:
# ..$ V1: num [1:2] 5 5
# ..$ V2: num [1:2] 5 5
# $ 3 :'data.frame': 3 obs. of 2 variables:
# ..$ V1: num [1:3] 5 5 5
# ..$ V2: num [1:3] 5 5 5
# $ 4 :'data.frame': 4 obs. of 2 variables:
# ..$ V1: num [1:4] 5 5 5 5
# ..$ V2: num [1:4] 5 5 5 5
# $ -1: NULL
# $ 5 :'data.frame': 5 obs. of 2 variables:
# ..$ V1: num [1:5] 5 5 5 5 5
# ..$ V2: num [1:5] 5 5 5 5 5
or just the results without the failed run:
str(Filter(Negate(is.null), sapply(rets, `[[`, "result")))
# List of 6
# $ 0:'data.frame': 0 obs. of 2 variables:
# ..$ V1: num(0)
# ..$ V2: num(0)
# $ 1:'data.frame': 1 obs. of 2 variables:
# ..$ V1: num 5
# ..$ V2: num 5
# $ 2:'data.frame': 2 obs. of 2 variables:
# ..$ V1: num [1:2] 5 5
# ..$ V2: num [1:2] 5 5
# $ 3:'data.frame': 3 obs. of 2 variables:
# ..$ V1: num [1:3] 5 5 5
# ..$ V2: num [1:3] 5 5 5
# $ 4:'data.frame': 4 obs. of 2 variables:
# ..$ V1: num [1:4] 5 5 5 5
# ..$ V2: num [1:4] 5 5 5 5
# $ 5:'data.frame': 5 obs. of 2 variables:
# ..$ V1: num [1:5] 5 5 5 5 5
# ..$ V2: num [1:5] 5 5 5 5 5
You were actually quite close. I'm not sure what did the trick in the end but I
Changed the 1:length(testnumbers) as this is unneccessary
Changed return(0) to a character
Wrapped your if in another if as it kept failing if the length was larger than 1 or could not be assessed.
Then you get the correct results. You could try and change the code bit by bit to see what was wrong.
test <- function(x) {y <- matrix( 5 , nrow = x , ncol = 2)
z<- data.frame(y)
return(z) }
errored <- numeric()
testnumbers <- c(0,1,2,3,4,-1,5)
for (i in testnumbers) {
tempo <- tryCatch(test(i), error = function(e) "error")
if (length(tempo) == 1) {
if (tempo == "error") errored <- c(errored, i)
}
}
errored
> -1
You need tryCatch to return the error, not zero.
testfun <- function(x) {
y <- matrix(5, nrow = x, ncol = 2)
z <- as.data.frame(y)
z
}
testnumbers <- c(0, 1, 2, 3, 4, -1, 5)
z <- numeric(length(testnumbers))
for (i in seq_along(testnumbers)) {
tempo <- tryCatch(testfun(testnumbers[i]), error = function(e) e)
if (inherits(tempo, "error")) {
z[i] <- testnumbers[i]
}
}
z
#[1] 0 0 0 0 0 -1 0
Also,
In order to coerce a matrix to data.frame use as.data.frame.
I have removed the calls to return since the last value of a function is its return value.
rep(0) is the same as just 0, replaced by numeric(length(testnumbers)).
seq_along(testnumbers) is always better than 1:length(testnumbers). Try it with testnumbers of length zero and see what happens.

Resources