2 Маніпуляції з даними за допомогою dplyr і не тільки


В минулій главі ми розібрали основні принципи мови програмування R. І тут слід зазначити, що окрім базового синтаксису існують й інші. Загалом, можна виділити три основні діалекти в мові програмування R:

  • base: основний фундамент мови, який ми розібрали (але не повністю) раніше.

  • Tidyverse: окремий напрямок розвитку мови програмування R, що сконцентрований у напрямку науки про дані (data science).

  • data.table: альтернативний напрямок, який дозволяє оброблювати об’ємні масиви даних за рекордний час “Database-Like Ops Benchmark” (2022).

Їх можна сміло поєднувати в своїх проектах, що значним чином підвищує ефективність та швидкість роботи.

2.1 Tidy-всесвіт


Tidyverse — це екосистема, набір пакетів, які спеціально створені для науки про дані (data science). В ньому є ключові пакети (ядро tidyverse) та побічні — які додатково розширюють можливості мови програмування R.

Концепція охайних даних (tidy-data) передбачає приведення даних до формату, в якому:

  • Кожна змінна міститься в окремому стовпчику

  • Кожне спостереження міститься в окремому рядку

  • Кожне значення міститься в окремій комірці

Ядро tidyverse:

  • ggplot2, для візуалізації

  • dplyr, для маніпуляції з даними

  • tidyr, для отримання охайних даних (tidy data)

  • readr, для зчитування та записування файлів в R

  • purrr, для функціонального програмування

  • tibble, для роботи з тібблами (tibble), просунутий варіант дата фреймів

  • stringr, для роботи з текстовими даними

  • forcats, для роботи з факторами (factors)

Крім того є ще низка допоміжних пакетів, які не входять до ядра tidyverse але вважаються його частиною:

  • vroom, для швидкого завантаження даних

  • DBI, для роботи з базами даних

  • haven, для даних SPSS, Stata та SAS

  • httr, для роботи з API

  • readxl для завантаження .xls та .xlsx файлів

  • googlesheets4, для роботи з Google Sheet

  • googledrive, для роботи з Google Drive

  • rvest, для скрапінгу веб-сторінок

  • jsonlite, для роботи з JSON-файлами

  • xml2, для роботи з XML

  • lubridate, для роботи з датами

  • dbplyr, для перетворення коду dplyr в SQL

  • dtplyr, для перетворення коду на data.table

  • magrittr, для використання конвеєрів %>% (pipe)

  • glue, для поєднання даних та тексту

  • tidymodels, для роботи з моделями машнинного навчання.

І це ще не повний список. Крім офіційних пакетів tidyverse є ще низка пакетів, які намагаються відповідати принципам tidyverse і доповнюють його.

Головним чином, для роботи з даними, я зосереджу свою увагу на роботі з пакетом dplyr.

Для завантаження tidyverse необхідно виконати наступний код:

install.packages("tidyverse")

Для підключення:

Концепція “охайних” даних передбачає альтернативу класичним data.frame у вигляді тібблів (tibble). Давайте розеберемо основі відмінності. В мові програмування R є вбудований популярний датасет iris. Він зберігається в форматі дата фрейму.

# Переглянемо перші декілька значень
head(iris)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1          5.1         3.5          1.4         0.2  setosa
## 2          4.9         3.0          1.4         0.2  setosa
## 3          4.7         3.2          1.3         0.2  setosa
## 4          4.6         3.1          1.5         0.2  setosa
## 5          5.0         3.6          1.4         0.2  setosa
## 6          5.4         3.9          1.7         0.4  setosa

Давайте створемо його альтернативу у вигляді тібблу:

iris_tbl <- as_tibble(iris)
iris_tbl
## # A tibble: 150 x 5
##    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
##           <dbl>       <dbl>        <dbl>       <dbl> <fct>  
##  1          5.1         3.5          1.4         0.2 setosa 
##  2          4.9         3            1.4         0.2 setosa 
##  3          4.7         3.2          1.3         0.2 setosa 
##  4          4.6         3.1          1.5         0.2 setosa 
##  5          5           3.6          1.4         0.2 setosa 
##  6          5.4         3.9          1.7         0.4 setosa 
##  7          4.6         3.4          1.4         0.3 setosa 
##  8          5           3.4          1.5         0.2 setosa 
##  9          4.4         2.9          1.4         0.2 setosa 
## 10          4.9         3.1          1.5         0.1 setosa 
## # ... with 140 more rows

Вже на цьому етапі видні основні відмінності: тіббл відображає одразу перші 10 спостережень і всі ствопчики, які поміщаються на екрані. Крім того до кожного стовпчика вказується його тип. Це дуже зручно, особливо коли працюєте з великим об’ємом даних.

2.2 Завантаження даних


2.2.1 Завантаження .csv, .tsv файлів

Стандартною функцією завантаження даних типу .csv є функція read.csv(), але на досить великих масивах даних краще використовувати read_csv() з пакету readr. Синтаксис цих функцій схожий, але read_csv() одразу приведе дані до формату tibble. Першим аргументом функції є шлях до файлу (із оберненим слешем /). Також можна використовувати прямі URL-посилання на файл:

read_file <- read_csv("docs/data/file.csv")
read_url <- read_csv("https://git.io/JztOr")

Аналогічно до read_csv() можна використовувати функцію vroom з однойменного пакету. Головною особливістю цього пакету є швидкість завантаження даних.

vroom_file <- vroom("docs/data/file.csv")
vroom_url <- vroom("https://git.io/JztOr")

Для завантаження одночасно декількох файлів однакової структури корисно використовувати наступну конструкцію

filse <- dir(pattern = "\\.csv$")
vroom_all <- vroom(filse)

2.2.2 Завантаження .xls, .xlsx файлів

Для завантаження файлів Excel використовується пакет readxl та функція read_excel(). На початку можна отримати перелік листів файлу Excel за допомогою функції excel_sheets()

readxl::excel_sheets("docs/data/tourism.xlsx")
## [1] "Sheet1" "Sheet2" "Sheet3"

Після чого зчитати данні з потрібного листа

excel_file <- read_excel("docs/data/tourism.xlsx", sheet = "Sheet1")

В більшості випадків цього інструментарію має бути достатньо, але для завантаження специфічних файлів завжди можна знайти потрібний пакет. Не соромтеся використовувати google.

2.3 Маніпуляції з даними за допомогою пакету dplyr


dplyr - це граматика маніпуляції з даними, яка має низку функцій, які допоможуть легко та зручно маніпулювати даними, наприклад:

  • створювати нові змінні

  • сортувати дані

  • проводити фільтрацію даних

  • агрегування даних і багато іншого.

В якості прикладу роботи з пакетом dplyr пропоную використати датасет gapminder з однойменного пакету. В ньому збережена інформація про ВВП, очікувану тривалість життя при народженні та населення для 142 країн світу з 1952 по 2007 роки.

# Підключаємо пакет (не забудьте його встановити) та подивимось на датасет
library(gapminder)
gapminder
## # A tibble: 1,704 x 6
##    country     continent  year lifeExp      pop gdpPercap
##    <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
##  1 Afghanistan Asia       1952    28.8  8425333      779.
##  2 Afghanistan Asia       1957    30.3  9240934      821.
##  3 Afghanistan Asia       1962    32.0 10267083      853.
##  4 Afghanistan Asia       1967    34.0 11537966      836.
##  5 Afghanistan Asia       1972    36.1 13079460      740.
##  6 Afghanistan Asia       1977    38.4 14880372      786.
##  7 Afghanistan Asia       1982    39.9 12881816      978.
##  8 Afghanistan Asia       1987    40.8 13867957      852.
##  9 Afghanistan Asia       1992    41.7 16317921      649.
## 10 Afghanistan Asia       1997    41.8 22227415      635.
## # ... with 1,694 more rows

Видно, що змінні country та continent — це фактори, а всі інші — числові.

2.3.1 dplyr::glimpse()

Для перегляду структури тібблу використовується функція glimpse():

glimpse(gapminder)
## Rows: 1,704
## Columns: 6
## $ country   <fct> "Afghanistan", "Afghanistan", "Afghanistan", "Afghanistan", "Afghan~
## $ continent <fct> Asia, Asia, Asia, Asia, Asia, Asia, Asia, Asia, Asia, Asia, Asia, A~
## $ year      <int> 1952, 1957, 1962, 1967, 1972, 1977, 1982, 1987, 1992, 1997, 2002, 2~
## $ lifeExp   <dbl> 28.801, 30.332, 31.997, 34.020, 36.088, 38.438, 39.854, 40.822, 41.~
## $ pop       <int> 8425333, 9240934, 10267083, 11537966, 13079460, 14880372, 12881816,~
## $ gdpPercap <dbl> 779.4453, 820.8530, 853.1007, 836.1971, 739.9811, 786.1134, 978.011~

Це альтернатива базовій функції str() для дата фреймів.

2.3.2 dplyr::filter()

Для фільтрації спостережень за певною умовою використовується функція filter(). Для прикладу відфільтруємо дані для Ірландії:

