【问题标题】:Why does as.matrix add extra spaces when converting numeric to character?为什么 as.matrix 在将数字转换为字符时会添加额外的空格?
【发布时间】:2013-03-15 03:41:36
【问题描述】:

如果您对具有字符和数字列的 data.frame 上的行使用 apply,则 apply 在内部使用 as.matrix 将 data.frame 转换为仅字符。但是如果数字列由不同长度的数字组成,as.matrix 会添加空格以匹配最高/“最长”的数字。

一个例子:

df <- data.frame(id1=c(rep("a",3)),id2=c(100,90,8), stringsAsFactors = FALSE) 
df
##   id1 id2
## 1   a 100
## 2   a  90
## 3   a   8
as.matrix(df)
##      id1 id2  
## [1,] "a" "100"
## [2,] "a" " 90"
## [3,] "a" "  8"

我本来希望结果是:

     id1 id2  
[1,] "a" "100"
[2,] "a" "90"
[3,] "a" "8"

为什么要多出空格?

在 data.frame 上使用 apply 时,它们可能会产生意想不到的结果:

myfunc <- function(row){
  paste(row[1], row[2], sep = "")
}
> apply(df, 1, myfunc)
[1] "a100" "a 90" "a  8"
> 

虽然循环给出了预期的结果。

> for (i in 1:nrow(df)){
  print(myfunc(df[i,]))
}
[1] "a100"
[1] "a90"
[1] "a8"

> paste(df[,1], df[,2], sep = "")
[1] "a100" "a90"  "a8"  

有没有用 as.matrix 添加的多余空格有用的情况?

【问题讨论】:

  • 感谢您的回答。我现在对 as.matrix 和 format 有了更好的理解,并学习了一些新技巧。我更新了我的问题,因为我也在寻找空间背后的理由,因为它们似乎妨碍了。
  • 我在使用内部调用as.matrixapply 时遇到了这个确切的问题。

标签: r


【解决方案1】:

这是因为在as.matrix.data.frame 方法中转换非数字数据的方式。有一个简单的解决方法,如下所示。

详情

?as.matrix 注意到转换是通过format() 完成的,并且在这里添加了额外的空格。具体来说,?as.matrix详细信息 部分中有此内容:

 ‘as.matrix’ is a generic function.  The method for data frames
 will return a character matrix if there is only atomic columns and
 any non-(numeric/logical/complex) column, applying ‘as.vector’ to
 factors and ‘format’ to other non-character columns.  Otherwise,
 the usual coercion hierarchy (logical < integer < double <
 complex) will be used, e.g., all-logical data frames will be
 coerced to a logical matrix, mixed logical-integer will give a
 integer matrix, etc.

?format 还指出

字符串用空格填充到最宽的显示宽度。

考虑这个说明行为的例子

> format(df[,2])
[1] "100" " 90" "  8"
> nchar(format(df[,2]))
[1] 3 3 3

format 没有 trim 那样工作:

trim: logical; if ‘FALSE’, logical, numeric and complex values are
      right-justified to a common width: if ‘TRUE’ the leading
      blanks for justification are suppressed.

例如

> format(df[,2], trim = TRUE)
[1] "100" "90"  "8"

但无法将此参数传递给as.matrix.data.frame 方法。

解决方法

解决此问题的一种方法是通过sapply 手动应用format()。在那里你可以传入trim = TRUE

> sapply(df, format, trim = TRUE)
     id1 id2  
[1,] "a" "100"
[2,] "a" "90" 
[3,] "a" "8"

或者,使用vapply,我们可以说明我们期望返回的内容(这里的字符向量长度为​​3 [nrow(df)]):

> vapply(df, format, FUN.VALUE = character(nrow(df)), trim = TRUE)
     id1 id2  
[1,] "a" "100"
[2,] "a" "90" 
[3,] "a" "8"

