将函数应用于数据框中的每一列,观察每一列的现有数据类型。

62

我正在尝试获取一个大数据框中每列的最小/最大值,作为了解我的数据的一部分。我的第一次尝试是:

apply(t,2,max,na.rm=1)

它将所有内容都视为字符向量,因为前几列是字符类型。因此,某些数字列的最大值会显示为" -99.5"
然后我尝试了这个:
sapply(t,max,na.rm=1)

但是它抱怨 max 对于因子没有意义。(lapply 也一样。)让我困惑的是,apply 认为 max 在因子中完全有意义,例如它对于第一列返回了 "ZEBRA"。

顺便说一句,我看了一下 在向量上使用sapply处理POSIXct,其中一个答案说 "当你使用 sapply 时,你的对象会被强制转换为数字,..."。这是发生在我身上的事吗?如果是,是否有另一种不强制转换的 apply 函数可用?毕竟数据框类型的一个关键特征是每列可以是不同的类型。


2
我会只传递那些具有有意义的数据类型的列以计算您的统计数据。 - Roman Luštrik
@Roman 谢谢,事实上昨天我就是这样做的,因为在这种情况下我已经有了一个数字列名列表。但对于大型数据框来说,这可能会变得耗时。 - Darren Cook
1
您可以找到数字列并自动化处理过程。 - Roman Luštrik
作为一种方法,如果您使用stringsAsFactors = FALSE读取文件,并在使用apply之前将列设置为它们应该属于的类别,例如日期作为as.POSIXct,数字作为numeric等,那么这比在sapply内部进行强制转换更容易?@DarrenCook - vagabond
这是一个非常好的问题,目前还没有一种令人满意的方法可以将函数应用于包含混合类型的数据框。唯一能保留每列类型的解决方案是使用for循环;对于数据框,没有lapply方法。 - Ben Rollert
8个回答

49
如果它是一个“有序因子”,情况就会有所不同。这并不是说我喜欢“有序因子”,我不喜欢,只是说一些关系被定义为“有序因子”,而对于“因子”则未定义。因子被认为是普通的分类变量。您正在看到因子的自然排序顺序,这是您所在区域设置的字母词汇顺序。如果您想为每个列获得自动转换为“数字”,包括日期和因子等,则可以尝试:
sapply(df, function(x) max(as.numeric(x)) )   # not generally a useful result

或者如果你想要先测试因子并按你预期返回,则可以这样做:

sapply( df, function(x) if("factor" %in% class(x) ) { 
            max(as.numeric(as.character(x)))
            } else { max(x) } )

@Darren的评论确实更有效:

 sapply(df, function(x) max(as.character(x)) )  

max可以成功处理字符向量。


谢谢。第二个sapply示例可以正常工作并完美地回答了这个问题(我发现如果删除as.numeric()子句,让max直接作用于字符字符串会更好)。 - Darren Cook
是的,一般来说那会更有用。 - IRTFM

21
max 使用 apply 的原因是,apply 首先将您的数据框转换为矩阵,并且矩阵只能容纳一种数据类型。所以你最终得到的是一个字符矩阵。sapply 只是 lapply 的包装器,因此两者产生相同的错误不足为奇。

创建数据框时的默认行为是将分类列存储为 factors。除非您指定它是有序的 factor,否则像 maxmin 这样的操作将是未定义的,因为 R 假定您已经创建了一个无序的 factor

您可以通过指定 options(stringsAsFactors = FALSE) 来更改此行为,这将为整个会话更改默认值,或者您可以在 data.frame() 构造调用中传递 stringsAsFactors = FALSE。请注意,这仅意味着 minmax 将默认假定为“字母顺序”。

或者,您可以手动指定每个 factor 的顺序,尽管我认为这不是您想要做的事情。

无论如何,sapply 通常会产生一个原子向量,在许多情况下会将所有内容转换为字符。解决此问题的一种方法如下:

#Some test data
d <- data.frame(v1 = runif(10), v2 = letters[1:10], 
                v3 = rnorm(10), v4 = LETTERS[1:10],stringsAsFactors = TRUE)

