R语言基本图形绘制

Published on 2017 - 04 - 20

条形图

条形图通过垂直的或水平的条形展示了类别型变量的分布(频数)。函数barplot()的最简单用法是:

barplot(height)

其中的height是一个向量或一个矩阵。

在接下来的示例中,我们将绘制一项探索类风湿性关节炎新疗法研究的结果。数据已包含在随vcd包分发的Arthritis数据框中。由于vcd包并没用包括在R的默认安装中,请确保在第一次使用之前先下载并安装它(install. packages("vcd"))。

简单的条形图

若height是一个向量,则它的值就确定了各条形的高度,并将绘制一幅垂直的条形图。使用选项horiz=TRUE则会生成一幅水平条形图。你也可以添加标注选项。选项main可添加一个图形标题,而选项xlab和ylab则会分别添加x轴和y轴标签。

在关节炎研究中,变量Improved记录了对每位接受了安慰剂或药物治疗的病人的治疗结果。

> library(vcd)
> counts <- table(Arthritis$Improved)
> counts
  None   Some Marked
    42     14     28

这里我们看到,28位病人有了明显改善,14人有部分改善,而42人没有改善。

你可以使用一幅垂直或水平的条形图来绘制变量counts。代码见代码清单1,结果如图1所示。

barplot(counts,            # 简单条形图
        main="Simple Bar Plot",
        xlab="Improvement", ylab="Frequency")

barplot(counts,            # 水平条形图
        main="Horizontal Bar Plot",
        xlab="Frequency", ylab="Improvement",
        horiz=TRUE)

小提示 若要绘制的类别型变量是一个因子或有序型因子,就可以使用函数plot()快速创建一幅垂直条形图。由于Arthritis$Improved是一个因子,所以代码:

plot(Arthritis$Improved, main="Simple Bar Plot",
    xlab="Improved", ylab="Frequency")
plot(Arthritis$Improved, horiz=TRUE, main="Horizontal Bar Plot",
    xlab="Frequency", ylab="Improved")

将和代码清单1生成相同的条形图,而无需使用table()函数将其表格化。

堆砌条形图和分组条形图

如果height是一个矩阵而不是一个向量,则绘图结果将是一幅堆砌条形图或分组条形图。若beside=FALSE(默认值),则矩阵中的每一列都将生成图中的一个条形,各列中的值将给出堆砌的“子条”的高度。若beside=TRUE,则矩阵中的每一列都表示一个分组,各列中的值将并列而不是堆砌。

考虑治疗类型和改善情况的列联表:

> library(vcd)
> counts <- table(Arthritis$Improved, Arthritis$Treatment)
> counts
        Treatment
Improved Placebo Treated
  None        29      13
  Some         7       7
  Marked       7      21

你可以将此结果绘制为一幅堆砌条形图或一幅分组条形图(见代码清单2)。结果如图2所示。

barplot(counts,            # 堆砌条形图
        main="Stacked Bar Plot",
        xlab="Treatment", ylab="Frequency",
        col=c("red", "yellow","green"),
        legend=rownames(counts))

barplot(counts,           # 分组条形图
        main="Grouped Bar Plot",
        xlab="Treatment", ylab="Frequency",
        col=c("red", "yellow", "green"),
        legend=rownames(counts), beside=TRUE)

第一个barplot函数绘制了一幅堆砌条形图,而第二个绘制了一幅分组条形图。我们同时使用col选项为绘制的条形添加了颜色。参数legend.text为图例提供了各条形的标签(仅在height为一个矩阵时有用)。

均值条形图

条形图并不一定要基于计数数据或频率数据。你可以使用数据整合函数并将结果传递给barplot()函数,来创建表示均值、中位数、标准差等的条形图。代码清单3展示了一个示例,结果如图3所示。

> states <- data.frame(state.region, state.x77)
> means <- aggregate(states$Illiteracy, by=list(state.region), FUN=mean)
> means
        Group.1    x
1     Northeast 1.00
2         South 1.74
3 North Central 0.70
4          West 1.02
> means <- means[order(means$x),]       # ❶ 将均值从小到大排序
> means
        Group.1    x