filter(gapminder, country == "Ireland")
## # A tibble: 12 x 6
##    country continent  year lifeExp     pop gdpPercap
##    <fct>   <fct>     <int>   <dbl>   <int>     <dbl>
##  1 Ireland Europe     1952    66.9 2952156     5210.
##  2 Ireland Europe     1957    68.9 2878220     5599.
##  3 Ireland Europe     1962    70.3 2830000     6632.
##  4 Ireland Europe     1967    71.1 2900100     7656.
##  5 Ireland Europe     1972    71.3 3024400     9531.
##  6 Ireland Europe     1977    72.0 3271900    11151.
##  7 Ireland Europe     1982    73.1 3480000    12618.
##  8 Ireland Europe     1987    74.4 3539900    13873.
##  9 Ireland Europe     1992    75.5 3557761    17559.
## 10 Ireland Europe     1997    76.1 3667233    24522.
## 11 Ireland Europe     2002    77.8 3879155    34077.
## 12 Ireland Europe     2007    78.9 4109086    40676.

І тут я одразу хочу познайомити вас з альтернативним варіантом запису коду в синтаксисі tidyverse. Справа в тому, що якщо послідовно використовувати низку різноманітних функцій, то читаймість такого коду стає дуже низькою. Покажу на прикладі:

  1. До кожного значення вектора від 1 до 10 з кроком 1 розрахуємо сінус.

  2. З отриманого на першому кроці візьмемо абсолютні значення.

  3. З отриманого результату на 2 кроці візьмемо корінь квадратний

  4. Відсортуємо результат.

В класичному вигляді все виглядає наступним чином:

sort(sqrt(abs(sin(1:10))))
##  [1] 0.3756594 0.5285977 0.6419646 0.7375779 0.8105471 0.8699440 0.9173173 0.9535709
##  [9] 0.9792468 0.9946649

Погодьтесь, що розібрати такий код досить складно. Тому в діалекті tidyverse використовуються пайпи, %>%, (pipe) — вони передають результат попереднього розрахунку першим аргументов наступної функції. Таким чином попередній код можна переписати наступним чином:

1:10 %>% 
  sin() %>% 
  abs() %>% 
  sqrt() %>% 
  sort()
##  [1] 0.3756594 0.5285977 0.6419646 0.7375779 0.8105471 0.8699440 0.9173173 0.9535709
##  [9] 0.9792468 0.9946649

Погодьтесь, що такий код читається значно легше. Для виклику оператора %>% в RStudio використовується комбінація клавіш CTRL + SHIFT + M для Windows і CMD + SHIFT + M для Mac.

Тож, якщо повернутися до фільтрації 2.3.2, код можна переписати:

# Попередній варіант
filter(gapminder, country == "Ireland")
## # A tibble: 12 x 6
##    country continent  year lifeExp     pop gdpPercap
##    <fct>   <fct>     <int>   <dbl>   <int>     <dbl>
##  1 Ireland Europe     1952    66.9 2952156     5210.
##  2 Ireland Europe     1957    68.9 2878220     5599.
##  3 Ireland Europe     1962    70.3 2830000     6632.
##  4 Ireland Europe     1967    71.1 2900100     7656.
##  5 Ireland Europe     1972    71.3 3024400     9531.
##  6 Ireland Europe     1977    72.0 3271900    11151.
##  7 Ireland Europe     1982    73.1 3480000    12618.
##  8 Ireland Europe     1987    74.4 3539900    13873.
##  9 Ireland Europe     1992    75.5 3557761    17559.
## 10 Ireland Europe     1997    76.1 3667233    24522.
## 11 Ireland Europe     2002    77.8 3879155    34077.
## 12 Ireland Europe     2007    78.9 4109086    40676.

# З використанням %>%
gapminder %>% 
  filter(country == "Ireland")
## # A tibble: 12 x 6
##    country continent  year lifeExp     pop gdpPercap
##    <fct>   <fct>     <int>   <dbl>   <int>     <dbl>
##  1 Ireland Europe     1952    66.9 2952156     5210.
##  2 Ireland Europe     1957    68.9 2878220     5599.
##  3 Ireland Europe     1962    70.3 2830000     6632.
##  4 Ireland Europe     1967    71.1 2900100     7656.
##  5 Ireland Europe     1972    71.3 3024400     9531.
##  6 Ireland Europe     1977    72.0 3271900    11151.
##  7 Ireland Europe     1982    73.1 3480000    12618.
##  8 Ireland Europe     1987    74.4 3539900    13873.
##  9 Ireland Europe     1992    75.5 3557761    17559.
## 10 Ireland Europe     1997    76.1 3667233    24522.
## 11 Ireland Europe     2002    77.8 3879155    34077.
## 12 Ireland Europe     2007    78.9 4109086    40676.

Тут і далі я буду часто використовувати пайпи.

Розберемо ще приклади фільтрації даних. Відберемо дані Ірландії та Іспанії за 2007 рік:

gapminder %>% 
  filter(country == "Ireland" | country == "Spain",
         year == 2007)
## # A tibble: 2 x 6
##   country continent  year lifeExp      pop gdpPercap
##   <fct>   <fct>     <int>   <dbl>    <int>     <dbl>
## 1 Ireland Europe     2007    78.9  4109086    40676.
## 2 Spain   Europe     2007    80.9 40448191    28821.

А тепер припустимо, що нам потрібно відібрати інформацію не по двом, а по низці країн. Переліковувати їх всіх через country == "Назва_країни" буде дуже довго і не зручно. В таких випадках зручно використовувати оператор %in%. Давайте відберемо інформацію по Ірландії, Іспанії, Норвегії та Польщі за 2007 рік:

gapminder %>% 
  filter(country %in% c("Ireland", "Spain", "Norway", "Poland"),
         year == 2007)
## # A tibble: 4 x 6
##   country continent  year lifeExp      pop gdpPercap
##   <fct>   <fct>     <int>   <dbl>    <int>     <dbl>
## 1 Ireland Europe     2007    78.9  4109086    40676.
## 2 Norway  Europe     2007    80.2  4627926    49357.
## 3 Poland  Europe     2007    75.6 38518241    15390.
## 4 Spain   Europe     2007    80.9 40448191    28821.

Всі потрібні умови фільтрації можна переліковувати всередині функції filter() через кому.

2.3.3 dplyr::slice()

Для отримання зрізу даних, тобто тільки певних спостережень (перші, останні тощо) використовуються варіації функції slice().

Перші п’ять спостережень:

gapminder %>% 
  slice(1:5)
## # A tibble: 5 x 6
##   country     continent  year lifeExp      pop gdpPercap
##   <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
## 1 Afghanistan Asia       1952    28.8  8425333      779.
## 2 Afghanistan Asia       1957    30.3  9240934      821.
## 3 Afghanistan Asia       1962    32.0 10267083      853.
## 4 Afghanistan Asia       1967    34.0 11537966      836.
## 5 Afghanistan Asia       1972    36.1 13079460      740.

Перші п’ять та десяте спостереження:

gapminder %>% 
  slice(1:5, 10)
## # A tibble: 6 x 6
##   country     continent  year lifeExp      pop gdpPercap
##   <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
## 1 Afghanistan Asia       1952    28.8  8425333      779.
## 2 Afghanistan Asia       1957    30.3  9240934      821.
## 3 Afghanistan Asia       1962    32.0 10267083      853.
## 4 Afghanistan Asia       1967    34.0 11537966      836.
## 5 Afghanistan Asia       1972    36.1 13079460      740.
## 6 Afghanistan Asia       1997    41.8 22227415      635.

Всі крім перших трьох:

gapminder %>% 
  slice(-(1:3))
## # A tibble: 1,701 x 6
##    country     continent  year lifeExp      pop gdpPercap
##    <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
##  1 Afghanistan Asia       1967    34.0 11537966      836.
##  2 Afghanistan Asia       1972    36.1 13079460      740.
##  3 Afghanistan Asia       1977    38.4 14880372      786.
##  4 Afghanistan Asia       1982    39.9 12881816      978.
##  5 Afghanistan Asia       1987    40.8 13867957      852.
##  6 Afghanistan Asia       1992    41.7 16317921      649.
##  7 Afghanistan Asia       1997    41.8 22227415      635.
##  8 Afghanistan Asia       2002    42.1 25268405      727.
##  9 Afghanistan Asia       2007    43.8 31889923      975.
## 10 Albania     Europe     1952    55.2  1282697     1601.
## # ... with 1,691 more rows

Перші 15 спостережень:

gapminder %>% 
  slice_head(n = 15)
