参考资料:
Data Analysis and Prediction Algorithms with R
到目前为止,我们一直在通过索引对向量进行重新排序。然而,一旦我们开始更高级的分析,数据存储的首选单元不是向量,而是数据框。接下来介绍R语言数据分析中很重要的一个库:tideverse
收入导入库,显示没有该库可以使用install.packages(‘要安装的库名’)
library(tidyverse)
首先我们介绍一下tidy数据,我们把每一行代表一个观测值,每一列代表这些观测的不同变量的数据称为tidy格式,murders
就是一个tidy数据
head(murders)
state | abb | region | population | total | rate | |
---|---|---|---|---|---|---|
1 | Alabama | AL | South | 4779736 | 135 | 2.824424 |
2 | Alaska | AK | West | 710231 | 19 | 2.675186 |
3 | Arizona | AZ | West | 6392017 | 232 | 3.629527 |
4 | Arkansas | AR | South | 2915918 | 93 | 3.189390 |
5 | California | CA | West | 37253956 | 1257 | 3.374138 |
6 | Colorado | CO | West | 5029196 | 65 | 1.292453 |
每一行代表一个州以及其他五个变量的观察数据,每一列都表示不同变量,符合tidy数据格式的描述,我们再来看看下面这个数据
#> country year fertility
#> 1 Germany 1960 2.41
#> 2 South Korea 1960 6.16
#> 3 Germany 1961 2.44
#> 4 South Korea 1961 5.99
#> 5 Germany 1962 2.47
#> 6 South Korea 1962 5.79
上述数据集也是一个tidy数据,但是它是处理后的数据,原始数据如下
#> country 1960 1961 1962
#> 1 Germany 2.41 2.44 2.47
#> 2 South Korea 6.16 5.99 5.79
每一行包含了几个观测值,并且year变量的值放到了表头中,因此该数据不是一个tidy格式,这种作数据分析时会有许多问题,我们在后面会更详细的介绍如何将上面这个数据转换
tidyverse的dplyr包引入了一些函数,这些函数在处理数据库执行一些最常见的操作,例如,要通过添加新列来更改数据表,我们使用mutate。为了将数据表过滤到行的子集,我们使用filter。最后,为了通过选择特定列来对数据进行子集划分,我们使用select
我们希望数据表中包含分析所需的所有信息。因此,第一项任务是将谋杀率添加到我们的谋杀数据框中。函数mutate将要处理的数据框作为第一个参数,将变量的名称和值作为第二个参数。因此,为了增加谋杀率列,我们使用:
library(dslabs)
data('murders')
murders <- mutate(murders, rate = total/population*100000)
注意:我们使用了total和population在上述函数中,但是并没有报错,这是因为dplyr
包的函数会默认的将第一个参数作为研究对象,那么就会自动查找第一个参数中相应的列,也就是murders$total,这样增强了代码的可读性和便捷性
# 看一下新的数据框
head(murders)
state | abb | region | population | total | rate | |
---|---|---|---|---|---|---|
1 | Alabama | AL | South | 4779736 | 135 | 2.824424 |
2 | Alaska | AK | West | 710231 | 19 | 2.675186 |
3 | Arizona | AZ | West | 6392017 | 232 | 3.629527 |
4 | Arkansas | AR | South | 2915918 | 93 | 3.189390 |
5 | California | CA | West | 37253956 | 1257 | 3.374138 |
6 | Colorado | CO | West | 5029196 | 65 | 1.292453 |
可以看到增加了rate一列,代表谋杀率,这里我们更新了murders数据集,但是如果我们使用data(murders)重新加载数据,可以得到之前的数据集
现在假设我们只想观察谋杀率小于0.7的数据集,我们使用filter()函数,和mutate一样,第一个变量是数据框,第二个变量是筛选条件
filter(murders, rate<=0.7)
state | abb | region | population | total | rate |
---|---|---|---|---|---|
Hawaii | HI | West | 1360301 | 7 | 0.5145920 |
Iowa | IA | North Central | 3046355 | 21 | 0.6893484 |
New Hampshire | NH | Northeast | 1316470 | 5 | 0.3798036 |
North Dakota | ND | North Central | 672591 | 4 | 0.5947151 |
Vermont | VT | Northeast | 625741 | 2 | 0.3196211 |
虽然给出的案例数据集只有六列,但有时数据集有上百列,但我们并不对所有的数据集都感兴趣,因此可以使用select得到原数据集的子集(我们关注的列),这个在数据分析时是很有用的。下面假设我们只想得到state,region,rate三列,使用如下代码:
new_table <- select(murders, state, region, rate)
filter(new_table, rate<=0.7)
state | region | rate |
---|---|---|
Hawaii | West | 0.5145920 |
Iowa | North Central | 0.6893484 |
New Hampshire | Northeast | 0.3798036 |
North Dakota | North Central | 0.5947151 |
Vermont | Northeast | 0.3196211 |
这样我们得到了哪些州以及地区的谋杀率低于0.7。
# 我们之前讲了rank函数,下面我们要得到rank函数
mutate(murders, rank=rank(rate)) %>% head()
state | abb | region | population | total | rate | rank | |
---|---|---|---|---|---|---|---|
1 | Alabama | AL | South | 4779736 | 135 | 2.824424 | 29 |
2 | Alaska | AK | West | 710231 | 19 | 2.675186 | 25 |
3 | Arizona | AZ | West | 6392017 | 232 | 3.629527 | 42 |
4 | Arkansas | AR | South | 2915918 | 93 | 3.189390 | 35 |
5 | California | CA | West | 37253956 | 1257 | 3.374138 | 38 |
6 | Colorado | CO | West | 5029196 | 65 | 1.292453 | 14 |
在r中我们可以通过管道操作符%>%
执行一系列操作,例如先select
再filter
。最新的R版本也可以使用|>
下面我们具体介绍一下管道操作符的应用。
在上面我们先使用select得到了一个新中间对象new_table
,然后在使用filter对其过滤。我们可以通过管道操作符实现在不使用中间对象的情况下也得到上述结果:
o r i g i n a l d a t a − − > s e l e c t − − > f i l t e r original data --> select --> filter originaldata−−>select−−>filter
murders %>% select(state, region, rate) %>% filter(rate <= 0.7)
state | region | rate |
---|---|---|
Hawaii | West | 0.5145920 |
Iowa | North Central | 0.6893484 |
New Hampshire | Northeast | 0.3798036 |
North Dakota | North Central | 0.5947151 |
Vermont | Northeast | 0.3196211 |
上述代码相当于之前分开写的两行代码。我们来分析一下管道运算符是如何起作用的,通常,将第一个管道的结果作为第二个管道的第一个参数,下面我们再来举一些例子来更好的理解管道
16 %>% sqrt()
4
16 %>% sqrt() %>% log2()
2
探索性数据分析的一个重要部分是描述性统计。平均值和标准差是广泛使用的描述数据的两个指标。本节介绍一些dplyr函数来处理数据,group_by
、summarize
和pull
dplyr中的summarize函数提供了一种通过直观易读的代码计算描述性统计信息的方法。我们以height
数据为例。height
数据集包括学生在课堂调查中报告的身高和性别。
# 导入相关库library(dplyr)
library(dslabs)
data(heights)
计算女性身高的平均值和标准差
s <- heights %>% filter(sex == 'Female') %>%
summarise(avg = mean(height), sd = sd(height))
s
avg | sd |
---|---|
64.93942 | 3.760656 |
可以看出和其他dplyr函数一样,使用summarize函数也会自动将第一个参数作为数据集研究,因此可以直接访问height
下面我们来看一下summarize函数的另一个例子:我们要计算美国的平均犯罪率,注意,各州犯罪率的平均值不等于美国的平均犯罪率,因为各州的人数不一样
us_murder_rate <- murders %>%
summarise(rate=sum(total)/sum(population)*100000)
us_murder_rate
rate |
---|
3.034555 |
例如我们要统计某个变量的中位数,最小值,最大值。我们可以使用quantile
分位数函数。
heights %>% filter(sex == 'Female') %>% summarise(meidian_min_max = quantile(height, c(0.5,0,1)))
meidian_min_max |
---|
64.98031 |
51.00000 |
79.00000 |
但是,请注意,上面这个每个都以一行的形式返回。为了在不同的列中获得结果,我们必须定义一个返回如下数据帧的函数:
median_min_max <- function(x){
qs <- quantile(x,c(0.5,0,1))
data.frame(median=qs[1],min=qs[2],max=qs[3])
}
heights %>%
filter(sex=='Female') %>%
summarise(median_min_max(height))
median | min | max |
---|---|---|
64.98031 | 51 | 79 |
数据探索中的一个常见操作是首先将数据分成若干组,然后计算每组的描述统计量。例如,我们可能想分别计算男性和女性身高的平均值和标准差。group_by
函数可以帮我们做到这一点。这点和python中的groupby基本一致
heights %>% group_by(sex) %>% head()
sex | height |
---|---|
Male | 75 |
Male | 70 |
Male | 68 |
Male | 74 |
Male | 61 |
Female | 65 |
上面数据看起来没什么区别,下面在计算统计量的时候可以发现我们可以得到分组后的统计量
heights %>% group_by(sex) %>% summarise(avg = mean(height))
sex | avg |
---|---|
Female | 64.93942 |
Male | 69.31475 |
从上图可以看出男性升高比女性升高平均高出4.3
上面定义的us_murder_rete
对象只是一个数字。但我们将其存储在一个数据框中:
class(us_murder_rate)
‘data.frame’
对于大多数dplyr函数,返回的基本都是一个数据框。如果我们想将这个结果用于需要数值的函数,那么会产生一些问题。我们可以使用pull
函数转换成数值型
us_murder_rate %>% pull(rate)
3.03455486317059
此时返回的是一个数值型的值,等价于us_murder_rate$rate
在检查数据集时,通按不同的列对表进行排序。我们可以使用order和sort函数,但是对于整张表的排序,dplyr的arrange函数有很大优势。例如,这里我们按人口规模对各州进行排序:
# 默认是升序
murders %>% arrange(population) %>% head()
state | abb | region | population | total | rate | |
---|---|---|---|---|---|---|
1 | Wyoming | WY | West | 563626 | 5 | 0.8871131 |
2 | District of Columbia | DC | South | 601723 | 99 | 16.4527532 |
3 | Vermont | VT | Northeast | 625741 | 2 | 0.3196211 |
4 | North Dakota | ND | North Central | 672591 | 4 | 0.5947151 |
5 | Alaska | AK | West | 710231 | 19 | 2.6751860 |
6 | South Dakota | SD | North Central | 814180 | 8 | 0.9825837 |
下面对谋杀率进行降序排序
murders %>% arrange(desc(rate)) %>% head()
state | abb | region | population | total | rate | |
---|---|---|---|---|---|---|
1 | District of Columbia | DC | South | 601723 | 99 | 16.452753 |
2 | Louisiana | LA | South | 4533372 | 351 | 7.742581 |
3 | Missouri | MO | North Central | 5988927 | 321 | 5.359892 |
4 | Maryland | MD | South | 5773552 | 293 | 5.074866 |
5 | South Carolina | SC | South | 4625364 | 207 | 4.475323 |
6 | Delaware | DE | South | 897934 | 38 | 4.231937 |
如果我们想要按多个变量来进行排序,例如先按区域排序,再按谋杀率排序
murders %>% arrange(region,rate) %>% head()
state | abb | region | population | total | rate | |
---|---|---|---|---|---|---|
1 | Vermont | VT | Northeast | 625741 | 2 | 0.3196211 |
2 | New Hampshire | NH | Northeast | 1316470 | 5 | 0.3798036 |
3 | Maine | ME | Northeast | 1328361 | 11 | 0.8280881 |
4 | Rhode Island | RI | Northeast | 1052567 | 16 | 1.5200933 |
5 | Massachusetts | MA | Northeast | 6547629 | 118 | 1.8021791 |
6 | New York | NY | Northeast | 19378102 | 517 | 2.6679599 |
在上面的代码中,我们使用了head()来避免页面被整个数据集填满。如果我们想看到更大的比例,我们可以使用top_n函数。此函数的第一个参数是数据框,第二个参数是要显示的行数,第三个参数是要过滤的变量。下面是如何查看前5行的示例:
murders %>% top_n(5, rate)
state | abb | region | population | total | rate |
---|---|---|---|---|---|
District of Columbia | DC | South | 601723 | 99 | 16.452753 |
Louisiana | LA | South | 4533372 | 351 | 7.742581 |
Maryland | MD | South | 5773552 | 293 | 5.074866 |
Missouri | MO | North Central | 5988927 | 321 | 5.359892 |
South Carolina | SC | South | 4625364 | 207 | 4.475323 |
注意,我们并不是按rate进行排序的,只是用于筛选,默认是按最后一列进行筛选,如果要排序的话还是得使用arrange
tibble数据可以看做成一个特殊的数据框,函数group_by和summarize总是返回这种类型的数据框。为了保持一致性,dplyr操纵函数(select、filter、mutate和arrange)保留了输入的类别:如果它们接收到常规数据框,则返回常规数据框,而如果它们接收到TIBLE,则返回TIBLE。但是tibble是tidyverse中的首选格式。事实上,可以把它们看作是数据框的现代版本。尽管如此,我们接下来将描述三个重要的区别。
tibbles的打印方法比数据帧的打印方法更具可读性。要看到这一点,比较输入murders的输出,我们通过as_tibble(murders)将其转换,可以得到tibble数据格式
如果对数据框的列作为子集,则可能会返回非数据框的对象,例如向量或标量。例如:
data(murders)
class(murders[,4])
‘numeric’
#返回的不是data.frame,但如果是tibble数据,返回的还是tibble
class(as_tibble(murders)[,4])
这在tidyverse中很有用,因为很多函数需要数据框作为输入。对于tibbles,如果您希望访问定义列的向量,而不是返回数据框,则需要使用访问器$:
class(as_tibble(murders)$population)
‘numeric’
虽然数据帧列需要是数字、字符串或逻辑值的向量,但TIBLE可以有更复杂的对象,例如列表或函数。此外,我们还可以使用以下功能创建Tibble:
tibble(id = c(1, 2, 3), func = c(mean, median, sd))
id | func |
---|---|
1 | function (x, ...) , UseMethod("mean") |
2 | function (x, na.rm = FALSE, ...) , UseMethod("median") |
3 | function (x, na.rm = FALSE) , sqrt(var(if (is.vector(x) || is.factor(x)) x else as.double(x), , na.rm = na.rm)) |
管道操作符%>%的一个优点就是不需要产生中间对象,这是因为我们做的这些操作都是以数据框作为第一个参数,但如果我们要访问某一特定列的值,并且pull
函数不好用,我们就可以使用点操作符
例如我们想要知道地区在South的谋杀率的中位数,我们可以使用pull和点操作符来实现
rates <- filter(murders, region == "South") %>%
mutate(rate = total / population * 10^5) %>%
.$rate
median(rates)
3.39806883024604
在第二章中,我们学习了sapply函数,它允许我们对向量的每个元素应用相同的函数。我们构造了一个函数,并使用sapply计算几个n值的前n个整数之和,如下所示:
compute_s_n <- function(n){
x <- 1:n
sum(x)
}
n <- 1:25
s_n <- sapply(n, compute_s_n)
这种将相同的函数或过程应用于某一对象的中的每个元素,在数据分析中非常常见。purrr软件包包含与sapply类似的功能,但可以更好地与其他tidyverse功能交互。主要优点是我们可以更好地控制函数的输出类型。相比之下,sapply可以返回几种不同的对象类型;例如,我们可能希望从一行代码中得到一个数字结果,但sapply可能会在某些情况下将结果转换为字符。purrr函数永远不会这样做:它们将返回指定类型的对象,如果不满足会报错。
我们将学习的第一个purrr函数是map,它的工作原理与sapply非常相似。
# 导入库
library(purrr)
s_n <- map(n, compute_s_n)
class(s_n)
‘list’
如果我们想得到一个数值型向量,我们可以使用map_dbl
s_n <- map_dbl(n, compute_s_n)
class(s_n)
‘numeric’
还有一个特别有用的purrr函数是map_df,它总是返回一个可编辑的数据框。
然而,被调用的函数需要返回一个带有名称的向量或列表。因此,以下代码将导致参数1必须有名称错误:
compute_s_n <- function(n){
x <- 1:n
tibble(sum = sum(x))
}
s_n <- map_df(n, compute_s_n)
我们之前介绍了ifelse
函数,下面我们介绍一些在dplyr的中的条件函数
case_when函数对矢量化条件语句有用。它与ifelse类似,但可以输出任意数值,而不仅仅是TRUE或FALSE。下面是一个将数字分为负数、正数和0的示例:
x <- c(-2,-1,0,1,2)
case_when(x<0 ~'Negtive',
x>0~'Positive',
TRUE ~ 'Zero')
此函数的一个常见用途是基于现有变量定义分类变量。例如,假设我们想比较四组州的谋杀率:新英格兰州、西海岸州、南部州和其他州。对于每个州,我们需要问它是否在新英格兰,如果不是,我们问它是否在西海岸,如果不是,我们问它是否在南部,如果不是,我们分配其他州。下面是我们在何时使用case_的方法:
murders %>%
mutate(group = case_when(
abb %in% c("ME", "NH", "VT", "MA", "RI", "CT") ~ "New England",
abb %in% c("WA", "OR", "CA") ~ "West Coast",
region == "South" ~ "South",
TRUE ~ "Other")) %>%
group_by(group) %>%
summarize(rate = sum(total) / sum(population) * 10^5)
group | rate |
---|---|
New England | 1.723796 |
Other | 2.708144 |
South | 3.626558 |
West Coast | 2.899001 |
数据分析中的一个常见操作是确定某个值是否在某个区间内。我们可以使用条件来检查这一点。例如,要检查向量x的元素是否在a和b之间
我们可以使用:x >= a & x<=b
然而,这可能会变得很麻烦,尤其是在tidyverse方法中。between函数执行相同的操作。
between(x, 0, 2)