Не ggplot2 едины: визуализация в R

Дисклеймер

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

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

Базовая Визуализация

На мой взгляд, основное преимущество базовой визуализации заключается в возможности легко создавать простые графики. В R поддерживаются четыре основных типа графиков: scatter plot (точечная диаграмма), histogram (гистограмма), bar plot (столбчатая диаграмма) и box plot (ящик с усами)

Scatter plot

head(mtcars) # встроенный датасен 
mpg <-mtcars$mpg 	# Miles/(US) gallon
disp <- mtcars$disp # disp	Displacement (cu.in.)
plot(mpg,disp, main="mpg/disp dependency")

7vjwb6ernu1wnt2ijxkoxs4dfxo.jpeg

Каким образом plot догадался о названиям осей без явного указания не много другая, но не менее интересная тема. Можно попробовать передать mtcars$mpg напрямую, но результат порадует вас прямолинейностью. Так что лучше всего использовать аргументы для контроля над свойствами осей

Hist

hist(mtcars$disp, xlab="Displacement (cu.in.)", main="Distribution of Engine Displacement")

shcr1wj7ax_eostdnby8mpi2yx0.jpeg

Boxplot

boxplot(mtcars$hp,disp,main="Distribution of Gross Horsepower and Engine Displacement",
        names = c("Hp","Miles/(US) gallon"),
        col = c("#f5424b","#4287f5")) # позвонил Спб и попросил вернуть свою цветовую палитру

bp7hm3shwjcdpz9c6hpgo1bda64.jpeg

Barplot

Для создания данных для barplot, используется фрйэмворк dplyr. Если вы активно не использовали Non Standard Evaluation, то этот блок кода может вызвать вопросы. Полный ответ можно найти тут. Или подождать статьи про это

# Установка и загрузка необходимых пакетов
if (!require("dplyr")) install.packages("dplyr")
library(dplyr)
mean_mpg_by_cyl <- mtcars %>%
  group_by(cyl)%>%
  summarise(mean_mpg=mean(mpg))
barplot(mean_mpg_by_cyl$mean_mpg,names.arg = mean_mpg_by_cyl$cyl, 
        main="Mean mpg by number of cyl", 
        xlab="Mean mpg",
        ylab="Number of cylinders", 
        horiz = TRUE, # сделаем график горизонтальным
        las=1) # развернем текст тиков оси. Откуда взялся las и почему 1 без ответа даже в документации ?barplot

zrnkfzybnyakmlwjhrd9exhhbsc.jpeg

Пока все относительно просто, указали что пойдет на оси х, y, labels, простую аэстетику. Но cтоит копнуть глубже и увидеть что plot поддерживают method dispatch (диспетчеризация методов). И можно передать data.frame целиком и не только его. Для более глубоко понимания этой темы, стоит ознакомиться с реализацией OOP в R. Там есть что почитать.

plot(mtcars, main = "Scatterplot Matrix of mtcars Data",
     col = "#03bafc", pch = 19)

3kzkchjjkxiiplkrcbcdackyltu.jpeg

И напоследок, пример c легендой и саб плотами

par(mfrow = c(2, 2)) # задаем размерность

# Plot 1: mpg vs. wt by cylinders
plot(mtcars$wt, mtcars$mpg, col = mtcars$cyl, pch = 19, 
     main = "Miles per Gallon vs. Weight", xlab = "Weight", ylab = "MPG")
legend("topright", legend = unique(mtcars$cyl), col = unique(mtcars$cyl), pch = 19, title = "Cylinders")

# Plot 2: mpg vs. hp by transmission
plot(mtcars$hp, mtcars$mpg, col = mtcars$am, pch = 17,
     main = "Miles per Gallon vs. Horsepower", xlab = "Horsepower", ylab = "MPG")
legend("topright", legend = c("Automatic", "Manual"), col = c(1, 2), pch = 17, title = "Transmission")

# Plot 3: mpg vs. qsec by gears
plot(mtcars$qsec, mtcars$mpg, col = mtcars$gear, pch = 16,
     main = "Miles per Gallon vs. Quarter Mile Time", xlab = "Quarter Mile Time", ylab = "MPG")
legend("topright", legend = unique(mtcars$gear), col = unique(mtcars$gear), pch = 16, title = "Gears")

