R数据可视化手册-下


library(ggplot2)
library(gcookbook)

第七章 注解

除了坐标轴标签、刻度线和图例这些标准的保留元素,还可以向图形添加独立的图形或文本元素。这些元素可用于增加额外的上下文信息、高亮图形的某个区域,或是补充一些关于数据的描述性文本。

添加文本注解

使用 annotate() 和一个文本类几何对象

p <- ggplot(faithful, aes(x=eruptions, y=waiting)) + geom_point()

p + annotate('text', x=3, y=48, label='Group 1', family='serif',fontface='italic',color='darkred',size=5) + 
 annotate('text', x=4.5, y=66, label='Group 2', family='serif',fontface='italic',color='darkred',size=5)

png

当你希望添加独立的文本对象时,千万不要使用geom_text()annotate(geom='text')会向图形添加一个单独的文本对象,而geom_text()却会根据数据创建许多的文本对象。
如果使用geon_text(),文本会在相同的位置被严重遮盖,每个数据点各重绘了一次:

p + annotate('text', x=3, y=48, label='Group 1', alpha=.1) +  # 正常
 geom_text(x=4.5, y=66, label='Group 2', alpha=.1)  # 遮盖绘制

png

在上图中,每个文本标签都是90%透明的,这样就很清楚地展示出了哪一个被遮盖绘制了(geom_text被遮盖绘制)。在输出为点阵格式时,遮盖绘制问题可能会导致边缘走样(有锯齿)。

如果坐标轴是连续型的,你可以使用特殊值 Inf 和 -Inf 在绘图区域的边缘放置文本注解。
同时,也需要使用hjust和vjust来调整文本相对于边角的位置——如果你让它们留在默认值的位置上,这些文本就会居中于边界线之上。
要将文本定位到理想的位置,可能需要进行一些尝试:

p + annotate('text', x=-Inf, y=Inf, label='Upper left', hjust=-.2, vjust=2, size=6) + 
 annotate('text', x=mean(range(faithful$eruptions)), y=-Inf, vjust=-0.4, 
         label='Bottom middle',size=6)

png

在注释中使用数学表达式

使用annotate(geom='text')并设置parse=TRUE

# 一条正态曲线

p <- ggplot(data.frame(x=c(-3,3)), aes(x=x)) + stat_function(fun = dnorm)

p + annotate('text', x=2, y=0.3, parse=TRUE,
           label='frac(1, sqrt(2 * pi)) * e ^ (-x^2 / 2)', size=8)

png

💬讨论
在ggplot2中使用parse=TRUE和文本类几何对象创建的数学表达式,和那些在R基础图形中利用 plotmath 和 expression 创建的数学表达式有着类似的格式,唯一的区别是,前者以字符串的形式存储,而后者是表达式对象。

要将常规文本融入表达式中,只需在双引号内使用单引号(或者反过来)标出纯文本的部分即可。通过内部引号闭合的每一块文本都将被作为数学表达式中的一个变量对待

切记,在R的数学表达式语法中,你不能简单地把一个变量直接放到另一个变量旁边而不在中间加上任何记号。

p + annotate('text', x=0, y=0.05, parse=TRUE, size=6,
            label="'Function:' * y==frac(1,sqrt(2 * pi)) * e^(-x^2/2) ")

png

❗ 表达式中,”=”是没有意义的,”==”才是等号的含义
在如上表达式中,’function’ 和 y 中间放了一个 * 操作符,它会被当作一个不可见的乘号对待(要显示一个可见的乘号,需要使用%*%).

添加直线

对于横线和竖线,使用 geom_hline()geom_vline()即可。对于有角度的直线,则可以使用geom_abline()。对于下例,我们将使用 heightweight 数据集:

p <- ggplot(heightweight, aes(x=ageYear, y=heightIn, color=sex)) + geom_point()

# 添加横线和竖线
p + geom_hline(yintercept = 60) + geom_vline(xintercept = 14)

png

# 添加有角度的直线
p + geom_abline(intercept = 37.4, slope = 1.75)

png

上例演示了手动设置直线位置的方法,效果是每添加一个几何对象绘制一条线。我们也可以将值从数据映射到xintercept、yintercept等之上,甚至是绘制另一个数据框中的值

我们将在这里计算男性和女性的平均身高,并将它们存储到一个数据框hw_means中。然后为每个均值绘制一条水平线,并手工设定linetype和size:

library(plyr) # 为了使用ddply()函数
hw_means <- ddply(heightweight, 'sex', summarise, heightIn=mean(heightIn))
hw_means
A data.frame: 2 × 2
sexheightIn
<fct><dbl>
f60.52613
m62.06000
p + geom_hline(aes(yintercept=heightIn,color=sex), data=hw_means,
              linetype='dashed', size=1)

png

如果某个坐标轴是离散型而不是连续型的,不能以字符串的形式直接指定截距——必须仍以数字的形式指定它们。
假设此坐标轴表示一个因子,那么第一个水平为数值1,第二个水平为数值2,依次类推。可以像下面这种手工指定数值型的截距,或者使用which(levels(…))计算所需数值:

pg <- ggplot(PlantGrowth, aes(x=group, y=weight)) + geom_point()

pg + geom_vline(xintercept = 2)

pg + geom_vline(xintercept = which(levels(PlantGrowth$group)=='ctrl'))

png

png

添加线段和箭头

使用annotate('segment')

options(repr.plot.width=8, repr.plot.height=4)
p <- ggplot(subset(climate, Source=='Berkeley'), aes(x=Year, y=Anomaly10y)) + 
 geom_line()

p + annotate('segment', x=1950, xend=1980,y=-.25, yend = -.25)

png

可以使用 grid 包中的arrow()函数向线段两端添加箭头或平头

library(grid)
p + annotate('segment', x = 1850, xend = 1820, y = -.8, yend = -.95, color = 'blue', size = 1, arrow=arrow(length = unit(.3, 'cm'))) + 
    annotate('segment', x=1950, xend=1980,y=-.25, yend = -.25,
            arrow=arrow(ends = 'both', angle = 90, length = unit(.2, 'cm')))

png

箭头线的默认角度(angle)为30度,默认长度(length)为0.2英寸(0.508厘米)。如果一个或多个坐标轴是离散型的,则 x 和 y 的位置即由拥有坐标值1,2,3等的类别项表示。

添加矩形阴影

💡 使用annotate('rect')

p <- ggplot(subset(climate, Source=='Berkeley'), aes(x=Year, y=Anomaly10y)) + 
        geom_line()

p + annotate('rect', xmin = 1950, xmax = 1980, ymin = -1, ymax = 1, alpha = .1, fill = 'blue')

png

💬讨论
每一个图层都是按照添加到ggplot对象的先后顺序绘制的。

只要传递了合适的参数,任意几何对象都是可以配合annotate()使用。在本例中,geom_rect()所需的参数是 x 和 y 的最大值和最小值。

高亮某一元素

要高亮一个或多个元素,需要在数据中创建一个新列并将其映射为颜色。在本例中,我们将创建一个新列 h1, 并根据 group 的值来设定它的值:

pg <- PlantGrowth
pg$h1 <- 'no'
pg$h1[pg$group=='trt2'] <- 'yes'
options(repr.plot.width=7, repr.plot.height=7)
ggplot(pg, aes(x=group, y=weight, fill=h1)) + geom_boxplot() + 
 scale_fill_manual(values = c('grey85', '#FFDDCC'), guide='none')

png

添加误差线

使用geom_errorbar()并将变量映射到 ymin 和 ymax(当横向作图时,为 xmin 和 xmax)。对于条形图和折线图,添加误差线的方法相同。

ce <- subset(cabbage_exp, Cultivar == 'c39')
head(ce,2)
A data.frame: 2 × 6
CultivarDateWeightsdnse
<fct><fct><dbl><dbl><int><dbl>
1c39d163.180.9566144100.30250803
2c39d202.800.2788867100.08819171
# 为条形图添加误差线
ggplot(ce, aes(x=Date, y=Weight)) + 
 geom_bar(fill='white', color='black', stat = 'identity', width = 0.5) + 
 geom_errorbar(aes(ymin=Weight - se, ymax=Weight + se), width = 0.15, size = 1)

# 为折线图添加误差线
ggplot(ce, aes(x=Date, y=Weight)) + 
 geom_line(aes(group=1)) + 
 geom_point(size = 4) + 
 geom_errorbar(aes(ymin=Weight - se, ymax=Weight + se), width = 0.15, size = 1)

png

png

关于 geom_line()中设置group,可参见第四章折线图

💬讨论
对于一幅分组的条形图,各误差线也必须被并列(dodged);(参见绘制簇状条形图以了解关于分组条形图和并列的更多信息)。

head(cabbage_exp, 2)
A data.frame: 2 × 6
CultivarDateWeightsdnse
<fct><fct><dbl><dbl><int><dbl>
1c39d163.180.9566144100.30250803
2c39d202.800.2788867100.08819171

geom_bar()的默认并列宽度为0.9,必须让误差线的并列宽度与此相同。如果不指定并列宽度,则默认按误差线的宽度并列,而此宽度通常小于条形的宽度:

# 反例:未指定并列宽度
ggplot(cabbage_exp, aes(x=Date, y=Weight, fill=Cultivar)) + 
 geom_bar(position = 'dodge', stat = 'identity') + 
 geom_errorbar(aes(ymin=Weight-se, ymax=Weight+se),
              position='dodge', width=0.2)

# 正例:设定列宽与条形的相同
ggplot(cabbage_exp, aes(x=Date, y=Weight, fill=Cultivar)) + 
 geom_bar(position = 'dodge', stat = 'identity', width = 0.6) + 
 geom_errorbar(aes(ymin=Weight-se, ymax=Weight+se),
              position=position_dodge(0.6), width=0.2, )

png

png

对于折线图和点图,应 先绘制误差线,这样它们就会位于点和线的下层。

应当同时并列所有的几何元素,这样它们就会同误差线对齐。

pd <- position_dodge(.3) # 保存并列设置,因为我们要重复使用

ggplot(cabbage_exp, aes(x=Date, y=Weight, color=Cultivar, group=Cultivar)) + 
 geom_errorbar(aes(ymin=Weight-se, ymax=Weight+se), position = pd, width=0.2, color = 'black') + 
 geom_line(position = pd) + 
 geom_point(position = pd, size = 2.5)

png

通过将 Cultivar 映射到 group 的方式来确保它被作为分组变量使用。
当一个离散型变量被映射到一个如 color 或 fill 的图形属性时,此变量就会被用于对数据进行分组。

添加误差线实例

condition <- c(rep('wt1',3), rep('MutL',3), rep('ΔL',3), rep('blank',3), rep('wt2',3), rep('ΔupL',3), rep('ΔlowL',3), rep('A1c',3))
value <- c(1.00, 1.03, 0.96, 0.47, 0.51, 0.50, 0.60, 0.56, 0.57, rep(0,3),0.97, 1.02, 1.00, 0.24, 0.27, 0.25, 0.23, 0.25, 0.23, 0.11, 0.12, 0.10)
df <- data.frame(condition, value)
ord <- unique(df$condition)
ord
  1. 'wt1'
  2. 'MutL'
  3. 'ΔL'
  4. 'blank'
  5. 'wt2'
  6. 'ΔupL'
  7. 'ΔlowL'
  8. 'A1c'
mean_df <- as.data.frame(tapply(df$value, df$condition, FUN = mean))  #得到平均值
means <- mean_df[ord,] # 按condition的顺序取平均值

sd_df <- as.data.frame(tapply(df$value, df$condition, FUN = sd))
sds <- sd_df[ord,]

mean_col <- rep(means,rep(3,length(means))) # 重复平均值用于做新列
sd_col <- rep(sds,rep(3,length(sds)))

df$mean <- mean_col
df$sd <- sd_col
df$condition <- factor(condition,levels = rev(ord)) # 设定condition 的 level 顺序
#pdf(file='LH_plot.pdf')
ggplot(df, aes(x=condition)) + 
 theme_classic(base_size = 25) +
 geom_bar(mapping = aes(y = mean) ,color=NA, fill="GoldEnrod",width = 0.4, stat = 'summary') + 
 geom_errorbar(aes(ymin=mean-sd, ymax=mean+sd),width=0.4, size = 1.5) +
 geom_point(aes(x=factor(condition), y = mean),size=2.5, position = position_jitter(width = 0.26)) + 
 theme(axis.text.y = element_text(hjust = 1,family = 'sans', face = 'bold.italic')) + 
 coord_flip() + 
 scale_y_continuous(position = 'right', expand = c(0,0.0)) + 
 labs(x= '', y = '') + 
 theme(axis.ticks.y = element_blank())