3 North Central 0.70
1     Northeast 1.00
4          West 1.02
2         South 1.74
> barplot(means$x, names.arg=means$Group.1)
> title("Mean Illiteracy Rate")         # ❷ 添加标题

代码清单3将均值从小到大排序❶。同时注意,使用title()函数❷与调用plot()时添加main选项是等价的。means$x是包含各条形高度的向量,而添加选项names.arg=means$Group.1是为了展示标签。

条形图的微调

有若干种方式可以微调条形图的外观。例如,随着条数的增多,条形的标签可能会开始重叠。你可以使用参数cex.names来减小字号。将其指定为小于1的值可以缩小标签的大小。可选的参数names.arg允许你指定一个字符向量作为条形的标签名。你同样可以使用图形参数辅助调整文本间隔。代码清单4给出了一个示例,输出如图4所示。

par(mar=c(5,8,4,2))
par(las=2)
counts <- table(Arthritis$Improved)

barplot(counts,
           main="Treatment Outcome",
           horiz=TRUE, cex.names=0.8,
        names.arg=c("No Improvement", "Some Improvement",
                    "Marked Improvement"))

本例中,我们(使用las=2)旋转了条形的标签、修改了标签文本,并(使用mar)增加了y边界的大小,为了让标签更合适,(使用cex.names=0.8)缩小了字体大小。par()函数能够让你对R的默认图形做出大量修改。

棘状图

在结束关于条形图的讨论之前,让我们再来看一种特殊的条形图,它称为棘状图(spinogram)。棘状图对堆砌条形图进行了重缩放,这样每个条形的高度均为1,每一段的高度即表示比例。棘状图可由vcd包中的函数spine()绘制。以下代码可以生成一幅简单的棘状图:

library(vcd)
attach(Arthritis)
counts <- table(Treatment, Improved)
spine(counts, main="Spinogram Example")
detach(Arthritis)

输出如图5所示。治疗组同安慰剂组相比,获得显著改善的患者比例明显更高。

除了条形图,饼图也是一种用于展示类别型变量分布的流行工具,接下来讨论它。

饼图

饼图在商业世界中无所不在,然而多数统计学家,包括相应R文档的编写者却都对它持否定态度。相对于饼图,他们更推荐使用条形图或点图,因为相对于面积,人们对长度的判断更精确。也许由于这个原因,R中饼图的选项与其他统计软件相比十分有限。

饼图可由以下函数创建:

pie(x, labels)

其中x是一个非负数值向量,表示每个扇形的面积,而labels则是表示各扇形标签的字符型向量。代码清单6-5给出了四个示例,结果如图6所示。

par(mfrow=c(2, 2))
slices <- c(10, 12,4, 16, 8)         # ❶ 将四幅图形组合为一幅
lbls <- c("US", "UK", "Australia", "Germany", "France")

pie(slices, labels = lbls,
    main="Simple Pie Chart")

pct <- round(slices/sum(slices)*100)        # ❷ 为饼图添加比例数值
lbls2 <- paste(lbls, " ", pct, "%", sep="")
pie(slices, labels=lbls2, col=rainbow(length(lbls2)),
    main="Pie Chart with Percentages")
library(plotrix)
pie3D(slices, labels=lbls,explode=0.1,
      main="3D Pie Chart ")

mytable <- table(state.region)        # ❸ 从表格创建饼图
lbls3 <- paste(names(mytable), "\n", mytable, sep="")
pie(mytable, labels = lbls3,
    main="Pie Chart from a Table\n (with sample sizes)")

首先,你做了图形设置,这样四幅图形就会被组合为一幅❶。(多幅图形的组合在第3章中介绍过。)然后,你输入了前三幅图形将会使用的数据。

对于第二幅饼图❷,你将样本数转换为比例值,并将这项信息添加到了各扇形的标签上。第二幅饼图使用rainbow()函数定义了各扇形的颜色。这里的rainbow(length(lbls2))将被解析为rainbow(5),即为图形提供了五种颜色。

