Треки
/
R
R
/
Вправи
/
Дерева та тиббл-випробування
Дерева та тиббл-випробування

Дерева та тиббл-випробування

Навчальна вправа

Вступ

В інших частинах силабусу ми бачили різні типи даних із різними властивостями.

  • Атомарні вектори одновимірні й однорідні за типом.
  • Списки одновимірні, а їхні елементи можуть мати різні типи.
  • Матриці та масиви багатовимірні й однорідні.

У цій концепції ми розглянемо способи зберігати багатовимірні різнорідні дані. На практиці більшість реальних даних має саме такий вигляд, тож тепер ми дістаємося до самої суті того, як R використовують (переважно) на практиці.

Різновиди датафреймів

За десятиліття R додав чимало типів даних для роботи з табличними даними.

У цьому силабусі ми зосередимося переважно на tibble, але корисно знати й про альтернативи.

data.frame

У Base R data.frame - це list, елементами якого є vectors однакової довжини. Це можна уявити як прямокутну таблицю даних, у якій кожен стовпець однорідний, а кожен рядок може (і зазвичай так і робить) містити дані різних типів.

Ось приклад, який це ілюструє:

# create the column vectors
languages <- c("Fortran", "R", "Python", "Julia")
created <- c(1957, 1993, 1991, 2012)
has.syllabus <- c(FALSE, TRUE, TRUE, TRUE)

# join columns to create the dataframe
df <- data.frame(languages, created, has.syllabus)
df
#>   languages created has.syllabus
#> 1   Fortran    1957        FALSE
#> 2         R    1993         TRUE
#> 3    Python    1991         TRUE
#> 4     Julia    2012         TRUE

Маємо стовпець рядків тексту (англ. string), стовпець чисел і стовпець булевих значень (англ. Boolean). У більшому масштабі це інтуїтивний спосіб подати чимало наборів реальних даних.

tibble

Конструкція data.frame застаріла.

Багаторічний досвід, а також зміна того, як використовують R, привели до переосмислення й появи в Tidyverse сучасної альтернативи: tibble.

Порівняно з Base R tibble мають:

  • Інші типові значення, щоб зменшити типові проблеми.
  • Меншу схильність до приведення типів даних під час уведення.
  • Більше й чіткіші повідомлення про помилки.
  • Інші, зазвичай кращі, формати виведення.

Коротко кажучи, tibble прагне «робити менше, а скаржитися більше», що також описують як «ледачий і буркотливий».

Ці типи зазвичай взаємозамінні: будь-яка функція, яка приймає data.frame, прийме й tibble, і навпаки.

Перетворення між типами прості: as_tibble(df) і as.data.frame(tbl).

Для нової роботи tibble, найімовірніше, допоможуть створювати надійніший код. Однак у світі R дуже багато спадкового коду й даних, тож data.frame, імовірно, ще довго буде залишатися поширеним.

# column vectors are same as for data.frame
library(tibble)
tbl <- tibble(languages, created, has.syllabus)
tbl
  # A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE       
#> 2 R            1993 TRUE        
#> 3 Python       1991 TRUE        
#> 4 Julia        2012 TRUE      

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

Робота з tibble

Tibble - основна складова Tidyverse, тож додати їх можна через library(tibble) або library(tidyverse).

Створення tibble

Найпростіше - використати функцію tibble(), щоб поєднати вектори-стовпці, так само як для data.frame(). Приклад цього був у попередньому розділі.

Якщо зручніше вводити значення порядково, для цього є функція tribble().

На практиці є десятки способів створити tibble, адже вони - типовий формат виведення для найрізноманітніших функцій Tidyverse.

Опрацювання tibble

У концепції функціонального програмування ми говорили про бібліотеку purrr для роботи з векторами та списками (одновимірними структурами даних).

Для датафреймів (традиційних чи tibble) відповідна бібліотека - dplyr.

Підмножини

