上一篇:R语言-分析与绘图(2):使用rvest库抓取和分析网页信息

@Keren Wang:函数名称不用记,用的时候可以查;但是为了对名称、功能和用法有印象,还是建议多写代码训练一下肌肉记忆,不然考试的时候可能查也来不及。

学习如何收集和分析数据是商业/金融/市场领域中的一项关键技能,但是如果没有有效表征结果的能力,这些数据可能毫无意义。“数据可视化”一词描述了将信息在可视化的上下文中展现出来的过程,以帮助我们以更完整和有效的方式理解数据。 我们可以用图表可以显示多个变量之间的关系,或者以图形方式突出显示不同数据集之间的差异。

本模块介绍了一些使用R绘图的基本技术,特别是扩展了使用tidyverse工具包中的ggplot2的知识。我们将使用来自NASA系外行星档案的真实天文数据来支持本章节的数据可视化学习。除了我们介绍到的内容,你也可以在 R cookbook for graphs 获取有用的学习资源。

首先,我们加载tidyverse库并读取行星数据表planets.csv。在提前被告知该表有32行是表头(介绍表格的内容,而非有用的数据)的情况下,我们使用read_delim()函数中的skip = 32参数来指定“在读取数据前先跳过32行”。

1
2
3
library(tidyverse)
kdf <- read_delim("planets.csv", delim=",", skip=32)
head(kdf)

示例:使用ggplot2库绘制第一个曲线图

ggplot() 函数

ggplot2是tidyverse里绘图的库。ggplot(data=df, aes(x,y)) 函数的作用是使用df这张表中的xy两列作为x轴和y轴,在二维坐标系下绘制图像。如果写成data = NULL,则需要自己建立两个长度相同的向量x和向量y作为横轴和纵轴的坐标。注意,这一步只是绘制出来了画布p,我们还没有定义曲线。如果只执行ggplot() 函数,我们将会得到下面的结果:

1
2
3
4
5
6
7
8
9
10
# 建立两个向量x和y作为横轴和纵轴坐标
x <- c(0. , 0.15, 0.3 , 0.45, 0.6 , 0.75, 0.9 , 1.05, 1.2 ,
1.35, 1.5 , 1.65, 1.8 , 1.95, 2.1 , 2.25, 2.4 , 2.55,
2.7 , 2.85)
y <- c(-0.51, -0.29, 1.13, -2.06, -0.63, -0.36, 1.41, -0.24, 1.27,
0.37, 0.23, 2.96, 2.54, 1.11, 2.92, 0.95, 2.17, 3.41,
4.87, 5.36)

p <- ggplot(NULL,aes(x,y)) # NULL表示我们不从表中取数据,直接使用自己创建的向量作为数据源
print(p)

geom_line()函数添加曲线

接下来我们需要在画布p上添加元素(点、线等)。ggplot2库规定我们使用加号+表示在图上增加元素。比如想要在刚才建立的画布p上绘制一个曲线,我们需要在p上使用加号叠加一个geom_line()函数,默认画的是黑色实线。

1
2
3
p <- ggplot(NULL,aes(x,y)) # NULL表示我们不从表中取数据,直接使用自己创建的向量作为数据源
p <- p + geom_line() # 画上一条曲线
print(p)

曲线出来了,但不是很好看。这是因为我们在geom_line()函数中没有定义样式。下面尝试把刚才的代码修改一下,把绘制曲线的函数改为geom_line(colour = 'red', linetype=2),其中colour = 'red'代表曲线颜色是红色,linetype=2代表我们使用长虚线(你可以试着修改这个数字,看看各个数字分别代表什么样式)。当然你也可以只定义颜色或者只定义线条种类。

1
2
3
p <- ggplot(NULL,aes(x,y)) # NULL表示我们不从表中取数据,直接使用自己创建的向量作为数据源
p <- p + geom_line(colour="red", linetype=2) # 画上一条曲线
print(p)

geom_point()函数添加坐标点

曲线画完了,如果我们想把这些点都标出来呢?同样地,ggplot2也提供了geom_point()函数用来画点,默认画的是黑色实心点。