No summary function supplied, defaulting to `mean_se()`

png

向独立分面添加注解

使用分面变量创建一个新的数据框,并设定每个分面要绘制的值。然后配合新数据框使用geom_text():

head(mpg, 2)
A tibble: 2 × 11
manufacturermodeldisplyearcyltransdrvctyhwyflclass
<chr><chr><dbl><int><int><chr><chr><int><int><chr><chr>
audia41.819994auto(l5) f1829pcompact
audia41.819994manual(m5)f2129pcompact
options(repr.plot.width=8, repr.plot.height=4)
# 基本图形
p <- ggplot(mpg, aes(x=displ, y=hwy)) + geom_point() + facet_grid(. ~ drv) 

# 存有每个分面所需要标签的数据框
f_labels <- data.frame(drv = c('4', 'f', 'r'), label = c('4wd', 'Front', 'Rear'))

p + geom_text(x=6, y=40, aes(label=label), data = f_labels)

# 如果你使用annotate(), 标签将在所有分面上出现
p + annotate('text',x=6, y=42, label='label text')

png

png

第八章 坐标轴

交换x轴和y轴

使用coord_flip()来翻转坐标轴:

ggplot(PlantGrowth, aes(x=group, y=weight)) + geom_boxplot()

ggplot(PlantGrowth, aes(x=group, y=weight)) + geom_boxplot() + coord_flip()

png

png

对于散点图来说,调换纵轴和横轴上显示的元素非常简单:仅仅交换映射到 x 和 y 的变量就可以了。但并不是所有 ggplot2 中的几何对象都会同等对待 x轴 和 y轴。
举例来说,箱线图依 y 轴对数据计算统计摘要,折线图中的线段只沿 x 轴移动,误差线只有一个单独的 x 值但具有若干 y 值,等等。如果你正在使用这些几何对象,并且希望在图形中交换它们的坐标轴,那么 coord_flip() 正是你所需要的。

有时在交换坐标轴后,各项的顺序可能正好与你想要的相反。如果 x 变量是一各 factor 变量,则排列顺序可以通过使用scale_x_distance()和参数 limit=rev(levels(...))进行反转,

ggplot(PlantGrowth, aes(x=group, y=weight)) + geom_boxplot() + coord_flip() + 
 scale_x_discrete(limits=rev(levels(PlantGrowth$group)))

png

设置连续型坐标轴的值域

可以使用xlim()ylim()来设置一条连续型坐标轴的最小值和最大值。

head(PlantGrowth, 2)
A data.frame: 2 × 2
weightgroup
<dbl><fct>
14.17ctrl
25.58ctrl
p <- ggplot(PlantGrowth, aes(x=group, y=weight)) + geom_boxplot()

p + ylim(0, max(PlantGrowth$weight))

png

💬讨论
使用ylim()来设定范围是通过scale_y_continuous()来设定范围的简便写法。一下两种表达方式等价:

ylim(0, 10)
scale_y_continuous(limits = c(0, 10))
<ScaleContinuousPosition>
 Range:  
 Limits:    0 --   10



<ScaleContinuousPosition>
 Range:  
 Limits:    0 --   10
p + scale_y_continuous(limits = c(0, 10), breaks = c(0, 5, 10))

png

ggplot2中有两种设置坐标轴值域的方式。第一种是修改标度,第二种是应用一个坐标变换。
当你修改x标度和y标度的范围时,任何在范围以外的数据都会被移除——换言之,超出范围的数据不仅不会被展示,而且会被完全移出考虑处理的数据范围。

以上箱线图为例,如果你限制了 y 的值域,使得某些原始数据被剪掉,则箱线图中统计量的计算都会基于修剪后的数据,而箱线的形状也会随之改变

p + scale_y_continuous(limits = c(5.0, 6.5))
p + coord_cartesian(ylim = c(5.0, 6.5))
Warning message:
"Removed 13 rows containing non-finite values (stat_boxplot)."

png

png

通过使用坐标变换,数据则不会被修剪,从本质上说,它只是将数据放大或缩小到指定的范围。

最后,使用expand_limits()来单向扩展值域也是可以的。不过,不能用它来缩减值域:

p + expand_limits(y=0)

png

反转一条连续型坐标轴

使用 scale_y_reversescale_x_reverse。坐标轴的方向也可通过指定反序的范围来反转,先写最大值,再写最小值:

ggplot(PlantGrowth, aes(x=group, y=weight)) + geom_boxplot() + scale_y_reverse()

# 通过指定反序的范围产生类似的效果
ggplot(PlantGrowth, aes(x=group, y=weight)) + geom_boxplot() + scale_y_reverse()

png

png

💬讨论
scale_y_continuous()类似,scale_y_reverse()也无法与ylim配合工作(对x轴属性也一样)。如果你希望反转某条坐标轴并为它设定值域,则必须使用scale_y_continuous()

ggplot(PlantGrowth, aes(x=group, y=weight)) + geom_boxplot() + 
 scale_y_reverse(limits=c(8,0))

png

修改类别型坐标轴上项目的顺序

对于类别型(或者说离散型)坐标轴来说,会有一个因子型变量映射到它上面,坐标轴上项目的顺序可以通过设定scale_x_discrete()scale_y_discrete()中的参数limits来修改。

要手动设定坐标轴上项目的顺序,将一个依理想顺序排列的水平向量指定给limits即可。也可以通过这种方式忽略掉某些项目。

unique(PlantGrowth$group)
  1. ctrl
  2. trt1
  3. trt2
Levels:
  1. 'ctrl'
  2. 'trt1'
  3. 'trt2'
p <- ggplot(PlantGrowth, aes(x=group, y=weight)) + geom_boxplot()

p + scale_x_discrete(limits=c('ctrl', 'trt1', 'trt2'))

p + scale_x_discrete(limits=c('ctrl', 'trt1'))
Warning message:
"Removed 10 rows containing missing values (stat_boxplot)."

png

png

要反转项目顺序,设定 limits=rev(levels(...)),将因子型变量放入括号中即可。

p + scale_x_discrete(limits=rev(levels(PlantGrowth$group)))

png

设置x轴和y轴的缩放比例

使用 coord_fixed()。 以下代码将得到 x 轴和 y 轴之间 1:1 的缩放结果。这里的1:1指的是坐标轴单位长度表示的数值范围,而不是总长宽比例。

sp <- ggplot(marathon, aes(x=Half, y=Full)) + geom_point()

sp + coord_fixed()

png

通过在scale_y_continuous()scale_x_continuous中调整参数breaks,从而将刻度间距设为相同,也会有所帮助

sp + coord_fixed() + 
 scale_y_continuous(breaks = seq(0, 420, 30)) + 
 scale_x_continuous(breaks = seq(0, 420, 30))

png

如果你希望为两个坐标轴之间指定其他的固定比例而非相同的比例,可以设置参数ratio。对于marathon数据集,我们可能想让对应半程马拉松时间的坐标轴被拉伸到全程马拉松时间坐标轴的两倍。

sp + coord_fixed(ratio = 1/2) + 
 scale_y_continuous(breaks = seq(0, 420, 30)) + 
 scale_x_continuous(breaks = seq(0, 420, 15))

png

设置刻度线的位置

通常来说ggplot()会自动将刻度线摆放在合适的位置,但如果你希望改变它们的位置,设置标度中的参数breaks即可。

ggplot(PlantGrowth, aes(x=group, y=weight)) + geom_boxplot()

ggplot(PlantGrowth, aes(x=group, y=weight)) + geom_boxplot() + 
 scale_y_continuous(breaks = c(4, 4.25, 4.5, 5, 6, 8)) + 
 theme_classic()

png

png

💬讨论
可以使用seq()函数或运算符:来生成刻度线的位置向量:

seq(4, 7, by=.5)

5:10
  1. 4
  2. 4.5
  3. 5
  4. 5.5
  5. 6
  6. 6.5
  7. 7
  1. 5
  2. 6
  3. 7
  4. 8
  5. 9
  6. 10

设定breaks将会决定为哪些水平加上标签,但不会移除它们或是改变它们的顺序。可以通过指定limits来修改项目的顺序或移除项目.

ggplot(PlantGrowth, aes(x=group, y=weight)) + geom_boxplot() + 
 scale_x_discrete(limits=c('trt2','ctrl'), breaks = 'ctrl')
Warning message:
"Removed 10 rows containing missing values (stat_boxplot)."

png

移除刻度线和标签

仅移除刻度标签,使用 theme(axis.text.y = element_blank()) (也可对axis.text.x做相同处理)即可。这种方法对于连续型和离散型坐标轴均有效:

p <- ggplot(PlantGrowth, aes(x=group, y=weight)) + geom_boxplot()

p + theme(axis.text.y = element_blank())

png

要移除刻度线,可使用theme(axis.ticks=element_blank()).这样会同时移除两轴的刻度线(无法仅隐藏单个坐标轴的刻度线)。

p + theme(axis.ticks = element_blank(), axis.text.y = element_blank())

png

要移除刻度线、刻度标签和网格线,将breaks设置为NULL即可:

p + scale_y_continuous(breaks = NULL)

png

💬讨论
事实上,共有三种项目可以控制:刻度标签、刻度线和网格线。对于连续型坐标轴,ggplot()通常会在每个breaks值的位置放置刻度线、刻度标签和主网格线。对于类别型坐标轴,这些元素则出现在每个limits值的位置。
我们可以独立控制每条坐标轴上的刻度标签。但是,刻度线和网格线必须同时控制。

修改刻度标签的文本

hwp <- ggplot(heightweight, aes(x=ageYear, y=heightIn)) + 
        geom_point()

hwp

png

要想任意设定标签,在标度中为breaks和labels赋值即可。其中一个标签含有一个换行符(\n),意为让ggplot()在那里另起一行:

hwp + scale_y_continuous(breaks = c(50,56,60,66,72),
                        labels = c('Tiny', 'Really\nshort', 'Short',
                                  'Medium', 'Tallish'))

png

💬讨论
除了完全任意地设置标签以外,更常见的情况是数据以某种格式存储,而我们希望以另外一种格式显示标签。
我们可能向让身高变量显示为英尺和英寸的格式,而不是仅仅显示一个英寸数值。要完成这项任务,我们可以定义一个格式刷(formatter)函数,这样的函数可以读入数值并返回相应的字符串。例如,以下函数可将英寸数值转换为英尺加英寸的格式:

footinch_formatter <- function(x){
    foot <- floor(x/12)
    inch <- x %% 12
    return(paste(foot, "'", inch, "\"", sep=""))
}

下面是此函数对输入值56~64的返回结果:

footinch_formatter(56:64)
  1. '4\'8"'
  2. '4\'9"'
  3. '4\'10"'
  4. '4\'11"'
  5. '5\'0"'
  6. '5\'1"'
  7. '5\'2"'
  8. '5\'3"'
  9. '5\'4"'

现在就可以使用参数labels把我们的函数传递给标度了:

hwp + scale_y_continuous(labels = footinch_formatter)

png

在图中,每隔五英寸放置了一个自动生成的刻度线,但是对于这个数据来说看起来有点古怪。我们可以通过指定参数breaks让ggplot()每隔四英寸设置一条刻度线取而代之:

hwp + scale_y_continuous(breaks = seq(48,72,4), labels = footinch_formatter)

png

另一项常见任务是将时间测度转换为HH:MM:SS(时:分:秒)或者其他类似的格式。以下函数可以读入分钟的数值并将它们转换为这种格式,同时舍入到最接近的秒数(也可以按照自己的特殊需要来定义):

timeHMS_formatter <- function(x){
    h <- floor(x/60)
    m <- floor(x %% 60)
    s <- round(60 * (x %% 1)) # 舍入到最接近的秒数
    lab <- sprintf("%02d:%02d:%02d", h, m, s) # 格式化字符串为HH:MM:SS 的格式
    lab <- gsub("^00:", "", lab) # 如果开头存在00:则移除
    lab <- gsub("^0", "", lab) # 如果开头存在0 则移除
    return(lab)
}
timeHMS_formatter(c(.33, 50, 51.25, 59.32, 60, 60.1, 130.32))
  1. '0:20'
  2. '50:00'
  3. '51:15'
  4. '59:19'
  5. '1:00:00'
  6. '1:00:06'
  7. '2:10:19'

