前面我们已经讲过了在生统课上会用到基本的数据结构以及怎么来提取我们想要的数据,这一部分我们来讲讲数据的清洗。
在生统课上,我们基本上会遇到两种数据结构。一种是我把其叫做宽数据,就比如我们在第五次生统作业中,碰到的第二题的数据。
> test2 <- read.table("rawdata/test2.txt",header = T)
> head(test2)
control low middle high
1 20.79 22.22 28.56 31.93
2 22.91 24.74 28.67 37.94
3 27.21 21.53 25.28 39.76
4 19.34 19.66 30.28 27.94
5 17.85 25.89 23.13 29.65
6 23.79 29.10 23.47 34.23
这种数据列名其实是不同的处理,每一列都对应不同处理下的值。
实际上,这种数据还不能称之为真正意义上的宽数据,这里拿来指代是为了方便与长数据区分。
另一种就是长数据,也就是我上一节翻来覆去提到过的数据结构。其每一行都是一个观测值,列名则是变量名。典型的就是我们在第五次生统作业中,碰到过的第一题的数据。
> test1 <- read.table("rawdata/test1.txt",header = T)
> head(test1)
yield seed
1 383 1
2 406 1
3 351 1
4 400 1
5 390 1
6 361 1
由于宽数据在线性回归,方差分析等分析中都无法被函数所识别,所以我们首先讲讲如何把宽数据转换成长数据。
gather函数转换
首先介绍的是 tidyr 包的 gather 函数。tidyr 包的安装就是我们之前讲过的方法
install.packages("tidyr")
有点建议大家直接装 tidyverse 包,这是个各种包的合集,里面还包括了 ggplot2 等。不过有可能安不上 tidyverse , 如果安装有问题,欢迎大家在下面提出问题。
gather 函数的使用非常方便,你只需要指定你转换后的 key 那列的列名,value 那列的列名,以及你需要转成长数据的那几列。我们以 test2 为例。
# 加载包
library(tidyr)
# 使用gahter
> test2_long <- gather(test2, key = "Treatment_dose", value = "survive_time",control, low, middle, high)
> head(test2_long)
Treatment_dose survive_time
1 control 20.79
2 control 22.91
3 control 27.21
4 control 19.34
5 control 17.85
6 control 23.79
gather 函数你需要输入参数为
第一个参数是你的数据集
-
第二个参数是你新构建的关键列的名称(该列的内容由原先数据集的列名组成)
名字自己取,像我这里就取名为"Treatment_dose"
-
第三个参数是新构建的数值列的名称
名字还是自己取,我这里取名为"survive_time"
-
后面的几个参数都是你要用来构建关键列的那几个列名
这里就是control, low, middle, high,即原来的几个列名。
key 和 value 大家可能还是比较懵逼,但对于我们普通的生统数据,不需要太过于纠结其意义。
gather用法在转换的时候还要考虑uniq key的问题,但我们生统的数据应该也不需要考虑这一点。
也有人提到过用 reshape2 包的 melt 函数,或者基本包 transform 函数来转换。但我觉得没有 gather 这个函数直观,简单。还有,gather 转换的时候对于不等长数据的支持也比较好。就比如我们在第五次生统作业的第三题,药物 1 和 2 有 15 只小鼠,药物 3 只有 10 只老鼠。如果只是单纯地按我们之前的做法读入数据框,就会报错。
> test3 <- read.table("rawdata/test3.txt",header = T)
Error in scan(file = file, what = what, sep = sep, quote = quote, dec = dec, :
line 11 did not have 3 elements
就是因为3列数据不等长,所以R才会报错。这时候,我们就可以设置一个参数
> test3 <- read.table("rawdata/test3.txt",header = T,fill = T)
> test3
med1 med2 med3
1 40 50 60
2 10 20 30
3 35 45 100
4 25 55 85
5 20 20 20
6 15 15 55
7 35 80 45
8 15 -10 30
9 -5 105 77
10 30 75 105
11 25 10 NA
12 70 60 NA
13 65 45 NA
14 45 60 NA
15 50 30 NA
然后就可以顺利地读入了,而且也可以顺利地用gather函数来整理成长数据。
gather(test3,key = "different_med", value = "weight", med1,med2,med3)
会发现NA还是存在,但我们同样可以设置一个参数
gather(test3,key = "different_med", value = "weight", med1,med2,med3,na.rm = T)
这样,就顺利地转换成我们需要的格式了。
基本包转换
其实,宽数据到长数据的转换,不一定需要特殊的包的函数,也可以用最基本的方法。尽管最基本的方法有些麻烦,但对于提高自己的数据转换能力还是很有帮助的。
rbind和cbind
在使用基本函数转换前,我们先来介绍两个我们以后可能会用到的函数,rbind
和 cbind
。rbind是纵向合并,cbind是横向合并。具体操作我们来看一个例子
> data1 <- data.frame(A1 = sample(1:10,3),
+ A2 = sample(1:10,3),
+ A3 = sample(1:10,3))
> data1
A1 A2 A3
1 3 6 2
2 7 9 5
3 5 10 4
> data2 <- data.frame(B1 = sample(10:20,3),
+ B2 = sample(10:20,3),
+ B3 = sample(10:20,3))
> data2
B1 B2 B3
1 15 17 14
2 13 14 11
3 17 15 12
> rbind(data1,data2)
Error in match.names(clabs, names(xi)) :
names do not match previous names
> cbind(data1,data2)
A1 A2 A3 B1 B2 B3
1 3 6 2 15 17 14
2 7 9 5 13 14 11
3 5 10 4 17 15 12
可以看到rbind需要两个数据框有同样的变量(同样的列名),cbind则需要两个数据框的行数是一样的。
rbind尽管需要两个数据框有同样的变量,但顺序不一定要一样,比如
> data3 <- data.frame(A1 = sample(10:20,3),
+ A3 = sample(10:20,3),
+ A2 = sample(10:20,3))
> data3
A1 A3 A2
1 10 12 14
2 11 20 20
3 15 14 19
> rbind(data1,data3)
A1 A2 A3
1 3 6 2
2 7 9 5
3 5 10 4
4 10 14 12
5 11 20 20
6 15 19 14
这里data1和data3的列名是一样的,但顺序是不一样的,但rbind还是可以合并。
关于rbind和cbind,《R语言实战》4.9也有提到,大家可以去看看
数据转换
介绍完了rbind和cbind,我们就可以来转换数据框了。我用test2做例子
> test2
control low middle high
1 20.79 22.22 28.56 31.93
2 22.91 24.74 28.67 37.94
3 27.21 21.53 25.28 39.76
4 19.34 19.66 30.28 27.94
5 17.85 25.89 23.13 29.65
6 23.79 29.10 23.47 34.23
7 22.60 18.93 28.88 32.63
8 18.53 18.64 29.62 29.13
9 23.23 26.39 24.82 39.62
10 20.14 25.49 34.64 36.15
11 26.71 20.43 22.29 28.85
12 19.36 22.69 29.22 24.07
13 17.22 29.67 25.63 29.29
14 24.13 20.36 35.12 35.24
15 25.85 22.74 32.32 36.13
宽数据转换成长数据,本质上就像堆积木一样。你把每一列的数据拿出来,变成一块积木,然后你就一层层地堆起积木,最后就形成了长数据。是不是感觉特别像rbind干的事情?没错,我们这里就用rbind来构建长数据。
control <- data.frame(Treatment_dose = rep("control",15),
survive_time = test2$control)
low <- data.frame(Treatment_dose = rep("low",15),
survive_time = test2$low)
middle <- data.frame(Treatment_dose = rep("middle",15),
survive_time = test2$middle)
high <- data.frame(Treatment_dose = rep("high",15),
survive_time = test2$high)
rbind(control,low,middle,high)
大家可能是一遍遍地打了以上的代码,机智的小伙伴可能还是复制粘贴的,然后把变量名改一下。不过,实际上我们可以用函数来解决这些重复操作的问题。函数这一部分就留待后面讲了。
双因素ANOVA的数据格式整理
在第五次生统作业的最后一题,我们需要考虑的是双因素ANOVA分析。但word文件里面的表格却不是一个双因素ANOVA的格式。让我们来看看如何利用上面讲过的内容,把它变成一个能让 aov
读入的双因素AONVA。
首先复制粘贴A1,A2,A3三列数据到txt文件中,然后读入R中。
> test4 <- read.table("rawdata/test4.txt",header = T)
> head(test4)
A1 A2 A3
1 282.1 296.7 300.1
2 264.2 318.0 307.5
3 274.2 295.3 294.2
4 276.4 292.8 312.0
5 283.7 304.5 300.2
6 288.0 305.9 292.6
然后转换成长数据框
test4_long <- gather(test4, key = temperature, value = weight, A1, A2, A3)
但这样还不够,这里只有单因素,即饲养温度这一列的信息,还没有饲料的信息。所以我们要自己加上一列饲料的信息。
feed <- c(rep("B1",10),rep("B2",10))
test4_data <- cbind(feed,test4_long)
head(test4_data)
> head(test4_data)
feed temperature weight
1 B1 A1 282.1
2 B1 A1 264.2
3 B1 A1 274.2
4 B1 A1 276.4
5 B1 A1 283.7
6 B1 A1 288.0
这样就变成了双因素的ANOVA格式,可以顺利让aov
函数读入了。
> test4_aov <- aov(weight ~ feed * temperature, data = test4_data)
> summary(test4_aov)
Df Sum Sq Mean Sq F value Pr(>F)
feed 1 127 127 1.589 0.213
temperature 2 9080 4540 56.809 5.22e-14 ***
feed:temperature 2 17 9 0.108 0.897
Residuals 54 4316 80
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1