1
2
3
4
p <- ggplot(NULL,aes(x,y)) # NULL表示我们不从表中取数据,直接使用自己创建的向量作为数据源
p <- p + geom_line(colour="red", linetype=2) # 画上一条曲线
p <- p + geom_point() # 把点也画上
print(p)

如果你觉得不好看,当然也可以自定义点的样式。我们把画点的函数修改为geom_point(colour="blue", shape=0)colour="blue"含义为点的颜色为蓝色,shape=0的含义为正方形空心点(你可以试着修改这个数字,看看各个数字分别代表什么样式)。同样地,你也可以只定义颜色或者形状。

1
2
3
4
5
6
7
8
9
10
11
12
# 建立两个向量x和y作为横轴和纵轴坐标
x <- c(0. , 0.15, 0.3 , 0.45, 0.6 , 0.75, 0.9 , 1.05, 1.2 ,
1.35, 1.5 , 1.65, 1.8 , 1.95, 2.1 , 2.25, 2.4 , 2.55,
2.7 , 2.85)
y <- c(-0.51, -0.29, 1.13, -2.06, -0.63, -0.36, 1.41, -0.24, 1.27,
0.37, 0.23, 2.96, 2.54, 1.11, 2.92, 0.95, 2.17, 3.41,
4.87, 5.36)

p <- ggplot(NULL,aes(x,y)) # NULL表示我们不从表中取数据,直接使用自己创建的向量作为数据源
p <- p + geom_line(colour="red", linetype=2) # 画上一条曲线
p <- p + geom_point(colour="blue", shape=0) # 把点也画上
print(p)

坐标轴和图表的标题:xlab()ylab()ggtitle()

这三个就没啥好理解的啦,xlab()ylab()明显就是设定x和y轴的标题(不写这两个函数的话就默认像上面一样显示x和y),ggtitle()也很显然就是设定图表的标题。这三个默认都不支持中文哈,如果需要显示中文的话可以找我帮你解决(可能和python的matplotlib一样需要修改字体文件)。运行一下试试:

1
2
p <- p + xlab("hahahahaha") + ylab("hehehehehe") + ggtitle("lalalalala")
print(p)

设定图片尺寸:使用options()函数

图画的实在是太小了怎么办?可以使用options(repr.plot.width=8, repr.plot.height=6)来解决。这句话的意思就是把宽度和高度设为某个值,这个值你可以自定义。

1
2
options(repr.plot.width=8, repr.plot.height=6)
print(p)

效果如下:

使用NASA行星数据画基本散点图

绘制轨道周期和轨道偏心率关系的散点图

在本节中,我们将使用数据库中的两个关键参数来创建2D图形:pl_orbopriod(轨道周期period)和pl_orbeccen(轨道偏心率eccentricity)。轨道周期定义了行星完成绕恒星轨道所花费的时间;轨道偏心率是介于0和1之间的数字,它确定行星轨道是完美的圆形(0)还是椭圆形(1)。

数据预处理——分组、按需取出子集

首先,由于我们想比较用不同方法发现的系外行星的属性,因此我们可以使用之前学习过的分组统计方法,对所有行星的数据按照“pl_discmethod”列也就是“发现方式”对数据进行分组和频率的统计。

1
2
3
4
5
6
# 按照“pl_discmethod”列对数据进行分组并生成频率统计表
kdf_new <- kdf %>%
group_by(pl_discmethod) %>%
summarise(n = n())

print(kdf_new)

假设我们想要把Transit、Radial Velocity、Imaging三种“发现方式”的行星放到一个表里去作图,我们可以用两种方法制作这样一个数据集。第一种是使用filter()先按照发现方式分别把这三种过滤到三个表中,再通过一个叫做rbind()的函数把这三个表堆叠起来,就合并成了一个表。

1
2
3
4
5
6
7
8
9
10
11
# 分别过滤出三个发现方式的所有数据
T_group <- kdf %>%
filter(pl_discmethod == "Transit")
RV_group <- kdf %>%
filter(pl_discmethod == "Radial Velocity")
I_group<- kdf %>%
filter(pl_discmethod == "Imaging")

# 堆叠并打印
plot.df <- rbind(T_group, RV_group, I_group)
print(plot.df)

