10  Введение в tidyverse

Автор

И.С. Поздняков

10.1 Вселенная tidyverse

Тайдиверс (tidyverse) - это не один, а целое множество пакетов, объединенных общей философией, грамматикой и структурами данных. Пакеты tidyverse можно разделить на две группы:

  1. Основные пакеты, которые составляют ядро tidyverse. Сам по себе пакет {tidyverse} – это пакет для подключения и обновления основных пакетов tidyverse. Короче говоря, {tidyverse} – это такой пакет с пакетами.

Давайте сначала установим пакет {tidyverse}, если он у вас еще не установлен.

install.packages("tidyverse")

Установка может занять довольно большое время: у вас установятся как основные пакеты tidyverse, так и огромное количество зависимостей – других пакетов, которые используются основными пакетами tidyverse. Зато при работе с новыми для себя пакетами вы приятно удивитесь тому, как много нужных вам пакетов уже установлено!

Все пакеты tidyverse, включая, конечно, основные пакеты, объединены tidy философией и взаимосовместимым синтаксисом. Это означает, что, во многих случаях даже не нужно думать о том, из какого именно пакета tidyverse пришла функция. Можно просто подключить один пакет – пакет {tidyverse}.

library(tidyverse)
Warning: package 'dplyr' was built under R version 4.2.3
Warning: package 'stringr' was built under R version 4.2.3
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.4
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.4.4     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.0
✔ purrr     1.0.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

Подключение пакета {tidyverse} автоматически приводит к подключению основных пакетов tidyverse, дополнительные пакеты нужно подключать дополнительно при необходимости.

Вот эти пакеты:

  • {dplyr} – для преобразованиия данных, основной пакет всего тайдиверс,
  • {tidyr} – для приведения данных к чистому виду (tidy data),
  • {tibble} – для работы с тибблами, продвинутый вариант датафрейма,
  • {purrr} – для функционального программирования (замена семейства функций *apply(); см. Глава 8.5.1),
  • {readr} – для чтения таблиц в текстовом виде, замена стандартным функциям семейства read.table(),
  • {ggplot2} – для визуализации данных с использованием Grammar of Graphics (Глава 14),
  • {stringr} – для работы со строковыми переменными,
  • {forcats} – для работы с переменными-факторами.

В 2023 году к ним добавился еще один пакет:

  • {lubridate} – для работы с датами и временем.

    1. Дополнительные пакеты tidyverse: они не подключаются при вызове library(tidyverse), но они также разделяют подход tidyverse, дополняя и развивая его. Определить границы “расширенного” tidyverse очень сложно. Во-первых, есть еще много других небольших пакетов от команды tidyverse, которые тоже считаются частью tidyverse. Во-вторых, кроме официальных пакетов от команды tidyverse есть множество пакетов от других разработчиков, которые пытаются соответствовать принципам tidyverse и дополняют их.
Для продвинутых: Расширенная Вселенная tidyverse

Вселенная “расширенного” tidyverse необъятна. Перечислить все пакеты не представляется возможным, но можно выделить отдельные группы пакетов.

  1. Пакеты для импорта и экспорта данных, работы с базами данных:
  • {vroom} – для быстрой загрузки таблиц в текстовом виде,
  • {readxl} – для чтения файлов Microsoft Excel,
  • {jsonlite} – для работы с JSON,
  • {xml2} – для работы с XML,
  • {DBI} и {dbplyr} – для работы с базами данных,
  • {rvest} – для веб-скреппинга,
  • {feather} – для быстрого чтения и записи данных формата feather,
  • {googledrive} – для взаимодействия с файлами на Google-диске,
  • {googlesheets4} – для импорта и экспорта Google-таблиц.
  1. Пакеты для разработки пакетов: {rlang}, {cli}, {crayon}, {rstudioapi}, {pillar}.

  2. {tidymodels} – еще один “пакет с пакетами”, который позволяет подключать другие пакеты для моделирования и машинного обучения. Включает себя пакеты {rsample}, {parsnip}, {recipes}, {tune}, {yardstick}.

Еще несколько важных пакетов из расширенного tidyverse:

  • {blob} – для работы с большими бинарными объектами (binary large object; BLOB),
  • {reprex} – для создания воспроизводимых примеров (reproducible examples) – чтобы при написании вопросов в R чатах, stackoverflow, issues на гитхабе описывать проблему таким образом, чтобы отвечающие могли воспроизвести вашу проблему у себя.
  • {glue} – для продвинутого объединения строк,
  • {tidytext}– для работы с текстами и корпусами,
  • {magrtittr} – с несколькими вариантами (%>%) пайп-оператора (Глава 10.3),
  • {dtplyr} – для ускорения {dplyr} за счет перевод синтаксиса на более быстрый {data.table} (Глава 9.1).

10.2 Загрузка данных с помощью {readr}

Стандартной функцией для чтения .csv файлов в R является функция read.csv(), но мы будем использовать функцию read_csv() из пакета readr. Синтаксис функции read_csv() очень похож на read.csv(): первым аргументом является путь к файлу (в том числе можно использовать URL), некоторые остальные параметры тоже совпадают.

heroes <- read_csv("https://raw.githubusercontent.com/Pozdniakov/tidy_stats/master/data/heroes_information.csv",
                   na = c("-", "-99"))
New names:
• `` -> `...1`
Warning: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
  dat <- vroom(...)
  problems(dat)
Rows: 734 Columns: 11
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (8): name, Gender, Eye color, Race, Hair color, Publisher, Skin color, A...
dbl (3): ...1, Height, Weight

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

Подробнее про импорт данных, в том числе в tidyverse, смотри в Глава 6.

10.3 tibble

Когда мы загрузили данные с помощью read_csv(), то мы получили объект класса tibble, а не data.frame:

class(heroes)
[1] "spec_tbl_df" "tbl_df"      "tbl"         "data.frame" 

Тиббл (tibble) - это такой “усовершенствованный” data.frame. Почти все, что работает с data.frame, работает и с тибблами. Однако у тибблов есть свои дополнительные фишки. Самая очевидная из них - более аккуратный вывод в консоль:

heroes
# A tibble: 734 × 11
    ...1 name          Gender `Eye color` Race     `Hair color` Height Publisher
   <dbl> <chr>         <chr>  <chr>       <chr>    <chr>         <dbl> <chr>    
 1     0 A-Bomb        Male   yellow      Human    No Hair         203 Marvel C…
 2     1 Abe Sapien    Male   blue        Icthyo … No Hair         191 Dark Hor…
 3     2 Abin Sur      Male   blue        Ungaran  No Hair         185 DC Comics
 4     3 Abomination   Male   green       Human /… No Hair         203 Marvel C…
 5     4 Abraxas       Male   blue        Cosmic … Black            NA Marvel C…
 6     5 Absorbing Man Male   blue        Human    No Hair         193 Marvel C…
 7     6 Adam Monroe   Male   blue        <NA>     Blond            NA NBC - He…
 8     7 Adam Strange  Male   blue        Human    Blond           185 DC Comics
 9     8 Agent 13      Female blue        <NA>     Blond           173 Marvel C…
10     9 Agent Bob     Male   brown       Human    Brown           178 Marvel C…
# ℹ 724 more rows
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>

Выводятся только первые 10 строк, если какие-то колонки не влезают на экран, то они просто перечислены внизу. Ну а тип данных написан прямо под названием колонки.

Функции различных пакетов tidyverse сами конвертируют в тиббл при необходимости. Если же нужно это сделать самостоятельно, то можно это сделать так:

heroes_df <- as.data.frame(heroes) #создаем простой датафрейм
class(heroes_df)
[1] "data.frame"
as_tibble(heroes_df) #превращаем обратно в тиббл
# A tibble: 734 × 11
    ...1 name          Gender `Eye color` Race     `Hair color` Height Publisher
   <dbl> <chr>         <chr>  <chr>       <chr>    <chr>         <dbl> <chr>    
 1     0 A-Bomb        Male   yellow      Human    No Hair         203 Marvel C…
 2     1 Abe Sapien    Male   blue        Icthyo … No Hair         191 Dark Hor…
 3     2 Abin Sur      Male   blue        Ungaran  No Hair         185 DC Comics
 4     3 Abomination   Male   green       Human /… No Hair         203 Marvel C…
 5     4 Abraxas       Male   blue        Cosmic … Black            NA Marvel C…
 6     5 Absorbing Man Male   blue        Human    No Hair         193 Marvel C…
 7     6 Adam Monroe   Male   blue        <NA>     Blond            NA NBC - He…
 8     7 Adam Strange  Male   blue        Human    Blond           185 DC Comics
 9     8 Agent 13      Female blue        <NA>     Blond           173 Marvel C…
10     9 Agent Bob     Male   brown       Human    Brown           178 Marvel C…
# ℹ 724 more rows
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>

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

Можно создавать тибблы вручную с помощью функции tibble(), которая работает аналогично функции data.frame():

tibble(
  a = 1:3,
  b = letters[1:3]
)
# A tibble: 3 × 2
      a b    
  <int> <chr>
1     1 a    
2     2 b    
3     3 c    

10.4 magrittr::%>%

Оператор %>% называется “пайпом” (pipe), т.е. “трубой”. Он означает, что следующая функция (справа от пайпа) принимает на вход в качестве первого аргумента результат выполнения предыдущей функции (той, что слева). Фактически, это примерно то же самое, что и вставлять результат выполнения функции в качестве первого аргумента в другую функцию. Просто выглядит это красивее и читабельнее. Как будто данные пропускаются через трубы функций или конвеерную ленту на заводе, если хотите. А то, что первый параметр функции - это почти всегда данные, работает нам здесь на руку. Этот оператор взят из пакета magrittr1. Возможно, даже если вы не захотите пользоваться tidyverse, использование пайпов Вам понравится.

Важно понимать, что пайп не дает какой-то дополнительной функциональности или дополнительной скорости работы2. Он создан исключительно для читабельности и комфорта.

С помощью пайпов вот эту команду…

sum(sqrt(abs(sin(1:22))))
[1] 16.72656

…можно переписать вот так:

1:22 %>% 
  sin() %>% 
  abs() %>% 
  sqrt() %>% 
  sum()
[1] 16.72656
Время мемов

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

"Всем привет!" %>%
  c("--", ., "--")
[1] "--"           "Всем привет!" "--"          

Основные функции в tidyverse …

10.5 Главные пакеты tidyverse: dplyr и tidyr

dplyr3 — это самая основа всего tidyverse. Этот пакет предоставляет основные функции для манипуляции с тибблами. Пакет dplyr является наследником и более усовершенствованной версией plyr, так что если увидите использование пакета plyr, то, скорее всего, скрипт был написан очень давно.

Пакет tidyr дополняет dplyr, предоставляя полезные функции для тайдификации тибблов. Тайдификация (“аккуратизация”) данных означает приведение табличных данных к такому формату, в котором:

  • Каждая переменная имеет собственный столбец
  • Каждый наблюдение имеет собственную строку
  • Каждое значение имеет свою собственную ячейку

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

Пакет tidyr — это более усовершенствованная версия пакета reshape2, который в свою очередь является усовершенствованной версией reshape. По аналогии с plyr, если вы видите использование этих пакетов, то это указывает на то, что перед вами морально устаревший код.

Код с использованием dplyr и tidyrсильно непохож на то, что мы видели раньше. Большинство функций dplyr и tidyr работают с целым тибблом сразу, принимая его в качестве первого аргумента и возвращая измененный тиббл. Это позволяет превратить весь код в последовательный набор применяемых функций, соединенный пайпами. На практике это выглядит очень элегантно, и вы в этом скоро убедитесь.

10.6 Работа с колонками тиббла

10.6.1 Выбор колонок: dplyr::select()

Функция dplyr::select() позволяет выбирать колонки по номеру или имени (кавычки не нужны).

heroes %>%
  select(1,5)
# A tibble: 734 × 2
    ...1 Race             
   <dbl> <chr>            
 1     0 Human            
 2     1 Icthyo Sapien    
 3     2 Ungaran          
 4     3 Human / Radiation
 5     4 Cosmic Entity    
 6     5 Human            
 7     6 <NA>             
 8     7 Human            
 9     8 <NA>             
10     9 Human            
# ℹ 724 more rows
heroes %>%
  select(name, Race, Publisher, `Hair color`)
# A tibble: 734 × 4
   name          Race              Publisher         `Hair color`
   <chr>         <chr>             <chr>             <chr>       
 1 A-Bomb        Human             Marvel Comics     No Hair     
 2 Abe Sapien    Icthyo Sapien     Dark Horse Comics No Hair     
 3 Abin Sur      Ungaran           DC Comics         No Hair     
 4 Abomination   Human / Radiation Marvel Comics     No Hair     
 5 Abraxas       Cosmic Entity     Marvel Comics     Black       
 6 Absorbing Man Human             Marvel Comics     No Hair     
 7 Adam Monroe   <NA>              NBC - Heroes      Blond       
 8 Adam Strange  Human             DC Comics         Blond       
 9 Agent 13      <NA>              Marvel Comics     Blond       
10 Agent Bob     Human             Marvel Comics     Brown       
# ℹ 724 more rows