## # A tibble: 15 x 6
##    country     continent  year lifeExp      pop gdpPercap
##    <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
##  1 Afghanistan Asia       1952    28.8  8425333      779.
##  2 Afghanistan Asia       1957    30.3  9240934      821.
##  3 Afghanistan Asia       1962    32.0 10267083      853.
##  4 Afghanistan Asia       1967    34.0 11537966      836.
##  5 Afghanistan Asia       1972    36.1 13079460      740.
##  6 Afghanistan Asia       1977    38.4 14880372      786.
##  7 Afghanistan Asia       1982    39.9 12881816      978.
##  8 Afghanistan Asia       1987    40.8 13867957      852.
##  9 Afghanistan Asia       1992    41.7 16317921      649.
## 10 Afghanistan Asia       1997    41.8 22227415      635.
## 11 Afghanistan Asia       2002    42.1 25268405      727.
## 12 Afghanistan Asia       2007    43.8 31889923      975.
## 13 Albania     Europe     1952    55.2  1282697     1601.
## 14 Albania     Europe     1957    59.3  1476505     1942.
## 15 Albania     Europe     1962    64.8  1728137     2313.

Останні 15 спостережень:

gapminder %>% 
  slice_tail(n = 15)
## # A tibble: 15 x 6
##    country  continent  year lifeExp      pop gdpPercap
##    <fct>    <fct>     <int>   <dbl>    <int>     <dbl>
##  1 Zambia   Africa     1997    40.2  9417789     1071.
##  2 Zambia   Africa     2002    39.2 10595811     1072.
##  3 Zambia   Africa     2007    42.4 11746035     1271.
##  4 Zimbabwe Africa     1952    48.5  3080907      407.
##  5 Zimbabwe Africa     1957    50.5  3646340      519.
##  6 Zimbabwe Africa     1962    52.4  4277736      527.
##  7 Zimbabwe Africa     1967    54.0  4995432      570.
##  8 Zimbabwe Africa     1972    55.6  5861135      799.
##  9 Zimbabwe Africa     1977    57.7  6642107      686.
## 10 Zimbabwe Africa     1982    60.4  7636524      789.
## 11 Zimbabwe Africa     1987    62.4  9216418      706.
## 12 Zimbabwe Africa     1992    60.4 10704340      693.
## 13 Zimbabwe Africa     1997    46.8 11404948      792.
## 14 Zimbabwe Africa     2002    40.0 11926563      672.
## 15 Zimbabwe Africa     2007    43.5 12311143      470.

Топ-3 з найбільшим значенням очікуваної тривалості життя:

gapminder %>% 
  slice_max(lifeExp, n = 3)
## # A tibble: 3 x 6
##   country          continent  year lifeExp       pop gdpPercap
##   <fct>            <fct>     <int>   <dbl>     <int>     <dbl>
## 1 Japan            Asia       2007    82.6 127467972    31656.
## 2 Hong Kong, China Asia       2007    82.2   6980412    39725.
## 3 Japan            Asia       2002    82   127065841    28605.

Топ-3 з найменшим значенням очікуваної тривалості життя:

gapminder %>% 
  slice_min(lifeExp, n = 3)
## # A tibble: 3 x 6
##   country     continent  year lifeExp     pop gdpPercap
##   <fct>       <fct>     <int>   <dbl>   <int>     <dbl>
## 1 Rwanda      Africa     1992    23.6 7290203      737.
## 2 Afghanistan Asia       1952    28.8 8425333      779.
## 3 Gambia      Africa     1952    30    284320      485.

Проста випадкова вибірка без повернення з трьох країн:

gapminder %>% 
  slice_sample(n = 3)
## # A tibble: 3 x 6
##   country     continent  year lifeExp      pop gdpPercap
##   <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
## 1 Canada      Americas   1972    72.9 22284500    18971.
## 2 El Salvador Americas   1957    48.6  2355805     3422.
## 3 Chad        Africa     1987    51.1  5498955      952.

Зверніть увагу, що при повторному виконанні попереднього коду ви будете отримувати кожного разу різні підвибірки. Для того щоб отримати відтворюваний результат, необхідно вказати початкове значення генератора випадкових чисел за допомогою функції set.seed(). Аргументом цієї функції може бути будь-яке ціле число.

set.seed(2022)
gapminder %>% 
  slice_sample(n = 3)
## # A tibble: 3 x 6
##   country   continent  year lifeExp      pop gdpPercap
##   <fct>     <fct>     <int>   <dbl>    <int>     <dbl>
## 1 Cambodia  Asia       2007    59.7 14131858     1714.
## 2 Swaziland Africa     1982    55.6   649901     3895.
## 3 Kenya     Africa     1982    58.8 17661452     1348.

Для формування простої випадкової вибірки з поверненням з трьох країн, необхідно додати аргумент replace = TRUE:

gapminder %>% 
  slice_sample(n = 3, replace = TRUE)
## # A tibble: 3 x 6
##   country continent  year lifeExp        pop gdpPercap
##   <fct>   <fct>     <int>   <dbl>      <int>     <dbl>
## 1 India   Asia       2007    64.7 1110396331     2452.
## 2 Italy   Europe     1977    73.5   56059245    14256.
## 3 India   Asia       1982    56.6  708000000      856.

2.3.4 dplyr::arrange()

Для впорядкування даних використовується функція arrange(). За замовчуванням сортування даних відбувається за зростанням. Сортування по змінній ВВП на душу населення:

gapminder %>% 
  arrange(gdpPercap)
## # A tibble: 1,704 x 6
##    country          continent  year lifeExp      pop gdpPercap
##    <fct>            <fct>     <int>   <dbl>    <int>     <dbl>
##  1 Congo, Dem. Rep. Africa     2002    45.0 55379852      241.
##  2 Congo, Dem. Rep. Africa     2007    46.5 64606759      278.
##  3 Lesotho          Africa     1952    42.1   748747      299.
##  4 Guinea-Bissau    Africa     1952    32.5   580653      300.
##  5 Congo, Dem. Rep. Africa     1997    42.6 47798986      312.
##  6 Eritrea          Africa     1952    35.9  1438760      329.
##  7 Myanmar          Asia       1952    36.3 20092996      331 
##  8 Lesotho          Africa     1957    45.0   813338      336.
##  9 Burundi          Africa     1952    39.0  2445618      339.
## 10 Eritrea          Africa     1957    38.0  1542611      344.
## # ... with 1,694 more rows

Для того щоб сотувати дані за спаданням, використовується додаткова функція desc() в середині arrange():

gapminder %>% 
  arrange(desc(gdpPercap))
## # A tibble: 1,704 x 6
##    country   continent  year lifeExp     pop gdpPercap
##    <fct>     <fct>     <int>   <dbl>   <int>     <dbl>
##  1 Kuwait    Asia       1957    58.0  212846   113523.
##  2 Kuwait    Asia       1972    67.7  841934   109348.
##  3 Kuwait    Asia       1952    55.6  160000   108382.
##  4 Kuwait    Asia       1962    60.5  358266    95458.
##  5 Kuwait    Asia       1967    64.6  575003    80895.
##  6 Kuwait    Asia       1977    69.3 1140357    59265.
##  7 Norway    Europe     2007    80.2 4627926    49357.
##  8 Kuwait    Asia       2007    77.6 2505559    47307.
##  9 Singapore Asia       2007    80.0 4553009    47143.
## 10 Norway    Europe     2002    79.0 4535591    44684.
## # ... with 1,694 more rows

Якщо сортування за спаданням виконується по одній змінній, замість desc() можна використати знак -:

# Але працює це тільки у випадку використання однієї змінної!
gapminder %>% 
  arrange(-gdpPercap)
## # A tibble: 1,704 x 6
##    country   continent  year lifeExp     pop gdpPercap
##    <fct>     <fct>     <int>   <dbl>   <int>     <dbl>
##  1 Kuwait    Asia       1957    58.0  212846   113523.
##  2 Kuwait    Asia       1972    67.7  841934   109348.
##  3 Kuwait    Asia       1952    55.6  160000   108382.
##  4 Kuwait    Asia       1962    60.5  358266    95458.
##  5 Kuwait    Asia       1967    64.6  575003    80895.
##  6 Kuwait    Asia       1977    69.3 1140357    59265.
##  7 Norway    Europe     2007    80.2 4627926    49357.
##  8 Kuwait    Asia       2007    77.6 2505559    47307.
##  9 Singapore Asia       2007    80.0 4553009    47143.
## 10 Norway    Europe     2002    79.0 4535591    44684.
## # ... with 1,694 more rows

2.3.5 dplyr::select()

У випадках, коли змінних у данних дуже багато, є сенс відібрати тільки ті, з якими ви будете працювати. В таких випадках нам допоможе функція select(), хоча тільки такими задачами вона не обмежується. У нашому випадку в наборі даних лише шість змінних, що в принципі небагато, але суть функції буде зрозуміла. Відберемо, наприклад змінні по країнам та населенню:

gapminder %>% 
  select(country, pop)
## # A tibble: 1,704 x 2
##    country          pop
##    <fct>          <int>
##  1 Afghanistan  8425333
##  2 Afghanistan  9240934
##  3 Afghanistan 10267083
##  4 Afghanistan 11537966
##  5 Afghanistan 13079460
##  6 Afghanistan 14880372
##  7 Afghanistan 12881816
##  8 Afghanistan 13867957
##  9 Afghanistan 16317921
## 10 Afghanistan 22227415
## # ... with 1,694 more rows