第三幅是使用plotrix包中的pie3D()函数创建的三维饼图。请在第一次使用之前先下载并安装这个包。如果说统计学家们只是不喜欢饼图的话,那么他们对三维饼图的态度就一定是唾弃了(即使他们私下感觉三维饼图好看)。这是因为三维效果无法增进对数据的理解,并且被认为是分散注意力的视觉花瓶。

第四幅图演示了如何从表格创建饼图❸。在本例中,你计算了美国不同地区的州数,并在绘制图形之前将此信息附加到了标签上。

饼图让比较各扇形的值变得困难(除非这些值被附加在标签上)。例如,观察(第一幅)最简单的饼图,你能分辨出美国(US)和德国(Germany)的大小吗?(如果你可以,说明你的洞察力比我好。)为改善这种状况,我们创造了一种称为扇形图(fan plot)的饼图变种。扇形图(Lemon & Tyagi,2009)为用户提供了一种同时展示相对数量和相互差异的方法。在R中,扇形图是通过plotrix包中的fan.plot()函数实现的。

考虑以下代码和结果图(图7):

library(plotrix)
slices <- c(10, 12,4, 16, 8)
lbls <- c("US", "UK", "Australia", "Germany", "France")
fan.plot(slices, labels = lbls, main="Fan Plot")

在一幅扇形图中,各个扇形相互叠加,并对半径做了修改,这样所有扇形就都是可见的。在这里可见德国对应的扇形是最大的,而美国的扇形大小约为其60%。法国的扇形大小似乎是德国的一半,是澳大利亚的两倍。请记住,在这里扇形的宽度(width)是重要的,而半径并不重要。

如你所见,确定扇形图中扇形的相对大小比饼图要简单得多。扇形图虽然尚未普及,但它仍然是新生力量。既然已经讲完了饼图和扇形图,就让我们转到直方图上吧。与条形图和饼图不同,直方图描述的是连续型变量的分布。

直方图

直方图通过在X 轴上将值域分割为一定数量的组,在Y 轴上显示相应值的频数,展示了连续型变量的分布。可以使用如下函数创建直方图:

hist(x)

其中的x是一个由数据值组成的数值向量。参数freq=FALSE表示根据概率密度而不是频数绘制图形。参数breaks用于控制组的数量。在定义直方图中的单元时,默认将生成等距切分。代码清单6提供了绘制四种直方图的代码,绘制结果见图8。

par(mfrow=c(2,2))

hist(mtcars$mpg)       # ❶ 简单直方图

hist(mtcars$mpg,        # ❷ 指定组数和颜色
     breaks=12,
     col="red",
     xlab="Miles Per Gallon",
     main="Colored histogram with 12 bins")

hist(mtcars$mpg,         # ❸ 添加轴须图
     freq=FALSE,
     breaks=12,
     col="red",
     xlab="Miles Per Gallon",
     main="Histogram, rug plot, density curve")
rug(jitter(mtcars$mpg))
lines(density(mtcars$mpg), col="blue", lwd=2)

x <- mtcars$mpg       # ❹ 添加正态密度曲线和外框
h<-hist(x,
        breaks=12,
        col="red",
        xlab="Miles Per Gallon",
        main="Histogram with normal curve and box")
xfit<-seq(min(x), max(x), length=40)
yfit<-dnorm(xfit, mean=mean(x), sd=sd(x))
yfit <- yfit*diff(h$mids[1:2])*length(x)
lines(xfit, yfit, col="blue", lwd=2)
box()

第一幅直方图❶展示了未指定任何选项时的默认图形。这个例子共创建了五个组,并且显示了默认的标题和坐标轴标签。对于第二幅直方图❷,我们将组数指定为12,使用红色填充条形,并添加了更吸引人、更具信息量的标签和标题。

第三幅直方图❸保留了上一幅图中的颜色、组数、标签和标题设置,又叠加了一条密度曲线和轴须图(rug plot)。这条密度曲线是一个核密度估计,会在下节中描述。它为数据的分布提供了一种更加平滑的描述。我们使用lines()函数叠加了这条蓝色、双倍默认线条宽度的曲线。最后,轴须图是实际数据值的一种一维呈现方式。如果数据中有许多结,你可以使用如下代码将轴须图的数据打散:

rug(jitter(mtcars$mpag, amount=0.01))

这样将向每个数据点添加一个小的随机值(一个±amount之间的均匀分布随机数),以避免重叠的点产生影响。

第四幅直方图❹与第二幅类似,只是拥有一条叠加在上面的正态曲线和一个将图形围绕起来的盒型。用于叠加正态曲线的代码来源于R-help邮件列表上由Peter Dalgaard发表的建议。盒型是使用box()函数生成的。

核密度图

在上节中,你看到了直方图上叠加的核密度图。用术语来说,核密度估计是用于估计随机变量概率密度函数的一种非参数方法。从总体上讲,核密度图不失为一种用来观察连续型变量分布的有效方法。绘制密度图的方法(不叠加到另一幅图上方)为:

Plot(density(x))

其中的x是一个数值型向量。由于plot()函数会创建一幅新的图形,所以要向一幅已经存在的图形上叠加一条密度曲线,可以使用lines()函数(如代码清单6所示)。

代码清单7给出了两幅核密度图示例,结果如图9所示。

par(mfrow=c(2,1))
d <- density(mtcars$mpg)

plot(d)

d <- density(mtcars$mpg)
plot(d, main="Kernel Density of Miles Per Gallon")
polygon(d, col="red", border="blue")
rug(mtcars$mpg, col="brown")

在第一幅图中,你看到的是完全使用默认设置创建的最简图形。在第二幅图中,你添加了一个标题,将曲线修改为蓝色,使用实心红色填充了曲线下方的区域,并添加了棕色的轴须图。polygon()函数根据顶点的x和y坐标(本例中由density()函数提供)绘制了多边形。

核密度图可用于比较组间差异。可能是由于普遍缺乏方便好用的软件,这种方法其实完全没有被充分利用。幸运的是,sm包漂亮地填补了这一缺口。

使用sm包中的sm.density.compare()函数可向图形叠加两组或更多的核密度图。使用格式为:

sm.density.compare(x, factor)

其中的x是一个数值型向量,factor是一个分组变量。请在第一次使用sm包之前先安装它。代码清单8中提供了一个示例,它比较了拥有4个、6个或8个汽缸车型的每加仑汽油行驶英里数。

par(lwd=2)          # ❶ 双倍线条宽度
library(sm)
attach(mtcars)

cyl.f <- factor(cyl, levels= c(4,6,8),        # ❷ 创建分组因子
              labels = c("4 cylinder", "6 cylinder",
                         "8 cylinder"))

sm.density.compare(mpg, cyl, xlab="Miles Per Gallon")      # ❸ 绘制密度图
title(main="MPG Distribution by Car Cylinders")

colfill<-c(2:(1+length(levels(cyl.f))))        # ❹ 通过鼠标单击添加图例
legend(locator(1), levels(cyl.f), fill=colfill)

detach(mtcars)

par()函数将所绘制的线条设置为双倍宽度(lwd=2),接下来载入了sm包,并绑定了数据框mtcars。

在数据框mtcars❷中,变量cyl是一个以4、6或8编码的数值型变量。为了向图形提供值的标签,这里cyl转换为名为cyl.f的因子。函数sm.density.compare()创建了图形❸,一条title()语句添加了主标题。

最后,添加了一个图例以增加可解释性❹。首先创建的是一个颜色向量,这里的colfill值为c(2, 3, 4)。然后通过legend()函数向图形上添加一个图例。第一个参数值locator(1)表示用鼠标点击想让图例出现的位置来交互式地放置这个图例。第二个参数值则是由标签组成的字符向量。第三个参数值使用向量colfill为cyl.f的每一个水平指定了一种颜色。结果如图10所示。

如你所见,核密度图的叠加不失为一种在某个结果变量上跨组比较观测的强大方法。你可以看到不同组所含值的分布形状,以及不同组之间的重叠程度。(这段话的寓意是,我的下一辆车将是四缸的——或是一辆电动的。)

箱线图