随ggplot2安装的scales包自带了一些内建的格式化函数:

  • comma() 在千、百万、十亿等位置向数字添加逗号。
  • dollar()添加一个美元符号并舍入到最接近的美分。
  • percent()乘以100,舍入到最接近的整数值,并添加一个百分号。
  • scientific()对大数字和小数字给出科学计数法表示,如 3.30e+05。

如果你希望使用这些函数,必须首先使用library(scales)加载scales包

修改刻度标签的外观

💡 方法
旋转标签文本

bp <- ggplot(PlantGrowth, aes(x=group, y=weight)) + geom_boxplot() + 
       scale_x_discrete(breaks=c('ctrl', 'trt1', 'trt2'),
                       labels=c('Control', 'Treatment 1', 'treatment 2'))

bp

png

要将文本逆时针旋转90°,只需使用:

bp + theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = .5))

png

将文本旋转30°可以占用更小的纵向空间,并且易于阅读。

bp + theme(axis.text.x = element_text(angle = 30, hjust = 1, vjust = 1))

png

参数hjust和vjust设置了横向对齐(左对齐/居中/右对齐)和纵向对齐(顶部对齐/居中/底部对齐)。

💬讨论
除了旋转以外,其他的文本属性,如大小、样式(粗体/斜体/常规)和字体族(如 Times 或 Helvetica)可以使用element_text()进行设置:

bp + theme(axis.text.x = element_text(family='Times', face='italic', color='darkred', size=rel(0.9)))
Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
"Windows字体数据库里没有这样的字体系列"
Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
"Windows字体数据库里没有这样的字体系列"

png

size 被设为 rel(0.9),意为当前主题基础字体大小的0.9倍。

这些命令仅仅控制了单个坐标轴上刻度标签的外观,并不影响其他坐标轴、坐标轴标签、整体的标题或图例。要同时控制所有这些元素的外观,可以使用主题系统。参见

修改坐标轴标签的文本

使用xlab()ylab()来修改坐标轴标签的文本:

hwp <- ggplot(heightweight, aes(x=ageYear, y=heightIn, color=sex)) + 
        geom_point()

# 使用默认的坐标轴标签
hwp

# 设置坐标轴标签
hwp + xlab('Age in years') + ylab('Height in inches')

png

png

💬讨论
默认情况下,图形将直接使用数据框中的列名作为数据轴标签。这对于探索数据来说可能还好,但在对外呈现数据时,你也许会希望更具描述力的坐标轴标签。

除了xlab()ylab(),也可以使用labs()

hwp + labs(x='Age in years', y='Height in inches')

png

设置坐标轴标签的另一种方法是在标度中指定,就像这样:

hwp + scale_x_continuous(name='Age in years')

这种方法看起来可能有点别扭,不过可能在你同时设定标度的其他属性(如刻度线位置、值域等)时会比较有用。

当然,这种方法同样适用于其他的坐标轴标度,如scale_y_continuous()、scale_x_discrete()等。

还可以适用\n来添加换行

hwp + scale_x_continuous(name = 'Age\n(years)')

png

移除坐标轴标签

💡 方法
使用theme(axis.title.x = element_black())。对于y轴标签,针对axis.title.y做同样处理。

移除坐标轴标签的另一种方法是将其设为一个空字符串。但如果以这种方式去做,那么图中将仍为文本留出空间。

移除坐标轴标签的外观

💡 方法
使用axis.title.x()即可

hwp <- ggplot(heightweight, aes(x=ageYear, y=heightIn)) + geom_point()

hwp + theme(axis.title.x = element_text(face='italic', color='darkred', size=14))

png

💬讨论
对于y轴标签来说,有时不对文本进行旋转会比较有用。
当调用element_text()时默认角度为0,所以如果设置了axis.title.y 但没有指定这个角度,它将以文本的顶部指向上方的朝向显示。如果修改了axis.title.y中的其他任何属性并且希望它以正常朝向,即旋转90°显示,则必须手动指定这个角度:

hwp + ylab('Height\n(inches)') + 
 theme(axis.title.y = element_text(angle = 90, face = 'italic', color = 'darkred', 'size'=14))

png

沿坐标轴显示直线

❓如何沿x轴和y轴显示直线,但不在图形的另外两侧显示?
💡使用主题设置中的 axis.line

p <- ggplot(heightweight, aes(x=ageYear, y=heightIn)) + geom_point()

p + theme_bw() +
  theme(panel.border = element_blank(),
      axis.line = element_line(color='black'))

png

如果最初使用的主题在绘图区域的周围就有一条边(如 theme_bw()),则需要同时重置参数 panel.border

如果边界线比较粗,则它们的末端将仅会部分地重叠。要让它们完全重叠,设置lineend="square"即可:

# 对于较粗地线条,只有一半重叠
p + theme_bw() + 
  theme(panel.border = element_blank(),
      axis.line = element_line(color='black', size=4))

# 完全重叠
p + theme_bw() +
  theme(axis.line = element_line(color='black', size=4, lineend = "square"),
        panel.border = element_blank())

png

png

使用对数坐标轴

💡使用scale_x_log10() 和/或 scale_y_log10:

library(MASS)
Warning message:
"package 'MASS' was built under R version 4.0.5"
head(Animals,2)
A data.frame: 2 × 2
bodybrain
<dbl><dbl>
Mountain beaver 1.35 8.1
Cow465.00423.0
p <- ggplot(Animals, aes(x=body, y=brain, label=row.names(Animals))) + 
      geom_text(size=3)
p

# 使用对数坐标轴
p + scale_x_log10() + 
 scale_y_log10()

png

png

关于将刻度线放到何处的问题,ggplot2会试着做出明智的选择,但是如果你不喜欢这些刻度,那么可以通过指定breaks(也可额外指定labels)来修改它们,在这个示例中,自动生成刻度线的间距较理想的间距更远。针对y轴的刻度线,可以像这样
10^(0:3)
获得一个含有从100到103的10的各次幂的向量

p + scale_x_log10(breaks=10^(-1:5)) + scale_y_log10(breaks=10^(0:3))

png

要让刻度标签而使用指数记数法,只要使用scales包中的函数trans_format()即可:

library(scales)
p + scale_x_log10(breaks=10^(-1:5),
                 labels=trans_format("log10", math_format(10^.x))) +
  scale_y_log10(breaks=10^(0:3),
               labels=trans_format("log10", math_format(10^.x)))

png

使用对数坐标轴的另一种方法是,在将数据映射到x和y坐标之前,先对其进行变换。从技术上讲,坐标轴仍然是线性的——它表示对数据变换后的数值:

ggplot(Animals, aes(x=log10(body), y=log10(brain), label=row.names(Animals))) + 
 geom_text(size=3)

png

使用这些变换有点复杂—— scales_x_log10() 可以简写,但是对于其他的对数标度而言,需要完整地定义它们:

library(scales)

p + scale_x_continuous(trans = log_trans(),
                      breaks = trans_breaks("log", function(x) exp(x)),
                      labels = trans_format("log", math_format(e^.x))) + 
    scale_y_continuous(trans = log2_trans(),
                      breaks = trans_breaks("log2", function(x) 2^x),
                      labels = trans_format("log2", math_format(2^x)))

png

也可以只使用一条对数坐标轴。这种做法对于呈现金融数据往往是有用的,因为这样往往能更好地展示出按比例的变化。

options(repr.plot.width = 8, repr.plot.height = 3 )
ggplot(aapl, aes(x=date, y=adj_price)) + geom_line()

ggplot(aapl, aes(x=date, y=adj_price)) + geom_line() + 
 scale_y_log10(breaks=c(2,10,50,250))

png

png

为对数坐标轴添加刻度

❓如何为对数坐标轴添加间距递减的刻度线
💡使用annotation_logticks()

library(MASS)
library(scales)
Warning message:
"package 'MASS' was built under R version 4.0.5"
head(Animals, 2)
A data.frame: 2 × 2
bodybrain
<dbl><dbl>
Mountain beaver 1.35 8.1
Cow465.00423.0
ggplot(Animals, aes(x=body, y=brain, label=rownames(Animals))) + 
 geom_text(size=3) + 
 annotation_logticks() + 
 theme_bw() +
 scale_x_log10(breaks = trans_breaks("log10", function(x) 10^x), 
               labels = trans_format("log10", math_format(10^.x))) + 
 scale_y_log10(breaks = trans_breaks("log10", function(x) 10^x),
               labels = trans_format("log10", math_format(10^.x)))

png

💬讨论
使用annotation_logticks()创建的刻度线事实上是绘图区域中的几何对象。在每个10的幂次处有一条长刻度线,在每个5的位置处有一条中等长度的刻度线。

绘制环状图形

💡方法
使用coord_polar()

head(wind,5)
A data.frame: 5 × 7
TimeUTCTempWindAvgWindMaxWindDirSpeedCatDirCat
<int><dbl><dbl><dbl><int><fct><dbl>
3 03.549.5210.398910-1590
4 53.529.10 9.90925-10 90
5103.538.73 9.51925-10 90
6153.638.97 9.90945-10 90
7203.718.51 9.41975-10 90

我们将使用geom_histogram()对每个SpeedCatDirCat的类别绘制样本数量的计数值。将binwidth设置为15以使直方图的origin开始于-7.5的位置,这样每个扇形就会居中于0、15、30等位置:

ggplot(wind, aes(x=DirCat, fill=SpeedCat)) + 
 geom_histogram(binwidth=15, boundary=-7.5) + 
 coord_polar() + 
 scale_x_continuous(limits=c(0,360))
Warning message:
"Removed 8 rows containing missing values (geom_bar)."

png

使用坐标图时要小心,因为这种图形会扭曲对数据的感知。本例中,在210°位置有15个风速为15-20的观测以及13个风速大于20的观测,但是对图形匆匆一瞥时,看起来风速大于20的观测更多一些,而且还存在三个风速10-15的观测,它们却几乎不可见。

在这个例子中,我们可以通过反转图例、使用不同的调色板、添加外框线以及将分割点设置为某些更熟悉的值的方式,让图形稍微美观一些:

ggplot(wind, aes(x=DirCat, fill=SpeedCat)) + 
 theme_bw() + 
 geom_histogram(binwidth=15, origin=-7.5, color='black', size=.25) + 
 guides(fill=guide_legend(reverse = TRUE)) + 
 coord_polar() + 
 scale_x_continuous(limits = c(0,360), breaks = seq(0,360,by=45),
                   minor_breaks=seq(0, 360, by=15)) + 
 scale_fill_brewer()
Warning message:
"`origin` is deprecated. Please use `boundary` instead."
Warning message:
"Removed 8 rows containing missing values (geom_bar)."

png

使用参数start设置图形起始的角度可能也是有用的,特别是当我们使用一个离散型变量映射为角度(theta)时。起始角度的值以弧度计,如果知道要调整的角度,则必须将它转换为弧度:

coord_polar(start = -45 * pi / 180)
<ggproto object: Class CoordPolar, Coord, gg>
    aspect: function
    backtransform_range: function
    clip: on
    default: FALSE
    direction: 1
    distance: function
    is_free: function
    is_linear: function
    labels: function
    modify_scales: function
    r: y
    range: function
    render_axis_h: function
    render_axis_v: function
    render_bg: function
    render_fg: function
    setup_data: function
    setup_layout: function
    setup_panel_guides: function
    setup_panel_params: function
    setup_params: function
    start: -0.785398163397448
    theta: x
    train_panel_guides: function
    transform: function
    super:  <ggproto object: Class CoordPolar, Coord, gg>

极坐标可与其他几何对象搭配使用,包括线和点。在使用这些几何对象时有一些重要的问题要牢记于心。首先,默认情况下,对于映射到y(或者说r)的变量,最小值将被映射到中心;

在坐标轴上使用日期

有用的链接

比较好的干货博客
r<-ggplot2 修改x和y轴刻度

第九章 控制图形的整体外观

设置图形标题

第十章 图例

移除图例

💡使用 guides(),并指定需要移除图例的标度

p <- ggplot(PlantGrowth, aes(x=group, y=weight, fill=group)) + 
 geom_boxplot() + 
 theme_bw(base_size = 25) + 
 theme(panel.grid = element_blank())
p + guides(fill="none")

png

或者在标度中设置 guide=”none”

p + scale_fill_discrete(guide="none")

png

使用主题系统 theme,会移除所有图例

p + theme(legend.position = "none")

png