Датафрейми, зокрема tibble, можна розглядати як списки векторів-стовпців, тож індексація списку дає змогу дістати вказаний стовпець.

tbl
# A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE       
#> 2 R            1993 TRUE        
#> 3 Python       1991 TRUE        
#> 4 Julia        2012 TRUE    

tbl$created
#> [1] 1957 1993 1991 2012

Датафрейм можна також індексувати в стилі матриці.

tbl[c(2, 4), 1:2]
  # A tibble: 2 × 2
#>   languages created
#>   <chr>       <dbl>
#> 1 R            1993
#> 2 Julia        2012

У сучасному R з екосистемою Tidyverse функції dplyr зазвичай гнучкіші та зручніші, і саме на них ми зосередимося до кінця цієї концепції.

Операції над стовпцями

Дістати один стовпець можна функцією pull(), вказавши назву або порядковий номер (відʼємні числа рахують справа наліво).

tbl |> pull(created)
#> [1] 1957 1993 1991 2012

Це той самий результат, що й tbl$created, але отриманий функцією, яку зручно використовувати в конвеєрі.

Щоб дістати кілька стовпців, відповідна функція - select(), яка надзвичайно універсальна. Отримати (або відкинути) стовпці за властивостями їхньої назви чи типу.

# Range with position and/or name
tbl |> select(1:created)
  # A tibble: 4 × 2
#>   languages created
#>   <chr>       <dbl>
#> 1 Fortran      1957
#> 2 R            1993
#> 3 Python       1991
#> 4 Julia        2012      

# Use type of column
tbl |> select(where(is.numeric))
  # A tibble: 4 × 1
#>   created
#>     <dbl>
#> 1    1957
#> 2    1993
#> 3    1991
#> 4    2012

Можна поєднувати кілька умов за допомогою булевих операторів &, | і ! (і, або, не).

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

# pick a subset of columns
starwars |> 
  select(name | ends_with("color")) |> 
  head(5)
  # A tibble: 5 × 4
#>   name           hair_color skin_color  eye_color
#>   <chr>          <chr>      <chr>       <chr>    
#> 1 Luke Skywalker blond      fair        blue     
#> 2 C-3PO          NA         gold        yellow   
#> 3 R2-D2          NA         white, blue red      
#> 4 Darth Vader    none       white       yellow   
#> 5 Leia Organa    brown      light       brown    

Операції над рядками

Дістати рядки, що відповідають певним умовам, можна функцією filter(), а виключити їх - функцією filter_out().

starwars |> 
  select(name:mass) |> 
  filter(between(height, 150, 165) & !is.na(mass))
  # A tibble: 4 × 3
#>   name               height  mass
#>   <chr>               <int> <dbl>
#> 1 Leia Organa           150    49
#> 2 Beru Whitesun Lars    165    75
#> 3 Nien Nunb             160    68
#> 4 Ben Quadinaros        163    65

Умови фільтрування можуть бути як завгодно складними, але завжди ґрунтуються на вмісті рядків.

Якщо відомі номери рядків, можна скористатися різними функціями slice().

starwars |> 
  select(name | homeworld) |> 
  slice(20:25)
  # A tibble: 6 × 2
#>   name             homeworld
#>   <chr>            <chr>    
#> 1 Palpatine        Naboo    
#> 2 Boba Fett        Kamino   
#> 3 IG-88            NA       
#> 4 Bossk            Trandosha
#> 5 Lando Calrissian Socorro  
#> 6 Lobot            Bespin   

Змінювання tibble

Перше застереження: типова поведінка copy-on-modify означає, що початковий tibble зазвичай залишається незмінним.

Більшість змін застосовуються до стовпців.

Назви стовпців можна змінити функцією rename(newname = oldname) або rename_with(), щоб застосувати функцію. Звернімо увагу на синтаксис усередині rename(). Вміст стовпця oldname привʼязується до назви newname - звідси й порядок.