箱线图(又称盒须图)通过绘制连续型变量的五数总括,即最小值、下四分位数(第25百分位数)、中位数(第50百分位数)、上四分位数(第75百分位数)以及最大值,描述了连续型变量的分布。箱线图能够显示出可能为离群点(范围±1.5*IQR以外的值,IQR表示四分位距,即上四分位数与下四分位数的差值)的观测。例如:

boxplot(mtcars$mpg, main="Box plot", ylab="Miles per Gallon")

生成了如图11所示的图形。为了图解各个组成部分,我手工添加了标注。

默认情况下,两条须的延伸极限不会超过盒型各端加1.5倍四分位距的范围。此范围以外的值将以点来表示(在这里没有画出)。

举例来说,在我们的车型样本中,每加仑汽油行驶英里数的中位数是19.2,50%的值都落在了15.3和22.8之间,最小值为10.4,最大值为33.9。我是如何从图中如此精确地读出了这些值呢?执行boxplot.stats(mtcars$mpg)即可输出用于构建图形的统计量(换句话说,我作弊了)。图中似乎不存在离群点,而且略微正偏(上侧的须较下侧的须更长)。

使用并列箱线图进行跨组比较

箱线图可以展示单个变量或分组变量。使用格式为:

boxplot(formula, data=dataframe)

其中的formula是一个公式,dataframe代表提供数据的数据框(或列表)。一个示例公式为y ~ A,这将为类别型变量A的每个值并列地生成数值型变量y的箱线图。公式y ~ A*B则将为类别型变量A和B所有水平的两两组合生成数值型变量y的箱线图。

添加参数varwidth=TRUE将使箱线图的宽度与其样本大小的平方根成正比。参数horizontal=TRUE可以反转坐标轴的方向。

在以下代码中,我们使用并列箱线图重新研究了四缸、六缸、八缸发动机对每加仑汽油行驶的英里数的影响。结果如图12所示。

boxplot(mpg ~ cyl, data=mtcars,
        main="Car Mileage Data",
        xlab="Number of Cylinders",
        ylab="Miles Per Gallon")

在图12中可以看到不同组间油耗的区别非常明显。同时也可以发现,六缸车型的每加仑汽油行驶的英里数分布较其他两类车型更为均匀。与六缸和八缸车型相比,四缸车型的每加仑汽油行驶的英里数散布最广(且正偏)。在八缸组还有一个离群点。

箱线图灵活多变,通过添加notch=TRUE,可以得到含凹槽的箱线图。若两个箱的凹槽互不重叠,则表明它们的中位数有显著差异(Chambers et al., 1983, p. 62)。以下代码将为我们的车型油耗示例创建一幅含凹槽的箱线图:

boxplot(mpg ~ cyl, data=mtcars,
        notch=TRUE,
        varwidth=TRUE,
        col="red",
        main="Car Mileage Data",
        xlab="Number of Cylinders",
        ylab="Miles Per Gallon")

参数col以红色填充了箱线图,而varwidth=TRUE则使箱线图的宽度与它们各自的样本大小成正比。

在图13中可以看到,四缸、六缸、八缸车型的油耗中位数是不同的。随着汽缸数的减少,油耗明显降低。

最后,你可以为多个分组因子绘制箱线图。代码清单9为不同缸数和不同变速箱类型的车型绘制了每加仑汽油行驶英里数的箱线图。同样地,这里使用参数col为箱线图进行了着色。请注意颜色的循环使用。在本例中,共有六幅箱线图和两种指定的颜色,所以颜色将重复使用三次。

mtcars$cyl.f <- factor(mtcars$cyl,          # 创建汽缸数量的因子
                       levels=c(4,6,8),
                       labels=c("4","6","8"))

mtcars$am.f <- factor(mtcars$am,        # 创建变速箱类型的因子
                      levels=c(0,1),
                      labels=c("auto", "standard"))

boxplot(mpg ~ am.f *cyl.f,          # 生成箱线图
        data=mtcars,
        varwidth=TRUE,
        col=c("gold","darkgreen"),
        main="MPG Distribution by Auto Type",
        xlab="Auto Type")

图形如图14所示。

图14再一次清晰地显示出油耗随着缸数的下降而减少。对于四缸和六缸车型,标准变速箱(standard)的油耗更高。但是对于八缸车型,油耗似乎没有差别。你也可以从箱线图的宽度看出,四缸标准变速箱的车型和八缸自动变速箱的车型在数据集中最常见。