当某个变量被映射到图形属性fill上时,默认使用的标度为scale_fill_discrete() (与scale_fill_hue()等价),这会将不同因子水平映射到色环上均匀分布的颜色值上。对于fill来说,也有其他的标度可用,如scale_fill_manual()。

修改图例的位置

💡使用theme(legend.position=…)即可。通过指定位置参数为top、left、right或bottom,图例即可被放置在顶部、左侧、右侧或底部:

p + 
 scale_fill_brewer(palette = 'Pastel2') + 
 theme(legend.position = 'top')

png

通过指定像 legend.position=c(1,0) 这样的位置坐标,图例亦可被置于绘图区域内部。空间中左下角为原点(0,0),右上角为(1,1)

也可以使用 legend.justification 来指定图例框的哪一部分被放置到 legend.position 所指定的位置上。默认情况下,图例的中心(0.5, 0.5)被置于给定的坐标处,但是指定一个不同的点往往是有用的。

p + theme(legend.position = c(0.95,0.025), legend.justification = c(1,0))

png

p + theme(legend.position = c(1,1), legend.justification = c(1,1))

png

在绘图区域内放置图例时,添加一个不透明的边界使其与图形分开可能会有所帮助

p + theme(legend.background = element_rect(fill = "white",color = "black"),
          legend.position = c(.85, .2))

png

修改图例项目的顺序

将对应标度的参数limits设置为理想的顺序即可

p + scale_fill_discrete(limits=c('trt1','trt2','ctrl'))

png

要注意的是,x轴上的顺序并没有改变。要修改这个顺序,需要设置 scale_x_discrete() 的limits参数,或者修改数据,使其拥有一个不同因子的水平顺序。

使用灰度调色板

p + scale_fill_grey(start = .5, end = .1, limits=c('trt1', 'trt2', 'ctrl'))

png

反转图例项目的顺序

💡添加guides(fill=guide_legend(reverse=TRUE))以反转图例的顺序(对于其他图形属性,使用相应图形属性的名称,如color或size替换fill即可):

p <- ggplot(PlantGrowth, aes(x=group, y=weight, fill=group)) +
 theme_bw(base_size = 25) + 
 geom_boxplot()
p

png

p + guides(fill=guide_legend(reverse = TRUE))

png

在设定标度的同时也可以控制图例

scale_fill_hue(guide=guide_legend(reverse = TRUE))

修改图例标题

p <- ggplot(PlantGrowth, aes(x=group, y=weight, fill=group)) + 
 geom_boxplot() + 
 theme_bw(base_size = 25)
p

png

# 指定图形属性进行命名
p + labs(fill='condition')

png

在设定标度时也可以设置图例标题。由于图例和坐标轴均为引导元素,这样做与设置x轴或y轴标题的原理是相同的。

p + scale_fill_discrete(name='condition')

如果有多个变量被映射到带有图例的图形属性(即除x和y以外的图形属性),可以分别设置每个图例的标题。

library(gcookbook)
hw <- ggplot(data = heightweight, aes(x=ageYear, y=heightIn, color=sex)) + 
 geom_point(aes(size=weightLb)) + scale_size_continuous(range = c(1,4)) + 
 theme_bw(base_size = 25) + 
 theme(panel.grid = element_blank())

hw

hw + labs(color='Male/Female', size='weight\n(pounds)')

png

png

修改图例标题的外观

使用theme(legend.title=element_text())

p <- ggplot(PlantGrowth, aes(x=group, y=weight, fill=group)) + 
 geom_boxplot() + theme_bw(base_size = 25)

p + theme(legend.title = element_text(face = 'italic', family = 'times', color = 'red', size = 14))
Warning message in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)):
"Windows字体数据库里没有这样的字体系列"
Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
"Windows字体数据库里没有这样的字体系列"
Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
"Windows字体数据库里没有这样的字体系列"

png

移除图例标题

添加语句guides(fill=guide_legend(title=NULL))可以从图例中移除标题

ggplot(PlantGrowth, aes(x=group, y=weight, fill=group)) + 
 geom_boxplot() + 
 guides(fill=guide_legend(title = NULL))

png

修改图例标签

library(gcookbook)

p <- ggplot(PlantGrowth, aes(x=group, y=weight, fill=group)) + 
 geom_boxplot()

p + scale_fill_discrete(labels=c('control','Treatmant1','Treatmant2'))

png

如果有一个变量被分别映射到两个图形属性,则默认会生成一个组合了两种情况的图例。如果希望修改图例标签,则必须同时修改两种标度中的标签;否则会得到两个分离的图例。

p <- ggplot(heightweight, aes(x=ageYear, y=heightIn, shape=sex, color=sex))+ 
 geom_point() + 
 theme_bw(base_size = 25)

p 

# 修改一个标度中的标签
p + scale_shape_discrete(labels=c('Female','Male'))

p + scale_shape_discrete(labels=c('Female','Male')) + 
 scale_color_discrete(labels=c('Female','Male'))

png

png

png

修改图例标签的外观

使用theme(legend.text=element_text())

p + theme(legend.text = element_text(face = 'italic', family = 'Times', color = 'red', size = 14))
Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
"Windows字体数据库里没有这样的字体系列"
Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, :
"Windows字体数据库里没有这样的字体系列"

png

更改图例条目布局

参考链接R ggplot2 改图例条目布局

快捷调整方法在theme主题系统,精确调整在 guides指南系统

精准调整,参数有

  • ncol表示列数,
  • nrow表示行数,ncol和nrow设置一个即可
  • byrow 可选T/F,默认F,即按照列填充
  • reverse 可选T/F,默认F,即升序填充,反之则降序
# 快捷调整
base + theme(
  legend.direction = "horizontal"
) 

# 精准调整
base + guides(
  color = guide_legend(
    ncol = 2, 
    byrow = TRUE,
    reverse = T)
  )

第十一章 分面

使用分面将数据分割绘制到子图中

head(mpg,2)
A tibble: 2 × 11
manufacturermodeldisplyearcyltransdrvctyhwyflclass
<chr><chr><dbl><int><int><chr><chr><int><int><chr><chr>
audia41.819994auto(l5) f1829pcompact
audia41.819994manual(m5)f2129pcompact
p <- ggplot(mpg, aes(x=displ, y=hwy)) + geom_point()
# 纵向排列的子面板根据 drv 分面
p + facet_grid(drv ~ .)

也可以使用 facet_wrap( ~ drv)

png

# 横向排列的子面板根据 cyl 分面
p + facet_grid(. ~ cyl)

png

# 同时纵向(drv)和横向(cyl)分割
p + facet_grid(drv ~ cyl)

png

在不同坐标轴下使用分面

将标度设置为 “free_x”、”free_y”、或”free”

# 使用自由的 y 标度
p + facet_grid(drv ~ cyl, scales = 'free_y')

png

p + facet_grid(drv ~ cyl, scales = 'free')

png

修改分面的文本标签

💡修改因子各水平的名称即可

mpg2 <- mpg
levels(mpg2$drv)[levels(mpg2$drv)=="4"] <- "4wd"
levels(mpg2$drv)[levels(mpg2$drv)=="f"] <- "Font"
levels(mpg2$drv)[levels(mpg2$drv)=="r"] <- "Rear"
levels(mpg$drv)
NULL
ggplot(mpg2, aes(x=displ, y=hwy)) + geom_point() + facet_grid(drv ~ .)

png

修改分面标签和表标题的外观

使用主题系统,通过设置 strip.text() 来控制文本的外观,设置strip.background() 以控制背景的外观:

library(gcookbook)
head(cabbage_exp,2)
A data.frame: 2 × 6
CultivarDateWeightsdnse
<fct><fct><dbl><dbl><int><dbl>
1c39d163.180.9566144100.30250803
2c39d202.800.2788867100.08819171
ggplot(cabbage_exp, aes(x=Cultivar, y=Weight)) + geom_bar(stat = 'identity') + 
 facet_grid(. ~ Date) + 
 theme_bw(base_size = 20) + 
 theme(strip.background = element_rect(fill = 'lightblue', color='black', size=1),
       strip.text = element_text(face='bold', size=rel(1.5))
      )

png

facet_wrap 与 facet_grid

两个函数这两个函数都可以进行分面,但使用和达到的效果有所不同。
参考 ggplot2 分面相关设置(facet)

facet_wrap(facets, nrow = NULL, ncol = NULL, scales = "fixed", shrink = TRUE, as.table = TRUE, drop = TRUE)

facet_grid(facets, margins = FALSE, scales = "fixed", space = "fixed", shrink = TRUE, labeller = "label_value", as.table = TRUE, drop = TRUE)

其中facet_wrap和facet_grid不同在于facet_wrap是基于一个因子进行设置,facets表示形式为:变量(单元格)

而facet_grid是基于两个因子进行设置,facets表示形式为:变量变量(行列),如果把一个因子用点表示,也可以达到facet_wrap的效果,也可以用加号设置成两个以上变量

例如:变量+变量~变量 的形式,表示对三个变量设置分面。

具体参数:
具体的参数(把两个函数参数和在一起):

  • nrow,ncol 分面设置成的行和列,参数为数值,表示几行或者几列

  • scales 参数fixed表示固定坐标轴刻度,free表示反馈坐标轴刻度,也可以单独设置成free_x或free_y

  • shrink 也和坐标轴刻度有关,如果为TRUE(默认值)则按统计后的数据调整刻度范围,否则按统计前的数据设定坐标。

  • drop 表示是否去掉没有数据的分组,默认情况下不显示,逻辑值为FALSE

  • as.table 和小图排列顺序有关的选项。如果为TRUE(默认)则按表格方式排列,即最大值(指分组level值)排在表格最后即右下角,否则排在左上角。

  • margins 通过TRUE或者FALSE表示否设置而一个总和的分面变量,默认情况为FALSE,即不设置

  • space 表示分面空间是否可以按照数据进行缩放,参数和scales一样

第十二章 配色

对于点形来说,大多数点形,整个点的颜色是由 color 控制的,而不是 fill。例外的情况是21-25号点,它们不仅有填充色,也有边界色

函数scale_fill_hue()中,颜色来自 HCL 色系(hue-chroma-lightness:色相-色度-亮度)的色轮,默认亮度是54(取值为0~100)

library(gcookbook)
p <- ggplot(data = uspopage, aes(x = Year, y = Thousands, fill = AgeGroup)) + 
 geom_area()
h <- ggplot(heightweight, aes(x=ageYear, y=heightIn, color=sex)) + 
 geom_point()
# 默认亮度 65
h

# 略微加深
h + scale_color_hue(l=45)

png

png

ColorBrewer包提供了很多调色板,你可以生成一张图来查看该包中的所有调色板:

library(RColorBrewer)
display.brewer.all()

png

p + scale_fill_brewer(palette = 'Oranges')

png

你还可以使用灰度调色板,它很适合黑白打印。标度范围是0~1(其中0对应黑色,1对应白色),灰度调色板的默认范围是0.2~0.8,但这个可以改。

p + scale_fill_grey(start = 0, end = 0.7)

png

对离散型变量使用自定义调色板

# 使用颜色名
h + scale_color_manual(values = c('red','blue'))

png

# 使用RGB值
h + theme_bw(base_size = 25) +
 scale_color_manual(values = c('#CC6666','#7777DD'))

png

💬参数 values 向量中的元素顺序自动匹配离散标度对应因子水平的顺序。
查看因子顺序的方法:

levels(heightweight$sex)
  1. 'f'
  2. 'm'

如果变量是字符型向量而非因子形式,那么它会被自动转化为因子;顺序也默认地按照字母表排序.

# 自定义颜色分配的顺序
h + scale_color_manual(values = c(m='blue',f='red'))

png

RGB 颜色由6个数字组成:0-9,A-F(10-15).形如: #RRGGBB
每一个颜色由两个数字来表示,范围从 00 到 FF( 对应十进制中的255 )
💡十六进制中每个颜色通道常常重复同样的数字,因为这样更容易阅读并且第二个数字的精确值对外观的影响并不是很明显.

这里总结了一些设置RGB颜色的经验:

  • 在一般情况下,更大的数字更明亮,较小的数字更暗淡.
  • 如果想得到灰色,将三个颜色通道设置为相同的值.

RGB颜色十六进制表

对连续型变量使用自定义调色板

对连续型变量自定义渐变式的调色板.颜色可以用已命名的,也可以用RGB值来指定.