# Plot 4: mpg vs. disp by engine type
plot(mtcars$disp, mtcars$mpg, col = mtcars$vs + 1, pch = 15,
     main = "Miles per Gallon vs. Displacement", xlab = "Displacement", ylab = "MPG")
legend("topright", legend = c("V-shaped", "Straight"), col = c(1, 2), pch = 15, title = "Engine Type")

zklx4s6vwoxwhhuy7lyhlvebqog.jpeg

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

Полезные материалы и ссылки:

Lattice

Первая библиотека, которую я хотел бы упомянуть, это lattice. Ее релиз состоялся в далеком 1997 году и серьезно расширил возможности визуализации в R. Она поддерживает множество типов графиков, передачу данных по формуле; в общем виде график формируется по следующей схеме xyplot(y ~ x | group, data). Главное преимущество lattice заключается в возможности быстро строить многопанельные графики. Каждая панель представляет собой полноценный независимый график, который позволяет укладывать в панель некоторое подобие слоев. Конечно, сейчас ggplot2 практически вытеснил lattice из статей и отчетов, но упомянуть о нем стоит.

# Установка и загрузка необходимых пакетов
if (!require("lattice")) install.packages("lattice")
library(lattice)
df <- mtcars
df$am <- ifelse(mtcars$am==0, "automatic", "manaul")
df$cyl <- paste(mtcars$cyl, "cylinders")
# Cоздание графика
lattice_plot<- xyplot(mpg ~ hp | factor(cyl) * factor(am), data = df,
                      main = "Scatterplot of mpg vs. hp conditioned by cyl and am",
                      xlab = "Horsepower",
                      ylab = "Miles per Gallon",
                      pch = 16,
                      panel = function(x, y, ...) {
                        panel.xyplot(x, y, ...)  # Scatter plot
                        panel.lmline(x,y) # reggression line
                      },
                      auto.key = list(space = "top", columns = 3,
                                      points = TRUE, lines = FALSE,
                                      title = "Legend"))
lattice_plot

myycpszh8rlcei1thatwjylhi0a.jpeg

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

dim(lattice_plot)
# А значит к этим атрибутам можно обратиться
lattice_plot[3,1]

cknyiwrkqg2gozelvlsrpo1aejg.jpeg

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

Полезные материалы и ссылки:

ggplot2

Название может натолкнуть на мысль что это не первая версия. Но это не так, ggplot2 был выпущен в 2007 году и его название — дань уважения книге. В основе ggplot2 лежит систематический подход созданию графиков (сформированный в этой книге). Он предполагает декомпозицию графиков и дальнейшее комбинирование и модификацию компонентов. К этим компонентам относят:

  • Данные (Data):

Графики строятся на основе набора данных. В ggplot2 данные передаются в функции через аргумент data.

Аэстетики определяют, какие переменные данных соответствуют различным визуальным элементам графика, таким как координаты, цвет, размер, форма и т.д. В ggplot2 это задается с помощью функции aes ().

*Геометрии (Geometries):

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

  • Фасеты (Facets):

Фасеты позволяют разбивать график на несколько подграфиков, отображающих различные подмножества данных. В ggplot2 это делается с помощью функций facet_wrap () и facet_grid ().

  • Слои (Layers):

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

  • Шкалы (Scales):

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

Темы управляют общим внешним видом графика, включая шрифты, цвета фона, сетки и другие элементы оформления. В ggplot2 можно использовать функцию theme () для настройки этих элементов.

Каждая из этих составляющих может быть настроена до мелочей. Годы активного использования этой библиотеки как золотого стандарта визуализации позволяют достигать высококачественных результатов с минимальными настройками. Но если вы хотите углубиться в детали, будьте готовы изучать значительное количество документации, богато иллюстрированной продвинутыми примерами. Помимо официального пакета, сообщество выпустило множество расширений для решения узкоспециализированных задач. Я не буду демонстрировать копипасту и вводить читателя в ступор большим количеством кода ради хитрого графика, но уверяю вас, что за годы работы с R в проектах от GWAS (Genome-wide association studies) до менеджерской аналитики, он меня ни разу не подвел.

