Введение в R: часть 6

Pavel Polishchuk, 2014

Содержание

Графические возможности R с пакетом ggplot2

Литература

  1. Книга от создателя пакета ggplot2 - Wickham, Hadley. ggplot2: Elegant Graphics for Data Analysis. Dordrecht, Heibelberg, London, New York: Springer, 2009.
  2. http://docs.ggplot2.org/current/ - подробная авторская документация с примерами

Идеология представления графиков в рамках пакета ggplot2

Пакет ggplot2 предназначен для построения двумерных графиков и диаграмм.

График в рамках пакета ggplot2 представляется как конструктор, состоящий из отдельных частей, которые можно соединять произвольным образом, чтобы достичь желаемого эффекта.

Основные кирпичики графического конструктора (первые три их них являются обязательными составляющими):

  1. data - data.frame, содержащий данные для представления.
  2. aes() - задает связь между данными и их представлением, определяет какие переменные отображаются на осях, какие переменные отвечают за цвет и форму данных, представленных на графике.
  3. geom_ - группа функций, отвечающих за то что, вы непосредственно видите на графике (точки, гистограммы, линии, текст и т.п.).
  4. scale_ - группа функций, соотносящих реальные данные с их графическим представлением. Это может быть цвет, форма, размер, диапазон осей координат и т.п.
  5. stat_ - группа функций, добавляющих на график различные статистические показатели, например, такие как, среднее значение по группе, линейная или иная аппроксимация данных и т.п.
  6. coord_ - группа функций, которая задает систему координат для представления данных на плоскости. Это могут быть обычные картезианские координаты, полярные координаты, или географические координаты для представления соответствующих данных.
  7. facet_ - группа функций, которые позволяют группировать графики по заданному параметру и представлять результаты в виде набора графиков (сетки из графиков).
  8. theme - группа функций, позволяющих менять оформление графика, например размер и цвет шрифта координатных осей и делений на них, фон графика и всего рисунка и т.п.

Подготовка данных для визуализации

Возьмем за основу значения растворимости предсказанные ранее различными моделями. Эти данные можно загрузить с сайта, а можно использовать те, которые были получены в ходе самостоятельного моделирования.

cv <- read.table("data/cv_pred.txt", sep="\t", header=TRUE, as.is=TRUE)
test <- read.table("data/test_pred.txt", sep="\t", header=TRUE, as.is=TRUE)
head(cv)
              m.gbm     m.knn     m.pls      m.rf     m.svm      mean
sol_10001 -2.799118 -3.626667 -2.148206 -3.000497 -2.677521 -2.850402
sol_10002 -2.683472 -3.553333 -2.214603 -2.639931 -2.540740 -2.726416
sol_10003 -2.589948 -3.603333 -2.378869 -3.280679 -3.100913 -2.990748
sol_10004 -2.639194 -3.853333 -2.297653 -3.249204 -3.106995 -3.029276
sol_10005 -3.001957 -2.823333 -1.795756 -3.036543 -2.766391 -2.684796
sol_10006 -3.231845 -3.556667 -2.535705 -3.353757 -3.070914 -3.149778
               nnls
sol_10001 -2.823609
sol_10002 -2.657990
sol_10003 -2.973854
sol_10004 -2.984015
sol_10005 -2.922233
sol_10006 -3.233097
head(test)
               m.gbm     m.knn      m.pls       m.rf      m.svm       mean
sol_10801 -1.1778582 -0.440000 -0.3168113 -0.8795913 -0.6089708 -0.6846463
sol_10802 -1.0579281 -0.440000 -0.2862568 -1.0169123 -1.0142728 -0.7630740
sol_10803 -0.7143699 -0.440000 -0.2963245 -1.1513807 -1.7784464 -0.8761043
sol_10804 -0.8748695 -0.440000 -0.0164618 -1.1532090 -2.2388806 -0.9446842
sol_10805 -0.9348645 -1.346667 -1.4939263 -1.1176407 -0.8709753 -1.1528147
sol_10806 -1.7129724 -1.473333 -1.3859660 -1.2913827 -1.3519756 -1.4431260
                nnls