library(gcookbook)
# 基础图形
p <- ggplot(heightweight, aes(x=ageYear, y=heightIn, color=weightLb)) + 
 geom_point(size=3)
# 默认颜色
p 

png

# 使用两种颜色的渐变色
p + scale_color_gradient(low = 'black', high = 'white')

png

# 渐变色中间用白色划分
library(scales)
# muted, 来自 scales 包: 使标准颜色变得更柔和, 饱和度更低
p + scale_color_gradient2(low = muted('blue'), mid = 'white', high = muted('red'), midpoint = 110)

png

# n个颜色的渐变
p + scale_color_gradientn(colors = c('darkred','orange','yellow','white'))

png

根据数值设定阴影颜色

💡增加一列来对y进行划分,然后将该列映射到填充色标度上. 在本例中,首先对数据进行正负划分.

library(gcookbook)
cb<- subset(climate, Source=='Berkeley')
cb$valence[ cb$Anomaly10y >= 0 ] <- 'pos'
cb$valence[ cb$Anomaly10y < 0 ] <- 'neg'
head(cb,2)
A data.frame: 2 × 7
SourceYearAnomaly1yAnomaly5yAnomaly10yUnc10yvalence
<chr><dbl><dbl><dbl><dbl><dbl><chr>
1Berkeley1800NANA-0.4350.505neg
2Berkeley1801NANA-0.4530.493neg

对数据划分正负之后,就可以将valence变量映射到填充色上来作图了

options(repr.plot.width=12)
ggplot(cb, aes(x=Year, y=Anomaly10y)) + 
 geom_area(aes(fill=valence)) + 
 geom_line(size=1.5) + 
 geom_hline(yintercept = 0) + 
 theme_bw(base_size = 20) + 
 theme(panel.grid = element_blank(),
       legend.position = 'top') + 
 scale_fill_manual(values = c('neg'='blue','pos'='red'))

png

如果你仔细观察此图,会发现在0水平线附近有一些凌乱的阴影。这是因为两个颜色区域都是各自的数据点多边形包围形成的,而这些数据点并不都在0上。
为了解决这个问题,可以用approx()将数据插值到1000个点左右。

# approx() 返回一个列表,包含 x 和 y向量
interp <- approx(cb$Year, cb$Anomaly10y, n = 1000)
# 放在数据框中,并重新计算valence
cbi <- data.frame(Year=interp$x, Anomaly10y=interp$y) 
cbi$valence[ cbi$Anomaly10y >=0 ] <- 'pos'
cbi$valence[ cbi$Anomaly10y <0 ] <- 'neg'
options(repr.plot.width=12)
ggplot(cbi, aes(x=Year, y=Anomaly10y)) + 
 geom_area(aes(fill=valence), alpha=0.6) + 
 geom_line(size=1.5) + 
 geom_hline(yintercept = 0,size=1.2) + 
 theme_bw(base_size = 20) + 
 theme(panel.grid = element_blank(),
       legend.position = 'top') + 
 scale_fill_manual(values = c('neg'='#CCEEFF','pos'='#FFDDDD'))

png

第十三章 其他图形

绘制相关矩阵图

head(mtcars,3)
A data.frame: 3 × 11
mpgcyldisphpdratwtqsecvsamgearcarb
<dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl>
Mazda RX421.061601103.902.62016.460144
Mazda RX4 Wag21.061601103.902.87517.020144
Datsun 71022.84108 933.852.32018.611141

首先,使用cor函数计算相关矩阵,将会得到每两列之间的相关系数:

mcor <- cor(mtcars)
# 输出 mcor,保留两位小数
round(mcor, digits = 2)
A matrix: 11 × 11 of type dbl
mpgcyldisphpdratwtqsecvsamgearcarb
mpg 1.00-0.85-0.85-0.78 0.68-0.87 0.42 0.66 0.60 0.48-0.55
cyl-0.85 1.00 0.90 0.83-0.70 0.78-0.59-0.81-0.52-0.49 0.53
disp-0.85 0.90 1.00 0.79-0.71 0.89-0.43-0.71-0.59-0.56 0.39
hp-0.78 0.83 0.79 1.00-0.45 0.66-0.71-0.72-0.24-0.13 0.75
drat 0.68-0.70-0.71-0.45 1.00-0.71 0.09 0.44 0.71 0.70-0.09
wt-0.87 0.78 0.89 0.66-0.71 1.00-0.17-0.55-0.69-0.58 0.43
qsec 0.42-0.59-0.43-0.71 0.09-0.17 1.00 0.74-0.23-0.21-0.66
vs 0.66-0.81-0.71-0.72 0.44-0.55 0.74 1.00 0.17 0.21-0.57
am 0.60-0.52-0.59-0.24 0.71-0.69-0.23 0.17 1.00 0.79 0.06
gear 0.48-0.49-0.56-0.13 0.70-0.58-0.21 0.21 0.79 1.00 0.27
carb-0.55 0.53 0.39 0.75-0.09 0.43-0.66-0.57 0.06 0.27 1.00

如果数据含有不能用来计算系数的任何列(比如一列姓名),应该先将这些列剔除。如果在原始数据中存在缺失值(NA),得到的相关矩阵也会有缺失值。为了克服这个问题,可以使用函数选项use=”complete.obs”或者use=”pairwise.complete.obs”。

使用corrplot包来绘制相关矩阵图。

library(corrplot)
Warning message:
"package 'corrplot' was built under R version 4.0.5"
corrplot 0.89 loaded
corrplot(mcor)

png

💬讨论
corrplot()函数有相当多的选项。这里给出一个绘制相关矩阵图的例子,例图使用颜色方块和黑色文本标签,并且上边的文本标签呈45°右倾。

corrplot(mcor, method = "shade", shade.col = NA, tl.col = "black", tl.srt = 45)

png

将相关系数展示在矩阵的每一个小方块上,同时调淡一点调色板,会使系数更加可读;同时,移除多余的颜色图例。此外,对矩阵重新排序,使得相近的变量在图中更近。其中使用的参数是order=”AOE”(前两个特征向量的角排序)。

col <- colorRampPalette(c("#BB4444", "#EE9988", "#FFFFFF", "#77AADD", "#4477AA"))

corrplot(mcor, method = "shade", shade.col = NA, tl.col = "black", tl.srt = 45,
        col=col(200), addCoef.col="black", cl.pos = "no", order="AOE")
