Reputation: 125
Is it possible to scan a list of lists for elements with a certain name and change their datatype but retain their value?
As an example, the following list containing elements 'N' of class 'character' or 'numeric'
x = list(list(N=as.character(1)),
list(a=1,b=2,c="another element",N=as.character(5)),
list(a=2,b=2,N=as.character(7),c=NULL),
list(a=2,b=2,list(N=as.character(3))))
should then become:
x = list(list(N=as.numeric(1)),
list(a=1,b=2,c="another element",N=as.numeric(5)),
list(a=2,b=2,N=as.numeric(7),c=NULL),
list(a=2,b=2,list(N=as.numeric(3))))
To be clear, the solution should allow for deeper nesting, and respect the data type of fields with names other than "N". I have not been able to find a general solution that works for lists with an arbitrary structure.
I have tried something along the lines of the solution given in this post:
a <- as.relistable(x)
u <- unlist(a)
u[names(u) == "N"] <- as.numeric(u[names(u) == "N"])
relist(u, a)
Unfortunately the substitution does not work in it's current form. In addition, relist does not seem to work in case the list contains NULL elements.
Upvotes: 2
Views: 4802
Reputation: 125
The answer provided by marco sandri can be further generalised to:
is_num <- function(x) grepl("^[-]?[0-9]+[.]?[0-9]*|^[-]?[0-9]+[L]?|^[-]?[0-9]+[.]?[0-9]*[eE][0-9]+",x)
as_num <- function(x) {
if (is.null(x)||length(x) == 0) return(x)
if (class(x)=="list") return(lapply(x, as_num))
if (is.character(x) & is_num(x)) return(as.numeric(x))
return(x)
}
y <- as_num(z)
identical(y,z)
This solution also allows for list elements to contain numerical(0) and mixed datatypes such as 'data2005'.
Upvotes: 0
Reputation: 24252
A solution that works only on a list of lists containing numbers or strings with numbers:
x <- list(list(N=as.character(1)),
list(a=1,b=2,N=as.character(5)),
list(a=2,b=2,N=as.character(7)),
list(a=2,b=2))
y1 <- lapply(x, function(y) lapply(y, as.numeric))
y2 <- list(list(N=as.numeric(1)),
list(a=1,b=2,N=as.numeric(5)),
list(a=2,b=2,N=as.numeric(7)),
list(a=2,b=2))
identical(y1,y2)
# [1] TRUE
EDIT. Here is a more general code that works on nested lists of number and strings. It uses a recursive function as_num
and the list.apply
function of the rlist
package.
library(rlist)
x = list(list(N=as.character(1)),
list(a=1,b=2,c="another element",N=as.character(5)),
list(a=2,b=2,N=as.character(7),c=NULL),
list(a=2,b=2,list(N=as.character(3))))
# Test if the string contains a number
is_num <- function(x) grepl("[-]?[0-9]+[.]?[0-9]*|[-]?[0-9]+[L]?|[-]?[0-9]+[.]?[0-9]*[eE][0-9]+",x)
# A recursive function for numeric convertion of strings containing numbers
as_num <- function(x) {
if (!is.null(x)) {
if (class(x)!="list") {
y <- x
if (is.character(x) & is_num(x)) y <- as.numeric(x)
} else {
y <- list.apply(x, as_num)
}
} else {
y <- x
}
return(y)
}
y <- list.apply(x, as_num)
z = list(list(N=as.numeric(1)),
list(a=1,b=2,c="another element",N=as.numeric(5)),
list(a=2,b=2,N=as.numeric(7),c=NULL),
list(a=2,b=2,list(N=as.numeric(3))))
identical(y,z)
# [1] TRUE
Upvotes: 0
Reputation: 5335
Use lapply
to repeat the process over the list elements with a condition to check for your element of interest, so you don't inadvertently add elements to your sublists:
x <- lapply(x, function(i) {
if(length(i$N) > 0) {
i$N <- as.numeric(i$N)
}
return(i)
})
Upvotes: 1