sol_10801 -0.8803242
sol_10802 -1.0139525
sol_10803 -1.1994194
sol_10804 -1.4198597
sol_10805 -1.0024755
sol_10806 -1.5129118

Округлим результаты для лучшего восприятия данных

cv <- round(cv, 2)
test <- round(test, 2)
head(cv)
          m.gbm m.knn m.pls  m.rf m.svm  mean  nnls
sol_10001 -2.80 -3.63 -2.15 -3.00 -2.68 -2.85 -2.82
sol_10002 -2.68 -3.55 -2.21 -2.64 -2.54 -2.73 -2.66
sol_10003 -2.59 -3.60 -2.38 -3.28 -3.10 -2.99 -2.97
sol_10004 -2.64 -3.85 -2.30 -3.25 -3.11 -3.03 -2.98
sol_10005 -3.00 -2.82 -1.80 -3.04 -2.77 -2.68 -2.92
sol_10006 -3.23 -3.56 -2.54 -3.35 -3.07 -3.15 -3.23
head(test)
          m.gbm m.knn m.pls  m.rf m.svm  mean  nnls
sol_10801 -1.18 -0.44 -0.32 -0.88 -0.61 -0.68 -0.88
sol_10802 -1.06 -0.44 -0.29 -1.02 -1.01 -0.76 -1.01
sol_10803 -0.71 -0.44 -0.30 -1.15 -1.78 -0.88 -1.20
sol_10804 -0.87 -0.44 -0.02 -1.15 -2.24 -0.94 -1.42
sol_10805 -0.93 -1.35 -1.49 -1.12 -0.87 -1.15 -1.00
sol_10806 -1.71 -1.47 -1.39 -1.29 -1.35 -1.44 -1.51

Добавим к этим данным наблюдаемые значения растворимости

y <- local.load("data/sol_y1.RData")
y.test <- local.load("data/sol_y2.RData")
cv$obs <- y
test$obs <- y.test

И добавим колонку в которой укажем, какому набору принадлежат данные кросс-валидации или тестовой выборке.

cv$set <- "cv"
test$set <- "test"
head(cv)
          m.gbm m.knn m.pls  m.rf m.svm  mean  nnls   obs set
sol_10001 -2.80 -3.63 -2.15 -3.00 -2.68 -2.85 -2.82 -3.18  cv
sol_10002 -2.68 -3.55 -2.21 -2.64 -2.54 -2.73 -2.66 -2.64  cv
sol_10003 -2.59 -3.60 -2.38 -3.28 -3.10 -2.99 -2.97 -3.84  cv
sol_10004 -2.64 -3.85 -2.30 -3.25 -3.11 -3.03 -2.98 -3.74  cv
sol_10005 -3.00 -2.82 -1.80 -3.04 -2.77 -2.68 -2.92 -3.55  cv
sol_10006 -3.23 -3.56 -2.54 -3.35 -3.07 -3.15 -3.23 -3.10  cv
head(test)
          m.gbm m.knn m.pls  m.rf m.svm  mean  nnls   obs  set
sol_10801 -1.18 -0.44 -0.32 -0.88 -0.61 -0.68 -0.88 -1.11 test
sol_10802 -1.06 -0.44 -0.29 -1.02 -1.01 -0.76 -1.01 -0.91 test
sol_10803 -0.71 -0.44 -0.30 -1.15 -1.78 -0.88 -1.20 -1.76 test
sol_10804 -0.87 -0.44 -0.02 -1.15 -2.24 -0.94 -1.42 -2.36 test
sol_10805 -0.93 -1.35 -1.49 -1.12 -0.87 -1.15 -1.00 -0.86 test
sol_10806 -1.71 -1.47 -1.39 -1.29 -1.35 -1.44 -1.51 -1.16 test

Объединим эти два набора данных в один. Поскольку порядок следования данных в колонках совпадает можно применить функцию объединения по строкам rbind

df <- rbind(cv, test)

Все эти манипуляции конечно не обязательны при работе с реальными данными, а служат лишь для того, чтобы продемонстрировать способы работы с данными и подготовки их к дальнейшей отрисовке.

Задание.