Обратите внимание, если в названии колонки присутствует пробел или, например, колонка начинается с цифры или точки и цифры, то это синтаксически невалидное имя (@ref(variables)). Это не значит, что такие названия колонок недопустимы. Но такие названия колонок нужно обособлять ` грависом (правый штрих, на клавиатуре находится там же где и буква ё и ~).

Еще обратите внимание на то, что функции tidyverse не изменяют сами изначальные тибблы/датафреймы. Это означает, что если вы хотите полученный результат сохранить, то нужно добавить присвоение:

heroes_some_cols <- heroes %>%
  select(name, Race, Publisher, `Hair color`)
heroes_some_cols
# A tibble: 734 × 4
   name          Race              Publisher         `Hair color`
   <chr>         <chr>             <chr>             <chr>       
 1 A-Bomb        Human             Marvel Comics     No Hair     
 2 Abe Sapien    Icthyo Sapien     Dark Horse Comics No Hair     
 3 Abin Sur      Ungaran           DC Comics         No Hair     
 4 Abomination   Human / Radiation Marvel Comics     No Hair     
 5 Abraxas       Cosmic Entity     Marvel Comics     Black       
 6 Absorbing Man Human             Marvel Comics     No Hair     
 7 Adam Monroe   <NA>              NBC - Heroes      Blond       
 8 Adam Strange  Human             DC Comics         Blond       
 9 Agent 13      <NA>              Marvel Comics     Blond       
10 Agent Bob     Human             Marvel Comics     Brown       
# ℹ 724 more rows

10.6.2 Мини-язык tidyselect для выбора колонок

Для выбора столбцов (не только в select(), но и для других функций tidyverse) используется специальный мини-язык tidyselect из одноименного пакета4. tidyselect дает очень широкие возможности для выбора колонок.

Можно использовать оператор : для выбора нескольких соседних колонок (по аналогии с созданием числового вектора с шагом 1).

heroes %>%
  select(name:Publisher)
# A tibble: 734 × 7
   name          Gender `Eye color` Race           `Hair color` Height Publisher
   <chr>         <chr>  <chr>       <chr>          <chr>         <dbl> <chr>    
 1 A-Bomb        Male   yellow      Human          No Hair         203 Marvel C…
 2 Abe Sapien    Male   blue        Icthyo Sapien  No Hair         191 Dark Hor…
 3 Abin Sur      Male   blue        Ungaran        No Hair         185 DC Comics
 4 Abomination   Male   green       Human / Radia… No Hair         203 Marvel C…
 5 Abraxas       Male   blue        Cosmic Entity  Black            NA Marvel C…
 6 Absorbing Man Male   blue        Human          No Hair         193 Marvel C…
 7 Adam Monroe   Male   blue        <NA>           Blond            NA NBC - He…
 8 Adam Strange  Male   blue        Human          Blond           185 DC Comics
 9 Agent 13      Female blue        <NA>           Blond           173 Marvel C…
10 Agent Bob     Male   brown       Human          Brown           178 Marvel C…
# ℹ 724 more rows
heroes %>%
  select(name:`Eye color`, Publisher:Weight)
# A tibble: 734 × 7
   name          Gender `Eye color` Publisher      `Skin color` Alignment Weight
   <chr>         <chr>  <chr>       <chr>          <chr>        <chr>      <dbl>
 1 A-Bomb        Male   yellow      Marvel Comics  <NA>         good         441
 2 Abe Sapien    Male   blue        Dark Horse Co… blue         good          65
 3 Abin Sur      Male   blue        DC Comics      red          good          90
 4 Abomination   Male   green       Marvel Comics  <NA>         bad          441
 5 Abraxas       Male   blue        Marvel Comics  <NA>         bad           NA
 6 Absorbing Man Male   blue        Marvel Comics  <NA>         bad          122
 7 Adam Monroe   Male   blue        NBC - Heroes   <NA>         good          NA
 8 Adam Strange  Male   blue        DC Comics      <NA>         good          88
 9 Agent 13      Female blue        Marvel Comics  <NA>         good          61
10 Agent Bob     Male   brown       Marvel Comics  <NA>         good          81
# ℹ 724 more rows

Используя ! можно вырезать ненужные колонки.

heroes %>%
  select(!...1)
# A tibble: 734 × 10
   name      Gender `Eye color` Race  `Hair color` Height Publisher `Skin color`
   <chr>     <chr>  <chr>       <chr> <chr>         <dbl> <chr>     <chr>       
 1 A-Bomb    Male   yellow      Human No Hair         203 Marvel C… <NA>        
 2 Abe Sapi… Male   blue        Icth… No Hair         191 Dark Hor… blue        
 3 Abin Sur  Male   blue        Unga… No Hair         185 DC Comics red         
 4 Abominat… Male   green       Huma… No Hair         203 Marvel C… <NA>        
 5 Abraxas   Male   blue        Cosm… Black            NA Marvel C… <NA>        
 6 Absorbin… Male   blue        Human No Hair         193 Marvel C… <NA>        
 7 Adam Mon… Male   blue        <NA>  Blond            NA NBC - He… <NA>        
 8 Adam Str… Male   blue        Human Blond           185 DC Comics <NA>        
 9 Agent 13  Female blue        <NA>  Blond           173 Marvel C… <NA>        
10 Agent Bob Male   brown       Human Brown           178 Marvel C… <NA>        
# ℹ 724 more rows
# ℹ 2 more variables: Alignment <chr>, Weight <dbl>
heroes %>%
  select(!(Gender:Height))
# A tibble: 734 × 6
    ...1 name          Publisher         `Skin color` Alignment Weight
   <dbl> <chr>         <chr>             <chr>        <chr>      <dbl>
 1     0 A-Bomb        Marvel Comics     <NA>         good         441
 2     1 Abe Sapien    Dark Horse Comics blue         good          65
 3     2 Abin Sur      DC Comics         red          good          90
 4     3 Abomination   Marvel Comics     <NA>         bad          441
 5     4 Abraxas       Marvel Comics     <NA>         bad           NA
 6     5 Absorbing Man Marvel Comics     <NA>         bad          122
 7     6 Adam Monroe   NBC - Heroes      <NA>         good          NA
 8     7 Adam Strange  DC Comics         <NA>         good          88
 9     8 Agent 13      Marvel Comics     <NA>         good          61
10     9 Agent Bob     Marvel Comics     <NA>         good          81
# ℹ 724 more rows

Другие известные нам логические операторы (& и |) тоже работают в tidyselect.

В дополнение к логическим операторам и :, в tidyselect есть набор вспомогательных функций, работающих исключительно в контексте выбора колонок с помощью tidyselect.

Вспомогательная функция last_col() позволит обратиться к последней колонке тиббла:

heroes %>%
  select(name:last_col())
# A tibble: 734 × 10
   name      Gender `Eye color` Race  `Hair color` Height Publisher `Skin color`
   <chr>     <chr>  <chr>       <chr> <chr>         <dbl> <chr>     <chr>       
 1 A-Bomb    Male   yellow      Human No Hair         203 Marvel C… <NA>        
 2 Abe Sapi… Male   blue        Icth… No Hair         191 Dark Hor… blue        
 3 Abin Sur  Male   blue        Unga… No Hair         185 DC Comics red         
 4 Abominat… Male   green       Huma… No Hair         203 Marvel C… <NA>        
 5 Abraxas   Male   blue        Cosm… Black            NA Marvel C… <NA>        
 6 Absorbin… Male   blue        Human No Hair         193 Marvel C… <NA>        
 7 Adam Mon… Male   blue        <NA>  Blond            NA NBC - He… <NA>        
 8 Adam Str… Male   blue        Human Blond           185 DC Comics <NA>        
 9 Agent 13  Female blue        <NA>  Blond           173 Marvel C… <NA>        
10 Agent Bob Male   brown       Human Brown           178 Marvel C… <NA>        
# ℹ 724 more rows
# ℹ 2 more variables: Alignment <chr>, Weight <dbl>

А функция everything() позволяет выбрать все колонки.

heroes %>%
  select(everything())
# A tibble: 734 × 11
    ...1 name          Gender `Eye color` Race     `Hair color` Height Publisher
   <dbl> <chr>         <chr>  <chr>       <chr>    <chr>         <dbl> <chr>    
 1     0 A-Bomb        Male   yellow      Human    No Hair         203 Marvel C…
 2     1 Abe Sapien    Male   blue        Icthyo … No Hair         191 Dark Hor…
 3     2 Abin Sur      Male   blue        Ungaran  No Hair         185 DC Comics
 4     3 Abomination   Male   green       Human /… No Hair         203 Marvel C…
 5     4 Abraxas       Male   blue        Cosmic … Black            NA Marvel C…
 6     5 Absorbing Man Male   blue        Human    No Hair         193 Marvel C…
 7     6 Adam Monroe   Male   blue        <NA>     Blond            NA NBC - He…
 8     7 Adam Strange  Male   blue        Human    Blond           185 DC Comics
 9     8 Agent 13      Female blue        <NA>     Blond           173 Marvel C…
10     9 Agent Bob     Male   brown       Human    Brown           178 Marvel C…
# ℹ 724 more rows
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>

При этом everything() не будет дублировать выбранные колонки, поэтому можно использовать everything() для перестановки колонок в тиббле:

heroes %>%
  select(name, Publisher, everything())
# A tibble: 734 × 11
   name          Publisher     ...1 Gender `Eye color` Race  `Hair color` Height
   <chr>         <chr>        <dbl> <chr>  <chr>       <chr> <chr>         <dbl>
 1 A-Bomb        Marvel Comi…     0 Male   yellow      Human No Hair         203
 2 Abe Sapien    Dark Horse …     1 Male   blue        Icth… No Hair         191
 3 Abin Sur      DC Comics        2 Male   blue        Unga… No Hair         185
 4 Abomination   Marvel Comi…     3 Male   green       Huma… No Hair         203
 5 Abraxas       Marvel Comi…     4 Male   blue        Cosm… Black            NA
 6 Absorbing Man Marvel Comi…     5 Male   blue        Human No Hair         193
 7 Adam Monroe   NBC - Heroes     6 Male   blue        <NA>  Blond            NA
 8 Adam Strange  DC Comics        7 Male   blue        Human Blond           185
 9 Agent 13      Marvel Comi…     8 Female blue        <NA>  Blond           173
10 Agent Bob     Marvel Comi…     9 Male   brown       Human Brown           178
# ℹ 724 more rows
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>

Впрочем, для перестановки колонок удобнее использовать специальную функцию relocate() (@ref(tidy_relocate)) Можно даже выбирать колонки по паттернам в названиях. Например, с помощью ends_with() можно выбрать все колонки, заканчивающиеся одинаковым суффиксом:

heroes %>%
  select(ends_with("color"))
# A tibble: 734 × 3
   `Eye color` `Hair color` `Skin color`
   <chr>       <chr>        <chr>       
 1 yellow      No Hair      <NA>        
 2 blue        No Hair      blue        
 3 blue        No Hair      red         
 4 green       No Hair      <NA>        
 5 blue        Black        <NA>        
 6 blue        No Hair      <NA>        
 7 blue        Blond        <NA>        
 8 blue        Blond        <NA>        
 9 blue        Blond        <NA>        
10 brown       Brown        <NA>        
# ℹ 724 more rows

Аналогично, с помощью функции starts_with() можно найти колонки с одинаковым префиксом, с помощью contains() — все колонки с выбранным паттерном в любой части названия колонки5.

heroes %>%
  select(starts_with("Eye") & ends_with("color"))
# A tibble: 734 × 1
   `Eye color`
   <chr>      
 1 yellow     
 2 blue       
 3 blue       
 4 green      
 5 blue       
 6 blue       
 7 blue       
 8 blue       
 9 blue       
10 brown      
# ℹ 724 more rows
heroes %>%
  select(contains("eight"))
# A tibble: 734 × 2
   Height Weight
    <dbl>  <dbl>
 1    203    441
 2    191     65
 3    185     90
 4    203    441
 5     NA     NA
 6    193    122
 7     NA     NA
 8    185     88
 9    173     61
10    178     81
# ℹ 724 more rows

Ну и наконец, можно выбирать по содержимому колонок с помощью where(). Это напоминает применение sapply()(@ref(apply_other)) на датафрейме для индексирования колонок: в качестве аргумента для where принимается функция, которая применяется для каждой из колонок, после чего выбираются только те колонки, для которых было получено TRUE.

heroes %>%
  select(where(is.numeric))
# A tibble: 734 × 3
    ...1 Height Weight
   <dbl>  <dbl>  <dbl>
 1     0    203    441
 2     1    191     65
 3     2    185     90
 4     3    203    441
 5     4     NA     NA
 6     5    193    122
 7     6     NA     NA
 8     7    185     88
 9     8    173     61
10     9    178     81
# ℹ 724 more rows

Функция where() дает невиданную мощь. Например, можно выбрать все колонки без NA:

heroes %>%
  select(where(function(x) !any(is.na(x))))
# A tibble: 734 × 3
    ...1 name          Publisher        
   <dbl> <chr>         <chr>            
 1     0 A-Bomb        Marvel Comics    
 2     1 Abe Sapien    Dark Horse Comics
 3     2 Abin Sur      DC Comics        
 4     3 Abomination   Marvel Comics    
 5     4 Abraxas       Marvel Comics    
 6     5 Absorbing Man Marvel Comics    
 7     6 Adam Monroe   NBC - Heroes     
 8     7 Adam Strange  DC Comics        
 9     8 Agent 13      Marvel Comics    
10     9 Agent Bob     Marvel Comics    
# ℹ 724 more rows

###Переименование колонок: dplyr::rename()

Внутри select() можно не только выбирать колонки, но и переименовывать их:

heroes %>%
  select(id = ...1)
# A tibble: 734 × 1
      id
   <dbl>
 1     0
 2     1
 3     2
 4     3
 5     4
 6     5
 7     6
 8     7
 9     8
10     9
# ℹ 724 more rows

Однако удобнее для этого использовать специальную функцию dplyr::rename(). Синтаксис у нее такой же, как и у select(), но rename() не выбрасывает колонки, которые не были упомянуты.

heroes %>%
  rename(id = ...1)
# A tibble: 734 × 11
      id name          Gender `Eye color` Race     `Hair color` Height Publisher
   <dbl> <chr>         <chr>  <chr>       <chr>    <chr>         <dbl> <chr>    
 1     0 A-Bomb        Male   yellow      Human    No Hair         203 Marvel C…
 2     1 Abe Sapien    Male   blue        Icthyo … No Hair         191 Dark Hor…
 3     2 Abin Sur      Male   blue        Ungaran  No Hair         185 DC Comics
 4     3 Abomination   Male   green       Human /… No Hair         203 Marvel C…
 5     4 Abraxas       Male   blue        Cosmic … Black            NA Marvel C…
 6     5 Absorbing Man Male   blue        Human    No Hair         193 Marvel C…
 7     6 Adam Monroe   Male   blue        <NA>     Blond            NA NBC - He…
 8     7 Adam Strange  Male   blue        Human    Blond           185 DC Comics
 9     8 Agent 13      Female blue        <NA>     Blond           173 Marvel C…
10     9 Agent Bob     Male   brown       Human    Brown           178 Marvel C…
# ℹ 724 more rows
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>

Для массового переименования колонок можно использовать функцию rename_with(). Эта функция так же использует tidyselect синтаксис для выбора колонок (по умолчанию выбираются все колонки) и применяет функцию в качестве аргумента, которая изменяет

heroes %>%
  rename_with(make.names)
# A tibble: 734 × 11
    ...1 name      Gender Eye.color Race  Hair.color Height Publisher Skin.color
   <dbl> <chr>     <chr>  <chr>     <chr> <chr>       <dbl> <chr>     <chr>     
 1     0 A-Bomb    Male   yellow    Human No Hair       203 Marvel C… <NA>      
 2     1 Abe Sapi… Male   blue      Icth… No Hair       191 Dark Hor… blue      
 3     2 Abin Sur  Male   blue      Unga… No Hair       185 DC Comics red       
 4     3 Abominat… Male   green     Huma… No Hair       203 Marvel C… <NA>      
 5     4 Abraxas   Male   blue      Cosm… Black          NA Marvel C… <NA>      
 6     5 Absorbin… Male   blue      Human No Hair       193 Marvel C… <NA>      
 7     6 Adam Mon… Male   blue      <NA>  Blond          NA NBC - He… <NA>      
 8     7 Adam Str… Male   blue      Human Blond         185 DC Comics <NA>      
 9     8 Agent 13  Female blue      <NA>  Blond         173 Marvel C… <NA>      
10     9 Agent Bob Male   brown     Human Brown         178 Marvel C… <NA>      
# ℹ 724 more rows
# ℹ 2 more variables: Alignment <chr>, Weight <dbl>

###Перестановка колонок: dplyr::relocate() {#sec-tidy_relocate}

Для изменения порядка колонок можно использовать функцию relocate(). Она тоже работает похожим образом на select() и rename()6. Как и rename(), функция relocate() не выкидывает неиспользованные колонки:

heroes %>%
  relocate(Publisher)
# A tibble: 734 × 11
   Publisher          ...1 name     Gender `Eye color` Race  `Hair color` Height
   <chr>             <dbl> <chr>    <chr>  <chr>       <chr> <chr>         <dbl>
 1 Marvel Comics         0 A-Bomb   Male   yellow      Human No Hair         203
 2 Dark Horse Comics     1 Abe Sap… Male   blue        Icth… No Hair         191
 3 DC Comics             2 Abin Sur Male   blue        Unga… No Hair         185
 4 Marvel Comics         3 Abomina… Male   green       Huma… No Hair         203
 5 Marvel Comics         4 Abraxas  Male   blue        Cosm… Black            NA
 6 Marvel Comics         5 Absorbi… Male   blue        Human No Hair         193
 7 NBC - Heroes          6 Adam Mo… Male   blue        <NA>  Blond            NA
 8 DC Comics             7 Adam St… Male   blue        Human Blond           185
 9 Marvel Comics         8 Agent 13 Female blue        <NA>  Blond           173
10 Marvel Comics         9 Agent B… Male   brown       Human Brown           178
# ℹ 724 more rows
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>

При этом relocate() имеет дополнительные параметры .after = и .before =, которые позволяют выбирать, куда поместить выбранные колонки.

heroes %>%
  relocate(Publisher, .after = name)
# A tibble: 734 × 11
    ...1 name          Publisher    Gender `Eye color` Race  `Hair color` Height
   <dbl> <chr>         <chr>        <chr>  <chr>       <chr> <chr>         <dbl>
 1     0 A-Bomb        Marvel Comi… Male   yellow      Human No Hair         203
 2     1 Abe Sapien    Dark Horse … Male   blue        Icth… No Hair         191
 3     2 Abin Sur      DC Comics    Male   blue        Unga… No Hair         185
 4     3 Abomination   Marvel Comi… Male   green       Huma… No Hair         203
 5     4 Abraxas       Marvel Comi… Male   blue        Cosm… Black            NA
 6     5 Absorbing Man Marvel Comi… Male   blue        Human No Hair         193
 7     6 Adam Monroe   NBC - Heroes Male   blue        <NA>  Blond            NA
 8     7 Adam Strange  DC Comics    Male   blue        Human Blond           185
 9     8 Agent 13      Marvel Comi… Female blue        <NA>  Blond           173
10     9 Agent Bob     Marvel Comi… Male   brown       Human Brown           178
# ℹ 724 more rows
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>

relocate() очень хорошо работает в сочетании с выбором колонок с помощью tidyselect. Например, можно передвинуть в одно место все колонки с одним типом данных:

heroes %>%
  relocate(Publisher, where(is.numeric), .after = name)
# A tibble: 734 × 11
   name      Publisher  ...1 Height Weight Gender `Eye color` Race  `Hair color`
   <chr>     <chr>     <dbl>  <dbl>  <dbl> <chr>  <chr>       <chr> <chr>       
 1 A-Bomb    Marvel C…     0    203    441 Male   yellow      Human No Hair     
 2 Abe Sapi… Dark Hor…     1    191     65 Male   blue        Icth… No Hair     
 3 Abin Sur  DC Comics     2    185     90 Male   blue        Unga… No Hair     
 4 Abominat… Marvel C…     3    203    441 Male   green       Huma… No Hair     
 5 Abraxas   Marvel C…     4     NA     NA Male   blue        Cosm… Black       
 6 Absorbin… Marvel C…     5    193    122 Male   blue        Human No Hair     
 7 Adam Mon… NBC - He…     6     NA     NA Male   blue        <NA>  Blond       
 8 Adam Str… DC Comics     7    185     88 Male   blue        Human Blond       
 9 Agent 13  Marvel C…     8    173     61 Female blue        <NA>  Blond       
10 Agent Bob Marvel C…     9    178     81 Male   brown       Human Brown       
# ℹ 724 more rows
# ℹ 2 more variables: `Skin color` <chr>, Alignment <chr>

Последняя важная функция для выбора колонок — pull(). Эта функция делает то же самое, что и индексирование с помощью $, т.е. вытаскивает из тиббла вектор с выбранным названием. Это лучше вписывается в логику tidyverse, поскольку позволяет извлечь колонку из тиббла с использованием пайпа:

heroes %>%
  select(Height) %>%
  pull() %>%
  head()
[1] 203 191 185 203  NA 193
heroes %>%
  pull(Height) %>%
  head()
[1] 203 191 185 203  NA 193

У функции pull() есть аргумент name =, который позволяет создать проименованный вектор:

heroes %>%
  pull(Height, name) %>%
  head()
       A-Bomb    Abe Sapien      Abin Sur   Abomination       Abraxas 
          203           191           185           203            NA 
Absorbing Man 
          193 

В отличие от базового R, tidyverse нигде не сокращает имплицитно результат вычислений до вектора, поэтому функция pull() - это основной способ извлечения колонки из тиббла как вектора.

10.7 Работа со строками тиббла

10.7.1 Выбор строк по номеру: dplyr::slice()

Начнем с выбора строк. Функция dplyr::slice() выбирает строчки по их числовому индексу.

heroes %>%
  slice(1:3)
# A tibble: 3 × 11
   ...1 name       Gender `Eye color` Race         `Hair color` Height Publisher
  <dbl> <chr>      <chr>  <chr>       <chr>        <chr>         <dbl> <chr>    
1     0 A-Bomb     Male   yellow      Human        No Hair         203 Marvel C…
2     1 Abe Sapien Male   blue        Icthyo Sapi… No Hair         191 Dark Hor…
3     2 Abin Sur   Male   blue        Ungaran      No Hair         185 DC Comics
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>

10.7.2 Выбор строк по условию: dplyr::filter()

Функция dplyr::filter() делает то же самое, что и slice(), но уже по условию. Причем для условий нужно использовать не векторы из тиббла, а название колонок (без кавычек) как будто бы они были переменными в окружении.

heroes %>% 
  filter(Publisher == "DC Comics")
# A tibble: 215 × 11
    ...1 name             Gender `Eye color` Race  `Hair color` Height Publisher
   <dbl> <chr>            <chr>  <chr>       <chr> <chr>         <dbl> <chr>    
 1     2 Abin Sur         Male   blue        Unga… No Hair         185 DC Comics
 2     7 Adam Strange     Male   blue        Human Blond           185 DC Comics
 3    13 Alan Scott       Male   blue        <NA>  Blond           180 DC Comics
 4    16 Alfred Pennywor… Male   blue        Human Black           178 DC Comics
 5    19 Amazo            Male   red         Andr… <NA>            257 DC Comics
 6    27 Animal Man       Male   blue        Human Blond           183 DC Comics
 7    31 Anti-Monitor     Male   yellow      God … No Hair          61 DC Comics
 8    35 Aquababy         Male   blue        <NA>  Blond            NA DC Comics
 9    36 Aqualad          Male   blue        Atla… Black           178 DC Comics
10    37 Aquaman          Male   blue        Atla… Blond           185 DC Comics
# ℹ 205 more rows
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>

10.7.3 Семейство функций slice()

У функции slice() есть множество родственников, которые объединяют функционал обычного slice() и filter(). Например, с помощью функций dplyr::slice_max() и dplyr::slice_min() можно выбрать заданное количество строк, содержащих наибольшие или наименьшие значения по колонке соответственно:

heroes %>%
  slice_max(Weight, n = 3)
# A tibble: 3 × 11
   ...1 name       Gender `Eye color` Race    `Hair color` Height Publisher    
  <dbl> <chr>      <chr>  <chr>       <chr>   <chr>         <dbl> <chr>        
1   575 Sasquatch  Male   red         <NA>    Orange          305 Marvel Comics
2   373 Juggernaut Male   blue        Human   Red             287 Marvel Comics
3   203 Darkseid   Male   red         New God No Hair         267 DC Comics    
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>
heroes %>%
  slice_min(Weight, n = 3)
# A tibble: 3 × 11
   ...1 name        Gender `Eye color` Race        `Hair color` Height Publisher
  <dbl> <chr>       <chr>  <chr>       <chr>       <chr>         <dbl> <chr>    
1   346 Iron Monger Male   blue        <NA>        No Hair          NA Marvel C…
2   302 Groot       Male   yellow      Flora Colo… <NA>            701 Marvel C…
3   350 Jack-Jack   Male   blue        Human       Brown            71 Dark Hor…
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>

Функция slice_sample() позволяет выбирать заданное количество случайных строчек:

heroes %>%
  slice_sample(n = 3)
# A tibble: 3 × 11
   ...1 name        Gender `Eye color` Race   `Hair color` Height Publisher    
  <dbl> <chr>       <chr>  <chr>       <chr>  <chr>         <dbl> <chr>        
1   229 Doomsday    Male   red         Alien  White           244 DC Comics    
2   275 Garbage Man Male   <NA>        Mutant <NA>             NA DC Comics    
3   162 Cat         Female blue        <NA>   Blond           173 Marvel Comics
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>

Или же долю строчек:

heroes %>%
  slice_sample(prop = .01)
# A tibble: 7 × 11
   ...1 name         Gender `Eye color` Race       `Hair color` Height Publisher
  <dbl> <chr>        <chr>  <chr>       <chr>      <chr>         <dbl> <chr>    
1   424 Magog        Male   blue        <NA>       Blond            NA DC Comics
2   512 Penance I    Female <NA>        <NA>       <NA>             NA Marvel C…
3     3 Abomination  Male   green       Human / R… No Hair         203 Marvel C…
4   494 Nite Owl II  Male   <NA>        <NA>       <NA>             NA DC Comics
5   122 Blue Beetle  Male   blue        <NA>       Brown            NA DC Comics
6   470 Moon Knight  Male   brown       Human      Brown           188 Marvel C…
7   685 Valerie Hart Female hazel       <NA>       Black           175 Team Epi…
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>

Если поставить значение параметра prop = равным 1, то таким образом можно перемешать порядок строчек в тиббле:

heroes %>%
  slice_sample(prop = 1)
# A tibble: 734 × 11
    ...1 name             Gender `Eye color` Race  `Hair color` Height Publisher
   <dbl> <chr>            <chr>  <chr>       <chr> <chr>         <dbl> <chr>    
 1   138 Brundlefly       Male   <NA>        Muta… <NA>            193 ""       
 2   520 Plastic Lad      Male   <NA>        <NA>  <NA>             NA "DC Comi…
 3   105 Black Panther    Male   brown       Human Black           183 "Marvel …
 4   628 Spider-Woman IV  Female red         <NA>  White           178 "Marvel …
 5    49 Atom             Male   blue        <NA>  Red             178 "DC Comi…
 6   201 Darkhawk         Male   brown       Human Brown           185 "Marvel …
 7   259 Firestorm        Male   brown       <NA>  Black            NA "DC Comi…
 8   595 Shrinking Violet Female <NA>        <NA>  <NA>             NA "DC Comi…
 9    15 Alex Woolsly     Male   <NA>        <NA>  <NA>             NA "NBC - H…
10   127 Bolt             Male   <NA>        <NA>  <NA>             NA "Marvel …
# ℹ 724 more rows
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>

10.7.4 Удаление строчек с NA: tidyr::drop_na()

Если нужно выбрать только строчки без пропущенных значений, то можно воспользоваться удобной функцией tidyr::drop_na().

heroes %>%
  drop_na()
# A tibble: 50 × 11
    ...1 name       Gender `Eye color` Race        `Hair color` Height Publisher
   <dbl> <chr>      <chr>  <chr>       <chr>       <chr>         <dbl> <chr>    
 1     1 Abe Sapien Male   blue        Icthyo Sap… No Hair         191 Dark Hor…
 2     2 Abin Sur   Male   blue        Ungaran     No Hair         185 DC Comics
 3    34 Apocalypse Male   red         Mutant      Black           213 Marvel C…
 4    39 Archangel  Male   blue        Mutant      Blond           183 Marvel C…
 5    41 Ardina     Female white       Alien       Orange          193 Marvel C…
 6    56 Azazel     Male   yellow      Neyaphem    Black           183 Marvel C…
 7    74 Beast      Male   blue        Mutant      Blue            180 Marvel C…
 8    75 Beast Boy  Male   green       Human       Green           173 DC Comics
 9    92 Bizarro    Male   black       Bizarro     Black           191 DC Comics
10   108 Blackout   Male   red         Demon       White           191 Marvel C…
# ℹ 40 more rows
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>

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

heroes %>%
  drop_na(Weight)
# A tibble: 495 × 11
    ...1 name          Gender `Eye color` Race     `Hair color` Height Publisher
   <dbl> <chr>         <chr>  <chr>       <chr>    <chr>         <dbl> <chr>    
 1     0 A-Bomb        Male   yellow      Human    No Hair         203 Marvel C…
 2     1 Abe Sapien    Male   blue        Icthyo … No Hair         191 Dark Hor…
 3     2 Abin Sur      Male   blue        Ungaran  No Hair         185 DC Comics
 4     3 Abomination   Male   green       Human /… No Hair         203 Marvel C…
 5     5 Absorbing Man Male   blue        Human    No Hair         193 Marvel C…
 6     7 Adam Strange  Male   blue        Human    Blond           185 DC Comics
 7     8 Agent 13      Female blue        <NA>     Blond           173 Marvel C…
 8     9 Agent Bob     Male   brown       Human    Brown           178 Marvel C…
 9    10 Agent Zero    Male   <NA>        <NA>     <NA>            191 Marvel C…
10    11 Air-Walker    Male   blue        <NA>     White           188 Marvel C…
# ℹ 485 more rows
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>

Для выбора колонок в drop_na() используется tidyselect, с которым мы недавно познакомились (@ref(tidyselect)).

10.7.5 Сортировка строк: dplyr::arrange()

Функция dplyr::arrange() сортирует строчки от меньшего к большему (или по алфавиту - для текстовых значений) по выбранной колонке.

heroes %>%
  arrange(Weight)
# A tibble: 734 × 11
    ...1 name            Gender `Eye color` Race   `Hair color` Height Publisher
   <dbl> <chr>           <chr>  <chr>       <chr>  <chr>         <dbl> <chr>    
 1   346 Iron Monger     Male   blue        <NA>   No Hair          NA Marvel C…
 2   302 Groot           Male   yellow      Flora… <NA>            701 Marvel C…
 3   350 Jack-Jack       Male   blue        Human  Brown            71 Dark Hor…
 4   272 Galactus        Male   black       Cosmi… Black           876 Marvel C…
 5   731 Yoda            Male   brown       Yoda'… White            66 George L…
 6   255 Fin Fang Foom   Male   red         Kakar… No Hair         975 Marvel C…
 7   330 Howard the Duck Male   brown       <NA>   Yellow           79 Marvel C…
 8   396 Krypto          Male   blue        Krypt… White            64 DC Comics
 9   568 Rocket Raccoon  Male   brown       Animal Brown           122 Marvel C…
10   208 Dash            Male   blue        Human  Blond           122 Dark Hor…
# ℹ 724 more rows
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>

Чтобы отсортировать в обратном порядке, воспользуйтесь функцией desc().

heroes %>%
  arrange(desc(Weight))
# A tibble: 734 × 11
    ...1 name       Gender `Eye color` Race        `Hair color` Height Publisher
   <dbl> <chr>      <chr>  <chr>       <chr>       <chr>         <dbl> <chr>    
 1   575 Sasquatch  Male   red         <NA>        Orange        305   Marvel C…
 2   373 Juggernaut Male   blue        Human       Red           287   Marvel C…
 3   203 Darkseid   Male   red         New God     No Hair       267   DC Comics
 4   283 Giganta    Female green       <NA>        Red            62.5 DC Comics
 5   331 Hulk       Male   green       Human / Ra… Green         244   Marvel C…
 6   549 Red Hulk   Male   yellow      Human / Ra… Black         213   Marvel C…
 7   119 Bloodaxe   Female blue        Human       Brown         218   Marvel C…
 8   718 Wolfsbane  Female green       <NA>        Auburn        366   Marvel C…
 9   657 Thanos     Male   red         Eternal     No Hair       201   Marvel C…
10     0 A-Bomb     Male   yellow      Human       No Hair       203   Marvel C…
# ℹ 724 more rows
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>

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

heroes %>%
  arrange(Gender, desc(Weight))
# A tibble: 734 × 11
    ...1 name      Gender `Eye color` Race         `Hair color` Height Publisher
   <dbl> <chr>     <chr>  <chr>       <chr>        <chr>         <dbl> <chr>    
 1   283 Giganta   Female green       <NA>         Red            62.5 DC Comics
 2   119 Bloodaxe  Female blue        Human        Brown         218   Marvel C…
 3   718 Wolfsbane Female green       <NA>         Auburn        366   Marvel C…
 4   591 She-Hulk  Female green       Human        Green         201   Marvel C…
 5   320 Hela      Female green       Asgardian    Black         213   Marvel C…
 6   686 Valkyrie  Female blue        <NA>         Blond         191   Marvel C…
 7   596 Sif       Female blue        Asgardian    Black         188   Marvel C…
 8   271 Frigga    Female blue        <NA>         White         180   Marvel C…
 9   667 Thundra   Female green       <NA>         Red           218   Marvel C…
10   592 She-Thing Female blue        Human / Rad… No Hair       183   Marvel C…
# ℹ 724 more rows
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>

10.8 Создание колонок: dplyr::mutate() и dplyr::transmute()

Функция dplyr::mutate() позволяет создавать новые колонки в тиббле.

heroes %>%
  mutate(imt = Weight/(Height/100)^2) %>%
  select(name, imt) %>%
  arrange(desc(imt))
# A tibble: 734 × 2
   name          imt
   <chr>       <dbl>
 1 Utgard-Loki 2510.
 2 Giganta     1613.
 3 Red Hulk     139.
 4 Darkseid     115.
 5 Machine Man  114.
 6 Thanos       110.
 7 Destroyer    108.
 8 A-Bomb       107.
 9 Abomination  107.
10 Hulk         106.
# ℹ 724 more rows

dplyr::transmute() - это аналог mutate(), который не только создает новые колонки, но и сразу же выкидывает все старые:

heroes %>%
  transmute(imt = Weight/(Height/100)^2)
# A tibble: 734 × 1
     imt
   <dbl>
 1 107. 
 2  17.8
 3  26.3
 4 107. 
 5  NA  
 6  32.8
 7  NA  
 8  25.7
 9  20.4
10  25.6
# ℹ 724 more rows

Внутри mutate() и transmute() мы можем использовать либо векторизованные операции (длина новой колонки должна равняться длине датафрейма), либо операции, которые возвращают одно значение. В последнем случае значение будет одинаковым на всю колонку, т.е. будет работать правило ресайклинга (@ref(recycling)):

heroes %>%
  transmute(name, weight_mean = mean(Weight, na.rm = TRUE))
# A tibble: 734 × 2
   name          weight_mean
   <chr>               <dbl>
 1 A-Bomb               112.
 2 Abe Sapien           112.
 3 Abin Sur             112.
 4 Abomination          112.
 5 Abraxas              112.
 6 Absorbing Man        112.
 7 Adam Monroe          112.
 8 Adam Strange         112.
 9 Agent 13             112.
10 Agent Bob            112.
# ℹ 724 more rows

Однако в функциях mutate() и transmute() правило ресайклинга не будет работать в остальных случаях: если полученный вектор будет не равен 1 или длине датафрейма, то мы получим ошибку.

heroes %>%
  mutate(one_and_two = 1:2)
Error in `mutate()`:
ℹ In argument: `one_and_two = 1:2`.
Caused by error:
! `one_and_two` must be size 734 or 1, not 2.

Это не баг, а фича: авторы пакета dplyr считают, что ресайклинг кратных друг другу векторов — это слишком удобное место для выстрелов себе в ногу. Поэтому в таких случаях разработчики dplyr рекомендуют использовать функцию rep(), знакомую нам уже очень давно (@ref(atomic)).

heroes %>%
  mutate(one_and_two = rep(1:2, length.out = nrow(.)))
# A tibble: 734 × 12
    ...1 name          Gender `Eye color` Race     `Hair color` Height Publisher
   <dbl> <chr>         <chr>  <chr>       <chr>    <chr>         <dbl> <chr>    
 1     0 A-Bomb        Male   yellow      Human    No Hair         203 Marvel C…
 2     1 Abe Sapien    Male   blue        Icthyo … No Hair         191 Dark Hor…
 3     2 Abin Sur      Male   blue        Ungaran  No Hair         185 DC Comics
 4     3 Abomination   Male   green       Human /… No Hair         203 Marvel C…
 5     4 Abraxas       Male   blue        Cosmic … Black            NA Marvel C…
 6     5 Absorbing Man Male   blue        Human    No Hair         193 Marvel C…
 7     6 Adam Monroe   Male   blue        <NA>     Blond            NA NBC - He…
 8     7 Adam Strange  Male   blue        Human    Blond           185 DC Comics
 9     8 Agent 13      Female blue        <NA>     Blond           173 Marvel C…
10     9 Agent Bob     Male   brown       Human    Brown           178 Marvel C…
# ℹ 724 more rows
# ℹ 4 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>,
#   one_and_two <int>

10.9 Агрегация данных в тиббле

10.9.1 Подытоживание: summarise()

Аггрегация по группам - это очень часто возникающая задача, например, это может использоваться для усреднения данных по испытуемым или условиям. Сделать аггрегацию в датафрейме удобной Хэдли Уикхэм пытался еще в предшественнике dplyr, пакете plyr. dplyr позволяет делать аггрегацию очень симпатичным и понятным способым. Аггрегация в dplyr состоит из двух этапов: группировки (group_by()) и подытоживания (summarise()). Начнем с последнего.

Функция dplyr::summarise()7 позволяет аггрегировать данные в тиббле. Работает она очень похоже на mutate(), но если внутри mutate() используются векторизованные функции, возвращающие вектор такой же длины, что и колонки, использовавшиеся для расчетов, то в summarise() используются функции, которые возвращают вектор длиной 1. Например, min(), mean(), max() и т.д. Можно создавать несколько колонок через запятую (это работает и для mutate()).

heroes %>%
  mutate(imt = Weight/(Height/100)^2) %>%
  summarise(min(imt, na.rm = TRUE),
            max(imt, na.rm = TRUE))
# A tibble: 1 × 2
  `min(imt, na.rm = TRUE)` `max(imt, na.rm = TRUE)`
                     <dbl>                    <dbl>
1                   0.0814                    2510.

В dplyr есть дополнительные суммирующие функции для более удобного индексирования в стиле tidyverse. Например, функции dplyr::nth(), dplyr::first() и dplyr::last(), которые позволяют вытаскивать значения из вектора по индексу (что-то вроде slice(), но для векторов)

heroes %>%
  mutate(imt = Weight/(Height/100)^2) %>%
  arrange(imt) %>%
  summarise(first = first(imt),
            tenth = nth(imt, 10),
            last = last(imt))
# A tibble: 1 × 3
   first tenth  last
   <dbl> <dbl> <dbl>
1 0.0814  16.7    NA

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

heroes %>%
  mutate(imt = Weight/(Height/100)^2) %>%
  summarise(imt_range = range(imt, na.rm = TRUE)) #функция range() возвращает вектор из двух значений: минимальное и максимальное
Warning: Returning more (or less) than 1 row per `summarise()` group was deprecated in
dplyr 1.1.0.
ℹ Please use `reframe()` instead.
ℹ When switching from `summarise()` to `reframe()`, remember that `reframe()`
  always returns an ungrouped data frame and adjust accordingly.
# A tibble: 2 × 1
  imt_range
      <dbl>
1    0.0814
2 2510.    

10.9.2 Группировка: group_by()

dplyr::group_by() - это функция для группировки данных в тиббле по дискретной переменной для дальнейшей аггрегации с помощью summarise(). После применения group_by() тиббл будет выглядеть так же, но у него появятся атрибут groups8:

heroes %>%
  group_by(Gender)
# A tibble: 734 × 11
# Groups:   Gender [3]
    ...1 name          Gender `Eye color` Race     `Hair color` Height Publisher
   <dbl> <chr>         <chr>  <chr>       <chr>    <chr>         <dbl> <chr>    
 1     0 A-Bomb        Male   yellow      Human    No Hair         203 Marvel C…
 2     1 Abe Sapien    Male   blue        Icthyo … No Hair         191 Dark Hor…
 3     2 Abin Sur      Male   blue        Ungaran  No Hair         185 DC Comics
 4     3 Abomination   Male   green       Human /… No Hair         203 Marvel C…
 5     4 Abraxas       Male   blue        Cosmic … Black            NA Marvel C…
 6     5 Absorbing Man Male   blue        Human    No Hair         193 Marvel C…
 7     6 Adam Monroe   Male   blue        <NA>     Blond            NA NBC - He…
 8     7 Adam Strange  Male   blue        Human    Blond           185 DC Comics
 9     8 Agent 13      Female blue        <NA>     Blond           173 Marvel C…
10     9 Agent Bob     Male   brown       Human    Brown           178 Marvel C…
# ℹ 724 more rows
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>

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

heroes %>%
  mutate(imt = Weight/(Height/100)^2) %>%
  group_by(Gender) %>%
  summarise(min(imt, na.rm = TRUE),
            max(imt, na.rm = TRUE))
# A tibble: 3 × 3
  Gender `min(imt, na.rm = TRUE)` `max(imt, na.rm = TRUE)`
  <chr>                     <dbl>                    <dbl>
1 Female                  15.5                       1613.
2 Male                     0.0814                    2510.
3 <NA>                    16.3                        114.

Схематически это выглядит вот так:

10.9.3 Подсчет строк: dplyr::n(), dplyr::count()

Для подсчет количества значений можно воспользоваться функцией n().

heroes %>%
  group_by(Gender) %>%
  summarise(n = n())
# A tibble: 3 × 2
  Gender     n
  <chr>  <int>
1 Female   200
2 Male     505
3 <NA>      29

Функция n() вместе с group_by() внутри filter() позволяет удобным образом “отрезать” от тиббла редкие группы…

heroes %>%
  group_by(Race) %>%
  filter(n() > 10) %>%
  select(name, Race)
# A tibble: 611 × 2
# Groups:   Race [6]
   name          Race             
   <chr>         <chr>            
 1 A-Bomb        Human            
 2 Abomination   Human / Radiation
 3 Absorbing Man Human            
 4 Adam Monroe   <NA>             
 5 Adam Strange  Human            
 6 Agent 13      <NA>             
 7 Agent Bob     Human            
 8 Agent Zero    <NA>             
 9 Air-Walker    <NA>             
10 Ajax          Cyborg           
# ℹ 601 more rows

или же наоборот, выделить только маленькие группы:

heroes %>%
  group_by(Race) %>%
  filter(n() == 1) %>%
  select(name, Race)
# A tibble: 34 × 2
# Groups:   Race [34]
   name          Race              
   <chr>         <chr>             
 1 Abe Sapien    Icthyo Sapien     
 2 Abin Sur      Ungaran           
 3 Alien         Xenomorph XX121   
 4 Azazel        Neyaphem          
 5 Bizarro       Bizarro           
 6 Boba Fett     Human / Clone     
 7 Darth Maul    Dathomirian Zabrak
 8 Fin Fang Foom Kakarantharaian   
 9 Gamora        Zen-Whoberian     
10 Gladiator     Strontian         
# ℹ 24 more rows

Таблицу частот можно создать без group_by() и summarise(n = n()). Функция count() заменяет эту конструкцию:

heroes %>%
  count(Gender)
# A tibble: 3 × 2
  Gender     n
  <chr>  <int>
1 Female   200
2 Male     505
3 <NA>      29

Эту таблицу частот удобно сразу проранжировать, указав в параметре sort = значение TRUE.

heroes %>%
  count(Gender, sort = TRUE)
# A tibble: 3 × 2
  Gender     n
  <chr>  <int>
1 Male     505
2 Female   200
3 <NA>      29

Функция count(), несмотря на свою простоту, является одной из наиболее используемых в tidyverse.

10.9.4 Уникальные значения: dplyr::distinct()

dplyr::distinct() - это более быстрый аналог unique(), позволяет извлекать уникальные значения для одной или нескольких колонок.

heroes %>%
  distinct(Gender)
# A tibble: 3 × 1
  Gender
  <chr> 
1 Male  
2 Female
3 <NA>  
heroes %>%
  distinct(Gender, Race)
# A tibble: 81 × 2
   Gender Race             
   <chr>  <chr>            
 1 Male   Human            
 2 Male   Icthyo Sapien    
 3 Male   Ungaran          
 4 Male   Human / Radiation
 5 Male   Cosmic Entity    
 6 Male   <NA>             
 7 Female <NA>             
 8 Male   Cyborg           
 9 Male   Xenomorph XX121  
10 Male   Android          
# ℹ 71 more rows

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

10.9.5 Создание колонок с группировкой

В tidyverse это можно сделать с помощью сочетания group_by() и mutate() (вместо summarise()):

heroes %>%
  group_by(Race) %>%
  mutate(Race_n = n()) %>%
  select(Race, name, Gender, Race_n)
# A tibble: 734 × 4
# Groups:   Race [62]
   Race              name          Gender Race_n
   <chr>             <chr>         <chr>   <int>
 1 Human             A-Bomb        Male      208
 2 Icthyo Sapien     Abe Sapien    Male        1
 3 Ungaran           Abin Sur      Male        1
 4 Human / Radiation Abomination   Male       11
 5 Cosmic Entity     Abraxas       Male        4
 6 Human             Absorbing Man Male      208
 7 <NA>              Adam Monroe   Male      304
 8 Human             Adam Strange  Male      208
 9 <NA>              Agent 13      Female    304
10 Human             Agent Bob     Male      208
# ℹ 724 more rows

Результаты аггрегации были записаны в отдельную колонку, при этом значения этой колонки внутри одной группы повторяются:

10.10 Заключение

Мы познакомились с основными принципами и функциями tidyverse. Этих функций, как можно заметить, очень, очень много, каждая из которых посвящена отдельному действию для работы с датафреймами. Чтобы не запутаться во всем многообразии, нужно запомнить самые основные из них:

  • select()` – для выбора строк,

  • filter() – для выбора строк по условию,

  • mutate() – для создания новых колонок,

  • group_by() и summarise() для аггрегации данных.