d[4,] <- NA

#Similar function to DWin's answer          
fun <- function(x){
    if(is.numeric(x)){max(x,na.rm = 1)}
    else{max(as.character(x),na.rm=1)}
}   

#Use colwise from plyr package
colwise(fun)(d)
         v1 v2       v3 v4
1 0.8478983  j 1.999435  J

感谢详细的解释,非常有帮助。stringsAsFactors = FALSE确实使max()按预期工作(但后来我意识到我实际上想要这些字段成为因子;因此,在运行max()时将因子转换为字符串对我最有效)。 - Darren Cook

7

如果您想了解数据的概要信息,summary(df)会提供数值列的最小值、第一四分位数、中位数和平均值、第三���分位数以及最大值,以及因子列顶级别的频率。


是的,回想起来,我应该只是使用那个 :-) 它的输出有点丑陋(我想要每行一个字段,带有最小值列、最大值列等),但我想我只需要找到如何重新格式化表对象。 - Darren Cook
我建议你看一下summary()函数的代码。很多时候,我会找到一个基本函数,它做的事情与我想要的非常接近,并从中获取代码的一般思路。 - Rob
遗憾的是,summary() 也不可扩展。例如,没有简单的方法可以将平均函数添加到其中。 - ivo Welch

2
在 @ltamar 的回答基础上继续深入:
使用summary并将输出转换为有用的内容!
library(tidyr)
library(dplyr)

df %>% 
  summary %>% 
  data.frame %>%
  select(-Var1) %>%
  separate(data=.,col=Freq,into = c('metric','value'),sep = ':') %>%
  rename(column_name=Var2) %>%
  mutate(value=as.numeric(value),
         metric = trimws(metric,'both') 
  ) %>%  
  filter(!is.na(value)) -> metrics

虽然不太美观,也不是很快,但它可以完成任务!


2

避免使用基础的*apply函数,这会将整个数据框强制转换为一个数组,可能会丢失信息。

如果您想将函数as.numeric应用于每一列,一个简单的方法是使用dplyr中的mutate_all

t %>% mutate_all(as.numeric)

或者使用plyr中的colwise函数, 它可以"将一个作用于向量的函数转化为作用于数据框列的函数。"

t %>% (colwise(as.numeric))

在读取包含字符向量的数据表并将列强制转换为正确的数据类型的特殊情况下,使用readr中的type.converttype_convert
较为无趣的答案:我们可以使用 for 循环在每列上应用:
for (i in 1:nrow(t)) { t[, i] <- parse_guess(t[, i]) }

我不知道有没有一个好方法来 使用*apply完成作业并保留数据框结构


请注意,colwise 不再要求对象是数组,而是要求基本类型为 data.frame - stucash

1

现在循环速度非常快,所以这已经足够了:

for (I in 1L:length(c(1,2,3))) {
    data.frame(c("1","2","3"),c("1","3","3"))[,I] <- 
    max(as.numeric(data.frame(c("1","2","3"),c("1","3","3"))[,I]))
}

2
建议将超过一行的长代码格式化为多行,以便在网络上更容易阅读。 - Sven Viking

0
df <- head(mtcars)
df$string <- c("a","b", "c", "d","e", "f"); df

my.min <- unlist(lapply(df, min))
my.max <- unlist(lapply(df, max))

0

使用 retype() 函数从 hablar 包中将因子转换为字符或数字类型,具体取决于可行性。我会使用 dplyr 对每列应用 max 函数。

代码

library(dplyr)
library(hablar)

# Retype() simplifies each columns type, e.g. always removes factors
d <- d %>% retype()

# Check max for each column
d %>% summarise_all(max)

结果

不是新的列类型。

     v1 v2       v3 v4   
  <dbl> <chr> <dbl> <chr>
1 0.974 j      1.09 J   

数据

# Sample data borrowed from @joran
d <- data.frame(v1 = runif(10), v2 = letters[1:10], 
                v3 = rnorm(10), v4 = LETTERS[1:10],stringsAsFactors = TRUE)

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