Нарисуем графики распределения предсказанных и наблюдаемых значений для каждой из моделей. По мере продвижения к цели будут продемонстрированы некоторые возможности ggplot2.

Пример точечной диаграммы для одной модели

Начнем с самого простого. Нарисуем точечную диаграмму распределения наблюдаемых и предсказанных значений для модели gbm. Т.е. используем три обязательных компонента data, aes и geom_.

require(ggplot2)
# single model plot
g1 <- ggplot(df, aes(x=obs, y=m.gbm)) + geom_point()
g1

plot of chunk unnamed-chunk-11

В ggplot2 есть альтернативный более простой вариант для создания диаграмм - функция qplot, которая во многом похожа на обычную функцию plot. Она не обладает большим разнообразием возможностей и подходит для быстрого построения не очень сложных графиков.

# shorter version
g1.1 <- qplot(x=obs, y=m.gbm, data=df, geom="point")
g1.1

plot of chunk unnamed-chunk-12

# even shorter one
g1.2 <- qplot(x=df$obs, y=df$m.gbm, geom="point")
g1.2

plot of chunk unnamed-chunk-12

У нас на графике совместно представлены данные для кросс-валидации и внешнего теста, разделим их по цвету точек.

# add color to distinguish cv and test
g2 <- ggplot(df, aes(x=obs, y=m.gbm, color=set)) + 
  geom_point()  
g2

plot of chunk unnamed-chunk-13

Добавим дифференциацию точек по форме, задав соответствующее условие.

# add color and shape to distinguish cv and test
g3 <- ggplot(df, aes(x=obs, y=m.gbm, color=set, shape=set)) + 
  geom_point()  
g3

plot of chunk unnamed-chunk-14

Если мы хотим увеличить размер точек (задать фиксированный размер), то для этого используются параметры функции geom_point.

# change point size
g4 <- ggplot(df, aes(x=obs, y=m.gbm, color=set, shape=set)) + 
  geom_point(size=3)  
g4

plot of chunk unnamed-chunk-15

Чтобы было лучше видно перекрывающиеся точки, их можно сделать полупрозрачными.

# add transparency to view overlapping points
g5 <- ggplot(df, aes(x=obs, y=m.gbm, color=set, shape=set)) + 
  geom_point(size=3, alpha=0.5)  
g5

plot of chunk unnamed-chunk-16

Добавим на наш график диагональную линию соответствующую идеальному случаю прогноза.

# add a diagonal line of perfect prediction
# since aes in main function relates to all data we set shape and color inside the geom_point function
g6 <- ggplot(df, aes(x=obs, y=m.gbm, color=set, shape=set)) + 
  geom_point(size=3, alpha=0.5) +
  geom_abline(intercept=0, slope=1)
g6

plot of chunk unnamed-chunk-17

Отметим важность очередности следоваания вызова функция для рисования отдельных слоев. Эта очередность определяет последовательность отображения данных на графике.

# order of calls is important it determines to order of layers on the plot
g7 <- ggplot(df, aes(x=obs, y=m.gbm)) + 
  geom_abline(intercept=0, slope=1) +
  geom_point(aes(color=set, shape=set), size=3, alpha=0.5)
g7

plot of chunk unnamed-chunk-18

Добавляем stat_

Добавим на график элемент stat_ - в нашем случае это будет прямая представляющая собой линейную зависимость предсказанных и наблюдаемых значений. Обратите внимание, что мы к ранее созданному объекту класса ggplot может добавлять новые элементы (режим конструктора!).

# add stat to modify previously created plot - it's really like a LEGO!
g8 <- g7 + stat_smooth(method="lm")
g8

plot of chunk unnamed-chunk-19

Чтобы аппроксимация строилась для каждой из двух групп точек отдельно укажем это явно в вызываемой функции.

# make stat for each group of point separately
g9 <- g7 + stat_smooth(method="lm", aes(group=set))
g9

plot of chunk unnamed-chunk-20

В дальнейшем мы будем использовать полный вызов функции.

Удалим из аппроксимирующей зависимости отображение диапазона ошибки аппроксимации, отобразим линии на всем диапазоне графика и представим их цветом соответствующем набору данных.