Эти функции достаточно гибкие, чтобы их хватало для большинства задач работы с данными. Однако важный принципп tidyverse – это максимальная эксплицитность: если вам нужно, скажем, поменять местами колонки, вы используете специальную функцию relocate(), которая предназначено специально для этой задачи.

Такой код очень легко читается, да и тому кто использует tidyverse для написания кода позволяет мыслить более четко.


  1. Если быть точным, то оператор %>% был импортирован во все основные пакеты tidyverse, а сам пакет magrittr не входит в набор базовых пакетов tidyverse. Тем не менее, в самом magrittr есть еще несколько интересных операторов.↩︎

  2. Даже наоборот, использование пайпов незначительно снижает скорость выполнения команды.↩︎

  3. Есть споры о том, как это правильно читать. Используемые варианты: “диплаер”,”диплюр”, “диплир”, ”диплёр”, ”дипилюр”. Правильный вариант все-таки”диплаер”: пакет {dplyr} – это результат развития идей, заложенных в старом пакете Хэдли Уикхэма {plyr}, где ply означает apPLY. {plyr} был попыткой развить идею функций семейства *apply(). Хэдли Уикхэм↩︎

  4. Как и в случае с magrittr, пакет tidyselect не содержатся в базовом tidyverse, но функции импортируются основыми пакетами tidyverse.↩︎

  5. Выбранный паттерн будет найден посимвольно, если же вы хотите искать по регулярным выражениям, то вместо contains() нужно использовать matches().↩︎

  6. relocate() не позволяет переименовывать колонки в отличие от select() и rename()↩︎

  7. У функции dplyr::summarise() есть синоним dplyr::summarize(), которая делает абсолбтно то же самое. Просто потому что в американском английском и британском английском это слово пишется по-разному.↩︎

  8. Снять группировку можно с помощью функции ungroup().↩︎