Через двокрапку можна вказати послідовність з декількох стовпчиків для відбору:

gapminder %>% 
  select(country, lifeExp:gdpPercap)
## # A tibble: 1,704 x 4
##    country     lifeExp      pop gdpPercap
##    <fct>         <dbl>    <int>     <dbl>
##  1 Afghanistan    28.8  8425333      779.
##  2 Afghanistan    30.3  9240934      821.
##  3 Afghanistan    32.0 10267083      853.
##  4 Afghanistan    34.0 11537966      836.
##  5 Afghanistan    36.1 13079460      740.
##  6 Afghanistan    38.4 14880372      786.
##  7 Afghanistan    39.9 12881816      978.
##  8 Afghanistan    40.8 13867957      852.
##  9 Afghanistan    41.7 16317921      649.
## 10 Afghanistan    41.8 22227415      635.
## # ... with 1,694 more rows

Замість назв можна використовувати порядковий номер стовпчика (нагадую, що індексація в R починається з 1):

gapminder %>% 
  select(1, 4:6)
## # A tibble: 1,704 x 4
##    country     lifeExp      pop gdpPercap
##    <fct>         <dbl>    <int>     <dbl>
##  1 Afghanistan    28.8  8425333      779.
##  2 Afghanistan    30.3  9240934      821.
##  3 Afghanistan    32.0 10267083      853.
##  4 Afghanistan    34.0 11537966      836.
##  5 Afghanistan    36.1 13079460      740.
##  6 Afghanistan    38.4 14880372      786.
##  7 Afghanistan    39.9 12881816      978.
##  8 Afghanistan    40.8 13867957      852.
##  9 Afghanistan    41.7 16317921      649.
## 10 Afghanistan    41.8 22227415      635.
## # ... with 1,694 more rows

Коли змінних дійсно багато, простіше вказати змінні, які хочеться викинути. Для цього використовується знак - (для відкидання однієї змінної) і ! (для декількох, які необхідно помістити у вектор). Відкинемо змінну континенту:

# Відкидаємо одну змінну за допомогою `-`
gapminder %>% 
  select(-continent)
## # A tibble: 1,704 x 5
##    country      year lifeExp      pop gdpPercap
##    <fct>       <int>   <dbl>    <int>     <dbl>
##  1 Afghanistan  1952    28.8  8425333      779.
##  2 Afghanistan  1957    30.3  9240934      821.
##  3 Afghanistan  1962    32.0 10267083      853.
##  4 Afghanistan  1967    34.0 11537966      836.
##  5 Afghanistan  1972    36.1 13079460      740.
##  6 Afghanistan  1977    38.4 14880372      786.
##  7 Afghanistan  1982    39.9 12881816      978.
##  8 Afghanistan  1987    40.8 13867957      852.
##  9 Afghanistan  1992    41.7 16317921      649.
## 10 Afghanistan  1997    41.8 22227415      635.
## # ... with 1,694 more rows

Відкинемо змінну континенту та населення:

# Відкидаємо декілька змінних за допомогою `!`
gapminder %>% 
  select(!c(continent, pop))
## # A tibble: 1,704 x 4
##    country      year lifeExp gdpPercap
##    <fct>       <int>   <dbl>     <dbl>
##  1 Afghanistan  1952    28.8      779.
##  2 Afghanistan  1957    30.3      821.
##  3 Afghanistan  1962    32.0      853.
##  4 Afghanistan  1967    34.0      836.
##  5 Afghanistan  1972    36.1      740.
##  6 Afghanistan  1977    38.4      786.
##  7 Afghanistan  1982    39.9      978.
##  8 Afghanistan  1987    40.8      852.
##  9 Afghanistan  1992    41.7      649.
## 10 Afghanistan  1997    41.8      635.
## # ... with 1,694 more rows

Крім того, за допомогою функцій starts_with(), ends_with(), contains() та matches() ми можемо задавати специфічні умови відбору змінних. Для відбору стовпчиків, які починаються на певний вираз використовується starts_with(). Відберемо змінні, які починаються на co:

gapminder %>% 
  select(starts_with("c"))
## # A tibble: 1,704 x 2
##    country     continent
##    <fct>       <fct>    
##  1 Afghanistan Asia     
##  2 Afghanistan Asia     
##  3 Afghanistan Asia     
##  4 Afghanistan Asia     
##  5 Afghanistan Asia     
##  6 Afghanistan Asia     
##  7 Afghanistan Asia     
##  8 Afghanistan Asia     
##  9 Afghanistan Asia     
## 10 Afghanistan Asia     
## # ... with 1,694 more rows

Змінні, які закінчуються на англійську літеру p:

gapminder %>% 
  select(ends_with("p"))
## # A tibble: 1,704 x 3
##    lifeExp      pop gdpPercap
##      <dbl>    <int>     <dbl>
##  1    28.8  8425333      779.
##  2    30.3  9240934      821.
##  3    32.0 10267083      853.
##  4    34.0 11537966      836.
##  5    36.1 13079460      740.
##  6    38.4 14880372      786.
##  7    39.9 12881816      978.
##  8    40.8 13867957      852.
##  9    41.7 16317921      649.
## 10    41.8 22227415      635.
## # ... with 1,694 more rows

Змінні, які містять англійську літеру x:

gapminder %>% 
  select(contains("x"))
## # A tibble: 1,704 x 1
##    lifeExp
##      <dbl>
##  1    28.8
##  2    30.3
##  3    32.0
##  4    34.0
##  5    36.1
##  6    38.4
##  7    39.9
##  8    40.8
##  9    41.7
## 10    41.8
## # ... with 1,694 more rows

При роботі з даними часто використовуються регулярні вирази (regular expression). Це певні патерни тексту, які відповідають певній умові. Ми можемо використовувати їх при відборі змінних за допомогою функції matches(). Наприклад відберемо стовпчики, які в назві містять літери op або ap:

gapminder %>% 
  select(matches("[oa]p"))
## # A tibble: 1,704 x 2
##         pop gdpPercap
##       <int>     <dbl>
##  1  8425333      779.
##  2  9240934      821.
##  3 10267083      853.
##  4 11537966      836.
##  5 13079460      740.
##  6 14880372      786.
##  7 12881816      978.
##  8 13867957      852.
##  9 16317921      649.
## 10 22227415      635.
## # ... with 1,694 more rows

Для більш детального ознайомлення з цією темою рекомендую роботу Friedl (2006).

За допомогою функції where() можна обирати змінні за їх типом. Наприклад відберемо факторні змінні:

# Зверніть увагу, що умова "is.factor" 
# вживається без дужок в середині where()
gapminder %>% 
  select(where(is.factor))
## # A tibble: 1,704 x 2
##    country     continent
##    <fct>       <fct>    
##  1 Afghanistan Asia     
##  2 Afghanistan Asia     
##  3 Afghanistan Asia     
##  4 Afghanistan Asia     
##  5 Afghanistan Asia     
##  6 Afghanistan Asia     
##  7 Afghanistan Asia     
##  8 Afghanistan Asia     
##  9 Afghanistan Asia     
## 10 Afghanistan Asia     
## # ... with 1,694 more rows

Або залишимо тільки числові змінні:

gapminder %>% 
  select(where(is.numeric))
## # A tibble: 1,704 x 4
##     year lifeExp      pop gdpPercap
##    <int>   <dbl>    <int>     <dbl>
##  1  1952    28.8  8425333      779.
##  2  1957    30.3  9240934      821.
##  3  1962    32.0 10267083      853.
##  4  1967    34.0 11537966      836.
##  5  1972    36.1 13079460      740.
##  6  1977    38.4 14880372      786.
##  7  1982    39.9 12881816      978.
##  8  1987    40.8 13867957      852.
##  9  1992    41.7 16317921      649.
## 10  1997    41.8 22227415      635.
## # ... with 1,694 more rows

Але і це ще не все: за допомогою функції select() та everything() можна змінювати позиції змінних у наборі даних. Спочатку ми вказуємо змінні, які хочемо помістити першими, після чого пишемо everything(). Наприклад, помістимо всі числові змінні на початку:

gapminder %>% 
  select(where(is.numeric), everything())
## # A tibble: 1,704 x 6
##     year lifeExp      pop gdpPercap country     continent
##    <int>   <dbl>    <int>     <dbl> <fct>       <fct>    
##  1  1952    28.8  8425333      779. Afghanistan Asia     
##  2  1957    30.3  9240934      821. Afghanistan Asia     
##  3  1962    32.0 10267083      853. Afghanistan Asia     
##  4  1967    34.0 11537966      836. Afghanistan Asia     
##  5  1972    36.1 13079460      740. Afghanistan Asia     
##  6  1977    38.4 14880372      786. Afghanistan Asia     
##  7  1982    39.9 12881816      978. Afghanistan Asia     
##  8  1987    40.8 13867957      852. Afghanistan Asia     
##  9  1992    41.7 16317921      649. Afghanistan Asia     
## 10  1997    41.8 22227415      635. Afghanistan Asia     
## # ... with 1,694 more rows