# Установка и загрузка необходимых пакетов
if (!require("ggplot2")) install.packages("ggplot2")
if (!require("dplyr")) install.packages("dplyr")
library("ggplot2")
library("dplyr")
# Создание графика
mtcars %>%
  mutate(am = ifelse(mtcars$am==0, "automatic", "manaul"))%>%
  ggplot(aes(x = mpg)) + # данные и аэстетика
  geom_histogram(binwidth = 2, color = "white", fill = "#69b3a2") + # геометрия и слои
  facet_wrap(~ factor(am), ncol = 2) + # фасеты
  labs(title = "Distribution of MPG by Transmission Type", # Шкалы
       x = "Miles per Gallon (MPG)",
       y = "Frequency") +
  theme_minimal() + # темы
  theme(plot.title = element_text(hjust = 0.5),
        axis.title = element_text(size = 12),
        strip.text = element_text(face = "bold", size = 10))

-uai3031granhlyif6mhhleuwm4.jpeg

Полезные материалы и ссылки:

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

Шаг в интерактивность

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

До сих пор мы рассматривали лишь статические графики. Наиболее простым решением для создание интерактивного графика является plotly::gplotly(). Данная функция постарается трансформировать ggplot2 график в plotly график. Но это не раскрывает и доли возможностей plotly, плюс работает относительно медленно. Попробуем сделать интерактивным график из прошлого примера, используя этот подход

# Установка и загрузка необходимых пакетов
if (!require("ggplot2")) install.packages("ggplot2")
if (!require("dplyr")) install.packages("dplyr")
if (!require("plotly")) install.packages("plotly")
library("ggplot2")
library("dplyr")
library("plotly")
# Cоздание графика
ggplot_graph<- mtcars %>%
  mutate(am = ifelse(mtcars$am==0, "automatic", "manaul"))%>%
  ggplot(aes(x = mpg)) + # данные и аэстетика
  geom_histogram(binwidth = 2, color = "white", fill = "#69b3a2") + # геометрия и слои
  facet_wrap(~ factor(am), ncol = 2) + # фасеты
  labs(x = "Miles per Gallon (MPG)",
       y = "Frequency") +
  theme_minimal() + # темы
  theme(plot.title = element_text(hjust = 0.5),
        axis.title = element_text(size = 12),
        strip.text = element_text(face = "bold", size = 10))
# Преобразование в интерактивный график
plotly_graph <- ggplotly(ggplot_graph)
plotly_graph

k9ldhthexqy-e2ma6c49oipuwva.gif

Если обратиться к атрибутам объекта plotly_graph, можно увидеть:

$names
[1] "x"             "width"         "height"        "sizingPolicy"  "dependencies"  "elementId"    
[7] "preRenderHook" "jsHooks"      

$class
[1] "plotly"     "htmlwidget"

$package
[1] "plotly"

Наличие preRenderHook и jsHooks, а также класс htmlwidget позволяют модифицировать полученный график с использованием JavaScript. Попробуем добавить пропущенный title к нашему графику

custom_js <- "
function(el, x) {
  Plotly.relayout(el.id, {
    title: 'Distribution of MPG by Transmission Type'
  });
}
"
plotly_graph <- htmlwidgets::onRender(plotly_graph, custom_js) %>%
  layout(width=900, height=620)
plotly_graph

eknyihvd607tswanctoouiyyomu.gif

Но обязательно ли изобретать велосипед ? Предлагаю обзорно оценить возможности самого plotly

Plotly

R Plotly как уже описано выше, позволяет конвертировать статические ggplot2 графики, так и создавать их с нуля используя туже ключевую идею, что и ggplot2.

Связанные графики

Мне кажется одна из самых частых задач для plotly: создать связанные интерактивные графики. И к сожалению R Plotly пока лишь позволяет иметь общие оси у subplots. Конечно, можно достичь эффекта связывания через htmlwidgets, но предлагаю воспользоваться более высокоуровневой библиотекой crosstalk. Утилизируя уже знакомый htmlwidget, она предоставляет R6 class, мимикрирующий под стандартный data.frame

# Установка и загрузка необходимых пакетов
if (!require("crosstalk")) install.packages("crosstalk")
if (!require("plotly")) install.packages("plotly")
if (!require("dplyr")) install.packages("dplyr")

library(crosstalk)
library(plotly)
library(dplyr)

