R语言apply调用table统计有序因子丢失0计数的解决方案
问题背景
假设存在如下列均为有序因子的data.frame:
dat0 <- data.frame(X1 = 1:5, X2 = 1:5, X3 = c(1,1:4), X4 = c(2,2:5)) dat <- data.frame(lapply(dat0, factor, ordered=TRUE, levels=1:5, labels=letters[1:5]))
现需创建规整的表格,统计dat每列中a:e各因子水平的出现次数(需包含计数为0的水平),table()是实现该需求的首选函数。
尝试的简洁实现方案未达到预期效果,具体表现如下:
- 单独对单列应用
table()时可正常返回预期结果,即完整返回5个因子水平的计数,即使某水平计数为0也会保留:
table(dat[,1]) # a b c d e # 1 1 1 1 1 table(dat[,3]) # a b c d e # 2 1 1 1 0 # 注:缺失的因子水平会自动返回计数0
- 但使用
apply()对整个data.frame逐列应用table(),试图整合所有列的计数结果到同一张表时,输出格式异常,存在0计数的因子水平被自动丢弃:
apply(dat, 2, table) # $X1 # a b c d e # 1 1 1 1 1 # # $X2 # a b c d e # 1 1 1 1 1 # # $X3 # a b c d # 2 1 1 1 # # $X4 # b c d e # 2 1 1 1
- 经测试可定位问题规律:仅保留所有因子水平均存在非0计数的列时,可得到预期格式的输出,即移除存在任意0计数因子水平的列后,apply结合table可返回规整的矩阵结果:
apply(dat[1:2], 2, table) # 仅保留包含全部5个因子水平(无0计数)的列 # X1 X2 # a 1 1 # b 1 1 # c 1 1 # d 1 1 # e 1 1
核心疑问:使用
table()实现该需求是否存在简单的变通方案,还是必须更换其他实现思路?
- 备注:已知可逐列计算table结果后通过
cbind()合并,但在实际复杂数据集场景下该方法操作过于繁琐。
解决方案
问题出在apply()的运行逻辑上:它会先把传入的data.frame强制转换成普通矩阵,这个过程中因子列预设的levels属性会被丢弃,后续table()拿不到完整的水平列表,自然会自动剔除计数为0的水平。这不是table()本身的功能缺陷,不需要更换实现思路。
最简单的变通方案不需要逐列计算再手动cbind,两种写法都可以直接得到结果:
- 方案1:用
sapply()代替apply()逐列计算。sapply会直接按list结构遍历data.frame的每一列,完整保留列的因子属性,table()会自动按预设水平补全0计数,最终直接输出规整的矩阵:
sapply(dat, table)
运行输出:
X1 X2 X3 X4 a 1 1 2 0 b 1 1 1 2 c 1 1 1 1 d 1 1 1 1 e 1 1 0 1
- 方案2:不用循环,直接转换数据结构后单次调用
table()。用stack()把宽格式的因子表转成两列的长格式(一列存因子值,一列存原列名),因子属性会完整保留,直接调用table()就能得到对齐好的计数表:
table(stack(dat))
输出和方案1完全一致。
补充提示:只要是需要保留data.frame列的原始属性(比如因子水平、日期格式、特殊类属性)的运算,都尽量避免用apply(),优先选择lapply/sapply等基于list操作的遍历函数,从根源上避免属性丢失问题。
内容的提问来源于stack exchange,提问作者theforestecologist
相关产品推荐
相关产品推荐

