ggplot2数据操作

简介

本文主要介绍关于数据清洗、操作和变换的一些问题。

plyr包简介

plyr包提供了一整套工具集来处理列表(list)、数组(array)和数据框(data.frame),把复杂的数据分割成几个部分,分别对各个部分进行处理,最后把这些结果综合到一起。这里主要用到其中的ddply()函数。该函数能够根据行的取值,把数据框分解成几个子集,分别把各个子集输入某个函数,最后把结果综合在一个数据框内。函数基本用法是ddply(.data, .variables, .fun, …)。

  • .data 是用来作图的数据。
  • .variables 是对数据取自己的分组变量,形式是.(var1, var2)。
  • .fun 是要在各子集上运行的统计汇总函数。
  • subset()用来对数组取子集的函数,选择数据中前(或后)n个(或x%的)观测值,或是在某个阈值之上或下的观测值:
## 选取各个颜色里最小的钻石
ddply(diamonds, .(color), subset, carat == min(carat)
## 选取最小的两颗砖石
ddply(diamonds, .(color), subset, order(carat) <= 2)
## 选取每组里大小为前1%的钻石
ddply(diamonds, .(color), subset, carat > quantile(carat, 0.99))
## 选出所有比组平均值大的钻石
ddply(diamonds, .(color), subset, price > mean(price))
  • transform()是用来进行数据变换的函数,与ddply()一起可以计算分组统计量,例如各组的标准差,并且加到原数据上去。
## 把每个颜色组里钻石的价格标准化,使其均值为0,方差为1
ddply(diamonds, .(color), transform, price = scale(price))
## 减去组均值
ddply(diamonds, .(color), transform, price = price - mean(price))
  • colwise()用来向量化一个普通函数,也就是说colwise()能把原本只接受向量输入的函数变成可以接受数据框输入的函数。
> nmissing <- function(x) sum(is.na(x))
> nmissing(msleep$name)
[1] 0
> nmissing(msleep$brainwt)
[1] 27
> nmissing_df <- colwise(nmissing)
> nmissing_df(msleep)
  name genus vore order conservation sleep_total sleep_rem sleep_cycle awake brainwt bodywt
1    0     0    7     0           29           0        22          51     0      27      0
## 更便捷的方法
> colwise(nmissing)(msleep)
  name genus vore order conservation sleep_total sleep_rem sleep_cycle awake brainwt bodywt
1    0     0    7     0           29           0        22          51     0      27      0
  • numcolwise()是colwise()的一个特殊版本,功能类似,但numcolwise()只对数值类型的列操作。
> msleep2 <- msleep[, -6]       ## 移除第六列
> numcolwise(median)(msleep2, na.rm = T)
  sleep_rem sleep_cycle awake brainwt bodywt
1       1.5   0.3333333  13.9  0.0124   1.67
> numcolwise(quantile)(msleep2, na.rm = T)
  sleep_rem sleep_cycle awake brainwt   bodywt
1       0.1   0.1166667  4.10 0.00014    0.005
2       0.9   0.1833333 10.25 0.00290    0.174
3       1.5   0.3333333 13.90 0.01240    1.670
4       2.4   0.5791667 16.15 0.12550   41.750
5       6.6   1.5000000 22.10 5.71200 6654.000
> numcolwise(quantile)(msleep2, probs = c(0.25, 0.75), na.rm = T)
  sleep_rem sleep_cycle awake brainwt bodywt
1       0.9   0.1833333 10.25  0.0029  0.174
2       2.4   0.5791667 16.15  0.1255 41.750

## 以上这些函数与ddply一起可以对数据进行各种分组统计
> ddply(msleep2, .(vore), numcolwise(median), na.rm = T)
     vore sleep_rem sleep_cycle awake  brainwt bodywt
1   carni      1.95   0.3833333  13.6 0.044500 20.490
2   herbi      0.95   0.2166667  13.7 0.012285  1.225
3 insecti      3.00   0.1666667   5.9 0.001200  0.075
4    omni      1.85   0.5000000  14.1 0.006600  0.950
5    <NA>      2.00   0.1833333  13.4 0.003000  0.122
> ddply(msleep2, .(vore), numcolwise(mean), na.rm = T)
     vore sleep_rem sleep_cycle    awake    brainwt    bodywt
1   carni  2.290000   0.3733333 13.62632 0.07925556  90.75111
2   herbi  1.366667   0.4180556 14.49062 0.62159750 366.87725
3 insecti  3.525000   0.1611111  9.06000 0.02155000  12.92160
4    omni  1.955556   0.5924242 13.07500 0.14573118  12.71800
5    <NA>  1.880000   0.1833333 13.81429 0.00762600   0.85800
  • 如果以上提供的函数不够用,读者可以编写自己的函数,只要它也能接收、输出数据框就可以。例如下面的函数用于计算价格和克拉的秩相关系数以及对数变换后的普通相关系数。
> my_summary <- function(df) {
+   with(df, data.frame(
+       pc_cor = cor(price, carat, method = "spearman"),
+       lpc_cor = cor(log(price), log(carat))
+   ))
+   }
> ddply(diamonds, .(cut), my_summary)
        cut    pc_cor   lpc_cor
1      Fair 0.9056144 0.9085131
2      Good 0.9599684 0.9687510
3 Very Good 0.9688534 0.9716746
4   Premium 0.9605332 0.9697578
5     Ideal 0.9537275 0.9661884

> ddply(diamonds, .(color), my_summary)
  color    pc_cor   lpc_cor
1     D 0.9561208 0.9606617
2     E 0.9600994 0.9643845
3     F 0.9641572 0.9623876
4     G 0.9633681 0.9696785
5     H 0.9730390 0.9801569
6     I 0.9834392 0.9865118
7     J 0.9846710 0.9879449

拟合多个模型

这里展示stat_smooth生成平滑数据(smoothed data)的过程(“平滑”是指用一个函数来拟合数据间的关系,例如回归就是一种平滑过程。而“平滑数据”是指用平滑函数得到的预测值)。下面先给用ggplot2的例子:

qplot(carat, price, data = diamonds, geom = "smooth", colour = color)

id

dense <- subset(diamonds, carat < 2)
qplot(carat, price, data = dense, geom = "smooth", colour = color, fullrange = TRUE)

id

再用plyr生成这样的图。先拟合这个模型,然后在一系列等距的点上求模型的预测值。

library(mgcv)
smooth <- function(df) {
    mod <- gam(price ~ s(carat, bs = "cs"), data = df)
    grid <- data.frame(carat = seq(0.2, 2, length = 50))
    pred <- predict(mod, grid, se = T)
    grid$price <- pred$fit
    grid$se <- pred$se.fit
    grid
}
smoothes <- ddply(dense, .(color), smooth)
qplot(carat, price, data = smoothes, colour = color, geom = "line")

id

qplot(carat, price, data = smoothes, colour = color, geom = "smooth", ymax = price + 2 * se, ymin = price - 2 * se)

id

mod <- gam(price ~ s(carat, bs = "cs") + color, data = dense)
grid <- with(diamonds, expand.grid(carat = seq(0.2, 2, length = 50), color = levels(color)))
grid$pred <- predict(mod, grid)
qplot(carat, pred, data = grid, colour = color, geom = "line")

id

把数据化“宽”为“长”

ggplot2进行数据分组时必须根据行,而不能根据列。这里介绍如何把“宽”的数据变成“长”的数据,也就是说变量不再是放在各个列上,而是排成一列,每个变量都分别占其中的几行。这样就能根据行把每个变量分组,然后作图。

这里会用到reshape2包中的melt()函数。melt()函数有三个参数:

  • data:待变形的原数据
  • id.vars:依旧放在列上、位置保持不变的变量。id.vars通常是离散的,并且是预先给定的。用方差分析的角度来理解,id.vars好比是变量Yijk的下标(i,j,k);用数据库的角度来理解,id.vars好比是数据表的主键。
  • measure.vars:需要被放进同一列的变量。不同的变量放在同一列后,根据原变量名来分组,这样这些变量就可以同时画在一张图里。

参考文献

[1][ggplot2:数据分析与图形艺术]