2.3.6 dplyr::relocate()

Насправді в dplyr є окрема функція для гнучкої зміни позиції — relocate(). За замовчуванням вона поміщає вказані змінні на перші позиції:

gapminder %>% 
  relocate(pop)
## # A tibble: 1,704 x 6
##         pop country     continent  year lifeExp gdpPercap
##       <int> <fct>       <fct>     <int>   <dbl>     <dbl>
##  1  8425333 Afghanistan Asia       1952    28.8      779.
##  2  9240934 Afghanistan Asia       1957    30.3      821.
##  3 10267083 Afghanistan Asia       1962    32.0      853.
##  4 11537966 Afghanistan Asia       1967    34.0      836.
##  5 13079460 Afghanistan Asia       1972    36.1      740.
##  6 14880372 Afghanistan Asia       1977    38.4      786.
##  7 12881816 Afghanistan Asia       1982    39.9      978.
##  8 13867957 Afghanistan Asia       1987    40.8      852.
##  9 16317921 Afghanistan Asia       1992    41.7      649.
## 10 22227415 Afghanistan Asia       1997    41.8      635.
## # ... with 1,694 more rows

У функції relocate() є два додаткових аргументи: .before() та .after(). Вони використовуються для вказування місця зміни позиції стовпчика:

# Помістимо стовпчик continent після year
gapminder %>% 
  relocate(continent, .after = year)
## # A tibble: 1,704 x 6
##    country      year continent lifeExp      pop gdpPercap
##    <fct>       <int> <fct>       <dbl>    <int>     <dbl>
##  1 Afghanistan  1952 Asia         28.8  8425333      779.
##  2 Afghanistan  1957 Asia         30.3  9240934      821.
##  3 Afghanistan  1962 Asia         32.0 10267083      853.
##  4 Afghanistan  1967 Asia         34.0 11537966      836.
##  5 Afghanistan  1972 Asia         36.1 13079460      740.
##  6 Afghanistan  1977 Asia         38.4 14880372      786.
##  7 Afghanistan  1982 Asia         39.9 12881816      978.
##  8 Afghanistan  1987 Asia         40.8 13867957      852.
##  9 Afghanistan  1992 Asia         41.7 16317921      649.
## 10 Afghanistan  1997 Asia         41.8 22227415      635.
## # ... with 1,694 more rows

# Помістимо стовпчик continent після year
gapminder %>% 
  relocate(lifeExp, .before = gdpPercap)
## # A tibble: 1,704 x 6
##    country     continent  year      pop lifeExp gdpPercap
##    <fct>       <fct>     <int>    <int>   <dbl>     <dbl>
##  1 Afghanistan Asia       1952  8425333    28.8      779.
##  2 Afghanistan Asia       1957  9240934    30.3      821.
##  3 Afghanistan Asia       1962 10267083    32.0      853.
##  4 Afghanistan Asia       1967 11537966    34.0      836.
##  5 Afghanistan Asia       1972 13079460    36.1      740.
##  6 Afghanistan Asia       1977 14880372    38.4      786.
##  7 Afghanistan Asia       1982 12881816    39.9      978.
##  8 Afghanistan Asia       1987 13867957    40.8      852.
##  9 Afghanistan Asia       1992 16317921    41.7      649.
## 10 Afghanistan Asia       1997 22227415    41.8      635.
## # ... with 1,694 more rows

Можна переміщати й одразу цілі групи:

# Помістимо всі цілочислові змінні після факторних
gapminder %>% 
  relocate(where(is.integer), .after = where(is.factor))
## # A tibble: 1,704 x 6
##    country     continent  year      pop lifeExp gdpPercap
##    <fct>       <fct>     <int>    <int>   <dbl>     <dbl>
##  1 Afghanistan Asia       1952  8425333    28.8      779.
##  2 Afghanistan Asia       1957  9240934    30.3      821.
##  3 Afghanistan Asia       1962 10267083    32.0      853.
##  4 Afghanistan Asia       1967 11537966    34.0      836.
##  5 Afghanistan Asia       1972 13079460    36.1      740.
##  6 Afghanistan Asia       1977 14880372    38.4      786.
##  7 Afghanistan Asia       1982 12881816    39.9      978.
##  8 Afghanistan Asia       1987 13867957    40.8      852.
##  9 Afghanistan Asia       1992 16317921    41.7      649.
## 10 Afghanistan Asia       1997 22227415    41.8      635.
## # ... with 1,694 more rows

# Помістимо всі факторні змінні після числових
gapminder %>% 
  relocate(where(is.numeric), .before = where(is.factor))
## # A tibble: 1,704 x 6
##     year lifeExp      pop gdpPercap country     continent
##    <int>   <dbl>    <int>     <dbl> <fct>       <fct>    
##  1  1952    28.8  8425333      779. Afghanistan Asia     
##  2  1957    30.3  9240934      821. Afghanistan Asia     
##  3  1962    32.0 10267083      853. Afghanistan Asia     
##  4  1967    34.0 11537966      836. Afghanistan Asia     
##  5  1972    36.1 13079460      740. Afghanistan Asia     
##  6  1977    38.4 14880372      786. Afghanistan Asia     
##  7  1982    39.9 12881816      978. Afghanistan Asia     
##  8  1987    40.8 13867957      852. Afghanistan Asia     
##  9  1992    41.7 16317921      649. Afghanistan Asia     
## 10  1997    41.8 22227415      635. Afghanistan Asia     
## # ... with 1,694 more rows

2.3.7 dplyr::rename()

Настав час ознайомитися, як змінювати назви стовпчиків. І в цьому нам допоможе функція rename(). Змінємо назву стовпчика pop на population:

# Спочатку вказуємо нову назву, а після стару
gapminder %>% 
  rename(population = pop)
## # A tibble: 1,704 x 6
##    country     continent  year lifeExp population gdpPercap
##    <fct>       <fct>     <int>   <dbl>      <int>     <dbl>
##  1 Afghanistan Asia       1952    28.8    8425333      779.
##  2 Afghanistan Asia       1957    30.3    9240934      821.
##  3 Afghanistan Asia       1962    32.0   10267083      853.
##  4 Afghanistan Asia       1967    34.0   11537966      836.
##  5 Afghanistan Asia       1972    36.1   13079460      740.
##  6 Afghanistan Asia       1977    38.4   14880372      786.
##  7 Afghanistan Asia       1982    39.9   12881816      978.
##  8 Afghanistan Asia       1987    40.8   13867957      852.
##  9 Afghanistan Asia       1992    41.7   16317921      649.
## 10 Afghanistan Asia       1997    41.8   22227415      635.
## # ... with 1,694 more rows

Можна змінювати регістр назв за допомогою функцій rename_with() та toupper() для запису великими літерами або tolower() для запису маленькими.

Запишемо всі назви великими:

gapminder %>% 
  rename_with(toupper)
## # A tibble: 1,704 x 6
##    COUNTRY     CONTINENT  YEAR LIFEEXP      POP GDPPERCAP
##    <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
##  1 Afghanistan Asia       1952    28.8  8425333      779.
##  2 Afghanistan Asia       1957    30.3  9240934      821.
##  3 Afghanistan Asia       1962    32.0 10267083      853.
##  4 Afghanistan Asia       1967    34.0 11537966      836.
##  5 Afghanistan Asia       1972    36.1 13079460      740.
##  6 Afghanistan Asia       1977    38.4 14880372      786.
##  7 Afghanistan Asia       1982    39.9 12881816      978.
##  8 Afghanistan Asia       1987    40.8 13867957      852.
##  9 Afghanistan Asia       1992    41.7 16317921      649.
## 10 Afghanistan Asia       1997    41.8 22227415      635.
## # ... with 1,694 more rows

Згадуючи попередні розділи, можемо враховувати специфічні патерни:

gapminder %>% 
  rename_with(toupper, ends_with("p"))
## # A tibble: 1,704 x 6
##    country     continent  year LIFEEXP      POP GDPPERCAP
##    <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
##  1 Afghanistan Asia       1952    28.8  8425333      779.
##  2 Afghanistan Asia       1957    30.3  9240934      821.
##  3 Afghanistan Asia       1962    32.0 10267083      853.
##  4 Afghanistan Asia       1967    34.0 11537966      836.
##  5 Afghanistan Asia       1972    36.1 13079460      740.
##  6 Afghanistan Asia       1977    38.4 14880372      786.
##  7 Afghanistan Asia       1982    39.9 12881816      978.
##  8 Afghanistan Asia       1987    40.8 13867957      852.
##  9 Afghanistan Asia       1992    41.7 16317921      649.
## 10 Afghanistan Asia       1997    41.8 22227415      635.
## # ... with 1,694 more rows

В роботі досить часто постає питання заміни певної частини назви. Наприклад, прибрати пробіли або замінити їх певний символ. Оскільки в нашому прикладі з назвами все більш-менш нормально, я спеціально вставлю пробіли перед великими літерами в назвах стовпчиків. В цьому випадку нам знову приходять на допомогу регулярні вирази. Результат я збережу у змінній gapminder_rename:

gapminder_rename <- gapminder %>% 
  rename_with(~ gsub("([a-z])([A-Z])","\\1 \\2", .x))

gapminder_rename
## # A tibble: 1,704 x 6
##    country     continent  year `life Exp`      pop `gdp Percap`
##    <fct>       <fct>     <int>      <dbl>    <int>        <dbl>
##  1 Afghanistan Asia       1952       28.8  8425333         779.
##  2 Afghanistan Asia       1957       30.3  9240934         821.
##  3 Afghanistan Asia       1962       32.0 10267083         853.
##  4 Afghanistan Asia       1967       34.0 11537966         836.
##  5 Afghanistan Asia       1972       36.1 13079460         740.
##  6 Afghanistan Asia       1977       38.4 14880372         786.
##  7 Afghanistan Asia       1982       39.9 12881816         978.
##  8 Afghanistan Asia       1987       40.8 13867957         852.
##  9 Afghanistan Asia       1992       41.7 16317921         649.
## 10 Afghanistan Asia       1997       41.8 22227415         635.
## # ... with 1,694 more rows

Використання стовпчиків в назвах яких зустрічаються пробіли незручно. Тому давайте виправимо цю ситуацію. Покажу декілька варіантів. Перший варіант з використанням базової функції gsub(), де першим аргументом ми вказуємо спочатку що змінюємо, а другим - на що змінюємо. Замінимо пробіли в назвах стовпчиків на знак підкреслення ("_"):

# Знак "тільда" (~) в R використовуються в якості формули,
# ми познайомимось з її використанням в наступних темах
gapminder_rename %>% 
  rename_with(~ gsub(" ", "_", .x))
## # A tibble: 1,704 x 6
##    country     continent  year life_Exp      pop gdp_Percap
##    <fct>       <fct>     <int>    <dbl>    <int>      <dbl>
##  1 Afghanistan Asia       1952     28.8  8425333       779.
##  2 Afghanistan Asia       1957     30.3  9240934       821.
##  3 Afghanistan Asia       1962     32.0 10267083       853.
##  4 Afghanistan Asia       1967     34.0 11537966       836.
##  5 Afghanistan Asia       1972     36.1 13079460       740.
##  6 Afghanistan Asia       1977     38.4 14880372       786.
##  7 Afghanistan Asia       1982     39.9 12881816       978.
##  8 Afghanistan Asia       1987     40.8 13867957       852.
##  9 Afghanistan Asia       1992     41.7 16317921       649.
## 10 Afghanistan Asia       1997     41.8 22227415       635.
## # ... with 1,694 more rows

Другий варіант варіант ближчий до синтаксису tidyverse і використовує функції з пакету stringr, який створений для роботи з текстом:

gapminder_rename %>% 
    set_names(names(.) %>% str_replace(" ", "_") %>% str_to_title())
## # A tibble: 1,704 x 6
##    Country     Continent  Year Life_exp      Pop Gdp_percap
##    <fct>       <fct>     <int>    <dbl>    <int>      <dbl>
##  1 Afghanistan Asia       1952     28.8  8425333       779.
##  2 Afghanistan Asia       1957     30.3  9240934       821.
##  3 Afghanistan Asia       1962     32.0 10267083       853.
##  4 Afghanistan Asia       1967     34.0 11537966       836.
##  5 Afghanistan Asia       1972     36.1 13079460       740.
##  6 Afghanistan Asia       1977     38.4 14880372       786.
##  7 Afghanistan Asia       1982     39.9 12881816       978.
##  8 Afghanistan Asia       1987     40.8 13867957       852.
##  9 Afghanistan Asia       1992     41.7 16317921       649.
## 10 Afghanistan Asia       1997     41.8 22227415       635.
## # ... with 1,694 more rows

2.3.8 dplyr::mutate()

Майже жодна маніпуляція з даними не обходиться без створення нових стовпчиків. Для цього використовується функція mutate(). За замовчуванням нова змінна записується в кінці набору даних. Давайте розрахуємо загальний ВВП кожної країни в певний момент часу. Для цього створимо нову змінну gdp_billion в якій перемножимо змінні “кількість населення” на “ВВП на душу населення” та поділимо на один мільярд:

gapminder %>% 
  mutate(gdp_billion = pop * gdpPercap / 10^9)
## # A tibble: 1,704 x 7
##    country     continent  year lifeExp      pop gdpPercap gdp_billion
##    <fct>       <fct>     <int>   <dbl>    <int>     <dbl>       <dbl>
##  1 Afghanistan Asia       1952    28.8  8425333      779.        6.57
##  2 Afghanistan Asia       1957    30.3  9240934      821.        7.59
##  3 Afghanistan Asia       1962    32.0 10267083      853.        8.76
##  4 Afghanistan Asia       1967    34.0 11537966      836.        9.65
##  5 Afghanistan Asia       1972    36.1 13079460      740.        9.68
##  6 Afghanistan Asia       1977    38.4 14880372      786.       11.7 
##  7 Afghanistan Asia       1982    39.9 12881816      978.       12.6 
##  8 Afghanistan Asia       1987    40.8 13867957      852.       11.8 
##  9 Afghanistan Asia       1992    41.7 16317921      649.       10.6 
## 10 Afghanistan Asia       1997    41.8 22227415      635.       14.1 
## # ... with 1,694 more rows

За допомогою аргументів .before та .after можна змінювати й позицію запису нової змінної:

gapminder %>% 
  mutate(gdp_billion = pop * gdpPercap / 10^9,
         .after = year)
## # A tibble: 1,704 x 7
##    country     continent  year gdp_billion lifeExp      pop gdpPercap
##    <fct>       <fct>     <int>       <dbl>   <dbl>    <int>     <dbl>
##  1 Afghanistan Asia       1952        6.57    28.8  8425333      779.
##  2 Afghanistan Asia       1957        7.59    30.3  9240934      821.
##  3 Afghanistan Asia       1962        8.76    32.0 10267083      853.
##  4 Afghanistan Asia       1967        9.65    34.0 11537966      836.
##  5 Afghanistan Asia       1972        9.68    36.1 13079460      740.
##  6 Afghanistan Asia       1977       11.7     38.4 14880372      786.
##  7 Afghanistan Asia       1982       12.6     39.9 12881816      978.
##  8 Afghanistan Asia       1987       11.8     40.8 13867957      852.
##  9 Afghanistan Asia       1992       10.6     41.7 16317921      649.
## 10 Afghanistan Asia       1997       14.1     41.8 22227415      635.
## # ... with 1,694 more rows

Інколи необхідно свторити стовпчик індексів: порядковий номер спостереження. Для цього можна використати функцію row_number():

gapminder %>% 
  mutate(record = row_number(),
         .before = country)
## # A tibble: 1,704 x 7
##    record country     continent  year lifeExp      pop gdpPercap
##     <int> <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
##  1      1 Afghanistan Asia       1952    28.8  8425333      779.
##  2      2 Afghanistan Asia       1957    30.3  9240934      821.
##  3      3 Afghanistan Asia       1962    32.0 10267083      853.
##  4      4 Afghanistan Asia       1967    34.0 11537966      836.
##  5      5 Afghanistan Asia       1972    36.1 13079460      740.
##  6      6 Afghanistan Asia       1977    38.4 14880372      786.
##  7      7 Afghanistan Asia       1982    39.9 12881816      978.
##  8      8 Afghanistan Asia       1987    40.8 13867957      852.
##  9      9 Afghanistan Asia       1992    41.7 16317921      649.
## 10     10 Afghanistan Asia       1997    41.8 22227415      635.
## # ... with 1,694 more rows

Або використати базовий синтаксис R:

gapminder %>% 
  mutate(record = seq(1:n()),
         .before = country)
## # A tibble: 1,704 x 7
##    record country     continent  year lifeExp      pop gdpPercap
##     <int> <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
##  1      1 Afghanistan Asia       1952    28.8  8425333      779.
##  2      2 Afghanistan Asia       1957    30.3  9240934      821.
##  3      3 Afghanistan Asia       1962    32.0 10267083      853.
##  4      4 Afghanistan Asia       1967    34.0 11537966      836.
##  5      5 Afghanistan Asia       1972    36.1 13079460      740.
##  6      6 Afghanistan Asia       1977    38.4 14880372      786.
##  7      7 Afghanistan Asia       1982    39.9 12881816      978.
##  8      8 Afghanistan Asia       1987    40.8 13867957      852.
##  9      9 Afghanistan Asia       1992    41.7 16317921      649.
## 10     10 Afghanistan Asia       1997    41.8 22227415      635.
## # ... with 1,694 more rows

Якщо ж хочется залишити лише розрахунковий стовпчик — використовується функція transmute(). При цьому вона дає можливість залишити й певні стовпчики з оригінального набору даних.

Залишимо змінну country та розрахуємо загальний ВВП:

gapminder %>% 
  transmute(country,
            gdp_billion = pop * gdpPercap / 10^9)