第二种方式是在过滤器中直接使用“或”,也就是|符号,来取三个条件的并集。(显然这种更推荐)

1
2
3
plot.df <- kdf %>%
filter(pl_discmethod == "Transit" | pl_discmethod == "Radial Velocity" | pl_discmethod == "Imaging")
print(plot.df)

这两行代码的输出和上面的基本是一样的,如果看上去不太一样,那是因为堆叠是一种一种叠起来,而多条件筛选显然是在原表中一行一行筛选的,顺序会不太一样,总归没什么区别就是了。

按照取出的数据集画散点图

我们来厘清一下要求:绘制散点图,横轴是轨道周期,纵轴是轨道偏心率,用颜色区分不同的“发现方式”——这个功能可以在生成画布的时候使用colour = pl_discmethod参数来实现。ggplot2中,如果我们想让颜色colour或者尺寸size这种样式的差别来区分不同类别,需要在ggplot()的参数aes()中补充形如aes(x, y, size=…, colour=…)这种形式的参数。而如果我们只是想设定画线或者画点的尺寸,则是在geom_line()或者geom_point()函数中指定colour=..., size=...等,同时使用scale_colour_discrete(name='name')或者scale_size=(name=name)来绘制图例和图例的title。这一点要记得区分一下。

首先输入xy轴参数和颜色参数生成画布,然后再加入绘制散点的函数geom_point(),似乎就大功告成了。我们来写一下代码:

1
2
3
4
5
6
7
# 用 pl_discmethod 这一列数据来区分颜色
p <- ggplot(plot.df, aes(pl_orbper,pl_orbeccen, colour = pl_discmethod)) + geom_point()
# 加上两个坐标轴的标题
p <- p + xlab("Period (Days)") + ylab("Eccentricity")
# 图片搞大一点方便看
options(repr.plot.width=9, repr.plot.height=6)
print(p)

啊这……点怎么都挤在一起了,右边空空荡荡,左边几乎变成一条实线,这能看出来个啥?所以我们不能就此罢休,还是要继续搞一下。

我们发现,问题主要是出在因为x轴的尺度上。y轴的数据只在0-1的区间上变化,而轨道周期除了极个别的大数值以外,也在较小的数字区间上密集分布。这种不均匀分布我们称之为倾斜分布/偏斜分布。这时候如果我们能把刻度转为非线性的形式,就能够清晰地展示更多数据。

我们尝试在绘图过程中增加scale_x_log10()把x轴尺度转换成以10为底的对数刻度来绘制这个散点图(对应的如果数据在y轴上倾斜分布可以用scale_y_log10())。同时,我也希望能够使用scale_colour_discrete(name = "Discovery Method")给图例命名成“Discovery Method”,毕竟“pl_discmethod”很少有人能理解是什么意思。

1
2
3
4
5
6
7
8
9
# 和上面一样的代码
p <- ggplot(plot.df, aes(pl_orbper,pl_orbeccen, colour = pl_discmethod)) + geom_point()
p <- p + xlab("Period (Days)") + ylab("Eccentricity")
p <- p + ggtitle("Figure 1: Discovery methods")
# x轴转换为非线性的对数尺度,同时给区分颜色的图例加上标题
p <- p + scale_x_log10() + scale_colour_discrete(name = "Discovery Method")
# 图搞大点
options(repr.plot.width=9, repr.plot.height=6)
print(p)

这样效果就很棒棒了。

“这张图显示了有关我们太阳系的一些有趣信息。 尽管我们系统中的所有行星都具有相似的低偏心率,但系外行星的偏心率范围更广,从0到0.8。 同样,低偏心率的行星的周期很短,这表明它们离恒星非常近,这是我们在太阳系中没有观察到的,这使我们成为稀有且独特的行星系。”

绘制恒星质量和轨道周期关系的散点图

下面要求绘制x轴和y轴均使用对数尺度的恒星质量(st_mass)与行星轨道周期(pl_orbper)的函数关系,我们试试给数据点指定size为10且颜色为蓝色,看看效果是否很怪异。

