社会科学研究经常会遇到“超多变量”的情况——多量表、多维度、多题项,以及复杂的正反计分题……如何更高效地计算量表总分?如何更简洁地进行反向计分?传统的统计工具(Excel、SPSS等)虽然也能解决这些问题,但在超多变量的情况下会比较繁琐。相反,R语言以其扩展包的强大功能和代码的极简风格,狂甩传统软件几十条街。今天我们就来掌握如何使用R语言优雅地计算超多变量(包括计算总分、计算平均值、缺失值处理、数值计数、重新编码、反向计分等)。
本文目录:
- 温故——传统软件的解决办法概览
- 知新——R语言和data.table包
- 进阶——自编R函数优雅计算多变量(附相关代码)
1 / 传统软件的解决办法概览
我们以经典的10题Rosenberg自尊量表(RSES)为例,这是一个4点Likert量表(1-4),其中有5题反向计分(第3、5、8、9、10题)。
如果用Excel来计算其总分或平均分,大家应该已经很熟悉了,可以分别用SUM和AVERAGE函数:比如从A列~J列是RSES的10道题,那么在K2格输入“=AVERAGE(A2,B2,5-C2,D2,5-E2,F2,G2,5-H2,5-I2,5-J2)”,然后双击K2格右下角的黑色小十字,就自动填充算出了所有人的量表平均分。如果没有反向计分题,会简单很多,可以直接“=AVERAGE(A2:J2)”。想必这是大多数读者再熟悉不过的方法。
如果换做是SPSS,可能稍微复杂一点,比如原始变量名是RSES1、RSES2……RSES10,那么计算平均分可以先RECODE其中5道反向计分题并生成新变量再COMPUTE,也可以不RECODE直接COMPUTE(当然如果要计算信度,还是得先RECODE生成新变量):
COMPUTE RSES=MEAN(RSES1, RSES2, 5-RSES3, RSES4, 5-RSES5, RSES6, RSES7, 5-RSES8, 5-RSES9, 5-RSES10).
EXECUTE.
同样,在不涉及反向计分的情况下,SPSS的语句也能通过“to”(大小写不敏感)得到简化(但变量需要按顺序排好):
COMPUTE X=MEAN(X1 to X50).
EXECUTE.
如此看来,传统方法似乎还比较方便,所以这可能也是目前很多同学或老师不愿意学习使用R的原因:原来的软件已经够用、学编程需要花费比较多的成本、不想写代码、畏惧写代码……或者,学着学着就“从入门到放弃”了……
其实可以理解,毕竟我们有时候不太想跳出舒适区。
然而,随着变量增多,Excel和SPSS就会变得越来越不方便、越来越繁琐。
2 / R语言和data.table包
R语言会极大提高数据分析的效率,并且可以做几乎所有的统计分析。
自从两年前入坑R,我在一次次分析数据的过程中逐渐体会到R语言的美妙之处,无论是数据处理的简洁程度、统计功能的强大程度,还是作图的美观程度。虽说一个心理学研究者并不必要掌握太多编程语言(不可否认,问题比方法更重要,想法比技术更重要),但R绝对是一个巨大的宝库,经常会给我们很多惊喜,长远来看也会节省我们很多时间和精力。
在数据预处理方面,data.table作为data.frame的继承和升级版,不仅在处理大容量数据的时候速度更快,而且在拥有强大功能的同时代码设计却非常优雅简洁。官方速查手册请参考data.table (cheat sheet)。
data.table的基本使用方式是“data[i, j, by]”(i为行,j为列,by为分组变量)。下面是一些例子:
require(data.table)
data=data.table(x=rep(1:2, each=3), y=1:6, z=6:1)
# x y z
# 1: 1 1 6
# 2: 1 2 5
# 3: 1 3 4
# 4: 2 4 3
# 5: 2 5 2
# 6: 2 6 1# 筛选符合条件的行/个案
data[x==1 & y>1]
# x y z
# 1: 1 2 5
# 2: 1 3 4# 筛选特定的列/变量
data[, c("y", "z")]
data[, .(y, z)]
# y z
# 1: 1 6
# 2: 2 5
# 3: 3 4
# 4: 4 3
# 5: 5 2
# 6: 6 1# 同时筛选行和列
data[y %between% c(2, 4), "z"]
# z
# 1: 5
# 2: 4
# 3: 3# 分组汇总
data[order(x), .(TotalN=.N, Ymean=mean(y), Zsd=sd(z), Zmax=max(z)), by=x]
# x TotalN Ymean Zsd Zmax
# 1: 1 3 2 1 6
# 2: 2 3 5 1 3# 变量计算(“原地更新”,无需赋值给新变量)
data[, sum:=x+y+z]
# x y z sum
# 1: 1 1 6 8
# 2: 1 2 5 8
# 3: 1 3 4 8
# 4: 2 4 3 9
# 5: 2 5 2 9
# 6: 2 6 1 9# 多变量同时计算(“:=”实则为data.table的一个特殊函数)
data[,":="(sum_xy=x+y,`y*z`=y*z)]
# x y z sum sum_xy y*z
# 1: 1 1 6 8 2 6
# 2: 1 2 5 8 3 10
# 3: 1 3 4 8 4 12
# 4: 2 4 3 9 6 12
# 5: 2 5 2 9 7 10
# 6: 2 6 1 9 8 6# 数据匹配合并(即merge)
d1=data.table(x=letters[1:4], y=1:4)
# x y
# 1: a 1
# 2: b 2
# 3: c 3
# 4: d 4
d2=data.table(x=letters[3:1], z=1:3)
# x z
# 1: c 1
# 2: b 2
# 3: a 3
d2[d1, on="x"] # 保留d1所有case
# x z y
# 1: a 3 1
# 2: b 2 2
# 3: c 1 3
# 4: d NA 4
d1[d2, on="x"] # 保留d2所有case
# x y z
# 1: c 3 1
# 2: b 2 2
# 3: a 1 3
通过上面这些简单的例子,大家对data.table应该已经有了基本的感受——非常简洁。事实上,如果要在data.frame实现相同的功能,还需要用到许多其他的函数和包(例如which、aggregate、merge,多变量计算则需要用到dplyr包的mutate等等),而在data.table里面,一切都变得非常优雅。
然而,优雅才刚刚开始。
3 / 自编R函数优雅计算多变量
虽然data.table提供了“:=”函数使得多变量计算容易很多,但现实情况往往比较复杂,尤其是本文开头所说的“超多变量”情况。颇为遗憾的是,data.table本身并没有提供特别有效的函数来“优雅地”进行“超多变量”的计算。
我们不妨先创建一个案例数据,用来模拟纷繁复杂的现实情况,这里我刻意把“x2”和“x4”的位置倒了过来,并且加了几个缺失值NA:
d=data.table(x1=1:5, x4=c(2,2,5,4,5), x3=c(3,2,NA,NA,5), x2=c(4,4,NA,4,5), x5=c(5,4,3,4,5))
# x1 x4 x3 x2 x5
# 1: 1 2 3 4 5
# 2: 2 2 2 4 4
# 3: 3 5 NA NA 3
# 4: 4 4 NA 4 4
# 5: 5 5 5 5 5# 如果要计算x1-x5的平均值:
# (以下均为错误代码!)
d[,":="(X=mean(x1, x2, x3, x4, x5))] # 报错
d[,":="(X=mean(c(x1, x2, x3, x4, x5)))] # 依然不对,返回值都是NA
d[,":="(X=mean(c(x1, x2, x3, x4, x5), na.rm=TRUE))] # 还是不对,返回值都是同一个数字
d[,":="(X=(x1+x2+x3+x4+x5)/5)] # 回到了最笨的办法,但缺失值依然无法处理
可以看到,如果我们想计算x1-x5的平均值,想想容易,真正实现起来却处处碰壁。其实要想实现简便的变量计算,需要用到很多特别的函数,比如mapply、eval,有的地方还需要用到正则表达式。
但不用担心,我已经针对几个常用的变量计算需求编好了相应的R函数。由于具体思路比较复杂,这里不详细展示源代码,大家可以从我的GitHub下载这些函数的源代码并调用(BruceFunctions.R)。
# 或者在线调用BruceFunctions.R
source("https://raw.githubusercontent.com/psychbruce/stats/master/BruceFunctions.R")
自编函数包括:
COUNT:统计每个被试的不同变量(题目)中某个值出现的个数SUM:简便计算多个变量(题目)的总分MEAN:简便计算多个变量(题目)的平均分STD:简便计算多个变量(题目)的标准差CONSEC:统计多个变量(题目)中连续相同数字出现最多的个数……
主要的参数包括:
data:原数据var & item:需计算的变量及其序号(同时定义),例如var="RSES", item=1:10;如果变量的原始顺序是乱序,则变量会以1-10的顺序重新排列;此为推荐用法,可省略参数名vars:直接规定使用哪些变量,例如vars=c("x1", "x2");不可省略该参数名varrange:原数据中某段范围的变量(原始顺序),例如varrange="x1:x5";不可省略该参数名
(以上三种变量界定方式只需选择其中一种即可,当然函数在实际运行时会把不同情况统一转换为vars)rev:反向计分题有哪些,可以是单个数字(与item参数的顺序对应),可以是具体的变量名,也可以是数字/变量名组成的向量likert:如果有反向计分题,则需要同时定义Likert是几点量表,可以是c(1, 7)的形式,也可以是1:7的形式na.rm:是否跳过缺失值,默认为TRUE(适用大多数情况),一般不用再做设置
下面是具体用法:
d[,":="(n_na=COUNT(d, "x", 1:5, value=NA), # 统计x1-x5中缺失值的个数n_2=COUNT(d, "x", 1:5, value=2), # 统计2的个数sum=SUM(d, "x", 1:5), # 计算x1-x5的总分(缺失值跳过)mean1=MEAN(d, "x", 1:5), # 计算x1-x5的平均分(缺失值跳过)mean2=MEAN(d, vars=c("x1", "x4")), # 只计算x1和x4的平均分mean3=MEAN(d, varrange=c("x1", "x2")), # 计算从x1到x2的平均分(实际有4个变量)mean4=MEAN(d, varrange="x1:x2", rev="x2", likert=1:5), # 同上,但规定x2为反向计分题sd=STD(d, "x", 1:5, rev=c(2, 5), likert=1:5), # 计算x1-x5的标准差,规定x2和x5为反向计分题cons1=CONSEC(d, "x", 1:5), # 统计x1-x5连续相同数字出现最多的个数(变量按照1-5重新排列)cons2=CONSEC(d, varrange="x1:x5"))] # 同上,但以变量原始顺序排列# x1 x4 x3 x2 x5 n_na n_2 sum mean1 mean2 mean3 mean4 sd cons1 cons2
# 1: 1 2 3 4 5 0 1 15 3.000000 1.5 2.5 2.000000 0.836660 0 0
# 2: 2 2 2 4 4 0 3 14 2.800000 2.0 2.5 2.000000 0.000000 2 3
# 3: 3 5 NA NA 3 2 0 11 3.666667 4.0 4.0 4.000000 1.154701 0 0
# 4: 4 4 NA 4 4 1 0 16 4.000000 4.0 4.0 3.333333 1.154701 2 2
# 5: 5 5 5 5 5 0 0 25 5.000000 5.0 5.0 4.000000 2.190890 5 4
另外,还有两个函数是在已有包的基础上进行的简单扩展(所谓“站在巨人的肩膀上”):
RECODE:变量转换/重新赋值,功能同SPSS的RECODE;该函数是直接调用了car包的recode函数
# RECODE用法示例
data[, BirthGroup:=RECODE(BirthYear, "lo:1989=1; 1990:1999=2; 2000:2009=3; 2010:hi=4; else=NA")]
Alpha:更方便地计算内部一致性信度,不需要对反向计分题额外生成新变量;该函数是调用了jmv包的reliability函数(jmv其实是jamovi软件的程序包),并做了必要的参数设置和参数扩展
(输出结果为三线表,指标包括5个——量表平均分、量表标准差、量表α系数、校正后的题总相关 [即item-rest correlation]、该题如果删除后的量表α系数,以及会智能输出一些Note)
最后仍然以RSES自尊量表为例,演示上述函数的用法:
# 计算平均分(数据“原地更新”)
data[, ":="(RSES=MEAN(data, "RSES", 1:10, rev=c(3, 5, 8, 9, 10), likert=1:4))]# 计算Cronbach's α系数
Alpha(data, "RSES", 1:10, rev=c(3, 5, 8, 9, 10))
结语
面对“多变量计算”问题,虽然Excel和SPSS也能解决,但局限较多、过程繁琐。而基于data.table和自编函数,我们既可以很方便地用少量代码计算量表总分、量表平均分,也能在不生成新变量的情况下直接进行反向计分的处理和量表信度的计算,而且基本上所有的单个功能都只需要短短一行代码就能搞定。大道至简。
再啰(ān)嗦(lì)几句
- 用R千万别用R本身(我一般只用R本身当计算器),RStudio才是写代码必需的IDE(IDE = 集成开发环境)
- jamovi作为一款基于R的新兴统计软件,具有比SPSS更简洁美观的界面设计,同时又拥有更强大丰富的统计功能,未来几年应该会大范围取代SPSS,和R一起占领统计市场(jamovi的一个新出的module甚至已经能做多重中介和有调节的中介了)
- 简单数据用jamovi、复杂数据用R——可以说是一种新的标配
- 到底有多好,谁用谁知道!