## # A tibble: 1,704 x 2
##    country     gdp_billion
##    <fct>             <dbl>
##  1 Afghanistan        6.57
##  2 Afghanistan        7.59
##  3 Afghanistan        8.76
##  4 Afghanistan        9.65
##  5 Afghanistan        9.68
##  6 Afghanistan       11.7 
##  7 Afghanistan       12.6 
##  8 Afghanistan       11.8 
##  9 Afghanistan       10.6 
## 10 Afghanistan       14.1 
## # ... with 1,694 more rows

2.3.9 dplyr::group_by() та summarise()

Ще однією популярною задачею є групування та агрегація даних: розрахунок певних статистик чи значень для окремих груп. Для цього використовується функції group_by() та summarise(), які, як правило, використовуються разом.

Для прикладу, розрахуємо середню (mean()) очікувану тривалість життя для кожного континенту. Для цього спочатку групуємо дані, а потім розраховуємо статистичний показник, який назвемо mean_life_exp:

gapminder %>% 
  group_by(continent) %>% 
  summarise(mean_life_exp = mean(lifeExp))
## # A tibble: 5 x 2
##   continent mean_life_exp
##   <fct>             <dbl>
## 1 Africa             48.9
## 2 Americas           64.7
## 3 Asia               60.1
## 4 Europe             71.9
## 5 Oceania            74.3

Аналогічно, ми можемо розраховувати одночасно декілька статистик. Додамо до розрахунку медіану (median()) та стандартне відхилення (sd()) очікуваної тривалості життя:

gapminder %>% 
  group_by(continent) %>% 
  summarise(mean_life_exp = mean(lifeExp),
            md_life_exp = median(lifeExp),
            sd_life_exp = sd(lifeExp))
## # A tibble: 5 x 4
##   continent mean_life_exp md_life_exp sd_life_exp
##   <fct>             <dbl>       <dbl>       <dbl>
## 1 Africa             48.9        47.8        9.15
## 2 Americas           64.7        67.0        9.35
## 3 Asia               60.1        61.8       11.9 
## 4 Europe             71.9        72.2        5.43
## 5 Oceania            74.3        73.7        3.80

Слід звернути увагу, що всі зазначені вище статистики в R чутливі до пропущених значень: у разі їх наявності, функції будуть повертати NA (not available). Для того, щоб при розрахунку статистик не враховувались пропущені значення необхідно додати аргумент na.rm = TRUE.

2.3.10 dplyr::across()

Але що, якщо нам необхідно розрахувати, наприклад, середні значення для всіх числових стовпчиків? Звичайно можна перерахувати їх всіх вручну:

gapminder %>% 
  group_by(continent) %>% 
  summarise(mean_life_exp = mean(lifeExp),
            mean_pop = mean(pop),
            mean_gdpPercap = mean(gdpPercap))
## # A tibble: 5 x 4
##   continent mean_life_exp  mean_pop mean_gdpPercap
##   <fct>             <dbl>     <dbl>          <dbl>
## 1 Africa             48.9  9916003.          2194.
## 2 Americas           64.7 24504795.          7136.
## 3 Asia               60.1 77038722.          7902.
## 4 Europe             71.9 17169765.         14469.
## 5 Oceania            74.3  8874672.         18622.

Але якщо числових змінних багато — такий підхід буде неоптимальним. В таких випадках краще скористатися функцією across(). Продемонструю декілька варіантів.

Для початку розрахуємо, як в попередньому прикладі, середні значення для числових змінних:

gapminder %>% 
  select(-year) %>% # прибираємо змінну року
  group_by(continent) %>% 
  summarise(across(where(is.numeric), mean))
## # A tibble: 5 x 4
##   continent lifeExp       pop gdpPercap
##   <fct>       <dbl>     <dbl>     <dbl>
## 1 Africa       48.9  9916003.     2194.
## 2 Americas     64.7 24504795.     7136.
## 3 Asia         60.1 77038722.     7902.
## 4 Europe       71.9 17169765.    14469.
## 5 Oceania      74.3  8874672.    18622.

Або можна перелічити потрібні змінні у векторі:

gapminder %>% 
  group_by(continent) %>% 
  summarise(across(lifeExp:gdpPercap, mean))
## # A tibble: 5 x 4
##   continent lifeExp       pop gdpPercap
##   <fct>       <dbl>     <dbl>     <dbl>
## 1 Africa       48.9  9916003.     2194.
## 2 Americas     64.7 24504795.     7136.
## 3 Asia         60.1 77038722.     7902.
## 4 Europe       71.9 17169765.    14469.
## 5 Oceania      74.3  8874672.    18622.

Якщо уважно придивитися до результатів, то видно, що результати розрахунку повертаються з тими ж названими, як в оригінальному наборі даних. Якщо показати такий розрахунок третій стороні - можуть виникнути проблеми сприйняття. Як бути? Насправді функція across() досить гнучка і розробники передбачили такий варіант. Для цього нам знадобиться аргумент .names, де ми вкажемо правило запису назв результуючих змінних:

gapminder %>% 
  group_by(continent) %>% 
  summarise(across(lifeExp:gdpPercap,
                   mean,
                   .names = "mean_{.col}"))
## # A tibble: 5 x 4
##   continent mean_lifeExp  mean_pop mean_gdpPercap
##   <fct>            <dbl>     <dbl>          <dbl>
## 1 Africa            48.9  9916003.          2194.
## 2 Americas          64.7 24504795.          7136.
## 3 Asia              60.1 77038722.          7902.
## 4 Europe            71.9 17169765.         14469.
## 5 Oceania           74.3  8874672.         18622.

І навіть розрахунки декількох різних статистик теж можливий. Для цього їх перелік необхідно записати списоком в аргументів функції across():

gapminder %>% 
  group_by(continent) %>% 
  summarise(across(lifeExp:gdpPercap,
                   list(avg = mean, stdev = sd, md = median),
                   .names = "{.fn}_{.col}"))
## # A tibble: 5 x 10
##   continent avg_lifeExp stdev_lifeExp md_lifeExp avg_pop stdev_pop md_pop avg_gdpPercap
##   <fct>           <dbl>         <dbl>      <dbl>   <dbl>     <dbl>  <dbl>         <dbl>
## 1 Africa           48.9          9.15       47.8  9.92e6    1.55e7 4.58e6         2194.
## 2 Americas         64.7          9.35       67.0  2.45e7    5.10e7 6.23e6         7136.
## 3 Asia             60.1         11.9        61.8  7.70e7    2.07e8 1.45e7         7902.
## 4 Europe           71.9          5.43       72.2  1.72e7    2.05e7 8.55e6        14469.
## 5 Oceania          74.3          3.80       73.7  8.87e6    6.51e6 6.40e6        18622.
## # ... with 2 more variables: stdev_gdpPercap <dbl>, md_gdpPercap <dbl>

2.3.11 dplyr::count()

Якщо нам необхідно порахувати кількість значень в групі, то це можна зробити за допомогою group_by() та summarise(). Підрахуємо кількість країн на кожному континенті у наборі даних. Оскільки для кожної країни є 12 спостережень (з 1952 по 2007 рік кожні 5 років), необхідно враховувати тільки унікальні значення - для таких випадків є функція distinct():

gapminder %>% 
  group_by(continent) %>% 
  distinct(country) %>% # унікальні значення
  summarise(n = n())
## # A tibble: 5 x 2
##   continent     n
##   <fct>     <int>
## 1 Africa       52
## 2 Americas     25
## 3 Asia         33
## 4 Europe       30
## 5 Oceania       2

Але є простіший варіант - через функцію count():

gapminder %>% 
  count(continent) %>% 
  mutate(n = n / 12) # поправка на к-ть років
## # A tibble: 5 x 2
##   continent     n
##   <fct>     <dbl>
## 1 Africa       52
## 2 Americas     25
## 3 Asia         33
## 4 Europe       30
## 5 Oceania       2

2.3.12 dplyr::case_when()

Яка маніпуляція з даними обходиться без конструкції “if - else?” В базовому синтаксисі R є функція ifelse() але я пропоную користуватися векторизованим варіантом з dplyr case_when(). Для прикладу давайте створимо нову номінативну змінну, яка буде оцінювати середню очікувану тривалість життя в форматі “низька-середня-висока”:

gapminder %>% 
  group_by(country) %>% 
  summarise(mean_life_exp = mean(lifeExp)) %>% 
  mutate(
    bin_life = case_when(
      mean_life_exp > 70 ~ "hight", # умова 1
      mean_life_exp > 50 & mean_life_exp <= 70 ~ "medium", # умова 2
      TRUE ~ "low" # для всіх інших випадків
    )
  )
## # A tibble: 142 x 3
##    country     mean_life_exp bin_life
##    <fct>               <dbl> <chr>   
##  1 Afghanistan          37.5 low     
##  2 Albania              68.4 medium  
##  3 Algeria              59.0 medium  
##  4 Angola               37.9 low     
##  5 Argentina            69.1 medium  
##  6 Australia            74.7 hight   
##  7 Austria              73.1 hight   
##  8 Bahrain              65.6 medium  
##  9 Bangladesh           49.8 low     
## 10 Belgium              73.6 hight   
## # ... with 132 more rows