【讨论】:

  • 大概,这里的基本原理是使用format 比对字符和日期列做不同的事情要简单,对吧?
  • @joran format 有许多开箱即用的不同类的方法。因此它正在对数字和日期对象做一些不同的事情(由于format 上的方法调度)。一旦确定存在非数字数据,唯一的解决方案就是生成一个字符矩阵,format 是最简单的方法。
  • 难道不能简单地将as.matrix.data.frame 中的format(xj) 更改为format(xj,...) 吗?这将允许我们将trim=TRUE 传递给format
  • 是的,我知道。我想我的意思是我不清楚为什么formatas.character 更受欢迎(它也有很多开箱即用的方法)。
  • @Joran - 我推测这是 format 通常有更多的方法,并且可能向后兼容 S (S-PLU)?
【解决方案2】:

as.matrix 在内部调用format

 > format(df$id2)
[1] "100" " 90" "  8"

这就是多余空间的来源。 format 有一个额外的参数 trim 来删除那些:

> format(df$id2, trim = TRUE)
[1] "100" "90"  "8"  

但是您不能将此参数提供给as.matrix

【讨论】:

    【解决方案3】:

    这似乎有点奇怪。在手册 (?as.matrix) 中,它解释了调用 format 来转换为字符矩阵:

    如果有的话,数据框的方法会返回一个字符矩阵 只有原子列和任何非(数字/逻辑/复杂)列, 将 as.vector 应用于因子,将 format 应用于其他非字符 列。

    你可以看到,如果你直接调用format,它会做as.matrix做的事情:

    format(df$id2)
    [1] "100" " 90" "  8"
    

    您需要做的是传递trim 参数:

    format(df$id2,trim=TRUE)
    [1] "100" "90"  "8" 
    

    但是,不幸的是,as.matrix.data.frame 函数不允许您这样做。

    else if (non.numeric) {
        for (j in pseq) {
            if (is.character(X[[j]])) 
                next
            xj <- X[[j]]
            miss <- is.na(xj)
            xj <- if (length(levels(xj))) 
                as.vector(xj)
            else format(xj) # This could have ... as an argument
            # else format(xj,...)
            is.na(xj) <- miss
            X[[j]] <- xj
        }
    }
    

    所以,您可以修改as.data.frame.matrix。但是,我认为将其包含在基础中会是一个不错的功能添加。

    但是,一个快速的解决方案是:

    as.matrix(data.frame(lapply(df,as.character)))
         id1 id2  
    [1,] "a" "100"
    [2,] "a" "90" 
    [3,] "a" "8"  
    # As mentioned in the comments, this also works:
    sapply(df,as.character)
    

    【讨论】:

    • +1 用于解决方法。请注意,考虑到sapply 所做的简化性质,它可以简化为sapply(df, format, trim = TRUE)。为了更加确定,您可以改用vapply 并指定返回对象的类型。
    • as.matrix() 在这里完全是多余的 - sapply 正在返回一个矩阵。试试看:class(sapply(df,as.character))
    【解决方案4】:

    之前的答案已经解释了这种行为的原因,但我想提供另一种规避方法:

    df <- data.frame(id1=c(rep("a",3)),id2=c(100,90,8), stringsAsFactors = FALSE) 
    do.call(cbind,df)
         id1 id2  
    [1,] "a" "100"
    [2,] "a" "90" 
    [3,] "a" "8"  
    

    请注意,如果使用stringsAsFactors = TRUE,这将不起作用,因为因子水平会转换为数字。

    【讨论】:

      【解决方案5】:

      另一种解决方案:如果您不介意下载软件包,trimWhiteSpace(x)(来自 limma R pckg)也可以完成这项工作。

      source("https://bioconductor.org/biocLite.R")
      biocLite("limma")
      library(limma)
      df <- data.frame(id1=c(rep("a",3)),id2=c(100,90,8), stringsAsFactors = FALSE) 
      as.matrix(df)
       id1 id2  
      [1,] "a" "100"
      [2,] "a" " 90"
      [3,] "a" "  8"
      
      trimWhiteSpace(as.matrix(df))
       id1 id2  enter code here
      [1,] "a" "100"
      [2,] "a" "90" 
      [3,] "a" "8"
      

      【讨论】:

        猜你喜欢
        • 2021-04-06
        • 2011-04-07
        • 1970-01-01
        • 2020-11-25
        • 2018-08-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多