1
2
3
4
5
6
7
8
9
10
11
# 数据点指定size为10且颜色为蓝色,注意不需要区分类的情况下,样式参数是写在aes外面的
p <- ggplot(kdf, aes(pl_orbper,st_mass)) + geom_point(size=10,colour='blue')
# 设置x和y两个坐标轴标题
p <- p + xlab('Planet Orbital Period (days)') + ylab('Stellar Mass (Solar mass)')
# 设置图片标题
p <- p + ggtitle('Figure 2: Log-Log Plot')
# 转换坐标轴尺度,x轴和y轴均使用对数尺度
p <- p + scale_x_log10() + scale_y_log10()
# 画大一点
options(repr.plot.width=7, repr.plot.height=7)
print(p)

绘制更复杂的散点图

接下来这段代码可能看起来很唬人,但是分解开来理解还是比较直观的。

我们的目的是使用颜色区分“行星平衡温度”的差别,希望散点的颜色不再是简单的红绿蓝,而是可以在一个色带中平滑过度,就好像用红外线成像仪拍出来的热力图一样,颜色越冷代表温度越低,颜色越暖代表温度越高,颜色总体在红和蓝之间过渡。

但是我们会遇到一些阻力。首先是平滑过渡的“jet colours”在R语言中并没有定义出来,我们需要单独使用一个向量、利用colorRampPalette()调色盘函数创建一个平滑过渡的jet色带。代码如下:

1
2
3
4
# 自己去定义色谱范围:蓝色、靛青、亮黄、红色作为四个基准点过渡的平滑色带
jet.colors <-
colorRampPalette(c("#00007F", "blue", "#007FFF", "cyan",
"#7FFF7F", "yellow", "#FF7F00", "red", "#7F0000"))

第二个是在原始数据表中,有些星球由于尚未探测,所以温度标注为“NA”。这对于画图的函数来说是一个困扰,因为无法给它分配颜色。我们希望能够简单地将NA的数据改写为1,保证图像顺利生成。is.na(table["col"])可以返回一个向量,这个向量包含了tablename表内col这一列的值为NA的所有行号。我们使用这个行号和col作为下标,把所有NA改为1。

1
2
# is.na(kdf['pl_eqt'])输出kdf表内pl_eqt这一列为NA的所有行序号的向量,把这些行的pl_eqt改为1
kdf[is.na(kdf['pl_eqt']),'pl_eqt'] <- 1

最后一个问题就是,前面创建的色带怎么设置为我们画图的颜色?还记得上面有一个给图例命名的函数,我们只需要在这个函数里加一个colours=jet.colors(10)就可以调用我们的色带,其中10的意思是我们使用这个色带中的10个色阶,也就是只用这个色带中的10个“断点”作为我们绘图的颜色。

了解了这些,我们来看看完整的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 自己去定义色谱范围:蓝色、靛青、亮黄、红色作为四个基准点过渡的平滑色带
jet.colors <-
colorRampPalette(c("#00007F", "blue", "#007FFF", "cyan",
"#7FFF7F", "yellow", "#FF7F00", "red", "#7F0000"))

# is.na(kdf['pl_eqt'])输出kdf表内pl_eqt这一列为NA的所有行序号的向量,把这些行的pl_eqt改为1
kdf[is.na(kdf['pl_eqt']),'pl_eqt'] <- 1
# 创建画布,pl_orbper作为横轴,st_mass作为纵轴,使用颜色编码数据+绘制散点
p <- ggplot(kdf, aes(pl_orbper,st_mass,colour=pl_eqt)) + geom_point()
# 创建色带图例和标题
p <- p + scale_colour_gradientn(name='Planet Equilibrium Temperature (K)',
colours=jet.colors(10))

# 标记坐标轴和图表标题
p <- p + xlab('Planet Orbital Period (days)') + ylab('Solar Mass (Solar mass)') + ggtitle('Figure 3: Colour-Coded scatter plot')
# 两个坐标轴都用log10尺度
p <- p + scale_x_log10() + scale_y_log10()
# 设定一下画布尺寸
options(repr.plot.width=7, repr.plot.height=5)
print(p)

结果非常的amazing啊!温度分布一目了然,处于左上角的明显温度偏高。@Keren Wang 请问这个你用excel能画嘛?三个维度,平滑过渡,画死你。