# Подготовка данных с использованием crosstalk
df <- mtcars %>% mutate(am = ifelse(am == 0, "Automatic", "Manual"), cyl=paste(cyl, "cylinders"))
shared_mtcars <- SharedData$new(df) # R6 class 

# Создание scatter plot
scatter_plot <- plot_ly(shared_mtcars, x = ~mpg, y = ~hp, type = 'scatter', mode = 'markers',
                        color = ~factor(cyl), colors = "Set1",
                        text = ~paste("Car:", rownames(mtcars)),
                        marker = list(size = 10)) %>%
  layout(title = "Scatter Plot of MPG vs HP",
         xaxis = list(title = "Miles per Gallon (MPG)"),
         yaxis = list(title = "Horsepower (HP)")
         )

# Создание box plot для mpg
box_plot_mpg <- plot_ly(shared_mtcars, y = ~mpg, type = 'box', color = ~factor(cyl), colors = "Set1") %>%
  layout(title = "Box Plot of MPG by Cylinders",
         yaxis = list(title = "Miles per Gallon (MPG)"))

# Создание box plot для hp
box_plot_hp <- plot_ly(shared_mtcars, y = ~hp, type = 'box', color = ~factor(cyl), colors = "Set1") %>%
  layout(title = "Box Plot of HP by Cylinders",
         yaxis = list(title = "Horsepower (HP)"))

# Объединение графиков в один с помощью subplot
combined_plot <- subplot(scatter_plot, box_plot_mpg, box_plot_hp, nrows = 2, titleX = TRUE, titleY = TRUE) %>%
  layout(title = "Combined Plot of MPG and HP with Box Plots", width=900, height=620)%>%
  hide_legend()

# Отображение графика
combined_plot

dxfhuszatbeml59akzaf0u9ysxa.gif

Виджеты для контроля

Для того чтобы минимизировать объем работ с чистым JS, plotly любезно предоставляет виджеты из своей коробки

*Dropdown (dropdown).

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

# Установка и загрузка необходимых пакетов
if (!require("plotly")) install.packages("plotly")
if (!require("dplyr")) install.packages("dplyr")

library(plotly)
library(dplyr)

# Подготовка данных
mtcars <- mtcars %>%
  mutate(am = ifelse(am == 0, "automatic", "manual"))

# Создание графика с переключателем (dropdown)
plot <- plot_ly(data = mtcars, type = "box",
                x = ~am, y = ~mpg,
                color = ~am, colors = "Set1",
                text = ~paste("Car:", rownames(mtcars))) %>%
  layout(title = "MPG by Transmission Type",
         xaxis = list(title = "Transmission Type"),
         yaxis = list(title = "Miles per Gallon (MPG)"),
         width=900, height=620,
         updatemenus = list( # dropdown
           list(
             buttons = list(
               list(method = "restyle",
                    args = list("type", "box"),
                    label = "Boxplot"),
               list(method = "restyle",
                    args = list("type", "violin"),
                    label = "Violin Plot")
             )
           )
         ))

# Отображение графика
plot

yq6hh7af5q7znapzj3bxnoziytk.gif

Анимация

Опытный читатель справедливо отметит, что анимацию можно сделать и в ggplot2 с помощью gganinimate. Но я посчитал, что анимация больше подходит plotly, хотя тут на вкус и цвет и задачу, которую нужно сделать. К сожалению придумать анимацию для датасета mtcars без относительно сложных трансформаций данных мне не удалось, так что возьмем классику — gapminder. Данный датасет содержит социально-экономические хавактеристики для стран в различные годы.

if (!require("plotly")) install.packages("plotly")
if (!require("dplyr")) install.packages("dplyr")
if (!require("gapminder")) install.packages("gapminder")
library(plotly)
library(dplyr)
library(gapminder)
# Создание plotly графкики
plotly_graph <- gapminder %>%
  plot_ly(x = ~gdpPercap, y = ~lifeExp, size = ~pop, 
          text = ~country, hoverinfo = "text") %>%
  layout(xaxis = list(type = "log"), width=900, height=620)

