明确概念:探索性数据分析(exploratory data analysis, EDA),一般过程为:
(1) 对数据提出问题。
(2) 对数据进行可视化、转换和建模,进而找出问题的答案。
(3) 使用上一个步骤的结果来精炼问题,并提出新问题。
5.3.1 对分布进行可视化表示
确定变量是分类变量还是连续变量,要想检查分类变量的分布,可以使用条形图:
library(tidyverse)
head(diamonds)
ggplot(diamonds,aes(x=cut))+ geom_bar()
条形的高度表示每个 x 值中观测的数量,可以使用 dplyr::count() 手动计算出这些值:
diamonds%>% count(cut)
要想检查连续变量的分布,可以使用直方图:
ggplot(data = diamonds) +
geom_histogram(mapping = aes(x = carat), binwidth = 0.5)
可以通过 dplyr::count() 和 ggplot2::cut_width() 函数的组合来手动计算结果. binwidth 参数来设定直方图中的间隔的宽度,该参数是用 x 轴变量的单位来度量的。
diamonds %>%
count(cut_width(carat, 0.5))
smaller <- diamonds %>% filter(carat < 3)
ggplot(diamonds,aes(carat)) + geom_histogram(bindwidth=0.1)
在同一张图上叠加多个直方图, 用geom_freqploy()代替geom_histogram(),用折线表示。
ggplot(smaller,aes(carat,color=cut)) + geom_freqpoly(binwidth=0.1)
相似值聚集形成的簇表示数据中存在子组。
5.3.3 异常值
ggplot(diamonds) + geom_histogram(aes(y),bindwidth=0.5)
# 可以看出y轴有一大片空白,因为有异常值出现
# 放大y轴看一下
ggplot(diamonds) +
geom_histogram(aes(y),binwidth = 0.5) +
coord_cartesian(ylim = c(0,50))
# 用dplyr中的filter将异常值找出来
unusual <- diamonds %>% filter(y<3 | y>20) %>% arrange(y)
unusual
coord_cartesian() 函数中有一个用于放大 x 轴的 xlim() 参数。 ggplot2 中也有功能稍有区
别的 xlim() 和 ylim() 函数:它们会忽略溢出坐标轴范围的那些数据。
如果带有异常值和不带异常值的数据分别进行分析,结果差别较大的话要找出异常值的原因,如果差别不大,可以用NA代替。
5.3.4 练习
(1)研究 x、 y 和 z 变量在 diamonds 数据集中的分布。你能发现什么?思考一下,对于一条
钻石数据,如何确定表示长、宽和高的变量?
ggplot(diamonds) + geom_density(aes(z))
head(diamonds)
diamonds %>%
mutate(id = row_number()) %>%
select(x, y, z, id) %>%
gather(variable, value, -id) %>%
ggplot(aes(x = value)) +
geom_density() +
geom_rug() +
facet_grid(variable ~ .)
(2)研究 price 的分布,你能发现不寻常或令人惊奇的事情吗?(提示:仔细考虑一下
binwidth 参数,并确定试验了足够多的取值。)
head(diamonds)
diamonds %>% ggplot() + geom_histogram(aes(price),binwidth = 10)
ggplot(filter(diamonds,price<2500)) + geom_histogram(aes(price),binwidth = 10)
ggplot(filter(diamonds), aes(x = price)) +
geom_histogram(binwidth = 100, center = 0)
diamonds %>%
mutate(ending = price %% 10) %>%
ggplot(aes(x = ending)) +
geom_histogram(binwidth = 1, center = 0) +
geom_bar()
diamonds %>%
mutate(ending = price %% 100) %>%
ggplot(aes(x = ending)) +
geom_histogram(binwidth = 1) +
geom_bar()
diamonds %>% mutate(ending=price%%1000) %>% filter(ending>500, ending<1000) %>%
ggplot(aes(x=ending)) +
geom_histogram(binwidth = 1) + geom_bar()
(3) 0.99 克拉的钻石有多少? 1 克拉的钻石有多少?造成这种区别的原因是什么?
head(diamonds)
diamonds %>% filter(carat >= 0.99, carat <= 1) %>% count(carat)
# 发现0.99的比较少,是因为0.01的差别,1克拉卖的比较贵吗?可以看下其他类型的数量
diamonds %>% filter(carat >= 0.9, carat <= 1.1) %>% count(carat) %>% print(n=30)
(4)比较并对比 coord_cartesina() 和 xlim()/ylim() 在放大直方图时的功能。如果不设置
binwidth 参数,会发生什么情况?如果将直方图放大到只显示一半的条形,那么又会发
生什么情况?
ggplot(diamonds) + geom_histogram(aes(price)) + coord_cartesian(xlim = c(100,5000),ylim = c(0,3000))
# 下面这种方法将不在xlim和ylim范围的值都去掉了,而coord_cartesian方法是计算后取值,并没有将范围外的点去掉。
ggplot(diamonds) + geom_histogram(aes(price)) + xlim(100,5000)+ylim(0,3000)
5.4 缺失值
数据中有异常值,可以将异常值去掉:
dim(diamonds)
diamonds2 <- diamonds %>% filter(between(y,3,20))
dim(diamonds2)
一般不建议去掉,建议使用缺失值来代替异常值。
diamonds2 <- diamonds %>% mutate(y=ifelse(y<3 | y>20, NA, y))
ifelse函数参数1放入逻辑判断,如果为T,结果就是第二个参数的值,如果为F,就是第三个参数的值。
ggplot2会忽略缺失值:
ggplot(diamonds2, aes(x,y)) + geom_point()
# na.rm=TRUE 可以去掉缺失值
# is.na将含NA的行进行区分
nycflights13::flights %>%
mutate(
cancelled = is.na(dep_time),
sched_hour = sched_dep_time %/% 100,
sched_min = sched_dep_time %% 100,
sched_dep_time = sched_hour + sched_min / 60
) %>%
ggplot(mapping = aes(sched_dep_time)) +
geom_freqpoly(
mapping = aes(color = cancelled),
binwidth = 1/4
)
练习
(1) 直方图如何处理缺失值?条形图如何处理缺失值?为什么会有这种区别?
diamonds2 <- diamonds %>%
mutate(y = ifelse(y < 3 | y > 20, NA, y))
ggplot(diamonds2, aes(x = y)) +
geom_histogram()
# 直方图对x进行计数,自动去掉缺失值
# 柱状图将NA也统计为一个变量进行计数
diamonds %>%
mutate(cut = if_else(runif(n()) < 0.1, NA_character_, as.character(cut))) %>%
ggplot() +
geom_bar(mapping = aes(x = cut))
(2) na.rm = TRUE 在 mean() 和 sum() 函数中的作用是什么?
移除缺失值再进行统计
5.5 相关变动
5.5.1 分类变量与连续变量
# geom_freqpoly对于变异较小的变量不容易看出分别
# 这里频率多边形图显示的是数量
ggplot(diamonds,aes(price)) + geom_freqpoly(aes(color=cut),binwidth=500)
# 可以将y轴转变为密度
ggplot(diamonds,aes(price,..density..)) + geom_freqpoly(aes(color=cut),binwidth=500)
按分类变量的分组显示连续变量分布的另一种方式是使用箱线图
ggplot(diamonds,aes(x=cut,y=price)) + geom_boxplot()
ggplot(mpg,aes(class,hwy)) + geom_boxplot()
# 为了更容易发现趋势,可以基于 hwy 值的中位数对 class 进行重新排序
ggplot(mpg,aes(x=reorder(class,hwy,FUN=median),hwy)) + geom_boxplot()
# coord_flip将图形旋转 90 度
ggplot(mpg,aes(x=reorder(class,hwy,FUN=median),hwy)) + geom_boxplot() + coord_flip()
练习
(1) 前面对比了已取消航班和未取消航班的出发时间,使用学习到的知识对这个对比的可视
化结果进行改善。
# 比较取消和未取消的航班出发时间,简化为分类变量与连续变量的关系,用boxplot
head(nycflights13::flights)
nycflights13::flights %>% mutate( canceled = is.na(dep_time),
sched_hour = sched_dep_time %/% 100,
sched_min = sched_dep_time %% 100,
sched_dep_time = sched_hour + sched_min / 60
) %>%
ggplot() +
geom_boxplot(mapping = aes(y = sched_dep_time, x = canceled))
(2) 在钻石数据集中,哪个变量对于预测钻石的价格最重要?这个变量与切割质量的关系是
怎样的?为什么这两个变量的关系组合会导致质量更差的钻石价格更高呢?
# 很明显carat与price的价格最相关了,这两个都是连续变量,可以用点图表示
ggplot(diamonds,aes(x=carat,y=price)) + geom_point()
# 也可以将x分割为一个个单元进行统计
ggplot(diamonds,aes(carat,price)) + geom_boxplot(aes(group=cut_width(carat,0.1)))
# 查看颜色与价格的关系
ggplot(diamonds,aes(color,price)) + geom_boxplot()
ggplot(data = diamonds) +
geom_boxplot(mapping = aes(x = clarity, y = price))
(3) 安装 ggstance 包,并创建一个横向箱线图。这种方法与使用 coord_flip() 函数有何区别?
ggplot(data = mpg) +
geom_boxplot(mapping = aes(x = reorder(class, hwy, FUN = median), y = hwy)) +
coord_flip()
library("ggstance")
# 可以看出x和y轴进行了调换
ggplot(data = mpg) +
geom_boxploth(mapping = aes(y = reorder(class, hwy, FUN = median), x = hwy))
(4) 箱线图存在的问题是,在小数据集时代开发而成,对于现在的大数据集会显示出数量极
其庞大的异常值。解决这个问题的一种方法是使用字母价值图。安装 lvplot 包,并尝试
使用 geom_lv() 函数来显示价格基于切割质量的分布。你能发现什么问题?如何解释这
种图形?
library("lvplot")
ggplot(diamonds, aes(x = cut, y = price)) +
geom_lv()
ggplot(diamonds, aes(x = cut, y = price)) +
geom_boxplot()
(5) 比较并对比 geom_violin()、分面的 geom_histogram() 和着色的 geom_freqploy()。每种方法的优缺点是什么?
ggplot(data = diamonds, mapping = aes(x = price, y = ..density..)) +
geom_freqpoly(mapping = aes(color = cut), binwidth = 500)
ggplot(diamonds,aes(price)) + geom_histogram() + facet_wrap(~ cut, ncol = 1, scales = "free_y")
ggplot(data = diamonds, mapping = aes(x = cut, y = price)) +
geom_violin() +
coord_flip()
(6) 对于小数据集,如果要观察连续变量和分类变量间的关系,有时使用 geom_jitter() 函数是特别有用的。 ggbeeswarm 包提供了和 geom_jitter()相似的一些方法。列出这些方法
并简单描述每种方法的作用。
library("ggbeeswarm")
ggplot(data = mpg) +
geom_quasirandom(mapping = aes(x = reorder(class, hwy, FUN = median),
y = hwy))
ggplot(data = mpg) +
geom_quasirandom(mapping = aes(x = reorder(class, hwy, FUN = median),
y = hwy),
method = "tukey")
ggplot(data = mpg) +
geom_quasirandom(mapping = aes(x = reorder(class, hwy, FUN = median),
y = hwy),
method = "tukeyDense")
ggplot(data = mpg) +
geom_quasirandom(mapping = aes(x = reorder(class, hwy, FUN = median),
y = hwy),
method = "frowney")
ggplot(data = mpg) +
geom_quasirandom(mapping = aes(x = reorder(class, hwy, FUN = median),
y = hwy),
method = "smiley")
5.5.2 两个分类变量
两个分类变量的关系肯定要先计数,可以用geom_count()函数
d3heatmap 或 heatmaply 包可以生成交互式图
ggplot(diamonds) + geom_count(aes(cut,color))
str(diamonds)
# dplyr也可以用来计数
diamonds %>% count(color,cut)
#geom_tile函数填充热图
diamonds %>% count(color,cut) %>% ggplot(aes(color,cut)) +
geom_tile(aes(fill=n))
练习
(1) 如何调整 count 数据,使其能更清楚地表示出切割质量在颜色间的分布,或者颜色在切
割质量间的分布?
# 另外一种表示方法就是求比例
library(viridis)
diamonds %>% count(color,cut) %>% group_by(color) %>% mutate(prop=n/sum(n)) %>% ggplot(aes(color,cut)) + geom_tile(aes(fill=prop)) + scale_fill_viridis(limits=c(0,1))
diamonds %>%
count(color, cut) %>%
group_by(cut) %>%
mutate(prop = n / sum(n)) %>%
ggplot(mapping = aes(x = color, y = cut)) +
geom_tile(mapping = aes(fill = prop)) +
scale_fill_viridis(limits = c(0, 1))
(2) 使用 geom_tile() 函数结合 dplyr来探索平均航班延误数量是如何随着目的地和月份的
变化而变化的。为什么这张图难以阅读?如何改进?
nycflights13::flights %>%
group_by(month, dest) %>%
summarise(dep_delay = mean(dep_delay, na.rm = TRUE)) %>%
ggplot(aes(x = factor(month), y = dest, fill = dep_delay)) +
geom_tile() +
labs(x = "Month", y = "Destination", fill = "Departure Delay")
# 可以看出信息很杂乱,可以对目的地排序,移除缺失值,改变颜色等进行改进
nycflights13::flights %>% group_by(month,dest) %>% summarise(dep_delay=mean(dep_delay,na.rm=T)) %>%
group_by(dest) %>% filter(n() == 12) %>% ungroup() %>% mutate(dest=reorder(dest,dep_delay)) %>%
ggplot(aes(x=factor(month),y=dest,fill=dep_delay)) +
geom_tile() + scale_fill_viridis() + labs(x = "Month", y = "Destination", fill = "Departure Delay")
(3) 为什么在以上示例中使用 aes(x = color, y = cut) 要比 aes(x = cut, y = color) 更好?
# 一般会将大数量的分类变量放在y轴或者名字比较长的放y轴
diamonds %>%
count(color, cut) %>%
ggplot(mapping = aes(y = color, x = cut)) +
geom_tile(mapping = aes(fill = n))
diamonds %>%
count(color, cut) %>%
ggplot(mapping = aes(x = color, y = cut)) +
geom_tile(mapping = aes(fill = n))
5.5.3 两个连续变量
连续变量之间的关系一般用散点图来表示。geom_point()
ggplot(data = diamonds) +
geom_point(mapping = aes(x = carat, y = price))
#
ggplot(data = diamonds) +
geom_point(
mapping = aes(x = carat, y = price),
alpha = 1 / 100
)
对于大数据集,为了避免重合,可以用geom_bin2d() 和 geom_hex()函数将坐标平面分为二维分箱,并使用一种填充颜色表示落入
每个分箱的数据点。
ggplot(diamonds,aes(carat,price)) + geom_bin2d()
另一种方式是对一个连续变量进行分箱,因此这个连续变量的作用就相当于分类变量。
cut_width(x, width) 函数将 x 变量分成宽度为 width 的分箱。参数 varwidth = TRUE 让箱线图的宽度与观测数量成正比。
cut_number() 函数近似地显示每个分箱中的数据点的数量
ggplot(diamonds,aes(carat,price)) +geom_boxplot(aes(group=cut_width(carat,0.1)))
ggplot(diamonds,aes(carat,price)) +geom_boxplot(aes(group=cut_number(carat,20)))
练习
(1) 除了使用箱线图对条件分布进行摘要统计,你还可以使用频率多边形图。使用 cut_
width() 函数或 cut_number() 函数时需要考虑什么问题?这对 carat 和 price 的二维分
布的可视化表示有什么影响?
ggplot(diamonds,aes(color=cut_width(carat,1,boundary = 0),x=price)) +
geom_freqpoly() +ylab('Carat')
(2) 按照 price 分类对 carat 的分布进行可视化表示。
ggplot(diamonds,aes(cut_number(price,10),y=carat)) + geom_boxplot() +coord_flip()
(3) 比较特别大的钻石和比较小的钻石的价格分布。结果符合预期吗?还是出乎意料?
(4) 组合使用你学习到的两种技术,对 cut、 carat 和 price 的组合分布进行可视化表示。
(5) 二维图形可以显示一维图形中看不到的离群点。例如,以下图形中的有些点具有异常的
x 值和 y 值组合,这使得这些点成为了离群点,即使这些点的 x 值和 y 值在单独检验时
似乎是正常的。
ggplot(data = diamonds) +
geom_point(mapping = aes(x = x, y = y)) +
coord_cartesian(xlim = c(4, 11), ylim = c(4, 11))
5.6 模式和模型
数据中的模式提供了关系线索,用于探索两个变量的相关性。
模型是用于从数据中抽取模式的一种工具。
残差(预测值和实际值之间的差别)
library(modelr)
mod <- lm(log(price) ~ log(carat),data=diamonds)
diamonds2 <- diamonds %>% add_residuals(mod) %>% mutate(resid=exp(resid))
ggplot(diamonds2) + geom_point(aes(carat,resid))
阅读推荐:
生信技能树公益视频合辑:学习顺序是linux,r,软件安装,geo,小技巧,ngs组学!
B站链接:https://m.bilibili.com/space/338686099
YouTube链接:https://m.youtube.com/channel/UC67sImqK7V8tSWHMG8azIVA/playlists
生信工程师入门最佳指南:https://mp.weixin.qq.com/s/vaX4ttaLIa19MefD86WfUA