小提琴图

在结束箱线图的讨论之前,有必要研究一种称为小提琴图(violin plot)的箱线图变种。小提琴图是箱线图与核密度图的结合。你可以使用vioplot包中的vioplot()函数绘制它。请在第一次使用之前先安装vioplot包。

vioplot()函数的使用格式为:

Vioplot(x1,x2,,names=,col=)

其中x1, x2, …表示要绘制的一个或多个数值向量(将为每个向量绘制一幅小提琴图)。参数names是小提琴图中标签的字符向量,而col是一个为每幅小提琴图指定颜色的向量。

代码清单10中给出了一个示例。

library(vioplot)
x1 <- mtcars$mpg[mtcars$cyl==4]
x2 <- mtcars$mpg[mtcars$cyl==6]
x3 <- mtcars$mpg[mtcars$cyl==8]
vioplot(x1, x2, x3,
        names=c("4 cyl", "6 cyl", "8 cyl"),
        col="gold")
title("Violin Plots of Miles Per Gallon")

注意 vioplot()函数要求你将要绘制的不同组分离到不同的变量中。结果如图15所示。

小提琴图基本上是核密度图以镜像方式在箱线图上的叠加。在图中,白点是中位数,黑色盒型的范围是下四分位点到上四分位点,细黑线表示须。外部形状即为核密度估计。小提琴图还没有真正地流行起来。同样,这可能也是由于普遍缺乏方便好用的软件导致的。时间会证明一切。

点图

点图提供了一种在简单水平刻度上绘制大量有标签值的方法。你可以使用dotchart()函数创建点图,格式为:

dotchart(x,labels=)

其中的x是一个数值向量,而labels则是由每个点的标签组成的向量。你可以通过添加参数groups来选定一个因子,用以指定x中元素的分组方式。如果这样做,则参数gcolor可以控制不同组标签的颜色,cex可控制标签的大小。这里是mtcars数据集的一个示例:

dotchart(mtcars$mpg, labels=row.names(mtcars), cex=.7,
         main="Gas Mileage for Car Models",
         xlab="Miles Per Gallon")

绘图结果已在图16中给出。

图16可以让你在同一个水平轴上观察每种车型的每加仑汽油行驶英里数。通常来说,点图在经过排序并且分组变量被不同的符号和颜色区分开的时候最有用。代码清单11给出了一个示例。

x <- mtcars[order(mtcars$mpg),]
x$cyl <- factor(x$cyl)
x$color[x$cyl==4] <- "red"
x$color[x$cyl==6] <- "blue"
x$color[x$cyl==8] <- "darkgreen"
dotchart(x$mpg,
         labels = row.names(x),
         cex=.7,
         groups = x$cyl,
         gcolor = "black",
         color = x$color,
         pch=19,
         main = "Gas Mileage for Car Models\ngrouped by cylinder",
         xlab = "Miles Per Gallon")

在本例中,根据每加仑汽油行驶英里数(从最低到最高)对数据框mtcars进行排序,结果保存为数据框x。数值向量cyl被转换为一个因子。一个字符型向量(color)被添加到了数据框x中,根据cyl的值,它所含的值为"red"、"blue"或"darkgreen"。另外,各数据点的标签取自数据框的行名(车辆型号)。数据点根据汽缸数量分组。数字4、6和8以黑色显示。点和标签的颜色来自向量color,点以填充的圆圈表示。代码清单11绘图的结果如图17所示。

在图17中,许多特征第一次明显起来。你再次看到,随着汽缸数的减少,每加仑汽油行驶的英里数有了增加。但你同时也看到了例外。例如,Pontiac Firebird有8个汽缸,但较六缸的Mercury 280C和Valiant的行驶英里数更多。六缸的Hornet 4 Drive与四缸的Volvo 142E的每加仑汽油行驶英里数相同。同样明显的是,Toyota Corolla的油耗最低,而Lincoln Continental和Cadillac Fleetwood是英里数较低一端的离群点。

参考文档