# to be clear further we will use full plot call
# remove SE and extend lines to full range and set a color for each line
g10 <- ggplot(df, aes(x=obs, y=m.gbm)) + 
  geom_abline(intercept=0, slope=1) +
  geom_point(aes(color=set, shape=set), size=3, alpha=0.5) +
  stat_smooth(method="lm", aes(group=set, color=set), fullrange=TRUE, se=FALSE)
g10

plot of chunk unnamed-chunk-21

scale_

Применим какую-либо функцию из семейства функций scale_, например, изменим цветовое представление графика.

# use scale for colors (it is possible to scale other aestetics)
g11 <- ggplot(df, aes(x=obs, y=m.gbm)) + 
  geom_abline(intercept=0, slope=1) +
  geom_point(aes(color=set, shape=set), size=3, alpha=0.5) +
  stat_smooth(method="lm", aes(group=set, color=set), fullrange=TRUE, se=FALSE) +
  scale_color_grey()
g11

plot of chunk unnamed-chunk-22

Выглядит не очень удачно. Зададим цвета самостоятельно.

# looks bad, lets scale manually to set colors
g12 <- ggplot(df, aes(x=obs, y=m.gbm)) + 
  geom_abline(intercept=0, slope=1) +
  geom_point(aes(color=set, shape=set), size=3, alpha=0.5) +
  stat_smooth(method="lm", aes(group=set, color=set), fullrange=TRUE, se=FALSE) +
  scale_color_manual(values=c("red", "blue"))
g12

plot of chunk unnamed-chunk-23

Цвет можно определять также и соответствующим RGB кодом цвета.

# you can use RGB codes for colors
g12.1 <- ggplot(df, aes(x=obs, y=m.gbm)) + 
  geom_abline(intercept=0, slope=1) +
  geom_point(aes(color=set, shape=set), size=3, alpha=0.5) +
  stat_smooth(method="lm", aes(group=set, color=set), fullrange=TRUE, se=FALSE) +
  scale_color_manual(values=c("#FF22FF", "#2255FF"))
g12.1

plot of chunk unnamed-chunk-24

Соответствующими функциями scale_ можно инвертировать оси координат.

# another usage of scale applied for axis
g12.2 <- ggplot(df, aes(x=obs, y=m.gbm)) + 
  geom_abline(intercept=0, slope=1) +
  geom_point(aes(color=set, shape=set), size=3, alpha=0.5) +
  stat_smooth(method="lm", aes(group=set, color=set), fullrange=TRUE, se=FALSE) +
  scale_x_reverse()
g12.2

plot of chunk unnamed-chunk-25

g12.3 <- ggplot(df, aes(x=obs, y=m.gbm)) + 
  geom_abline(intercept=0, slope=1) +
  geom_point(aes(color=set, shape=set), size=3, alpha=0.5) +
  stat_smooth(method="lm", aes(group=set, color=set), fullrange=TRUE, se=FALSE) +
  scale_x_reverse() +
  scale_y_reverse()
g12.3

plot of chunk unnamed-chunk-25

Изменение координатных осей

Координатные оси можно поменять местами при необходимости

# flip coords to exchange axis
g12.4 <- ggplot(df, aes(x=obs, y=m.gbm)) + 
  geom_abline(intercept=0, slope=1) +
  geom_point(aes(color=set, shape=set), size=3, alpha=0.5) +
  stat_smooth(method="lm", aes(group=set, color=set), fullrange=TRUE, se=FALSE) +
  scale_color_manual(values=c("red", "blue")) +
  coord_flip()
g12.4

plot of chunk unnamed-chunk-26

Управление отрисовкой отдельных элементов диаграммы - theme

Управлять отрисовкой отдельных элементов можно использую функции семейства theme_.

# use theme to control graphic output
# there are several predifined themes, but you may create your own
# to descrease amount of code to copy-paste return to the LEGO mode
g13 <- ggplot(df, aes(x=obs, y=m.gbm)) + 
  geom_abline(intercept=0, slope=1) +
  geom_point(aes(color=set, shape=set), size=3, alpha=0.5) +
  stat_smooth(method="lm", aes(group=set, color=set), fullrange=TRUE, se=FALSE) +
  scale_color_manual(values=c("red", "blue"))
