如何确定一个字符向量是否为有效的数字或整数向量

20

我正在尝试将嵌套的列表结构转换为数据框。列表类似于以下内容(这是使用httr包解析JSON读取的序列化数据)。

  myList <- list(object1 = list(w=1, x=list(y=0.1, z="cat")), object2 = list(w=NULL, x=list(z="dog")))

编辑:我的原始例子数据太简单了。实际数据是参差不齐的,意味着并非每个对象都存在所有变量,并且某些列表元素为空。我已编辑数据以反映这一点。

unlist(myList) 很好地递归地展开了列表,然后我可以使用 lapply 很好地展开所有对象。

  flatList <- lapply(myList, FUN= function(object) {return(as.data.frame(rbind(unlist(object))))}) 

最后,我可以使用plyr :: rbind.fill来合并它们。

  myDF <- do.call(plyr::rbind.fill, flatList)
  str(myDF)

  #'data.frame':    2 obs. of  3 variables:
  #$ w  : Factor w/ 2 levels "1","2": 1 2
  #$ x.y: Factor w/ 2 levels "0.1","0.2": 1 2
  #$ x.z: Factor w/ 2 levels "cat","dog": 1 2
问题在于 w 和 x.y 现在被解释为字符向量,在数据框中默认解析为因子。我认为 unlist() 是罪魁祸首,但我找不到另一种递归展开列表结构的方法。一种解决方法是后处理数据框,然后分配数据类型。最佳方法是确定向量是否为有效的数值或整数向量?

1
你可以使用 lapply(myList, as.data.frame) 创建 "flatList",因为有一个名为 as.data.frame.list 的方法;unlist 将你的 "list" 转换为原子向量,并将所有内容强制转换为 "character"。 - alexis_laz
是的!我最喜欢这个。plyr::rbind.fill(lapply(myList, as.data.frame))相当优雅!如果你回答的话,我会接受它。 - Andrew Barr
6个回答

20

正如在这里所讨论的那样,检查as.numeric是否返回NA值是一种简单的方法来检查一个字符数据中是否包含数字数据。现在你可以做如下操作:

myDF2 <- lapply(myDF, function(col) {
  if (suppressWarnings(all(!is.na(as.numeric(as.character(col)))))) {
    as.numeric(as.character(col))
  } else {
    col
  }
})
str(myDF2)
# List of 3
#  $ w  : num [1:2] 1 2
#  $ x.y: num [1:2] 0.1 0.2
#  $ x.z: Factor w/ 2 levels "cat","dog": 1 2

注意:我最终采用了这个解决方案。我所添加的唯一一件事是使用as.data.frame(myDF2)将其转换回数据框。 - Andrew Barr
3
通过编写suppressWarnings(any(is.na(as.numeric(as.character(col)))))),使那个冗长的逻辑表达式变得更简单。 - Robert Kubinec

13
当包含缺失值时,@josliber的原始函数无法工作(尽管对于示例数据它回答了问题)。@Amy M的函数应该可以工作,但需要加载Hmisc包。那么像这样的东西呢:
can.be.numeric <- function(x) {
    stopifnot(is.atomic(x) || is.list(x)) # check if x is a vector
    numNAs <- sum(is.na(x))
    numNAs_new <- suppressWarnings(sum(is.na(as.numeric(x))))
    return(numNAs_new == numNAs)
}

它会计算输入向量x中的NA,以及通过as.numeric(x)转换后输出中的NA并返回TRUE,如果该向量可以“安全”转换为numeric(即不添加任何其他NA值)。

更新:请求展示如何使用此函数。您需要对每列调用此函数,并仅转换可以转换为数字的列。

myDF2 <- lapply(myDF, function(col) {
  if (can.be.numeric(col)) {
    as.numeric(col)
  } else {
    col
  }
})
str(as.data.frame(myDF2))
# 'data.frame': 2 obs. of  3 variables:
#  $ w  : num  1 NA
#  $ x.y: num  0.1 NA
#  $ x.z: chr  "cat" "dog"

有可能展示一下如何使用它来识别和转换数据框中的数值列吗?看起来正是我需要的,但我不确定该如何应用它。 - Warren Spencer
1
更新以展示使用相同代码作为被接受答案的示例用法,但替换为该函数。 - Stefan Avey

1

我认为plyr::ldply相对于普通的基础R方法并没有任何优势:

 do.call(rbind, lapply(myList, data.frame) )
#-------------

        w x.y x.z
object1 1 0.1 cat
object2 2 0.2 dog

由于误导性地试图“压缩”数据而没有考虑其内在结构,出现了问题。

原来我的示例数据太简单了。实际数据是不规则的,意味着并非每个对象都存在所有变量。而且一些列表元素是NULL,在data.frame()中会产生错误。我编辑了问题以包含更好的示例数据。 - Andrew Barr
@AndrewBarr:在这种情况下,可以尝试使用类似于lapply(myList, function(x) as.data.frame(unlist(x, FALSE)))的代码。 - alexis_laz

1
您可以使用plyr::ldply:

ldply(myList,.fun=function(x)data.frame(x))

      .id w x.y x.z
1 object1 1 0.1 cat
2 object2 2 0.2 dog

0

如果您只想将被错误分类为字符的全数字向量转换为数字向量,您也可以使用Hmisc包中的all.is.numeric函数:

myDF2 <- lapply(myDF, Hmisc::all.is.numeric, what = "vector", extras = NA)

选择what = "vector"将把向量转换为数字,如果它只包含数字。 NAs或其他类型的缺失值将阻止转换,除非它们在上面的extras参数中指定。
但是请注意,如果应用于包含日期或POSIXct向量的整个数据框,这些向量也将被转换为数字。 要防止这种情况,您可以像下面一样将其包装在函数中:
catchNumeric <- function(dtcol) {
  require(Hmisc)
  if (is.character(dtcol)) {
    dtcol1 = all.is.numeric(dtcol, what = "vector", extras = NA)
  } else {
    dtcol1 = dtcol
  }
  return(dtcol1)
}

然后应用到你的数据框中:

myDF2 <- lapply(myDF, catchNumeric)

0
如果你有一个包含字符串的列表或向量,并且你希望仅将数字转换为数值型,其中一种可能的解决方案是:
catchNumeric <- function(mylist) {
  newlist <- suppressWarnings(as.numeric(mylist))
  mylist <- as.list(mylist)
  mylist[!is.na(newlist)] <- newlist[!is.na(newlist)]
  mylist
}

> catchNumeric(c("123", "c12", "abc", "123.12"))
[[1]]
[1] 123

[[2]]
[1] "c12"

[[3]]
[1] "abc"

[[4]]
[1] 123.12

> catchNumeric(list("123", "c12", "abc", "123.12"))
[[1]]
[1] 123

[[2]]
[1] "c12"

[[3]]
[1] "abc"

[[4]]
[1] 123.12

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接