2.3.13 Обертання даних: pivot()

Бувають випадки, коли дані приходять в “неохайному,” незручному форматі:

  • одна змінна розташована у декількох стовпчиках.

  • одне спостереження може бути розкидане по кільком рядкам.

Типовим прикладом таких даних можуть бути дані Всесвітнього банку. Давайте завантажимо їх і подивимось, як привести їх до “охайних.” В таких випадках нам допоможуть функції pivot_longer() та pivot_wider() з пакету tidyr (частина tidyverse).

В якості прикладу завантажимо інформацію по показнику Прямі іноземні інвестиції (Foreign direct investment, net (BoP, current US$)).

Дані можна завантажити з мого репозитарію GitHub: прямі іноземні інвестиції, дані Всісвітного банку. Давайте переглянемо їх 2.1.
Перегляд перших значень набору даних Всесвытнього банку

Рисунок 2.1: Перегляд перших значень набору даних Всесвытнього банку

На що звертаємо увагу:

  • Перші 4 рядочки неінформативні.

  • Дані зберігаються у “широкому форматі”: змінна року розташована горизонтально, що може ускладнити аналіз.

Завантажимо дані і одразу пропустимо перші 4 рядки за допомогою аргументу skip = 4:

fdi <- read_csv("https://raw.githubusercontent.com/Aranaur/datasets/main/datasets/world_bank/Foreign_direct_investment/FDI.csv",
                skip = 4)

fdi
## # A tibble: 266 x 66
##    `Country Name` `Country Code` `Indicator Name` `Indicator Code` `1960` `1961` `1962`
##    <chr>          <chr>          <chr>            <chr>             <dbl>  <dbl>  <dbl>
##  1 Aruba          ABW            Foreign direct ~ BN.KLT.DINV.CD       NA     NA     NA
##  2 Africa Easter~ AFE            Foreign direct ~ BN.KLT.DINV.CD       NA     NA     NA
##  3 Afghanistan    AFG            Foreign direct ~ BN.KLT.DINV.CD       NA     NA     NA
##  4 Africa Wester~ AFW            Foreign direct ~ BN.KLT.DINV.CD       NA     NA     NA
##  5 Angola         AGO            Foreign direct ~ BN.KLT.DINV.CD       NA     NA     NA
##  6 Albania        ALB            Foreign direct ~ BN.KLT.DINV.CD       NA     NA     NA
##  7 Andorra        AND            Foreign direct ~ BN.KLT.DINV.CD       NA     NA     NA
##  8 Arab World     ARB            Foreign direct ~ BN.KLT.DINV.CD       NA     NA     NA
##  9 United Arab E~ ARE            Foreign direct ~ BN.KLT.DINV.CD       NA     NA     NA
## 10 Argentina      ARG            Foreign direct ~ BN.KLT.DINV.CD       NA     NA     NA
## # ... with 256 more rows, and 59 more variables: `1963` <dbl>, `1964` <dbl>,
## #   `1965` <dbl>, `1966` <dbl>, `1967` <dbl>, `1968` <dbl>, `1969` <dbl>,
## #   `1970` <dbl>, `1971` <dbl>, `1972` <dbl>, `1973` <dbl>, `1974` <dbl>,
## #   `1975` <dbl>, `1976` <dbl>, `1977` <dbl>, `1978` <dbl>, `1979` <dbl>,
## #   `1980` <dbl>, `1981` <dbl>, `1982` <dbl>, `1983` <dbl>, `1984` <dbl>,
## #   `1985` <dbl>, `1986` <dbl>, `1987` <dbl>, `1988` <dbl>, `1989` <dbl>,
## #   `1990` <dbl>, `1991` <dbl>, `1992` <dbl>, `1993` <dbl>, `1994` <dbl>, ...

Використаємо функцію pivot_longer() для того щоб змінна року була окремим стовпчиком:

fdi_longer <- fdi %>% 
  # змінимо формат назв стовпчиків
  set_names(names(.) %>% str_to_lower() %>%  str_replace(" ", "_")) %>% 
  # приберемо зайві
  select(-c(indicator_name, indicator_code)) %>% 
  # обертаємо дані
    pivot_longer(cols = -c(country_name, country_code), # використовуємо всі 
                                                        # крім country_name, country_code
               names_to = "year", # назва нового стовпчика 
                                  # (роки з початкового набору даних)
               values_to = "fdi") %>% # назва нового стовпчика 
                                      # (значення ПІІ до кожного року)
  drop_na() # приберемо пропущені значення

fdi_longer
## # A tibble: 6,473 x 4
##    country_name country_code year          fdi
##    <chr>        <chr>        <chr>       <dbl>
##  1 Aruba        ABW          1990  -130502793.
##  2 Aruba        ABW          1991  -184748603.
##  3 Aruba        ABW          1992    36983240.
##  4 Aruba        ABW          1993    17932961.
##  5 Aruba        ABW          1994    73184358.
##  6 Aruba        ABW          1995     5530726.
##  7 Aruba        ABW          1996   -84134078.
##  8 Aruba        ABW          1997  -197597765.
##  9 Aruba        ABW          1998   -82178771.
## 10 Aruba        ABW          1999  -469329609.
## # ... with 6,463 more rows

З таким набором даних буде працювати значно зручніше.

Для прикладу роботи функції pivot_wider() повернемося до датасету gapminder:

gapminder
## # A tibble: 1,704 x 6
##    country     continent  year lifeExp      pop gdpPercap
##    <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
##  1 Afghanistan Asia       1952    28.8  8425333      779.
##  2 Afghanistan Asia       1957    30.3  9240934      821.
##  3 Afghanistan Asia       1962    32.0 10267083      853.
##  4 Afghanistan Asia       1967    34.0 11537966      836.
##  5 Afghanistan Asia       1972    36.1 13079460      740.
##  6 Afghanistan Asia       1977    38.4 14880372      786.
##  7 Afghanistan Asia       1982    39.9 12881816      978.
##  8 Afghanistan Asia       1987    40.8 13867957      852.
##  9 Afghanistan Asia       1992    41.7 16317921      649.
## 10 Afghanistan Asia       1997    41.8 22227415      635.
## # ... with 1,694 more rows

Перевернемо його у широкий формат:

gapminder_wide <- gapminder %>% 
  pivot_wider(names_from = year,
              values_from = c(lifeExp:gdpPercap))

gapminder_wide
## # A tibble: 142 x 38
##    country   continent lifeExp_1952 lifeExp_1957 lifeExp_1962 lifeExp_1967 lifeExp_1972
##    <fct>     <fct>            <dbl>        <dbl>        <dbl>        <dbl>        <dbl>
##  1 Afghanis~ Asia              28.8         30.3         32.0         34.0         36.1
##  2 Albania   Europe            55.2         59.3         64.8         66.2         67.7
##  3 Algeria   Africa            43.1         45.7         48.3         51.4         54.5
##  4 Angola    Africa            30.0         32.0         34           36.0         37.9
##  5 Argentina Americas          62.5         64.4         65.1         65.6         67.1
##  6 Australia Oceania           69.1         70.3         70.9         71.1         71.9
##  7 Austria   Europe            66.8         67.5         69.5         70.1         70.6
##  8 Bahrain   Asia              50.9         53.8         56.9         59.9         63.3
##  9 Banglade~ Asia              37.5         39.3         41.2         43.5         45.3
## 10 Belgium   Europe            68           69.2         70.2         70.9         71.4
## # ... with 132 more rows, and 31 more variables: lifeExp_1977 <dbl>,
## #   lifeExp_1982 <dbl>, lifeExp_1987 <dbl>, lifeExp_1992 <dbl>, lifeExp_1997 <dbl>,
## #   lifeExp_2002 <dbl>, lifeExp_2007 <dbl>, pop_1952 <int>, pop_1957 <int>,
## #   pop_1962 <int>, pop_1967 <int>, pop_1972 <int>, pop_1977 <int>, pop_1982 <int>,
## #   pop_1987 <int>, pop_1992 <int>, pop_1997 <int>, pop_2002 <int>, pop_2007 <int>,
## #   gdpPercap_1952 <dbl>, gdpPercap_1957 <dbl>, gdpPercap_1962 <dbl>,
## #   gdpPercap_1967 <dbl>, gdpPercap_1972 <dbl>, gdpPercap_1977 <dbl>, ...

В переважній більшості випадків вам знадобиться довгий формат даних. Але при потребі зберігання великих файлів - краще зберігати у широкому форматі, оскільки це збереже місце на диску.

Збережемо результати широкого і довго формату набору даних gapminder та перевіремо їх розмір:

write_csv(gapminder, "docs/data/gapminder.csv")
write_csv(gapminder_wide, "docs/data/gapminder_wide.csv")

fs::file_size("docs/data/gapminder.csv")
## 80.2K
fs::file_size("docs/data/gapminder_wide.csv")
## 47.3K

Широкий формат в 1.7 рази менший.