Порядок стовпців можна змінити функцією relocate(). Указані стовпці типово переміщуються на найлівіші позиції, але для точнішого розташування можна використати аргумент .before або .after.

sw <- starwars |> select(name:species) |> slice(1:4)
sw
  # A tibble: 4 × 11
#>   name           height  mass hair_color skin_color  eye_color birth_year sex   gender    homeworld species
#>   <chr>           <int> <dbl> <chr>      <chr>       <chr>          <dbl> <chr> <chr>     <chr>     <chr>  
#> 1 Luke Skywalker    172    77 blond      fair        blue            19   male  masculine Tatooine  Human  
#> 2 C-3PO             167    75 NA         gold        yellow         112   none  masculine Tatooine  Droid  
#> 3 R2-D2              96    32 NA         white, blue red             33   none  masculine Naboo     Droid  
#> 4 Darth Vader       202   136 none       white       yellow          41.9 male  masculine Tatooine  Human  

sw |> relocate(c(species, homeworld), .after = name)
  # A tibble: 4 × 11
#>   name           species homeworld height  mass hair_color skin_color  eye_color birth_year sex   gender   
#>   <chr>          <chr>   <chr>      <int> <dbl> <chr>      <chr>       <chr>          <dbl> <chr> <chr>    
#> 1 Luke Skywalker Human   Tatooine     172    77 blond      fair        blue            19   male  masculine
#> 2 C-3PO          Droid   Tatooine     167    75 NA         gold        yellow         112   none  masculine
#> 3 R2-D2          Droid   Naboo         96    32 NA         white, blue red             33   none  masculine
#> 4 Darth Vader    Human   Tatooine     202   136 none       white       yellow          41.9 male  masculine

Для більших змін mutate() дає змогу:

  • Створювати нові стовпці, які є функціями наявних стовпців.
  • Замінювати наявний стовпець, створивши новий стовпець із тією самою назвою.
  • Видаляти стовпець, присвоївши йому значення NULL.

Очевидно, mutate() потужна, часом заплутана, і саме через неї варто бути дуже вдячними за copy-on-modify.

Немає очевидної причини цікавитися індексом маси тіла персонажів «Зоряних воєн», але про всяк випадок:

starwars |> 
  select(c(name, species, height, mass)) |> 
  mutate(BMI = mass / (height / 100)^2) |> 
  head(4)
  # A tibble: 4 × 5
#>   name           species height  mass   BMI
#>   <chr>          <chr>    <int> <dbl> <dbl>
#> 1 Luke Skywalker Human      172    77  26.0
#> 2 C-3PO          Droid      167    75  26.9
#> 3 R2-D2          Droid       96    32  34.7
#> 4 Darth Vader    Human      202   136  33.3

Операції над рядками рідше застосовують для змінювання окремих tibble (обʼєднання кількох tibble розглянемо в одній із наступних концепцій).

Один виняток: arrange() сортує рядки за значеннями в одному чи кількох стовпцях.

tbl
#>       A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE       
#> 2 R            1993 TRUE        
#> 3 Python       1991 TRUE        
#> 4 Julia        2012 TRUE  

tbl |> arrange(languages)
#>       A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE       
#> 2 Julia        2012 TRUE        
#> 3 Python       1991 TRUE        
#> 4 R            1993 TRUE     

Вказівки

У великому вишневому саду зазвичай є невеликий запас зайвих дерев, які хотілося б продати як деревину. Це допомагає підтримувати сад здоровим і водночас дає додатковий дохід. Сад попросив нас допомогти написати код, який спростить вибір дерев, коли телефонує потенційний покупець.

Note

Вбудована бібліотека datasets містить багато невеликих демонстраційних наборів даних, і в цій вправі використовується набір даних trees. Щоб звернутися до набору даних, достатньо використати назву trees.

library(datasets) # loading is optional

trees |> head(3)
#>    A data.frame: 3 × 3 	
#>   Girth	Height	Volume
#>	 <dbl>	 <dbl>	 <dbl>
#> 1   8.3	    70	  10.3  
#> 2   8.6	    65	  10.3
#> 3   8.8	    63	  10.2

