如何绘制类似于维基百科页面上的扇形图

4

1
哇,看起来很酷。如果您在接下来的两天内没有得到答案,为什么不在Rhelp上发布呢?Jim Lemon是这种新绘图功能的首席图形作者,他不会访问SO。他的plotrix包具有大量专业的图表功能。 - IRTFM
1
例子数据(MathAchieve)来自nlme,但绘图使用的是lattice包。很遗憾,那个绘图的作者没有分享代码。 - David Robinson
1
我在谷歌上搜索gamlss包时找到了一个例子。可惜,它与那张图表几乎没有任何相似之处。 - IRTFM
“fanplot”包与众不同,似乎是用于预测/预报的,可以参考这个页面:http://gjabel.wordpress.com/2013/04/24/bank-of-england-fan-charts-in-r/。 - rnso
1
嘿,伙计,我实际上是在提到沃尔夫拉姆·菲舍尔做的原始图像,http://www.fischer-zim.ch/grafik/index.htm,抱歉。@gjabel - rawr
显示剩余4条评论
2个回答

5
我可以想到使用 lattice 的几种方法来完成这个任务。你可以使用 xyplot 并使用 panel.fill 填充面板,或者你可以使用 levelplot。多边形本身必须通过自定义面板和 lpolygon 添加。以下是我使用 levelplot 的方法。虽然我真的是一个 lattice 新手,但可能还有一些我不知道的快捷方式。
因为我使用的是 levelplot,所以我们首先创建一个矩阵,其中包含每个 MEANSESSES 组合的中位数 MathAch 分数。这些将用于绘制单元格颜色。
library(lattice)
library(nlme)
data(MathAchieve)

下面,我使用 cutSESMEANSES 转换为因子,断点与维基百科示例相同。

MathAchieve$SESfac <- as.numeric(cut(MathAchieve$SES, seq(-2.5, 2, 0.5)))
MathAchieve$MEANSESfac <- as.numeric(cut(MathAchieve$MEANSES, 
                                         seq(-1.25, 1, 0.25)))

我不确定如何像维基百科页面上那样绘制四个面板,因此我将仅对非少数族裔女性进行子集:

d <- subset(MathAchieve, Sex=='Female' & Minority=='No')

将这个数据框转换为矩阵,我将其拆分成列表,然后强制转换回具有适当维度的矩阵。矩阵的每个单元格包含特定组合的SESfacMEANSESfac的中位数MathAch
l <- split(d$MathAch, list(d$SESfac, d$MEANSESfac))
m.median <- matrix(sapply(l, median), ncol=9)

当我们使用levelplot时,我们将可以访问xy,它们是“当前”单元格的坐标。为了将MathAch向量传递给levelplot,以便为每个单元格绘制多边形,我创建了一个列表矩阵(与m.median相同的尺寸),其中每个单元格都是包含MathAch向量的列表。
m <- matrix(l, ncol=9)

在下面,我们创建一个颜色坡道,就像 Wolfram Fischer 在维基百科上的例子中使用的那样。
colramp <- colorRampPalette(c('#fff495', '#bbffaa', '#70ffeb', '#72aaff', 
                              '#bf80ff'))

现在我们定义自定义面板函数。我已经添加了注释以进行解释:
fanplot <- function(x, y, z, subscripts, fans, ymin, ymax, 
                    nmax=max(sapply(fans, length)), ...) {
  # nmax is the maximum sample size across all combinations of conditioning
  # variables. For generality, ymin and ymax are limits of the circle around 
  # around which fancharts are plotted. 
  # fans is our matrix of lists, which are used to plot polygons.
  get.coords <- function(a, d, x0, y0) {
    a <- ifelse(a <= 90, 90 - a, 450 - a)
    data.frame(x = x0 + d * cos(a / 180 * pi), 
               y = y0 + d * sin(a / 180 * pi))
  }
  # getcoords returns coordinates of one or more points, given angle(s), 
  # (i.e., a), distances (i.e., d), and an origin (x0 and y0).

  panel.levelplot(x, y, z, subscripts, ...)

  # Below, we scale the raw vectors of data such that ymin thru ymax map to 
  # 0 thru 360. We then calculate the relevant quantiles (i.e. 25%, 50% and 75%).
  smry <- lapply(fans, function(y) {
    y.scld <- (y - ymin)/(ymax - ymin) * 360
    quantile(y.scld, c(0.25, 0.5, 0.75)) - 90
  })

  # Now we use get.coords to determine relevant coordinates for plotting 
  # polygons and lines. We plot a white line inwards from the circle's edge,
  # with length according to the ratio of the sample size to nmax.
  mapply(function(x, y, smry, n) {
    if(!any(is.na(smry))) {
      lpolygon(rbind(c(x, y), 
                     get.coords(seq(smry['25%'], smry['75%'], length.out=200), 
                                0.3, x, y)), col='gray10', lwd=2)
      llines(get.coords(c(smry['50%'], 180 + smry['50%']), 0.3, 
                        x, y), col=1, lwd=3)
      llines(get.coords(smry['50%'], c(0.3, (1 - n/nmax) * 0.3), 
                        x, y), col='white', lwd=3)
    }
  }, x=x, y=y, smry=smry, n=sapply(fans, length))
}

最后,在 levelplot 中使用此自定义面板函数:

levelplot(m.median, fans=m, ymin=0, ymax=28,
          col.regions=colramp, at=seq(0, 25, 5), panel=fanplot, 
          scales=list(tck=c(1, 0), 
                      x=list(at=seq_len(ncol(m.median) + 1) - 0.5, 
                             labels=seq(-2.5, 2, 0.5)),
                      y=list(at=seq_len(nrow(m.median) + 1) - 0.5, 
                             labels=seq(-1.25, 1, 0.25))), 
          xlab='Socio-economic status of students',
          ylab='Mean socio-economic status for the school')

enter image description here

如果需要,我可以使用 lrect 来将样本量小于 7 的单元格涂成灰色,就像 维基百科页面 上的相应图表一样。


我提供了一个新的解决方案,我认为更加用户友好,但是会保留这个方案来展示另一种方法。 - jbaums

5

在我之前的回答中,我思考了一下,想出了一种更简单的方法来生成多面板(如果适用)风扇图,并叠加在 levelplot 上,如维基百科风扇图页面所示。此方法适用于具有两个独立变量和零个或多个调节变量将数据分成面板的 data.frame

首先,我们定义一个新的面板函数,panel.fanplot

panel.fanplot <- function(x, y, z, zmin, zmax, subscripts, groups, 
                          nmax=max(tapply(z, list(x, y, groups), 
                            function(x) sum(!is.na(x))), na.rm=T), 
                          ...) {

  if(missing(zmin)) zmin <- min(z, na.rm=TRUE)
  if(missing(zmin)) zmax <- max(z, na.rm=TRUE)
  get.coords <- function(a, d, x0, y0) {
    a <- ifelse(a <= 90, 90 - a, 450 - a)
    data.frame(x = x0 + d * cos(a / 180 * pi), 
               y = y0 + d * sin(a / 180 * pi))
  }

  z.scld <- (z - zmin)/(zmax - zmin) * 360
  fan <- aggregate(list(z=z.scld[subscripts]), 
                   list(x=x[subscripts], y=y[subscripts]), 
                   function(x) 
                     c(n=sum(!is.na(x)),
                       quantile(x, c(0.25, 0.5, 0.75), na.rm=TRUE) - 90))

  panel.levelplot(fan$x, fan$y, 
                  (fan$z[, '50%'] + 90) / 360 * (zmax - zmin) + zmin,
                  subscripts=seq_along(fan$x), ...)
  lapply(which(!is.na(fan$z[, '50%'])), function(i) {
    with(fan[i, ], {
      poly <- rbind(c(x, y), 
                    get.coords(seq(z[, '25%'], z[, '75%'], length.out=200), 
                               0.3, x, y))
      lpolygon(poly$x, poly$y, col='gray10', border='gray10', lwd=3)
      llines(get.coords(c(z[, '50%'], 180 + z[, '50%']), 0.3, x, y),
             col='black', lwd=3, lend=1)
      llines(get.coords(z[, '50%'], c(0.3, (1 - z[, 'n']/nmax) * 0.3), x, y), 
             col='white', lwd=3)
    })
  })
}

现在我们创建一些虚拟数据并调用levelplot
d <- data.frame(z=runif(1000), 
                x=sample(5, 1000, replace=TRUE),
                y=sample(5, 1000, replace=TRUE),
                grp=sample(4, 1000, replace=TRUE))

colramp <- colorRampPalette(c('#fff495', '#bbffaa', '#70ffeb', '#72aaff', 
                              '#bf80ff'))

levelplot(z ~ x*y|as.factor(grp), d, groups=grp, asp=1, col.regions=colramp, 
          panel=panel.fanplot, zmin=min(d$z, na.rm=TRUE), 
          zmax=max(d$z, na.rm=TRUE), at=seq(0, 1, 0.2))

重要的是通过参数group将筛选变量(将图形分成面板)传递给levelplot,如上所示使用变量grp,以便计算样本大小(由白线长度表示)。

fanplot1

以下是如何模仿维基百科的情况:

library(nlme)
data(MathAchieve)
MathAchieve$SESfac <- as.numeric(cut(MathAchieve$SES, seq(-2.5, 2, 0.5)))
MathAchieve$MEANSESfac <- 
  as.numeric(cut(MathAchieve$MEANSES, seq(-1.25, 1, 0.25)))
levels(MathAchieve$Minority) <- c('Non-minority', 'Minority')
MathAchieve$group <- 
  as.factor(paste0(MathAchieve$Sex, ', ', MathAchieve$Minority))

colramp <- colorRampPalette(c('#fff495', '#bbffaa', '#70ffeb', '#72aaff', 
                              '#bf80ff'))

levelplot(MathAch ~ SESfac*MEANSESfac|group, MathAchieve, 
          groups=group, asp=1, col.regions=colramp, 
          panel=panel.fanplot, zmin=0, zmax=28, at=seq(0, 25, 5),
          scales=list(alternating=1, 
                      tck=c(1, 0), 
                      x=list(at=seq(1, 11) - 0.5, 
                             labels=seq(-2.5, 2, 0.5)),
                      y=list(at=seq(1, 11) - 0.5, 
                             labels=seq(-1.25, 1, 0.25))),
          between=list(x=1, y=1), strip=strip.custom(bg='gray'),
          xlab='Socio-economic status of students',
          ylab='Mean socio-economic status for school')

fanplot2


干得好 - 希望我有机会使用这个。 - user20650

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接