当然你也可以试着用size来反应温度的差别:温度越高点越大。这部分试着自己理解一下权当复习了,反正我也是自己试出来的(用你老师的话说:多试几遍run several more goes),直接放代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
# is.na(kdf['pl_eqt'])输出kdf表内pl_eqt这一列为NA的所有行序号的向量,把这些行的pl_eqt改为1
kdf[is.na(kdf['pl_eqt']),'pl_eqt'] <- 1
# 创建画布,pl_orbper作为横轴,st_mass作为纵轴,使用“尺寸/10” 编码数据+绘制散点
p <- ggplot(kdf, aes(pl_orbper,st_mass,size=pl_eqt/10)) + geom_point()
# 创建尺寸图例和标题
p <- p + scale_size(name='Planet Equilibrium Temperature (K)')
# 标记坐标轴和图表标题
p <- p + xlab('Planet Orbital Period (days)') + ylab('Solar Mass (Solar mass)') + ggtitle('Figure 3: Colour-Coded scatter plot')
# 两个坐标轴都用log10尺度
p <- p + scale_x_log10() + scale_y_log10()
# 设定一下画布尺寸
options(repr.plot.width=7, repr.plot.height=5)
print(p)

或者在彩色图的基础上直接改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 自己去定义色谱范围:蓝色、靛青、亮黄、红色作为四个基准点过渡的平滑色带
jet.colors <-
colorRampPalette(c("#00007F", "blue", "#007FFF", "cyan",
"#7FFF7F", "yellow", "#FF7F00", "red", "#7F0000"))

# is.na(kdf['pl_eqt'])输出kdf表内pl_eqt这一列为NA的所有行序号的向量,把这些行的pl_eqt改为1
kdf[is.na(kdf['pl_eqt']),'pl_eqt'] <- 1
# 创建画布,pl_orbper作为横轴,st_mass作为纵轴,使用颜色编码数据+绘制散点
p <- ggplot(kdf, aes(pl_orbper,st_mass,colour=pl_eqt,size=pl_eqt/10)) + geom_point()
# 创建图例和标题
p <- p + scale_colour_gradientn(name='Planet Equilibrium Temperature (K)',
colours=jet.colors(10))
p <- p + scale_size_continuous(name='Planet Equilibrium Temperature (K)')

# 标记坐标轴和图表标题
p <- p + xlab('Planet Orbital Period (days)') + ylab('Solar Mass (Solar mass)') + ggtitle('Figure 3: Colour-Coded scatter plot')
# 两个坐标轴都用log10尺度
p <- p + scale_x_log10() + scale_y_log10()
# 设定一下画布尺寸
options(repr.plot.width=7, repr.plot.height=5)
print(p)

这几种呈现方式都是非常综合且直观的。小贴士:如果你不想要某个图例,那就把guide设置成None就好了,比如上面这个例子里我们想把size的示例去掉,只用改一句:

看看是不是你不想要的图例已经没有了?

使用geom_histogram()函数绘制直方图

我们的数据表中有一列pl_disc记录了这个行星被发现的年份。如果我们想知道每年有多少行星被发现,也就是一个年份为横坐标的频度图,那么直方图会是一个比较合适的选择。geom_histogram(binwidth=2, fill="red")表示绘制宽度为2、填充色为红色的直方图。还是依照原来的老样子,我们绘制aes(pl_disc)的画布(如果aes只有一个参数,那么它会去统计这个参数的频率分布),然后在上面增加直方图。

1
2
3
4
5
6
7
8
# 创建画布并添加宽度为2、填充色为红色的直方图
p <- ggplot(kdf,aes(pl_disc)) + geom_histogram(binwidth=2, fill="red")
# 添加xy轴标题和图表标题
p <- p + xlab("Years") + ylab("Number of exoplanets")
p <- p + ggtitle("Histogram of number of discovered exoplanets")
# 指定画布尺寸
options(repr.plot.width=8, repr.plot.height=6)
print(p)

曲线、散点、直方图,这就是使用R语言绘图的三个最基础的内容,更多的方法可以在书中、网上探索。

下一篇:R语言-分析与绘图(4):对twitter数据进行文本挖掘