Звернімо увагу на три стовпці: Girth, Height і Volume.

Спочатку набір даних має форму data.frame, але за бажанням можна працювати з ним як із tibble.

1. Перейменувати стовпець

На жаль, сталася канцелярська помилка, яку так і не виправили, і один стовпець у наборі даних має назву Girth, хоча насправді це Diameter.

Визначте data.frame/tibble із назвою tree_data, який виправляє назву стовпця Girth у trees на Diameter.

head(tree_data, 3)
#>     A tibble: 3 × 3 
#> Diameter	 Height	Volume
#>    <dbl>	  <dbl>	 <dbl>
#>      8.3	     70	  10.3
#>      8.6	     65	  10.3
#>      8.8	     63	  10.2

2. Додати стовпці обхвату й ваги

Обхват (тобто довжина кола) усе ще лишається корисною мірою, як і вага деревини. Обхват легко обчислити за формулою πD, де D - діаметр. Вага всієї деревини обчислюється за формулою ρV, де ρ = 35 - густина вишневої деревини, а V - обʼєм деревини.

Визначте функцію girth_n_weight(data, rnd_digits), яка приймає датафрейм і числові аргументи. Функція повинна повернути новий датафрейм із двома додатковими стовпцями: Girth і Weight, округленими до відповідної кількості знаків, заданої в rnd_digits.

girth_n_weight(tree_data, 1) |> head(3)
#>           A tibble: 3 × 5 
#> Diameter  Height	 Volume	 Girth	Weight
#>    <dbl>	  <dbl>	  <dbl>	 <dbl>	 <dbl>
#>      8.3	     70	   10.3	  26.1	 360.5
#>      8.6	     65	   10.3	  27.0	 360.5
#>      8.8	     63	   10.2	  27.6	 357.0

3. Копія набору даних для саду

Для кожного потенційного покупця сад зберігає окрему копію набору даних, щоб допомогти з продажем. У цьому наборі найважливіші стовпці, Weight і Height, переміщено на початок, а рядки відсортовано за Weight.

Визначте функцію orchard_copy(data, important_cols), яка приймає датафрейм. Вона повинна повернути новий датафрейм, у якому Weight і Height переміщено на початок, а рядки відсортовано за Weight.

tree_data |> girth_n_weight() |> orchard_copy() |> head(3)
#>            A tibble: 3 × 5 
#> Weight	Height	Diameter	Volume	Girth
#>  <dbl>	 <dbl>	   <dbl>	 <dbl>	<dbl>
#>    357	    63	       9	    10	   28
#>    360	    70	       8	    10	   26
#>    360	    65	       9	    10	   27

4. Копія набору даних для покупця

Кожен потенційний покупець отримає власну версію набору даних відповідно до своїх уподобань. Для цього покупець може вказати: мінімальну висоту, максимальну висоту та максимальну вагу окремого дерева.

Визначте функцію customer_copy(data, min_height, max_height, max_weight). Поверніть копію датафрейму для покупця зі стовпцями Height, Weight, Diameter і Girth та рядками, що задовольняють задані обмеження.

tree_data |> 
    girth_n_weight(1) |> 
    orchard_copy() |>
    customer_copy(65, 75, 1500) |> 
    head(3)
#>        A tibble: 3 × 4 
#> Height	Weight	Diameter	Girth
#>  <dbl>	 <dbl>	   <dbl>	<dbl>
#>     70	 360.5	     8.3	 26.1
#>     65	 360.5	     8.6	 27.0
#>     66	 546.0	    11.0	 34.6
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці
R Exercism

Час розпочати Дерева та тиббл-випробування?

Зареєструйтеся на Exercism, щоб вивчати й опановувати R, а також 21 концепція111 вправ та справжнє наставництво від людей, і все це безкоштовно.