g13

plot of chunk unnamed-chunk-27

g13.1 <- g13 + theme_bw()
g13.1

plot of chunk unnamed-chunk-27

g13.2 <- g13 + theme_classic()
g13.2

plot of chunk unnamed-chunk-27

g13.3 <- g13 + theme_minimal()
g13.3

plot of chunk unnamed-chunk-27

В пакете ggplot2 предусмотрен ограниченный набор функций theme_. Чтобы самостоятельно управлять отрисовкой отдельных элементов графика следует воспользоваться функцией theme.

Например, изменим цвет, положение, размер шрифта и угол наклона подписей по оси Х.

# change x labels
g13.4 <- g13 +
  theme(axis.text.x = element_text(color="red", size=15, angle=45, hjust=1))
g13.4

plot of chunk unnamed-chunk-28

Изменим цвет рамки и заливки для области построения диаграммы.

# change plot area background
g13.5 <- g13 +
  theme(axis.text.x = element_text(color="red", size=15, angle=45, hjust=1),
        panel.background = element_rect(color="pink", fill="lightyellow"))
g13.5

plot of chunk unnamed-chunk-29

Удалим с диаграммы все линии сетки (основные и промежуточные)

# remove all grid lines
g13.6 <- g13 +
  theme(axis.text.x = element_text(color="red", size=15, angle=45, hjust=1),
        panel.background = element_rect(color="pink", fill="lightyellow"),
        panel.grid = element_blank())
g13.6

plot of chunk unnamed-chunk-30

facet_

Разделим диаграммы для двух наборов данных.

# facet
g14 <- ggplot(df, aes(x=obs, y=m.gbm)) + 
  geom_abline(intercept=0, slope=1) +
  geom_point(aes(color=set, shape=set), size=3, alpha=0.5) +
  stat_smooth(method="lm", aes(group=set, color=set), fullrange=TRUE, se=FALSE) +
  scale_color_manual(values=c("red", "blue")) +
  facet_wrap(~set)
g14

plot of chunk unnamed-chunk-31

Пример построения гистограмм

Используем наш набор данных для того, чтобы посмотреть распределение свойства для обучающей и тестовой выборок. Ранее мы использовали с этой целью функцию hist.

# look at distribution of training and test sets data
g15 <- ggplot(df, aes(x=obs)) +
  geom_histogram()
g15
stat_bin: binwidth defaulted to range/30. Use 'binwidth = x' to adjust this.

plot of chunk unnamed-chunk-32

Зададим ширину каждого столбца в гистограмме

g15.1 <- ggplot(df, aes(x=obs)) +
  geom_histogram(binwidth=1)
g15.1

plot of chunk unnamed-chunk-33

Раскрасим столбцы в соответствии с выборками

g15.2 <- ggplot(df, aes(x=obs)) +
  geom_histogram(binwidth=1, aes(fill=set))
g15.2

plot of chunk unnamed-chunk-34

Добавим для большей привлекательности белые линии в качестве границ

g15.3 <- ggplot(df, aes(x=obs)) +
  geom_histogram(binwidth=1, aes(fill=set), color="white")
g15.3

plot of chunk unnamed-chunk-35

Разделим гистограмму на две в соответствии с выборками.

g15.4 <- ggplot(df, aes(x=obs)) +
  geom_histogram(binwidth=1, aes(fill=set), color="white") +
  facet_wrap(~set)
g15.4

plot of chunk unnamed-chunk-36

Чтобы расположить их друг под другом зададим параметр ncol

g15.5 <- ggplot(df, aes(x=obs)) +
  geom_histogram(binwidth=1, aes(fill=set), color="white") +
  facet_wrap(~set, ncol=1)
g15.5

plot of chunk unnamed-chunk-37

Можно изменить диапазон значений по каждой или по обеим осям такой диаграммы. Например, зададим, чтобы диаграмммы по оси Y имели независмые шкалы.

g15.6 <- ggplot(df, aes(x=obs)) +
  geom_histogram(binwidth=1, aes(fill=set), color="white") +
  facet_wrap(~set, ncol=1, scales="free_y")
g15.6

