我想绘制一个带有误差条和分割y轴的组条形图,以在同一个图中显示更小和更大的值? (如我的数据样本1中所示,与其他样本相比,样本数值较小,因此,我希望在y轴上产生10-200之间的间隙)
这是我的数据,
sample mean part sd
1 4.3161 G 1.2209
1 2.3157 F 1.7011
1 1.7446 R 1.1618
2 1949.13 G 873.42
2 195.07 F 47.82
2 450.88 R 140.31
3 2002.98 G 367.92
3 293.45 F 59.01
3 681.99 R 168.03
4 2717.85 G 1106.07
4 432.83 F 118.02
4 790.97 R 232.62
答案 0 :(得分:1)
您可以使用原始图形元素执行任何操作。出于这个原因,我总是喜欢用基本的R绘图函数来设计我自己的绘图,特别是points()
,segments()
,lines()
,abline()
,rect()
,polygon()
,text()
和mtext()
。您可以使用自己定义的粒度坐标向量中的segments()
和lines()
轻松创建曲线(例如圆圈)和更复杂的形状。例如,请参阅Plot angle between vectors。这样可以更好地控制您创建的绘图元素,但是,与更多预先打包的解决方案相比,它通常需要更多的工作和仔细的编码,因此这是一种权衡。
首先,以可运行的形式提供您的数据:
df <- data.frame(
sample=c(1,1,1,2,2,2,3,3,3,4,4,4),
mean=c(4.3161,2.3157,1.7446,1949.13,195.07,450.88,2002.98,293.45,681.99,2717.85,432.83,790.97),
part=c('G','F','R','G','F','R','G','F','R','G','F','R'),
sd=c(1.2209,1.7011,1.1618,873.42,47.82,140.31,367.92,59.01,168.03,1106.07,118.02,232.62),
stringsAsFactors=F
);
df;
## sample mean part sd
## 1 1 4.3161 G 1.2209
## 2 1 2.3157 F 1.7011
## 3 1 1.7446 R 1.1618
## 4 2 1949.1300 G 873.4200
## 5 2 195.0700 F 47.8200
## 6 2 450.8800 R 140.3100
## 7 3 2002.9800 G 367.9200
## 8 3 293.4500 F 59.0100
## 9 3 681.9900 R 168.0300
## 10 4 2717.8500 G 1106.0700
## 11 4 432.8300 F 118.0200
## 12 4 790.9700 R 232.6200
现在,作为参考,这里是您粘贴到评论中的ggplot代码产生的图表的屏幕截图:
library(ggplot2);
ggplot(df,aes(x=as.factor(sample),y=mean,fill=part)) +
geom_bar(position=position_dodge(),stat='identity',colour='black') +
geom_errorbar(aes(ymin=mean-sd,ymax=mean+sd),width=.2,position=position_dodge(.9));
另外,作为参考,您可以使用基础R barplot()
和legend()
生成类似的分组条形图。我已将自定义调用的错误栏添加到segments()
和points()
:
## reshape to wide matrices
dfw <- reshape(df,dir='w',idvar='part',timevar='sample');
dfw.mean <- as.matrix(dfw[grep(perl=T,'^mean\\.',names(dfw))]);
dfw.sd <- as.matrix(dfw[grep(perl=T,'^sd\\.',names(dfw))]);
rownames(dfw.mean) <- rownames(dfw.sd) <- dfw$part;
colnames(dfw.mean) <- colnames(dfw.sd) <- unique(df$sample);
## plot precomputations
ylim <- c(0,4000);
yticks <- seq(ylim[1L],ylim[2L],100);
xcenters <- (col(dfw.sd)-1L)*(nrow(dfw.sd)+1L)+row(dfw.sd)+0.5;
partColors <- c(G='green3',F='indianred1',R='dodgerblue');
errColors <- c(G='darkgreen',F='darkred',R='darkblue');
## plot
par(xaxs='i',yaxs='i');
barplot(dfw.mean,beside=T,col=partColors,ylim=ylim,xlab='sample',ylab='mean',axes=F);
segments(xcenters,dfw.mean-dfw.sd,y1=dfw.mean+dfw.sd,lwd=2,col=errColors);
points(rep(xcenters,2L),c(dfw.mean-dfw.sd,dfw.mean+dfw.sd),pch=19,col=errColors);
axis(1L,par('usr')[1:2],F,pos=0,tck=0);
axis(2L,yticks,las=1L,cex.axis=0.7);
legend(2,3800,dfw$part,partColors,title=expression(bold('part')),cex=0.7,title.adj=0.5[2:1]);
问题很明显。某些数据(样本1的含义和可变性)的细微差别在图中没有很好地表现出来。
有两种标准选项可以解决此问题。一种是使用对数标度。您可以使用log='y'
函数的barplot()
参数执行此操作。覆盖默认的y轴刻度选择也很好,因为默认的基准R刻度在密度上有点亮,而在范围上很短。 (对于大多数基础R绘图类型,这实际上是正确的;我为axis()
自定义调用我在此答案中生成的所有绘图。)
## plot precomputations
ylim <- c(0.1,4100); ## lower limit must be > 0 for log plot
yticks <- rep(10^seq(floor(log10(ylim[1L])),ceiling(log10(ylim[2L])),1),each=9L)*1:9;
xcenters <- (col(dfw.sd)-1L)*(nrow(dfw.sd)+1L)+row(dfw.sd)+0.5;
partColors <- c(G='green3',F='indianred1',R='dodgerblue');
errColors <- c(G='darkgreen',F='darkred',R='darkblue');
## plot
par(xaxs='i',yaxs='i');
barplot(log='y',dfw.mean,beside=T,col=partColors,ylim=ylim,xlab='sample',ylab='mean',axes=F);
segments(xcenters,dfw.mean-dfw.sd,y1=dfw.mean+dfw.sd,lwd=2,col=errColors);
points(rep(xcenters,2L),c(dfw.mean-dfw.sd,dfw.mean+dfw.sd),pch=19,col=errColors);
axis(1L,par('usr')[1:2],F,pos=0,tck=0);
axis(2L,yticks,yticks,las=1L,cex.axis=0.6);
legend(2,3000,dfw$part,partColors,title=expression(bold('part')),cex=0.7,title.adj=0.5[2:1]);
我们立即看到样本1的问题已修复。但我们引入了一个新问题:我们在其余数据中失去了精确度。换句话说,其余数据中存在的细微差别在视觉上较不明显。这是&#34;缩小&#34;不可避免的结果。从线性轴变为对数轴的效果。如果使用线性图但y轴太大,则会导致相同的精度损失,这就是为什么总是希望轴尽可能接近数据。这也表明对数y轴可能不是您数据的正确解决方案。当基础数据反映对数现象时,通常建议使用对数轴;它的范围超过几个数量级。在您的数据中,只有样本1与剩余数据处于不同的数量级;其余的集中在相同的数量级,因此不能用对数y轴表示。
第二个选项是创建具有完全不同的y轴缩放的单独图。应该注意的是,ggplot faceting实际上是创建单独的图。此外,你可以用基数R创建multifigure图,但我通常发现这比它的价值更麻烦。通过单独生成每个绘图通常更容易,然后使用发布或文字处理软件将它们彼此相邻放置。
有多种方法可以自定义此方法,例如是否组合轴标签,放置图例的位置,尺寸和相对于彼此排列不同图的方式等。这是一种方法这样做:
##--------------------------------------
## plot 1 -- high values
##--------------------------------------
dfw.mean1 <- dfw.mean[,-1L];
dfw.sd1 <- dfw.sd[,-1L];
## plot precomputations
ylim <- c(0,4000);
yticks <- seq(ylim[1L],ylim[2L],100);
xcenters <- (col(dfw.sd1)-1L)*(nrow(dfw.sd1)+1L)+row(dfw.sd1)+0.5;
partColors <- c(G='green3',F='indianred1',R='dodgerblue');
errColors <- c(G='darkgreen',F='darkred',R='darkblue');
par(xaxs='i',yaxs='i');
barplot(dfw.mean1,beside=T,col=partColors,ylim=ylim,xlab='sample',ylab='mean',axes=F);
segments(xcenters,dfw.mean1-dfw.sd1,y1=dfw.mean1+dfw.sd1,lwd=2,col=errColors);
points(rep(xcenters,2L),c(dfw.mean1-dfw.sd1,dfw.mean1+dfw.sd1),pch=19,col=errColors);
axis(1L,par('usr')[1:2],F,pos=0,tck=0);
axis(2L,yticks,las=1L,cex.axis=0.7);
legend(2,3800,dfw$part,partColors,title=expression(bold('part')),cex=0.7,title.adj=0.5[2:1]);
##--------------------------------------
## plot 2 -- low values
##--------------------------------------
dfw.mean2 <- dfw.mean[,1L,drop=F];
dfw.sd2 <- dfw.sd[,1L,drop=F];
## plot precomputations
ylim <- c(0,6);
yticks <- seq(ylim[1L],ylim[2L],0.5);
xcenters <- (col(dfw.sd2)-1L)*(nrow(dfw.sd2)+1L)+row(dfw.sd2)+0.5;
partColors <- c(G='green3',F='indianred1',R='dodgerblue');
errColors <- c(G='darkgreen',F='darkred',R='darkblue');
par(xaxs='i',yaxs='i');
barplot(dfw.mean2,beside=T,col=partColors,ylim=ylim,xlab='sample',ylab='mean',axes=F);
segments(xcenters,dfw.mean2-dfw.sd2,y1=dfw.mean2+dfw.sd2,lwd=2,col=errColors);
points(rep(xcenters,2L),c(dfw.mean2-dfw.sd2,dfw.mean2+dfw.sd2),pch=19,col=errColors);
axis(1L,par('usr')[1:2],F,pos=0,tck=0);
axis(2L,yticks,las=1L,cex.axis=0.7);
这解决了两个问题(小值可见性和大值精度)。但它也会扭曲样本2-4与样本1的相对大小。换句话说,样本1的数据已按比例放大&#34;相对于样本2-4,读者必须有意识地阅读轴并消化不同的尺度,以便正确理解这些图。
这里的教训是没有完美的解决方案。每种方法都有自己的优点和缺点,也有自己的权衡。
在您的问题中,您表示要在y范围10:200
之间添加间隙。从表面上看,这听起来像是提高样本1数据可见性的合理解决方案。然而,190单位范围的幅度与图的其余部分的范围相比相形见绌,因此最终对样本1可见性的影响可以忽略不计。
为了证明这一点,我将使用我编写的一些代码,这些代码可用于将输入坐标转换为新的数据域,从而允许轴的不同段的不一致缩放。从理论上讲,你可以将它用于x轴和y轴,但我只是将它用于y轴。
一些警告:这引入了一些显着的复杂性,并将图形引擎的y轴刻度与真实数据的概念分离。更具体地说,它根据分段序列中的累积位置将所有坐标映射到范围[0,1]
。
此时,我还将放弃barplot()
,转而使用rect()
调用手动绘制条形图。从技术上讲,可以将barplot()
与我的分段代码一起使用,但正如我之前所说,我更喜欢从头开始用原始图形元素设计我自己的图。这也可以更精确地控制图的各个方面。
这里是代码和情节,之后我会尝试更好地解释它:
dataCoordToPlot <- function(data,seg) {
## data -- double vector of data-world coordinates.
## seg -- list of two components: (1) mark, giving the boundaries between all segments, and (2) scale, giving the relative scale of each segment. Thus, scale must be one element shorter than mark.
data <- as.double(data);
seg <- as.list(seg);
seg$mark <- as.double(seg$mark);
seg$scale <- as.double(seg$scale);
if (length(seg$scale) != length(seg$mark)-1L) stop('seg$scale must be one element shorter than seg$mark.');
scaleNorm <- seg$scale/sum(seg$scale);
cumScale <- c(0,cumsum(scaleNorm));
int <- findInterval(data,seg$mark,rightmost.closed=T);
int[int%in%c(0L,length(seg$mark))] <- NA; ## handle values outside outer segments; will propagate NA to returned vector
(data - seg$mark[int])/(seg$mark[int+1L] - seg$mark[int])*scaleNorm[int] + cumScale[int];
}; ## end dataCoordToPlot()
## y dimension segmentation
ymax <- 4000;
yseg <- list();
yseg$mark <- c(0,10,140,ymax);
yseg$scale <- diff(yseg$mark);
yseg$scale[2L] <- 30;
yseg$jump <- c(F,T,F);
## plot precomputations
xcenters <- seq(0.5,len=length(unique(df$sample)));
xlim <- range(xcenters)+c(-0.5,0.5);
ylim <- range(yseg$mark);
yinc <- 100;
yticks.inc <- seq(ylim[1L],ylim[2L],yinc);
yticks.inc <- yticks.inc[!yseg$jump[findInterval(yticks.inc,yseg$mark,rightmost.closed=T)]];
yticks.jump <- setdiff(yseg$mark,yticks.inc);
yticks.all <- sort(c(yticks.inc,yticks.jump));
## plot
## define as reusable function for subsequent examples
custom.barplot <- function() {
par(xaxs='i',yaxs='i');
plot(NA,xlim=xlim,ylim=dataCoordToPlot(ylim,yseg),axes=F,ann=F);
abline(h=dataCoordToPlot(yticks.all,yseg),col='lightgrey');
axis(1L,seq(xlim[1L],xlim[2L]),NA,tck=0);
axis(1L,xcenters,unique(df$sample));
axis(2L,dataCoordToPlot(yticks.inc,yseg),yticks.inc,las=1,cex.axis=0.7);
axis(2L,dataCoordToPlot(yticks.jump,yseg),yticks.jump,las=1,tck=-0.008,hadj=0.1,cex.axis=0.5);
mtext('sample',1L,2L);
mtext('mean',2L,3L);
xgroupRatio <- 0.8;
xbarRatio <- 0.9;
partColors <- c(G='green3',F='indianred1',R='dodgerblue');
partsCanon <- unique(df$part);
errColors <- c(G='darkgreen',F='darkred',R='darkblue');
for (sampleIndex in seq_along(unique(df$sample))) {
xc <- xcenters[sampleIndex];
sample <- unique(df$sample)[sampleIndex];
dfs <- df[df$sample==sample,];
parts <- unique(dfs$part);
parts <- parts[order(match(parts,partsCanon))];
barWidth <- xgroupRatio*xbarRatio/length(parts);
gapWidth <- xgroupRatio*(1-xbarRatio)/(length(parts)-1L);
xstarts <- xc - xgroupRatio/2 + (match(dfs$part,parts)-1L)*(barWidth+gapWidth);
rect(xstarts,0,xstarts+barWidth,dataCoordToPlot(dfs$mean,yseg),col=partColors[dfs$part]);
barCenters <- xstarts+barWidth/2;
segments(barCenters,dataCoordToPlot(dfs$mean + dfs$sd,yseg),y1=dataCoordToPlot(dfs$mean - dfs$sd,yseg),lwd=2,col=errColors);
points(rep(barCenters,2L),dataCoordToPlot(c(dfs$mean-dfs$sd,dfs$mean+dfs$sd),yseg),pch=19,col=errColors);
}; ## end for
## draw zig-zag cutaway graphic in jump segments
zigCount <- 30L;
jumpIndexes <- which(yseg$jump);
for (jumpIndex in jumpIndexes) {
if (yseg$scale[jumpIndex] == 0) next;
jumpStart <- yseg$mark[jumpIndex];
jumpEnd <- yseg$mark[jumpIndex+1L];
lines(seq(xlim[1L],xlim[2L],len=zigCount*2L+1L),dataCoordToPlot(c(rep(c(jumpStart,jumpEnd),zigCount),jumpStart),yseg));
}; ## end for
legend(0.2,dataCoordToPlot(3800,yseg),partsCanon,partColors,title=expression(bold('part')),cex=0.7,title.adj=c(NA,0.5));
}; ## end custom.barplot()
custom.barplot();
关键功能是dataCoordToPlot()
。这代表&#34;数据坐标以绘制坐标&#34;,其中&#34;绘制坐标&#34;是指[0,1]
规范化域。
seg
参数定义轴的分段和每个段的缩放。其mark
组件指定每个段的边界,其scale
组件为每个段提供比例因子。 n
个分段必须具有n+1
个边界才能完全定义每个分段的开始和结束位置,因此mark
必须比scale
长一个元素。
在使用之前,scale
向量在函数内被归一化为总和为1,因此量值的绝对大小并不重要;重要的是它们的相对价值。
该算法是找到每个包含段的坐标,找到由坐标计算得到的段的累积距离,并计算段的相对比例,然后加上所有到达的累积距离先前的细分。
使用此设计,可以沿轴尺寸采用任何范围的坐标,并相对于其他线段向上或向下缩放。可以通过零刻度实现范围内的瞬时间隙。或者,您可以简单地缩小范围,使其具有一定的厚度,但对尺寸的进展贡献很小。在上图中,我使用后者来表示间隙,主要是因为我可以使用较小的厚度来增加锯齿状的美感,从视觉上可以看出是否存在间隙。
另外,我应该注意,我使用了10:140
代替10:200
来弥补差距。这是因为样本2 F部分错误栏向下延伸到147.25(195.07 - 47.82
)。差异可以忽略不计。
如您所见,结果与Linear Single图基本相同。差距不足以提高样本1数据的可见性。
只是为混合投入更多的可能性,现在冒险进入非标准且可能有问题的水域,我们可以使用分割变换将样本放大1个数量级,从而使其更加可见,同时仍然保持在单个图,直接与样本2-4一起。
对于这个例子,我保留了与10:140
之间的差距,这样你就可以看到它没有躺在基线附近时的样子。
## y dimension segmentation
ymax <- 4000;
yseg <- list();
yseg$mark <- c(0,10,140,ymax);
yseg$scale <- c(24,1,75);
yseg$jump <- c(F,T,F);
## plot precomputations
xcenters <- seq(0.5,len=length(unique(df$sample)));
xlim <- range(xcenters)+c(-0.5,0.5);
ylim <- range(yseg$mark);
yinc1 <- 1;
yinc2 <- 100;
yticks.inc1 <- seq(ceiling(yseg$mark[1L]/yinc1)*yinc1,yseg$mark[2L],yinc1);
yticks.inc2 <- seq(ceiling(yseg$mark[3L]/yinc2)*yinc2,yseg$mark[4L],yinc2);
yticks.inc <- c(yticks.inc1,yticks.inc2);
yticks.jump <- setdiff(yseg$mark,yticks.inc);
yticks.all <- sort(c(yticks.inc,yticks.jump));
## plot
custom.barplot();
最后,只是为了澄清差异不是段之间不一致缩放所必需的,这里的情节相同,但没有差距:
## y dimension segmentation
ymax <- 4000;
yseg <- list();
yseg$mark <- c(0,10,ymax);
yseg$scale <- c(25,75);
yseg$jump <- c(F,F);
## plot precomputations
xcenters <- seq(0.5,len=length(unique(df$sample)));
xlim <- range(xcenters)+c(-0.5,0.5);
ylim <- range(yseg$mark);
yinc1 <- 1;
yinc2 <- 100;
yticks.inc1 <- seq(ceiling(yseg$mark[1L]/yinc1)*yinc1,yseg$mark[2L],yinc1);
yticks.inc2 <- seq(ceiling(yseg$mark[2L]/yinc2)*yinc2,yseg$mark[3L],yinc2);
yticks.inc <- c(yticks.inc1,yticks.inc2);
yticks.jump <- setdiff(yseg$mark,yticks.inc);
yticks.all <- sort(c(yticks.inc,yticks.jump));
## plot
custom.barplot();
原则上,线性多重解决方案和失真解决方案之间确实没有区别。两者都涉及竞争数量级的视觉扭曲。 Linear Multiple简单地将不同的数量级分成单独的图,而Distorted解决方案将它们组合到同一个图中。
支持使用线性倍数的最佳理由可能是,如果你使用扭曲,你可能会被大量数据科学家钉在十字架上,因为这是一种非标准的数据绘图方式。另一方面,人们可能会争辩说,扭曲的方法更简洁,有助于表示每个数据点沿数字线的相对位置。选择是你的。
答案 1 :(得分:0)