# Прикручивание анимации
plotly_graph  %>%
  # задаем маркеры
  add_markers(color = ~continent, frame = ~year, ids = ~country) %>% 
  # опции анимации
  animation_opts(1000, easing = "elastic", redraw = FALSE) %>% 
  # свойста анимационных виджетов
  animation_button(
    x = 1, xanchor = "right", y = 0, yanchor = "bottom"
  ) %>%
  animation_slider(
    currentvalue = list(prefix = "YEAR ", font = list(color="red"))
  )

mp1v8gxsbpj3xidoxneblz1_et4.gif

3D

Не очень понимаю прелести 3 осевого графика в статике. Но plotly позволяет делать вещи куда веселее этого

# загрузка бибилотек
if (!require("plotly")) install.packages("plotly")
library(plotly)
volcan <- plot_ly(z = volcano, type = "surface") %>%
  layout(width=900, height=620)
volcan

lca8r5uombtl144fdg8jbfx7ij0.gif

Полезные материалы и ссылки:

Highcharter

R обзовелся своим врапером над JS библиотекой highcharts. Разработчики обещают красивые графики, широкие возможности для кастомизиции, плавную интеграцию с R Shiny Web Framework. Однако, сама JS библиотека распространяется под коммерческой лицензией. Графики и правда выглядят хорошо, однако пакет уступает в популярности plotly.

# загрузка бибилотек
if (!require("highcharter")) install.packages("highcharter")
if (!require("dplyr")) install.packages("dplyr")
library(highcharter)
library(dplyr)


# Создания графика
hc <- highchart() %>%
  #добавляем данные
  hc_add_series(
    data = mtcars,
    type = "scatter",
    hcaes(x = hp, y = mpg, group = as.factor(cyl))
  ) %>%
  # title
  hc_title(text = "Miles per Gallon vs Horsepower") %>%
  # title оси х
  hc_xAxis(title = list(text = "Horsepower")) %>%
  # title оси у
  hc_yAxis(title = list(text = "Miles per Gallon")) %>%
  # ховер эффккт
  hc_tooltip(pointFormat = "mpg: {point.y}, hp: {point.x}, cyl: {point.group}") %>%
  # легенда
  hc_legend(title = list(text = "Cylinders")) %>%
   hc_size(width = 900, height = 600)

# Отрисовываем график
hc

mhph_ix7tohbh0evqeymlzpv_z8.gif

Полезные материалы и ссылки:

Trelliscope

Перечисленные решения просто прекрасны, но могут потребовать достаточно больших усилий для работы, особенно если нужно создавать множество графиков на большом объеме данных. Trelliscope представляет из себя промежуточное решение между plotly и полноценным Shiny приложением. Полученный дашборд позволяет оперировать с большим объемом графиков производя их фильтрацию и сортировку. Когда я только начинал с ним работать, у него были проблемы с интеграцией в shiny web приложения, но с тех пор прошло много времени. Однозначно стоит полистать галлерею и репозитории с кодом.

vpnzlh_fit0fno3brh6wxmgbzwy.gif

Полезные материалы и ссылки:

trelliscope

Leaflet

R wrapper для одноименной библиотеки leaflet.js. Обладая мощностью оригинальной библиотеки, R пакет прекрасно интегрируется с Shiny и R в целом.

# Установка и загрузка пакета
if (!require("leaflet")) install.packages("leaflet")
library(leaflet)

# Создание данных для маркеров
cities <- data.frame(
  name = c("Нью-Йорк", "Лос-Анджелес", "Чикаго", "Хьюстон", "Финикс"),
  lat = c(40.7128, 34.0522, 41.8781, 29.7604, 33.4484),
  lng = c(-74.0060, -118.2437, -87.6298, -95.3698, -112.0740)
)

# Создание карты
map <- leaflet(width=909, height=620) %>%
  addTiles() %>%  # Добавление базового слоя карты
  
  # Добавление маркеров с всплывающими окнами
  addMarkers(data = cities, ~lng, ~lat, popup = ~name) 

# Отображение карты
map

3y7ama8dpmnmrfujqwdx_guybwu.gif

Заключение

Безусловно, это не исчерпывающий список библиотек. Есть еще rbokeh, dygraph и много нишевых либ.В этой статье я специально не стал приводить сравнения с Python решениями. Это свободно можно сделать и в комментариях. Да и как правило выбор между R и Python принимается задолго до первого графика. Но это уже совсем другая статья.

Habrahabr.ru прочитано 1943 раза