plot of chunk unnamed-chunk-38

Диаграмма плотности распределения

Вместо гистограмм можно представить распределение данных в виде диаграммы плотности распределения.

g15.7 <- ggplot(df, aes(x=obs)) +
  geom_density(aes(fill=set), color="white") +
  facet_wrap(~set, ncol=1)
g15.7

plot of chunk unnamed-chunk-39

Точечные диаграммы для всех моделей и выборок

Отобразим в виде точечных диаграмм наблюдаемые и прогнозируемые значения для каждой модели и для каждого набора данных одновременно.

Но прежде надо преобразовать соответствующим образом имеющиеся данные, чтобы их можно было использовать для создания диаграммы.

# let's plot predictions of all models simultaneously
# but first we need to prepare the data
df1 <- reshape::melt.data.frame(df, id.vars=c("set","obs"))
head(df1)
  set   obs variable value
1  cv -3.18    m.gbm -2.80
2  cv -2.64    m.gbm -2.68
3  cv -3.84    m.gbm -2.59
4  cv -3.74    m.gbm -2.64
5  cv -3.55    m.gbm -3.00
6  cv -3.10    m.gbm -3.23
dim(df1)
[1] 7231    4
colnames(df1)[3:4] <- c("model", "pred")
head(df1)
  set   obs model  pred
1  cv -3.18 m.gbm -2.80
2  cv -2.64 m.gbm -2.68
3  cv -3.84 m.gbm -2.59
4  cv -3.74 m.gbm -2.64
5  cv -3.55 m.gbm -3.00
6  cv -3.10 m.gbm -3.23

Теперь можно рисовать

# let's draw
g16 <- ggplot(df1, aes(x=obs, y=pred)) + 
  geom_point(aes(color=set)) +
  facet_wrap(~ model)
g16

plot of chunk unnamed-chunk-41

g16.1 <- g16 + theme_classic()
g16.1

plot of chunk unnamed-chunk-41

Пример столбчатой диаграммы

Другой пример - покажем в виде столбчатой диаграммы ошибки моделей (MSE) для кросс-валидации и внешнего теста.
Предварительно вычислим ошибку для каждой точки и построим диаграмму с одновременным подсчетом MSE модели.

# calc prediction error and visualize it
df1$mse <- (df1$obs - df1$pred) ^ 2

g17 <- ggplot(df1, aes(x=model, y=mse)) +
  geom_bar(stat="summary", fun.y="mean", aes(fill=set), position="dodge")
g17

plot of chunk unnamed-chunk-42

g17 + theme_bw()

plot of chunk unnamed-chunk-42

g17 + theme_classic()

plot of chunk unnamed-chunk-42

Сохранение диаграмм

Для этого используется функция ggsave, которая имеет много параметров, позволяющих управлять результатом.

ggsave("data/plot.jpg", plot=g17 + theme_classic(), width=20, height=15, units="cm", dpi=600)

Функция сохранения поддерживает различные форматы файлов (png, tiff и т.д.).

Домашнее задание

  1. Взяв за основу значения \( RMSE \) и \( R^2_{test} \) полученные ранее для разных моделей для случая кросс-валидации и внешнего теста построить столбчатую диаграмму, разделив представление значений \( RMSE \) и \( R^2_{test} \) на двух разных диаграммах (facet_wrap). Т.е. на каждой из двух диаграмм будут показаны \( RMSE \) и \( R^2_{test} \) для кросс-валидации и тестовыой выборки по каждой модели. Если файла с такими результатами нет, то можно воспользоваться текстовыми файлами с предсказанными значениями, прилагаемыми к этому занятию, и оттуда вычислить необходимые статистические характеристики и визуализировать их.
  2. Добавить к предыдущей диаграмме подписи к каждому столбцу с соответствующим значением величины \( RMSE \) или \( R^2_{test} \).
  3. Используя файлы этого занятия рассчитать абсолютную ошибку для каждого соединения в каждой модели и построить диаграмму ящик с усами (boxplot) для ошибок моделей, чтобы показать разброс ошибок для разных моделей по соединениям. Сгруппировать данные на одной диаграмме по принадлежности к кросс-валидации и к внешнему тесту.