首页 文章

为什么as.matrix在将数字转换为字符时会添加额外的空格?

提问于
浏览
29

如果在带有字符和数字列的data.frame上使用apply over rows,则apply会在内部使用as.matrix将data.frame转换为仅字符 . 但如果数字列由不同长度的数字组成,则.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添加的额外空格是有用的?

5 回答

  • 1

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

    详情

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

    ‘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"
    
  • 0

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

    如果只有原子列和任何非(数字/逻辑/复杂)列,则将数据帧的方法返回字符矩阵,将as.vector应用于因子并格式化为其他非字符列 .

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

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

    你需要做的是通过 trim arugment:

    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)
    
  • 23

    as.matrix 内部调用 format

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

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

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

    但是,您无法将此参数提供给 as.matrix .

  • 9

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

    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"
    

相关问题