Error in colorlegend(colbar = colbar, labels = round(labels, 2), offset = cl.offset, : 找不到对象'vertical'
Traceback:


1. corrplot(mcor, method = "shade", shade.col = NA, tl.col = "black", 
 .     tl.srt = 45, col = col(200), addCoef.col = "black", cl.pos = "no", 
 .     order = "AOE")

2. colorlegend(colbar = colbar, labels = round(labels, 2), offset = cl.offset, 
 .     ratio.colbar = 0.3, cex = cl.cex, xlim = xlim, ylim = ylim, 
 .     vertical = vertical, align = cl.align.text)

png

和其他独立的绘图函数一样,corrplot()有很多自己的选项。这里列出一些最有用的选项。

type = c("full", "lower", "upper")全部,仅使用下三角或上三角
diag = TRUE是否显示对角线上的数值
method = "shade" 使用颜色方块图形
method = "ellipse"使用椭圆图形
addCoef.col="color" 在图形上添加相关系数的颜色
tl.srt = number设定图形上方文本标签的倾斜角度
tl.col = "color"设定文本标签颜色
order = c("AOE", "FPC", "hclust")矩阵重排序,使用特征值角排序、第一主成分或层次聚类

绘制函数曲线

使用stat_function()函数。为了得到合适的x的范围,必须给ggplot()函数传递一个“哑”数据集。在本例中,使用正态分布的密度函数dnorm()来演示。

p <- ggplot(data.frame(x=c(-3,3)), aes(x=x))

p + stat_function(fun = dnorm)

png

某些函数需要设定额外的参数,比如t分布的密度函数dt()就需要一个参数来设定自由度。可以这样来设定额外的参数:把参数放到一个列表中,再传给 args 参数

p + stat_function(fun = dt, args = list(df=2))

png

也可以绘制自定义的函数。其中第一个参数必须是x轴的值,并且必须返回y轴的值。

myfun <- function(xvar) {
    1 / (1 + exp(-xvar + 10))
}

ggplot(data.frame(x=c(0,20)), aes(x=x))  + stat_function(fun=myfun)

png

计算函数值时默认使用给出的x范围内的101个点。想增加点数,使得曲线更平滑,可以给stat_function()传递一个更大的n,比如stat_function(fun=myfun, n=200)

在函数曲线下添加阴影

根据你的曲线函数定义一个新的函数。

如何绘制热图

使用geom_title()或者geom_raster(),并将一个连续变量映射到fill上。

presidents
# 时间序列对象 而不是数据框
A Time Series: 30 × 4
Qtr1Qtr2Qtr3Qtr4
1945NA878275
194663504332
194735605455
19483639NANA
194969575751
195045374639
195136243223
19522532NA32
195359747560
195471617157
195571687973
195676716775
195779626357
195860494852
195957626166
196071626157
196172837178
196279716274
196376646257
196480736969
196571646962
196663465644
196744523846
196836493544
196959656556
197066536152
197151485449
19724961NANA
197368444027
197428252424
str(presidents)
 Time-Series [1:120] from 1945 to 1975: NA 87 82 75 63 50 43 32 35 60 ...

首先将它转化为ggplot()可用的数据框格式,其中的列都是数值形式的。

pres_rating <- data.frame(
    rating  = as.numeric(presidents),
    year    = as.numeric(floor(time(presidents))),
    quarter = as.numeric(cycle(presidents))
)

pres_rating
A data.frame: 120 × 3
ratingyearquarter
<dbl><dbl><dbl>
NA19451
8719452
8219453
7519454
6319461
5019462
4319463
3219464
3519471
6019472
5419473
5519474
3619481
3919482
NA19483
NA19484
6919491
5719492
5719493
5119494
4519501
3719502
4619503
3919504
3619511
2419512
3219513
2319514
2519521
3219522
.........
3819673
4619674
3619681
4919682
3519683
4419684
5919691
6519692
6519693
5619694
6619701
5319702
6119703
5219704
5119711
4819712
5419713
4919714
4919721
6119722
NA19723
NA19724
6819731
4419732
4019733
2719734
2819741
2519742
2419743
2419744

现在我们可以使用geom_title()gem_raster()来绘图。简单地将几个变量分别映射到x, y 和 fill:

options(repr.plot.height=4 ,repr.plot.width=8)
# 基础图形
p <- ggplot(pres_rating, aes(x=year, y=quarter, fill=rating))

# 使用geom_title()
p + geom_tile()

# 使用 geom_raster()看起来一样,但效率略低
p + geom_raster()

png

png

为了更有效地表达信息,可能需要自定义热图地外观。在本例中,倒转y轴,这样顺序就是从上到下,并且在x轴上,每隔4年添加一个坐标刻度来表示一个总统任期。此外,更换之前的颜色标度,使用scale_fill_graadient2()调色盘,该调色盘可以设置一个中点和两个端点的色彩值:

p + geom_tile() + 
 scale_x_continuous(breaks = seq(1940, 1976, by=4)) +
 scale_y_reverse() + 
 scale_fill_gradient2(midpoint = 50, mid = 'grey70', limits=c(0,100))

png

绘制三维散点图

使用rgl包,该包提供了OpenGL图形库的3D绘图接口。要画三维散点图,可以使用plot3d()函数。其输入参数可以是两种形式:(1)一个数据框,前三列分别表示x、y、z;(2)直接传递三个向量,分别表示x、y、z的坐标。

library(rgl)
Warning message:
"package 'rgl' was built under R version 4.0.5"
plot3d(mtcars$wt, mtcars$disp, mtcars$mpg, type="s", size=.75, lit=FALSE)

绘制QQ图

❓如何绘制QQ图来比较经验分布和理论分布?

💡使用qqnorm()和正态分布比较。给qqnorm()一个数值向量,在此基础上用qqline()加上理论分布。

# height的QQ图
qqnorm(heightweight$heightIn)
qqline(heightweight$heightIn)

# age的QQ图
qqnorm(heightweight$ageYear)
qqline(heightweight$ageYear)

png

png

heightIn 的 点很接近理论线,这意味着它的分布很接近正态分布。相反,ageYear的点远离理论线,特别是在左面,这表明是有偏离的。此外,直方图在探索数据的分布上也是有帮助的。

绘制经验累积分布函数

❓如何画一个数据集的经验累积分布函数图(ECDF)?
💡使用stat_ecdf():

# heightIn 的 ecdf
ggplot(heightweight, aes(x=heightIn)) + stat_ecdf()

# ageYear的 ecdf
ggplot(heightweight, aes(x=ageYear)) + stat_ecdf()

png

png

ecdf表明了在观测数据中,小于或等于给定x值的观测所占的比例。因为是经验的,所以累积分布线在每个有观测值的x值处产生一个阶梯。

第十四章 输出图形用以展示

输出为PDF矢量文件

最常用的方法是使用 pdf()打开PDF图形设备,绘制图形,然后使用dev.off()关闭图形设备。

# width 和 height 的单位是 英寸: 1cm = 2.54英寸
pdf('myplot.pdf', width=4, height=4)

# 绘制图形
plot(mtcars$wt, mtcars$mpg)
print(ggplot(mtcars, aes(x=wt, y=mpg)) + geom_point())

dev.off()

png: 2

如果绘制的图形多于一副,则每一页将在PDF输出中列于独立的一页。
❗注意,使用print(),确保即使是一段脚本也能输出图形。

如果在创建图形过程中抛出一个错误,R无法执行到dev.off()这一步调用,并可能停留在PDF设备仍然开启的状态。当这种情况发生时,直到手动调用dev.off()之前,PDF文件将无法正常打开。

用ggplot创建图形,使用ggsave()会简单一点,但它只会保存最后一幅图形。

ggplot(mtcars, aes(x=wt, y=mpg)) + geom_point()

# 默认为英寸单位,不过也可指定
ggsave('myplot-ggsave.pdf', width = 8, height = 8, units = 'cm')

png

在PDF文件中使用字体

如何在PDF文件中使用由R提供的基本字体以外的字体?

💡 extrafont包可用于创建包含其他字体的PDF文件。
需要一次性的软件安装和配置。
下载并安装Ghostscript,然后再R中执行以下命令。

install.packages('extrafont')
library(extrafont)

# 查找并保存系统中已安装字体的信息
font_import()

# 列出字体
fonts()

第十五章 数据塑形

想深入学习数据处理,建议前往<R数据科学>进行深度学习

R中数据集常以数据框的形式存在。它们都是典型的二维数据结构,每行代表一个具体对象(case,object),每行代表一个描述对象的变量。
数据框本质上是由向量和因子组成的列表,其中每个向量或者因子代表了数据的一列。

library(gcookbook)
Warning message:
"package 'gcookbook' was built under R version 4.0.3"
head(heightweight,2)
A data.frame: 2 × 5
sexageYearageMonthheightInweightLb
<fct><dbl><int><dbl><dbl>
1f11.9214356.3 85
2f12.9215562.3105

使用str()函数了解数据结构

str(heightweight)
'data.frame':    236 obs. of  5 variables:
 $ sex     : Factor w/ 2 levels "f","m": 1 1 1 1 1 1 1 1 1 1 ...
 $ ageYear : num  11.9 12.9 12.8 13.4 15.9 ...
 $ ageMonth: int  143 155 153 161 191 171 185 142 160 140 ...
 $ heightIn: num  56.3 62.3 63.3 59 62.5 62.5 59 56.5 62 53.8 ...
 $ weightLb: num  85 105 108 92 112 ...

数据第一列 sex ,是一个两水平(“f” 和 “m”)的因子,其余四列都是数值型向量(其中ageMonth是整型向量,但在此处,它与其他数值向量并无明显区别)。

创建数据框

如何将若干向量组织成数据框

💡可以把向量放在data.frame()里面:

# 从两个简单的向量开始
g <- c('A','B','C')
x <- 1:3
gf <- as.factor(g)
dat <- data.frame(g,x,gf)
dat
A data.frame: 3 × 3
gxgf
<chr><int><fct>
A1A
B2B
C3C

如果向量在一个列表当中,可以用as.data.frame()函数直接将它们转化成数据框:

lst <- list(group = g, value = x, groupfactor = gf)
dat <- as.data.frame(lst)
dat
A data.frame: 3 × 3
groupvaluegroupfactor
<chr><int><fct>
A1A
B2B
C3C

从数据框中提取信息

str()函数在提取数据框更多信息的时候很实用。一个常见问题是,有时候一个数据框包含的向量是字符型向量而不是因子,反之亦然。这个问题会在分析和画图的时候给我们造成一些困扰。

在Rstudio里面,在提示符(>)后面输入数据框的名称,然后敲下回车键。不会显示每列数据的类型。只有通过str()函数,才能看出它们的区别。
在jupyter中,输出数据框,每列名称下面会标注数据类型。

image.png

str(dat)
'data.frame':    3 obs. of  3 variables:
 $ group      : chr  "A" "B" "C"
 $ value      : int  1 2 3
 $ groupfactor: Factor w/ 3 levels "A","B","C": 1 2 3

向数据框添加列

💡只需把值赋到新列即可:

如果把单个值赋到新列,整个列都将是这个值。下面的例子是增加一个新列,值全都是NA

dat$newcol <- NA

从数据框中删除一列

💡把该列的值赋为 NULL 即可

dat$group <- NULL
dat
A data.frame: 3 × 3
valuegroupfactornewcol
<int><fct><lgl>
1ANA
2BNA
3CNA

也可以用subset()函数并将一个**-**(减号)置于待删除的列之前:

# 返回不包含barcol列的数据
dat <- subset(dat, select = -value)
# 排除多列
dat <- subset(dat, select = c(-groupfactor, -newcol))

重命名数据框的列名

💡使用names(dat) <- 或者 colnames(dat) <-函数即可:

names(dat) <- c('name1', 'name2', 'name3')

如果你想通过列名,重命名某一列:

library(gcookbook) 
names(anthoming)
  1. 'angle'
  2. 'expt'
  3. 'ctrl'
names(anthoming)[names(anthoming)=='ctrl'] <- c('Control')
names(anthoming)
  1. 'angle'
  2. 'expt'
  3. 'Control'

也可以通过名字的数值位置重命名:

colnames(anthoming)[1] <- 'Angle'
colnames(anthoming)
  1. 'Angle'
  2. 'expt'
  3. 'Control'

重排序数据框的列

💡通过列的 数值位置 或者 名称 重排序

library(gcookbook)
anthoming
A data.frame: 5 × 3
AngleexptControl
<int><int><int>
-2010
-1073
023
1003
2001
# 列表风格的索引
anthoming[c(1,3,2)]
A data.frame: 5 × 3
AngleControlexpt
<int><int><int>
-2001
-1037
032
1030
2010
# 矩阵风格的索引
anthoming[,c(1,3,2)] # 逗号之前的空白表示输出所有行
A data.frame: 5 × 3
AngleControlexpt
<int><int><int>
-2001
-1037
032
1030
2010

这种情况下,两种方法都会得到数据框。
❗然而,当你单独检索一列的时候,列表风格的索引会得到数据框,而矩阵风格的索引得到的是向量,除非你加上参数drop=FALSE:

str(anthoming[3]) # 列表风格的索引
'data.frame':    5 obs. of  1 variable:
 $ Control: int  0 3 3 3 1
str(anthoming[,3]) # 矩阵风格的索引, 获得向量
 int [1:5] 0 3 3 3 1
str(anthoming[,3, drop = FALSE]) # 矩阵风格的索引,并添加参数 drop=FALSE, 获得数据框
'data.frame':    5 obs. of  1 variable:
 $ Control: int  0 3 3 3 1

从数据框提取子集

使用subset()函数。

library(gcookbook)
str(climate)
'data.frame':    499 obs. of  6 variables:
 $ Source    : chr  "Berkeley" "Berkeley" "Berkeley" "Berkeley" ...
 $ Year      : num  1800 1801 1802 1803 1804 ...
 $ Anomaly1y : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Anomaly5y : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Anomaly10y: num  -0.435 -0.453 -0.46 -0.493 -0.536 -0.541 -0.59 -0.695 -0.763 -0.818 ...
 $ Unc10y    : num  0.505 0.493 0.486 0.489 0.483 0.475 0.468 0.461 0.453 0.451 ...
# 选取 Source 是 Berkeley 的行,并且选取名字为 Year 和 Anomaly10y 的列
climate.berkeley <- subset(climate, Source=='Berkeley', select = c(Year, Anomaly10y))
head(climate.berkeley,2)
A data.frame: 2 × 2
YearAnomaly10y
<dbl><dbl>
11800-0.435
21801-0.453

还可以通过使用 | (OR) 和 & (AND)操作符同时施加多种筛选条件。

climate.berkeley.hundred <- subset(climate, Source=='Berkeley' & Year>=1900 & Year<=2000, select = c(Year, Anomaly10y))
head(climate.berkeley.hundred, 2)
A data.frame: 2 × 2
YearAnomaly10y
<dbl><dbl>
1011900-0.171
1021901-0.162

也可以在方括号内加入索引来得到子数据框,虽然这种方法不是很优雅。

# 逗号后面的部分提取列
climate.indexsub<- climate[ climate$Source=='Berkeley' & climate$Year >= 1900 & climate$Year <=2000, c('Year', 'Anomaly10y')]
head(climate.indexsub,2)
A data.frame: 2 × 2
YearAnomaly10y
<dbl><dbl>
1011900-0.171
1021901-0.162

如果用这种方式得到的结果只有一列,那么它会返回一个向量而不是数据框,除非使用参数 drop=FALSE:

最后,还可以通过行和列的数值位置提取子数据框。

# 不建议使用数据索引,可读性不好
climate.indexpos <- subset(climate[1:100, c(2,5)])

改变因子水平的顺序

💡因子的水平可以由函数factor()具体设定。

# 默认的因子水平的顺序是按字母排列的
sizes <- factor(c('small','large','large','small','medium'))
sizes
  1. small
  2. large
  3. large
  4. small
  5. medium
Levels:
  1. 'large'
  2. 'medium'
  3. 'small'
# 改变因子水平的顺序
sizes <- factor(sizes, levels = c('small', 'medium', 'large'))
sizes
  1. small
  2. large
  3. large
  4. small
  5. medium
Levels:
  1. 'small'
  2. 'medium'
  3. 'large'

因子的顺序也可以在第一次创建因子时通过levels参数来指定

💬R中有两种因子:顺序因子(order factor)和常规因子(regulator factor)。在两种类型中,因子水平都是按照某种顺序排列的;区别在于,对于顺序因子,因子水平的顺序是有意义的,而对于常规因子,因子水平的顺序却没有什么意义。

在ggplot2绘图过程中,因子变量被映射到图形属性中,图形属性会采用因子水平的顺序。如 x 轴,x 轴标签会按因子水平的顺序排列。

如果要颠倒因子水平的顺序,可以使用函数 rev(levels()):

factor(sizes, levels = rev(levels(sizes)))
  1. small
  2. large
  3. large
  4. small
  5. medium
Levels:
  1. 'large'
  2. 'medium'
  3. 'small'

根据数据的值改变因子水平的顺序

💡使用函数reorder(),该函数有三个参数:因子,排序依据的数据(以哪一列做排序依据), 汇总数据的函数(操作数据方法)。

# 复制一份数据,因为我们要修改它
iss <- InsectSprays
iss$spray
  1. A
  2. A
  3. A
  4. A
  5. A
  6. A
  7. A
  8. A
  9. A
  10. A
  11. A
  12. A
  13. B
  14. B
  15. B
  16. B
  17. B
  18. B
  19. B
  20. B
  21. B
  22. B
  23. B
  24. B
  25. C
  26. C
  27. C
  28. C
  29. C
  30. C
  31. C
  32. C
  33. C
  34. C
  35. C
  36. C
  37. D
  38. D
  39. D
  40. D
  41. D
  42. D
  43. D
  44. D
  45. D
  46. D
  47. D
  48. D
  49. E
  50. E
  51. E
  52. E
  53. E
  54. E
  55. E
  56. E
  57. E
  58. E
  59. E
  60. E
  61. F
  62. F
  63. F
  64. F
  65. F
  66. F
  67. F
  68. F
  69. F
  70. F
  71. F
  72. F
Levels:
  1. 'A'
  2. 'B'
  3. 'C'
  4. 'D'
  5. 'E'
  6. 'F'
head(iss,2)
A data.frame: 2 × 2
countspray
<dbl><fct>
110A
2 7A
iss$spray <- reorder(iss$spray, iss$count, FUN = mean)
iss$spray
  1. A
  2. A
  3. A
  4. A
  5. A
  6. A
  7. A
  8. A
  9. A
  10. A
  11. A
  12. A
  13. B
  14. B
  15. B
  16. B
  17. B
  18. B
  19. B
  20. B
  21. B
  22. B
  23. B
  24. B
  25. C
  26. C
  27. C
  28. C
  29. C
  30. C
  31. C
  32. C
  33. C
  34. C
  35. C
  36. C
  37. D
  38. D
  39. D
  40. D
  41. D
  42. D
  43. D
  44. D
  45. D
  46. D
  47. D
  48. D
  49. E
  50. E
  51. E
  52. E
  53. E
  54. E
  55. E
  56. E
  57. E
  58. E
  59. E
  60. E
  61. F
  62. F
  63. F
  64. F
  65. F
  66. F
  67. F
  68. F
  69. F
  70. F
  71. F
  72. F
Levels:
  1. 'C'
  2. 'E'
  3. 'D'
  4. 'A'
  5. 'B'
  6. 'F'

起始因子水平的顺序是ABCDEF,重排后的顺序是CEDABF。新的顺序是由 iss$spray 中每组 iss$count 的平均值决定的。

ggplot(data = iss, aes(x=spray,y=count)) + 
 geom_boxplot() + 
 theme_bw(base_size = 25)

png

注意到这些中位值并不是严格按照从左到右依次递增的顺序的,于是在reorder()函数中应用 median() 函数

iss$spray <- reorder(iss$spray, iss$count, FUN = median)
iss$spray
  1. A
  2. A
  3. A
  4. A
  5. A
  6. A
  7. A
  8. A
  9. A
  10. A
  11. A
  12. A
  13. B
  14. B
  15. B
  16. B
  17. B
  18. B
  19. B
  20. B
  21. B
  22. B
  23. B
  24. B
  25. C
  26. C
  27. C
  28. C
  29. C
  30. C
  31. C
  32. C
  33. C
  34. C
  35. C
  36. C
  37. D
  38. D
  39. D
  40. D
  41. D
  42. D
  43. D
  44. D
  45. D
  46. D
  47. D
  48. D
  49. E
  50. E
  51. E
  52. E
  53. E
  54. E
  55. E
  56. E
  57. E
  58. E
  59. E
  60. E
  61. F
  62. F
  63. F
  64. F
  65. F
  66. F
  67. F
  68. F
  69. F
  70. F
  71. F
  72. F
Levels:
  1. 'C'
  2. 'E'
  3. 'D'
  4. 'A'
  5. 'F'
  6. 'B'
# 以 count 的 median 进行排序
ggplot(data = iss, aes(x=spray,y=count)) + 
 geom_boxplot() + 
 theme_bw(base_size = 25)

png

改变因子水平的名称

使用 plyr包中的revalue()函数或mapvalues()函数。

levels(sizes)
  1. 'small'
  2. 'medium'
  3. 'large'
library(plyr)
sizes1 <- revalue(sizes, c(small="S", medium="M", large="L"))
sizes1
  1. S
  2. L
  3. L
  4. S
  5. M
Levels:
  1. 'S'
  2. 'M'
  3. 'L'
# 也可以使用引号——如果原因子水平名称中存在空格等特殊字符,这将很有用
revalue(sizes, c("small"="S", "medium"='M', "large"="L"))
  1. S
  2. L
  3. L
  4. S
  5. M
Levels:
  1. 'S'
  2. 'M'
  3. 'L'
# mapvalues()函数使用两组向量,而不是一组映射关系向量
mapvalues(sizes, c("small", "medium", "large"), c("S","M","L"))
  1. S
  2. L
  3. L
  4. S
  5. M
Levels:
  1. 'S'
  2. 'M'
  3. 'L'

revalue()函数和mapvalues()函数很方便,但是在R中有一个更传统(也更笨)的方法,使用levels() <- 函数

sizes <- factor(c('small','large','large','small','medium'))
levels(sizes)[levels(sizes)=='large'] <- 'L'
levels(sizes)[levels(sizes)=='medium'] <- 'M'
levels(sizes)[levels(sizes)=='small'] <- 'S'
sizes
  1. S
  2. L
  3. L
  4. S
  5. M
Levels:
  1. 'L'
  2. 'M'
  3. 'S'

如果你要改变所有水平的名称,这里有一个更简单的方法。可以给 levels() 传递一个 list 类型的参数:

sizes <- factor(c('small','large','large','small','medium'))
levels(sizes) <- list(S='small', M='medium', L='large')
sizes
  1. S
  2. L
  3. L
  4. S
  5. M
Levels:
  1. 'S'
  2. 'M'
  3. 'L'

在这个方法中,所有因子必须在一个list里面指定;如果list里面有任何的缺失,缺失的值最终会以NA代替

去掉因子中不再使用的水平

使用 droplevels() 函数

把一个分类变量转化为另一个分类变量

使用PlantGrowth数据集的一个子数据集

pg <- PlantGrowth[c(1,2,11,21,22),]
pg
A data.frame: 5 × 2
weightgroup
<dbl><fct>
14.17ctrl
25.58ctrl
114.81trt1
216.31trt2
225.12trt2

在这个例子中,使用cut()函数把一个连续变量weight转化为分类变量wtclass

pg$wtclass <- cut(pg$weight, breaks = c(0,2,6,Inf))
pg
A data.frame: 5 × 3
weightgroupwtclass
<dbl><fct><fct>
14.17ctrl(2,6]
25.58ctrl(2,6]
114.81trt1(2,6]
216.31trt2(6,Inf]
225.12trt2(2,6]

💬我们为三个类设定了四个边界,边界值可以包括无穷Inf.如果一个值落在了规定区间外,它的类别将被定为NA(缺失值)。cut()函数的输出结果是一个因子,可以从下面这个例子看出:因子水平的名称是以生成的区间命名的。

为了改变因子水平的名称,可以使用cut()中的labels参数

pg$wtclass <- cut(pg$weight, breaks = c(0,5,6,Inf), labels = c('small', 'medium', 'large'))
pg
A data.frame: 5 × 3
weightgroupwtclass
<dbl><fct><fct>
14.17ctrlsmall
25.58ctrlmedium
114.81trt1small
216.31trt2large
225.12trt2medium

cut生成的区间是左开右闭的。可以通过设置 include.lowest=TRUE使得它同时包含最小值和最大值。如果想让区间是左闭右开的,设定参数right=FALSE:

变量转换

可以使用$操作符来引用新列并对齐赋予新值。

library(gcookbook)
hw <- heightweight
head(hw,2)
A data.frame: 2 × 5
sexageYearageMonthheightInweightLb
<fct><dbl><int><dbl><dbl>
1f11.9214356.3 85
2f12.9215562.3105
hw$heighCm <- hw$heightIn * 22.54

💬为了使代码更容易阅读,可以使用transform()或plyr包中的mutate()函数。
只需指定数据框一次,将其作为函数的第一个参数;它们提供了非常清晰的语法,尤其适合转换多个变量:

hw <- transform(hw, heightCm = heightIn * 2.54, weightKg = weightLb / 2.204)
head(hw,1)
A data.frame: 1 × 8
sexageYearageMonthheightInweightLbheighCmheightCmweightKg
<fct><dbl><int><dbl><dbl><dbl><dbl><dbl>
1f11.9214356.3851269.002143.00238.56624
library(dplyr)
Warning message:
"package 'dplyr' was built under R version 4.0.5"

Attaching package: 'dplyr'


The following objects are masked from 'package:plyr':

    arrange, count, desc, failwith, id, mutate, rename, summarise,
    summarize


The following objects are masked from 'package:stats':

    filter, lag


The following objects are masked from 'package:base':

    intersect, setdiff, setequal, union
hw <- mutate(hw, heightCm = heightIn * 2.54, weightKg = weightLb / 2.204)

也可以根据多个变量产生一个新的变量:

transform() 和 mutate() 函数的最大区别是 transform() 会同时计算所有的新列,而 mutate() 将依次计算新列,这样在计算新列时,就可以依赖之前的新列。由于bmi是由 heightCm 和 weightKg 计算来的,因此用 transform() 不能同时计算这些变量;先算得到 heightCm 和 weightKg ,再计算 bmi。而 mutate 可以一次完成计算。

hw <- mutate(hw,
       heightCm = heightIn * 2.54,
       weightKg = weightLb / 2.204,
       bmi = weightKg / (heightCm / 100)^2
)
head(hw,2)
A data.frame: 2 × 9
sexageYearageMonthheightInweightLbheighCmheightCmweightKgbmi
<fct><dbl><int><dbl><dbl><dbl><dbl><dbl><dbl>
1f11.9214356.3 851269.002143.00238.5662418.85919
2f12.9215562.31051404.242158.24247.6406519.02542

按组转换数据

💡使用 plyr 包中的 ddply() 函数,在参数中调用 transform(),并指定运算:

library(MASS)
library(dplyr)
Warning message:
"package 'MASS' was built under R version 4.0.5"

Attaching package: 'MASS'


The following object is masked from 'package:dplyr':

    select
head(cabbages,2)
A data.frame: 2 × 4
CultDateHeadWtVitC
<fct><fct><dbl><int>
1c39d162.551
2c39d162.255
cb <- ddply(cabbages, "Cult", transform, DevWt = HeadWt - mean(HeadWt))
head(cb,2)
A data.frame: 2 × 5
CultDateHeadWtVitCDevWt
<fct><fct><dbl><int><dbl>
1c39d162.551-0.4066667
2c39d162.255-0.7066667

上面的代码首先会将cabbages数据根据Cult分割成几个独立的数据框。 Cult 上有两个水平,c39 和 c52, 因此也就是分割成两个。然后在这两个数据框上使用 transform() 函数,其他参数保持不变。

也可以根据多个变量分组、切割数据框,同时也可以在多个变量上进行运算。本例中将会根据 Cult 和 Date 切割数据,形成两者组合得到的分组,然后计算 HeadWt 和 VitC 在各个组的偏差:

cb <- ddply(cabbages, c('Cult','Date'), transform,
     DevWt = HeadWt - mean(HeadWt), DevVitC = VitC - mean(VitC))
head(cb,2)
A data.frame: 2 × 6
CultDateHeadWtVitCDevWtDevVitC
<fct><fct><dbl><int><dbl><dbl>
1c39d162.551-0.680.7
2c39d162.255-0.984.7

分组汇总数据

配合 summarise() 函数使用 plyr 包中的 ddply() 函数,并指定操作为 summarise

library(MASS)
library(plyr)
ddply(cabbages, c("Cult", "Date"), summarise, Weight = mean(HeadWt),
     VitC = mean(VitC))
A data.frame: 6 × 4
CultDateWeightVitC
<fct><fct><dbl><dbl>
c39d163.1850.3
c39d202.8049.4
c39d212.7454.8
c52d162.2662.5
c52d203.1158.9
c52d211.4771.8
summarise(cabbages, Weight = mean(HeadWt))
A data.frame: 1 × 1
Weight
<dbl>
2.593333

summarise()计算均值会更合适,得到一行一列的数据框,名为 Weight。

ddply(cabbages, 'Cult', summarise, Weight = mean(HeadWt))
A data.frame: 2 × 2
CultWeight
<fct><dbl>
c392.906667
c522.280000

上面的代码首先根据 Cult 的值将数据框 cabbages 切割成了几个小数据框。因子 Cult 有两个水平,c39和c52,因此也就有两个数据框,然后在每个数据框上套用 summarise(),利用 mean() 函数计算每个数据框中 HeadWt 的均值并赋给新的列 Weight。结果就得到了两个一行的数据框,然后 ddply() 将它们合并为一个。

除了求均值,我们还可以做很多其他的事。比方说,你可能想计算每个组的标准差和频数;使用 sd() 来计算标准差, length() 来计算频数

colnames(cabbages)
  1. 'Cult'
  2. 'Date'
  3. 'HeadWt'
  4. 'VitC'
ddply(cabbages, c('Cult','Date'), summarise, Weight = mean(HeadWt),sd = sd(HeadWt), n = length(HeadWt))
A data.frame: 6 × 5
CultDateWeightsdn
<fct><fct><dbl><dbl><int>
c39d163.180.956614410
c39d202.800.278886710
c39d212.740.983418110
c52d162.260.445221510
c52d203.110.790850510
c52d211.470.211081910

还有一些有用的函数也能够得到汇总统计量,包括 min(), max(), median() 等

处理缺失值

首先试一下会发生什么

c1 <- cabbages
c1$HeadWt[c(1,20,45)] <- NA
ddply(c1, c('Cult','Date'), summarise,
     Weight = mean(HeadWt),
     sd = sd(HeadWt),
     n = length(HeadWt))
A data.frame: 6 × 5
CultDateWeightsdn
<fct><fct><dbl><dbl><int>
c39d16 NA NA10
c39d20 NA NA10
c39d212.740.983418110
c52d162.260.445221510
c52d20 NA NA10
c52d211.470.211081910

两个问题。
第一个问题:如果任一输入值包含NA,mean() 和 sd()函数都会返回NA。幸运的是,这些函数都有一个参数来处理这个问题:设置 na.rm=TRUE即可忽略缺失值。

第二个问题:length()没有对缺失值进行特殊处理,而是将它们视为正常值,但这是不对的。可以使用sum(!is.na(...))达到相同的效果。 is.na() 返回一个逻辑向量:NA返回TRUE,非NA返回TRUE。用 ! 取反后,再用 sum() 函数将TRUE的数量加起来。最终的结果就是非缺失值的频数:

ddply(c1, c('Cult','Date'), summarise,
     Weight = mean(HeadWt, na.rm = TRUE),
     sd = sd(HeadWt, na.rm = TRUE),
     n = sum(!is.na(HeadWt)))
A data.frame: 6 × 5
CultDateWeightsdn
<fct><fct><dbl><dbl><int>
c39d163.2555560.9824855 9
c39d202.7222220.1394433 9
c39d212.7400000.983418110
c52d162.2600000.445221510
c52d203.0444440.8094923 9
c52d211.4700000.211081910

组合缺失

如果在分类变量中有任何‘空组合’,它们就不会出现在汇总的数据框中。缺失组合会给绘图带来麻烦。

# 复制 cabbages 并移除同时包含c52和d21的行
c2 <- subset(c1, !(Cult=='c52' & Date=='d21'))

c2a <- ddply(c2, c('Cult','Date'), summarise,
     Weight = mean(HeadWt, na.rm = TRUE),
     sd = sd(HeadWt, na.rm = TRUE),
     n = sum(!is.na(HeadWt)))
c2a
A data.frame: 5 × 5
CultDateWeightsdn
<fct><fct><dbl><dbl><int>
c39d163.2555560.9824855 9
c39d202.7222220.1394433 9
c39d212.7400000.983418110
c52d162.2600000.445221510
c52d203.0444440.8094923 9
ggplot(c2a, aes(x=Date, y=Weight, fill=Cult)) + 
 geom_bar(stat='identity', position = position_dodge()) + 
 theme_bw(base_size = 25)

png

为了填充缺失的组合,在ddply() 函数中使用 .drop=FALSE即可:

c2b <- ddply(c2, c('Cult','Date'), .drop = FALSE, summarise,
     Weight = mean(HeadWt, na.rm = TRUE),
     sd = sd(HeadWt, na.rm = TRUE),
     n = sum(!is.na(HeadWt)))
c2b
A data.frame: 6 × 5
CultDateWeightsdn
<fct><fct><dbl><dbl><int>
c39d163.2555560.9824855 9
c39d202.7222220.1394433 9
c39d212.7400000.983418110
c52d162.2600000.445221510
c52d203.0444440.8094923 9
c52d21 NaN NA 0
ggplot(c2b, aes(x=Date, y=Weight, fill=Cult)) + 
 geom_bar(stat='identity', position = position_dodge()) + 
 theme_bw(base_size = 25)
Warning message:
"Removed 1 rows containing missing values (geom_bar)."

png

使用标准误差和置信区间来汇总数据

ca <- ddply(cabbages, c('Cult', 'Date'), summarise,
     Weight = mean(HeadWt, na.rm = TRUE), 
     sd = sd(HeadWt, na.rm = TRUE),
     n = sum(!is.na(HeadWt)),
     se = sd / sqrt(n))
ca
A data.frame: 6 × 6
CultDateWeightsdnse
<fct><fct><dbl><dbl><int><dbl>
c39d163.180.9566144100.30250803
c39d202.800.2788867100.08819171
c39d212.740.9834181100.31098410
c52d162.260.4452215100.14079141
c52d203.110.7908505100.25008887
c52d211.470.2110819100.06674995

计算均值的标准差包括两步:首先计算各组的标准差和频数,然后用这些值来计算得到标准误差。各组的标准误差就是标准差除以样本量的平方根。

置信区间

置信区间是通过均值的标准误差和自由度计算得到的。要计算置信区间,首先使用qt()得到分位数,然后和标准误差相乘即可。
给定概率值和自由度,qt() 函数会计算出对应t分布的分位数。
对95%的置信区间来说,应该使用0.975的概率值;对钟形的t分布,这对应了曲线两端各2.5%的面积。自由度是样本量大小减去1.

下面的代码将会计算每组标准误差的乘数。由于一共有6个组,并且每个组都有10个观测值,因此它们会有相同的乘数。

ciMult <- qt(.975, ca$n-1)
ciMult
  1. 2.2621571627982
  2. 2.2621571627982
  3. 2.2621571627982
  4. 2.2621571627982
  5. 2.2621571627982
  6. 2.2621571627982

现在我们将上面的向量乘以标准误差来得到95%的置信区间:

ca$ci <- ca$se * ciMult
ca
A data.frame: 6 × 7
CultDateWeightsdnseci
<fct><fct><dbl><dbl><int><dbl><dbl>
c39d163.180.9566144100.302508030.6843207
c39d202.800.2788867100.088191710.1995035
c39d212.740.9834181100.310984100.7034949
c52d162.260.4452215100.140791410.3184923
c52d203.110.7908505100.250088870.5657403
c52d211.470.2110819100.066749950.1509989

误差条表示均值的标准误差,它和置信区间有相同的功能:估计总体均值的好坏程度。
标准误差是抽样分布的标准差

把数据从“宽”变“长”

💡使用 reshape2 包中的 melt() 函数。在 anthoming 数据集中,angle 表示蚂蚁行进方向与家的方向的角度,实验组(expt)、对照组(ctrl)的数值表示在两组中蚂蚁的数量。

library(gcookbook)
anthoming
A data.frame: 5 × 3
AngleexptControl
<int><int><int>
-2010
-1073
023
1003
2001

为了得到便于分析绘图的数据结果,对以上数据进行结构重塑

library(reshape2)
# 'Angle' 为 标识变量, 'expt' 和 'control' 为非标识变量,这里两个非标识变量都成为了度量变量,汇成了 condition 的一部分。
melt(anthoming, id.vars = 'Angle', variable.name = 'condition', value.name = 'count')
A data.frame: 10 × 3
Angleconditioncount
<int><fct><int>
-20expt 1
-10expt 7
0expt 2
10expt 0
20expt 0
-20Control0
-10Control3
0Control3
10Control3
20Control1

如果不想用所有的非标识变量(如上的 ‘expt’ 和 ‘control’)作为度量变量,可以指明哪些变量是你需要的(使用 measure.vars)。

drunk
A data.frame: 2 × 6
sex0-2930-3940-4950-5960+
<chr><int><int><int><int><int>
male 18520726018071
female 4 13 10 710
melt(drunk, id.vars = 'sex', measure.vars = c('0-29','30-39'), variable.name = 'age', value.name = 'count')
A data.frame: 4 × 3
sexagecount
<chr><fct><int>
male 0-29 185
female0-29 4
male 30-39207
female30-39 13

同时,也可以用多列作为标识度量:

plum_wide
A data.frame: 4 × 4
lengthtimedeadalive
<chr><chr><int><int>
long at_once 84156
long in_spring156 84
shortat_once 133107
shortin_spring209 31
melt(plum_wide, id.vars = c('length', 'time'), variable.name = 'survival', value.name = 'count')
A data.frame: 8 × 4
lengthtimesurvivalcount
<chr><chr><fct><int>
long at_once dead 84
long in_springdead 156
shortat_once dead 133
shortin_springdead 209
long at_once alive156
long in_springalive 84
shortat_once alive107
shortin_springalive 31

有些数据集并不具备标识变量。这种情况中,可以在用 melt() 函数之前,给这个数据框添加一个标识变量。

co <- corneas
co
A data.frame: 8 × 2
affectednotaffected
<int><int>
488484
478478
480492
426444
440436
410398
458464
460476
# 添加标识列
co$id <- as.character(1:nrow(co)) # 用数值做标识列,在后续分析中很可能会出问题,所以 用 as.character() 函数或者 用 factor() 转化一下
melt(co, id.vars = 'id', variable.name = 'eye', value.name = 'count')
A data.frame: 16 × 3
ideyecount
<chr><fct><int>
1affected 488
2affected 478
3affected 480
4affected 426
5affected 440
6affected 410
7affected 458
8affected 460
1notaffected484
2notaffected478
3notaffected492
4notaffected444
5notaffected436
6notaffected398
7notaffected464
8notaffected476

把数据框从“长”到宽

使用 reshape2 包中的 dcast() 函数。

library(gcookbook)
plum
A data.frame: 8 × 4
lengthtimesurvivalcount
<chr><chr><fct><int>
long at_once dead 84
long in_springdead 156
shortat_once dead 133
shortin_springdead 209
long at_once alive156
long in_springalive 84
shortat_once alive107
shortin_springalive 31
library(reshape2)
# ~ 左侧为标识变量, 右边表示可变变量
dcast(plum, length + time ~ survival, value.var = 'count')
A data.frame: 4 × 4
lengthtimedeadalive
<chr><chr><int><int>
long at_once 84156
long in_spring156 84
shortat_once 133107
shortin_spring209 31

在ggplot2图形上添加显著性差异注释

参考资料:
如何在ggplot2图形上添加显著性差异注释

library(ggsignif)
library(ggplot2)
data(iris)
head(iris,2)
A data.frame: 2 × 5
Sepal.LengthSepal.WidthPetal.LengthPetal.WidthSpecies
<dbl><dbl><dbl><dbl><fct>
15.13.51.40.2setosa
24.93.01.40.2setosa
ggplot(iris, aes(x=Species, y=Sepal.Length)) + 
 geom_boxplot() +
 theme_bw(base_size = 25) +
 theme(panel.grid = element_blank()) +
 geom_signif(comparisons = list(c("setosa", "versicolor"),
                                 c("setosa", "virginica"),
                                 c("versicolor", "virginica")),
              y_position=c(7.5, 8.5, 8.2), 
              tip_length = c(0.2, 0.05, 0.2, 0.05, 0.1, 0.05),
              map_signif_level=TRUE,
              textsize=8)

png

ggplot(dat, aes(Group, Value)) +
  geom_bar(aes(fill = Sub), stat="identity", position="dodge", width=.5) +
  geom_signif(y_position=c(6.0, 8.5), xmin=c(0.85, 1.85), xmax=c(1.15, 2.15),
              annotation=c("**", "NS"), tip_length=0.04) + 
  geom_signif(comparisons=list(c("S1", "S2")),
              y_position = 9.3, tip_length = 0.04, vjust=0.2)
1. ggplot(dat, aes(Group, Value))

参数解释

mapping # 由aes()或aes_()创建的美学映射集。如果指定且inherit.aes=TRUE(默认值),它将与绘图顶层的默认映射结合。如果没有绘图映射,则必须提供映射。
data # 绘图数据所在的数据框
position # 位置调整;可以是字符串,也可以是位置调整函数的结果
na.rm # 逻辑词,默认为FALSE,移除缺失值时显示警告信息,为TRUE,则不显示警告信息。
show.legend # 逻辑词,是否显示图例
comparisons # 长度为2的向量列表
test # 进行统计检验的方法名称,如t.test、wilcox.test、aov()、anova()、kruskal.test() 等。
test.args # 检验方法的其他参数
annotations # 替换P值注释的字符向量
map_signif_level # 布尔值,检验结果P值使用注释或者星号代替
y_position # 括号线在对齐y轴高度的数字向量
xmin # 括号线左侧位置的数字向量
xmax # 括号线右侧位置的数字向量
step_increase # 数字向量,减少括号线的重叠
tip_length # 数字向量,显示括号线两端的下降的小竖线,用来指向精确的组别
size # 设置括号线的宽度
textsize # 设置文本字体大小
family # 设置文本字体
vjust # 相对于括号线,上下调整文本的距离
parse # 逻辑词,为TRUE,则标签将解析为表达式


文章作者: 梁绍波
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 梁绍波 !
评论
  目录