Statistical Oracle — 04. Робота з даними в R






       

Д. Шабанов, М. Кравченко. «Статистичний оракул»: аналіз даних в зоології та екології

Тема 3. Використання мови R

Тема 4. Робота з даними в R

Тема 5. Візуалізація даних у Statistica

«Статистичний оракул»-03

«Статистичний оракул»-04

«Статистичний оракул»-05

 

Тема 4. Робота з даними в R

4.1. Логічні та арифметичні операції в R

Для непідготовленого читача текст цього розділу нашого підручнику може здатися непридатним для читання: він буде переважно складатися з R-діалогів. Так, спочатку, до отримання досвіду, читати такі діалоги важко. Але сенс нашого курсу саме в тому, щоб навчити студентів працювати з R. Погляд досвідченого R-користувача під час роботи з R швидко вихоплює команди та відповіді на них системи. Саме це показано в R-діалогах в нашому підручнику. Шановні читачі, коли ваші очі (а насправді — ваш мозок) навчиться легко читати діалоги з R, можна буде зробити висновок, що ви суттєво просунулися у оволодінні цією мовою.

До речі, як розібратися в таких R-діалогах, які тут наведені, краще? Один зі шляхів (у разі роботи з електронним підручником) — виділити та скопіювати текст з рамки. Викинути відповіді системи на введені команди (рядки з ними не починаються зі знаку >) — залишаться самі команди. Викинути знаки > на початку строки — їх вставив RStudio, щоб продемонструвати, що отримав ці вирази як команди. Вставити залишок в редактор скриптів RStudio, виділити потрібне та нажати Ctrl+Enter або кнопку Run (зелену стрілку). Вийшло так само, як у нашому прикладі? Що можна змінити? Як застосувати це до проблеми, яку ви вирішуєте? 

Ми розглянемо ще кілька способів роботи з фреймом PelophylaxExample, а потім перейдемо до більш послідовного огляду можливостей R. У певному сенсі, ми використовуємо «перевернуту» логіку знайомства з R.  Ми почали з прикладу на рис. 3.2.1, де показали, як R читає файл з даними, проводить розрахунки, створює і зберігає графіки — і усе це просто завдяки реалізації досить простого скрипту. Після простого прикладу з даними, що уведені просто в вікні R, ми створили фрейм PelophylaxExample і розглянули деякі засоби роботи з ним. Це стало приводом обговорити типи об'єктів і даних в R. Далі буде логічно більш послідовно пройти по можливостях R та розглянути логічні та арифметичні операції, а також деякі особливості векторів, та після цього застосувати їх до роботи з нашим фреймом. Автори сподіваються, що така «перевернута» логіка знайомства з R зробить більш зрозумілим, навіщо можна використовувати ті або інші особливості цього потужного засобу дослідження даних. Така логіка не підходить для довідника, але, сподіваємося, полегшить знайомство з R у разі, якщо студент послідовно пройде пункт за пунктом і тему за темою даного підручника.

Для роботи з файлами даних важливо навчитися застосовувати логічні конструкції. Їх приклади показані в R-діалозі 4.1.1. Забігаючи наперед, вкажемо, що логічні конструкції, наприклад, допомагають вибирати дані, що відповідають певним умовам. 

R-діалог 4.1.1.  Приклади логічних конструкцій в R

> a <- 2; b <- 5 # Створюємо два об'єкти
> a < b # Відношення менше - більше
[1] TRUE
> a > b
[1] FALSE
> a <= b
[1] TRUE
> # Якщо написати a = b, R перевизначить a за b (сприйме як a <- b)
> a == b # Перевірка рівності ==, нерівності !=
[1] FALSE
> a != b
[1] TRUE
> # Знак & означає "водночас", а | — "або", "хоча б один з": 
> a & b < 4
[1] FALSE
> a & b <= 5 # «a та b більше або дорівнює 5»
[1] TRUE
> a | b == 2 # «хоча б одне з a та b дорівнює 2»
[1] TRUE
> a<3 & b>3 # Поєднання двох умов
[1] TRUE
> a<3 & b>5
[1] FALSE
> 

 

І при використанні логічних умов, і, звісно, в безлічі інших ситуацій виконуються арифметичні операції. Деякі з них тривіальні, деякі потребують окремого згадування (R-діалог 4.1.2).

R-діалог 4.1.2. Деякі арифметичні операції: залишки та цілі частини при діленні, піднесення до ступеня, логарифми  

> a <- 2; b <- 5 # Створюємо два об'єкти
> b %% a # Залишок від ділення
[1] 1
> b %/% a # Ціла частина при діленні
[1] 2
> a ** b # Піднесення до ступеня (можна також ^)
[1] 32
> sqrt(4) # Квадратний корінь
[1] 2
> 1000 ** (1/3)
[1] 10
> round(a/b)
[1] 0
> round(a/b, 2) # Округлення до вказаної кількості знаків
[1] 0.4
> abs(a - b) # Абсолютне значення 
[1] 3
> log10(1000) # Десятковий логарифм
[1] 3
> log(4, base = 2) # Логарифм за вказаною основою
[1] 2
> 

 

4.2. Пропущені значення

У мові R використовується специфічне позначення NA, яке використовується для пропущених значень. Згадайте стовпці з вимірами частин тіла жаб у файлі PelophylaxExamples. В таблиці 2.2.1 нема незаповнених комірок. Але, припустимо, певне значення нам невідомо. Що робити? Ні у якому разі не використовувати значення 0: це буде означати, що ми виміряли певну частину тіла та зареєстрували її нульову довжину. Слід показати, що це — саме відсутність інформації. Найпростіше рішення — залишити відповідну комірку таблиці даних пустою. В R цьому відповідає розміщення у цій «комірці» позначення NA — пропущених даних. До речі, NA — не текстове значення, брати його позначення у лапки не треба. Це — саме відсутність значень.

Ми можемо прочитати файл PelophylaxExamples.csv так, щоб створити файл з пропущеними значеннями. Використаємо для цього аргумент na.strings до функції read.csv(). Створимо для цього іншій об'єкт, ніж наш основний фрейм PE. Наприклад, якщо ми використаємо команду PE1 <- read.csv('PelophylaxExamples.csv', sep = ";", dec = ",", na.strings = '"Dnipro"), функція читання файлів викине згадування про басейн Дніпра. Втім, цікавіше буде, якщо ми втратимо якісь числові значення. Наприклад, викинемо значення 42, що зустрічається у файлі двічі: це довжина вторинної голені у двох жаб, одна з яких — перша (R-діалог 4.2.1). 

До речі, якщо ви читаєте *.csv-файл, де є пусті комірки, слід пояснити R, що ці комірки слід розуміти як NA: DATAFRAMENAME <- read.csv('FILENAME', sep = "SIGN", dec = "SIGN", na.strings = '"").

R-діалог 4.2.1. Пропущені значення та деякі операції з ними

> # Штучно створюється фрейм, що містить пропущені значення:
> PE1 <- read.csv('PelophylaxExamples.csv', sep = ";", dec = ",", na.strings = 42)
> str(PE1$Ci)
 int [1:57] NA 41 37 38 45 37 37 34 31 33 ...
> complete.cases(PE1) # Функція, що відбирає рядки, в яких нема пропущених значень:
 [1] FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  
[15] TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  
[29] TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  FALSE TRUE  TRUE
[33] TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  
[47] TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
> sum(complete.cases(PE1)) # Можна порахувати, адже логічному значенню TRUE відповідає 1
[1] 55
> sum(!complete.cases(PE1)) # Підрахунок рядків, які не є повними
[1] 2
> sum(is.na(PE1)) # А це — просто підрахунок кількості пропущених значень у файлі:
[1] 2
> mean(PE1$Ci) # Розрахувати середнє (і багато іншого) з NA неможливо:
[1] NA
> # Але можливо додати аргумент (від NA remove), що примушує ігнорувати пропущені значення:
> mean(PE1$Ci, na.rm = TRUE)
[1] 37.65455
> 

 

Зверніть увагу: команда sum(is.na(OBJECT)) підраховує кількість пропущених значень, а sum(!complete.cases(OBJECT))  — кількість рядків, в яких є відсутні значення. Чи завжди команди sum(!complete.cases(OBJECT))  і  sum(is.na(OBJECT))  дадуть однаковий результат? Ні. У разі, якщо хоча б в одному рядку є більш, ніж одне пропущене значення, кількість неповних рядків буде меншою за загальну кількість пропущених значень. 

А як загалом убрати з фрейму PE1 рядки, де є пропущені значення? Це стане зрозуміло пізніше (R-діалог 4.5.2)...

 

4.3. Вектори, їх створення та вибір певних елементів

Як ви пам'ятаєте, вектор є базовим для R типом об'єктів. На прикладі векторів можна пояснити властивості об'єктів та функцій R, що допоможуть при роботі і з іншими об'єктами. 

R-діалог 4.3.1.  Створення векторів і деякі операції з ними

> # Короткі команди можна записувати в один рядок через кому з крапкою
> # Щоб вивести в консолі створений елемент, можна узяти команду у дужки
> re <- rep(c(1, 0, NA), 2); ad <- c(1, 2, 3, 4); (v <- c(re, ad))
 [1]  1  0 NA  1  0 NA  1  2  3  4
> unique(v) # Визначимо унікальні елементи вектора v
[1]  1  0 NA  2  3  4
> table(v) # Частоти елементів вектора v
v
0 1 2 3 4 
2 3 1 1 1 
> v1 <- v + 1 > # Можна виконувати аріфметичні операції
> v1 # Елементи NA при цьому не змінюються:
[1]  2  1 NA  2  1 NA  2  3  4  5
> (v2 <- v * 2)
[1]  2  0 NA  2  0 NA  2  4  6  8
> v2 > 5 # Можна виконувати логічні операції
[1] FALSE FALSE    NA FALSE FALSE    NA FALSE FALSE  TRUE  TRUE
> v2[v2 > 5] # Можна вибірати елементи завдяки логічним операціям
[1] NA NA  6  8
> v2[v2 > 5 & v2 < 7] # Можна поєднувати різні операції
[1] NA NA  6
>

Як можете побачити, команда rep() забезпечує повторення певної послідовності. Крім неї, для створення векторів може бути корисною команда seq(), що генерує певну арифметичну послідовність. Наприклад, команда se <- seq(from = 1, to = 24, by = 2), або тотожна їй більш скорочена команда  se <- seq(1, 24, 2), створюють вектор se, який починається з 1 і складається з елементів, кожен з яких більше попереднього на 2 (останнім буде значення 23, зрозуміло, чому?).

Функція unicue() убирає повтори та лишає унікальні значення; функція table() виводить розподіл значень в об'єкті, який вона аналізує.

Основні характеристики вектора — його тип mode() та довжина lenght(). За допомогою функції lenght() можна не лише узнати довжину вектора, а й змінити її. Команда length(VECTOR) <- 10 змінить довжину вектора до вказаної кількості елементів (якщо їх було більше — відріже зайві; якщо було замало — додасть NA). З використанням вектора v, що було створено в R-діалозі 4.3.1, розглянемо ще деякі способи роботи з векторами (R-діалог 4.3.2). 

R-діалог 4.3.2.  Індексація векторів та позбавлення від NA

> v
 [1]  1  0 NA  1  0 NA  1  2  3  4
> v[2] # Щоб отримати елемент вектору, треба вказати номер цього елементу:
[1] 0
> v[2:4] # Щоб отримати кілька елементів потрібен вектор (2:4 — це вектор!):
[1]  0 NA  1
> # Команда v[2, 5, 11] виконана не буде; R сприйме 2, 5 і 11 як координати
> v[c(2, 5, 11)] # Можна створити вектор функцією c()
[1]  0  0 NA
> v%%2 == 1 # Можна використовувати логічні конструкції
[1]  TRUE FALSE    NA  TRUE FALSE    NA  TRUE FALSE  TRUE FALSE
> is.na(v) # Така конструкція вибирає пропущені значення
[1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
> (v3 <- v[!is.na(v)]) # А така — убирає пропущені значення
[1] 1 0 1 0 1 2 3 4
>

 

У попередньому пункті ми обговорювали пропущені значення на прикладі фрейма PE1. Чи можна убрати з нього пропущені рядки таким же чином, як із вектора? Ні, оскільки фрейм є більш складним об'єктом. Як це зробити, стане зрозумілим після того, як ми обговоримо роботу з об'єктами, що мають більше вимірів, ніж один.

Створені вектори, природно, можна редагувати. У попередньому R-діалозі ми створили вектор v3. Змінимо його (R-діалог 4.3.3).

R-діалог 4.3.3. Додавання та убирання елементів з вектора; найменьші, найбільші, повторювані значення 

> v3 
[1] 1 0 1 0 1 2 3 4
> ap <- c(8, 8, 2) # Те, що додається до вектора, слід перетворити у інший вектор
> (v4 <- append(v3, ap)) # Завдяки дужкам створений вектор виводиться на консоль
 [1] 1 0 1 0 1 2 3 4 8 8 2
> rem <- c(2:5, 10) # Щоб щось убрати, також слід створити вектор
> (v5 <- v4[-rem])
[1] 1 2 3 4 8 2
> which.max(v3) # Вказує номер найбільшого елементу у векторі
[1] 8
> which.min(v3) # Вказує номер найменшого елементу у векторі
[1] 2
> v3[c(7, 3, 5, 2, 7, 7, 7)] # Задає (вектором!) порядок виводу елементів
[1] 3 1 1 0 3 3 3
> v3[9:1] # Номеру відсутнього елемента відповідатиме NA
[1] NA  4  3  2  1  0  1  0  1
> (v3[c(-2, -3, -5)]) # Від'ємні індекси — спосіб видалити елемент
[1] 1 0 2 3 4
> duplicated(v4) # Пошук значень, що повторюються:
[1] FALSE FALSE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE  TRUE  TRUE
> (v5 <- v4[!duplicated(v4)]) # Повторювані значення можна убрати
[1] 1 0 2 3 4 8
> v6 <- unique(v4) # Інший спосіб зробити те ж саме
> v5 == v6 # Перевірка
[1] TRUE TRUE TRUE TRUE TRUE TRUE
> 

 

У тому разі, якщо значення у векторі повторюються, функції which.max() та which.min() вкажуть номер першого з найбільшіх або найменших значень. Приклад цього можна побачити у R-діалозі 4.3.3 у випадку застосування функції which.min(): найменше значення, 0, розташовано у векторі v3 на другій та четвертій позиції; відповіддю на команду  which.min(v3)  є 2. 

У багатьох випадках корисними є різні засоби сортування елементів (R-діалог 4.3.4). 

R-діалог 4.3.4. Різні способи сортування, впорядкування та ранжування елементів

> v5 # Будемо екпериментувати з цим вектором (R-діалог 4.3.3)
[1] 1 2 3 4 8 2
> (v6 <- order(v5)) # Вказує номери елементів у векторі у порядку збільшення
[1] 1 2 6 3 4 5
> (v7 <- order(v5, decreasing = TRUE)) # А з таким атрибутом — у порядку зменшення
[1] 5 4 3 2 6 1
> (v8 <- sort(v5)) # Сортує елементи вектора у порядку збільшення
[1] 1 2 2 3 4 8
> (v9 <- v5[order(v5)]) # Інакше з тим самим результатом, що у попередньому випадку
[1] 1 2 2 3 4 8
> (v10 <- rank(v5)) # Вказує ранг елементів; для повторюваних елементів ранг є нецілим числом 
[1] 1.0 2.5 4.0 5.0 6.0 2.5
> (v11 <- rank(-v5)) # Зворотний порядок ранжування
[1] 6.0 4.5 3.0 2.0 1.0 4.5
>

Порівняйте функції order() та rank(). У разі, якщо у векторі нема повторюваних елементів, вони дають однаковий результат; якщо такі елементи є, вони будуть оброблені по-різному.

У разі, якщо вектор (або інший об'єкт) є текстовим, для редагування можна використовувати функції sub та gsub. Якщо застосувати ці функції до числового вектора, він перетвориться на текстовий, але ніщо не заважає зробити його знову числовим (R-діалог 4.3.5). 

R-діалог 4.3.5. Приклади заміни символів у векторі

> # Заміна символів в текстових об'єктах:
> (char_obj <- c("comp1", "comp2", "comp3", "comp4"))
[1] "comp1" "comp2" "comp3" "comp4"
> (char_obj1 <- sub("comp", "comp_", char_obj))
[1] "comp_1" "comp_2" "comp_3" "comp_4"
> # Команда sub замінює перший символ, gsub — усі
> (char_obj2 <- gsub("comp", "comp_", char_obj))
[1] "comp_1" "comp_2" "comp_3" "comp_4"
> (num_obj <- 1:5)
[1] 1 2 3 4 5
> (num_obj1 <- sub("2", "3", num_obj))
[1] "1" "3" "3" "4" "5"
> # Об'єкт num_obj1 став текстовим, його слід перетворити на числовий!
> (num_obj1 <- as.numeric(num_obj1))
[1] 1 3 3 4 5
> # До усіх елементів додано те ж саме; це вже у числа не перетворити:
> (num_obj <- paste0(num_obj, "-th"))
[1] "1-th" "2-th" "3-th" "4-th" "5-th"
> 

 

4.4. Імена рядків та стовпців у матрицях та фреймах

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

R-діалог 4.4.1. Приклади роботи з іменами рядків та стовпців в матриці

> ma <- 1:28 # Створюємо вектор
> dim(ma) <- c(4, 7) # Вказуємо розмірність матриці з вектора
> ma # Дивимось, що отримали
     [,1] [,2] [,3] [,4] [,5] [,6] [,7]
[1,]    1    5    9   13   17   21   25
[2,]    2    6   10   14   18   22   26
[3,]    3    7   11   15   19   23   27
[4,]    4    8   12   16   20   24   28
> colnames(ma) # Ця матриця ще не має назв рядків та стовпців
NULL
> rownames(ma)
NULL
> # Ці назви можна задати:
> (colnames(ma) <- c("One", "Two", "Three", "Four", "Five", "Six", "Seven"))
[1] "One"   "Two"   "Three" "Four"  "Five"  "Six"   "Seven"
> (rownames(ma) <- c("First", "Second", "Third", "Fourth"))
[1] "First"  "Second" "Third"  "Fourth"
> dimnames(ma) # Дивимось назви рядків та стовпців водночас
1
[1] "First"  "Second" "Third"  "Fourth"
2
[1] "One"   "Two"   "Three" "Four"  "Five"  "Six"   "Seven"
> ma # Тепер матриця виглядає так
       One Two Three Four Five Six Seven
First    1   5     9   13   17  21    25
Second   2   6    10   14   18  22    26
Third    3   7    11   15   19  23    27
Fourth   4   8    12   16   20  24    28
> 

 

Фрейм даних можна порівняти зі списком з відносно незалежних (таких, що можуть містити різні дані) векторів (R-діалог 4.4.2):

R-діалог 4.4.2. Приклади роботи з іменами рядків та стовпців в фреймі

> df1 <- c("One", "Two", "Three", "Four", "Five", "Six", "Seven")
> df2 <- 8:14 ; df3 <- 15:21; df4 <- 22:28
> (df <- data.frame(df1, df2, df3, df4))  # Створили фрейм
    df1 df2 df3 df4
1   One   8  15  22
2   Two   9  16  23
3 Three  10  17  24
4  Four  11  18  25
5  Five  12  19  26
6   Six  13  20  27
7 Seven  14  21  28
> dimnames(df) # Імена рядків та стовпців
1
[1] "1" "2" "3" "4" "5" "6" "7"
2
[1] "First"  "Second" "Third"  "Fourth"
> df # За необхідності можна використати colnames() та rownames()
  First Second Third Fourth
1   One      8    15     22
2   Two      9    16     23
3 Three     10    17     24
4  Four     11    18     25
5  Five     12    19     26
6   Six     13    20     27
7 Seven     14    21     28
> 

 

4.5. Індексація даних на прикладі фрейму PelophylaxExample

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

R-діалог 4.5.1. 

> PE <- read.csv('PelophylaxExamples.csv', sep = ";", dec = ",")
> PE[1, 6] # У прямих дужках: спочатку рядок, потім стовпчик
[1] female # У випадку фактора будуть вказано його значення та рівні:
Levels: female male
> PE[1, 9] # У випадку числової змінної буде вказано просто значення:
[1] 603 
> PE[2, ] # Якщо не вказувати координату, буде обрано усі значення
         X        Place  East North  Basin    Sex   DNA Genotyp   L Ltc
2 LL_f_562 Chernetchina 35.13 50.05 Dnipro female 13.95      LL 562 187
   Fm   T Dp Ci  Cs
2 266 249 62 41 152
> PE[2:5, c(9, 11, 13)] # Задати координати можна векторами з номерами елементів
    L  Fm Dp
2 562 266 62
3 592 281 79
4 595 285 75
5 602 287 80
> PE[10, c('Sex', 'Genotyp')] # Можна створити вектор з іменами змінних або рядків
      Sex Genotyp
10 female     LLR
> PE[which.max(PE$L), 'L'] # Найбільша зареєстрована у фреймі довжина тіла (L)
[1] 930
> max(PE$L) == PE[which.max(PE$L), 'L'] # Простіший спосіб отримати максимум
[1] TRUE
> PE[which.min(PE$L), 'L'] # Найменше значення змінної L
[1] 479
> PE[PE$Place == 'Zamulivka', c(6, 8)] # Можна використовувати умови!
      Sex Genotyp
14 female     LLR
15 female     LLR
56   male      RR
> PE_m <- subset(PE, Sex=="male") # Можна вибрати лише частину даних
> # Можна використовувати досить складні логічні конструкції:
> PE_f_2n <- subset(PE, Sex=="female" & (Genotyp=="LL" | Genotyp=="LR"| Genotyp=="RR"))
> PE_f_2n[c(4, 8, 12), c(1, 2, 6, 8)] # Подивимось, що вийшло:
          X         Place    Sex Genotyp
5  LL_f_602  Chernetchina female      LL
24 LR_f_877 SuhaGomol`sha female      LR
46 RR_f_701         Lipci female      RR
> 

 

У R-діалозі 4.3.3 пояснено команди which.max() та which.min(). Оскільки вони дозволяють отримати номер елемента, що є найбільшим або найменшим, їх можна використовувати для отримання самих цих значень, такими, наприклад, командами: PE[which.max(PE$L), 'L']. Втім, максимальне та мінімальне значення можна отримати й простішим способом, командами max(PE$L) та min(PE$L).

Зверніть увагу на команду в R-діалозі 4.5.1:  PE[PE$Place == 'Zamulivka, c(6, 8)]. Фактично, це питання: які жаби, за їх статтю та генотипом, походять у досліджуваній вибірці з Замулівки? Перша координата, адреса за рядками, задана тут умовою (походження з певного локалітету), друга координата, адреса за змінними, — задана вектором, який визначає, значення яких ознак слід отримати для випадків, що відповідають заданій умові.

Команда   PART <- subset(DATAFRAME, CONDITION) надає найширші можливості для вибору частини даних з фрейму.  PE_f_2n <- subset(PE, Sex=="female" & (Genotyp=="LL" | Genotyp=="LR" | Genotyp=="RR")) , фактично, задає для R вказівку сформувати об'єкт, у який входять самиці, що мають один з трьох диплоїдних генотипів (LL, LR або RR). 

Раніше (R-діалог 4.2.1) ми створили версію файлу даних, що містила пропущені значення. Ми навчилися убирати пропущені значення з векторів та розраховувати статистики для векторів без врахування пропусків. Під час роботи з матрицями та фреймами досить часто має сенс просто видалити усі рядки, що містять пропущені значення (R-діалог 4.5.2). 

R-діалог 4.5.2. Два способи убрати рядки з пропущеними значеннями з фрейму

> PE1 <- read.csv('PelophylaxExamples.csv', sep = ";", dec = ",", na.strings = 42)
> # Створюємо фрейм, що містить пропущені значення, NA  
> PE2 <- PE1[complete.cases(PE1), ] # Першій спосіб убрати рядки, що містять NA
> PE3 <- na.omit(PE1) # Другий спосіб убрати рядки, що містять NA
> dim(PE1) == dim(PE2) # Отримані результати однакові за розміром
[1] TRUE TRUE
> > sum(PE2 != PE3) # Кількість відмінностей між двома фреймами
[1] 0 # Результат демонструє повну тотожність усіх елементів PE2 і PE3 
> 

 

4.6. Робота зі стовпцями фрейму PelophylaxExample

Важлива частина роботи з даними — створювання, зміни, видалення самих змінних. Розглянемо кілька корисних команд. Перш за все, повернемося до прикладу, що розглядався в пункті 3.4. Там ми створили два вектори: L та Ltc. Щоб об'єднати їх у фрейм даних (припустимо, з назвою temp), слід виконати команду temp <- data.frame(L, Ltc).

Повернемося до фрейму PE. Як ви пам'ятаєте, перший стовпчик в ньому не мав назви. R сам дав йому назву X. До речі, якщо ми введемо команду  PE$X (тобто FRAMENAME$VARIABLENAME), R виведе у консолі усі 57 кодів особин. У такому разі цей стовпчик логічно перейменувати:  names(PE)[1]  <- "Code". Цифра у квадратних дужках — це номер стовпця; так ми повідомлюємо, що змінюємо назву саме першої змінної.

А як видалити назви, якщо вони стануть зайвими? Присвоїти їм спеціальний оператор NULL! Це можна зробити, наприклад, так: colnames(DATAFRAME) <- NULL. Назви перетворяться на NA, і до стовпців можна буде звертатися лише за індексами.

Більш важлива перебудова фрейму PelophylaxExample стосується переходу від абсолютних значень метричних ознак до пропорційних. Річ у тім, що при порівнянні жаб, які мають різні розміри (перш за все — різну довжину тіла, L) нема сенсу порівнювати їх метричні ознаки, що стосуються окремих частин. Жаба, ширина голови у якої (Ltc) ставить 200 мкм (20 мм) — має вузьку голову або широку? У разі, якщо її довжина тіла 50 мм, її голова широка, а якщо її довжина 70 мм — вузька. Тому у більшості аналізів оптимально використовувати довжину тіла у її абсолютному значенні, а усі інші метричні ознаки — у вигляді пропорцій, як частку від ділення абсолютного значення на довжину тіла. До речі, при дослідженні інших тварин може бути доцільним використовувати у якості «модуля» не довжину тіла, а якісь інші ознаки. Наприклад, при дослідженнях птахів та кажанів такою ознакою, відносно якої розраховують пропорції, є довжина плеча.

Існує кілька способів додати до фрейму PE нові змінні з пропорційними ознаками. Найпростіший з них такий. У редакторі скриптів (або відразу у консолі) слід додати та виконати команду PE$Ltc_L <- PE$Ltc / PE$L та усі аналогічні. Коли R отримує таку команду відносно стовпця, якого не існує, він відразу й створює такий стовпець, і виконує необхідні розрахунки.

Створюючі нові стовпці, можна використовувати й логічні вирази. Припустимо, нас цікавить зв'язок ширини голови з іншими ознаками. У стовпці Ltc_L ми розрахували відносну ширину голови, але ми бажаємо створити ще один стовпець, HeadWidth, де розділити жаб на три групи: з головою середньої ширини (middle), вузькою (narrow) та широкою (wide). Як провести межі між цими класами? Один зі способів — за квартилями. Позначимо жаб, що попадають у першій квартиль, як вузькоголових, а у четвертий — як широкоголових. Як це зробити, показано в R-діалозі 4.6.1.

R-діалог 4.6.1. Створення нових стовпців у фреймі PelophylaxExample

> > PE <- read.csv('PelophylaxExamples.csv', sep = ";", dec = ",")
> names(PE)[1]  <- "Code" # Присвоєння імен рядкам за кодами жаб
> PE$Ltc_L <- PE$Ltc / PE$L # Створення стовпців з пропорціями
> PE$Fm_L <- PE$Fm/ PE$L
> PE$T_L <- PE$Ltc / PE$L
> PE$Dp_L <- PE$Dp / PE$L
> PE$Ci_L <- PE$Ci / PE$L
> PE$Cs_L <- PE$Cs / PE$L
> (su <- summary(PE$Ltc_L)) # Детальніше про відносну ширину голови
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.2849  0.3345  0.3550  0.3554  0.3738  0.4343 
> (as.vector(su)) # Перетворити вивід результатів у вектор
[1] 0.2849462 0.3344538 0.3549669 0.3554291 0.3738318 0.4342688
> PE$HeadWidth[PE$Ltc_L >= su[5]] <- "wide"
> PE$HeadWidth[PE$Ltc_L < su[5] & PE$Ltc_L > su[2]] <- "middle"
> PE$HeadWidth[PE$Ltc_L <= su[2]] <- "narrow"
> table(PE$HeadWidth) # Дивимось, що вийшло
middle narrow   wide 
    27     15     15 
> 

 

Іноді корисно отримати змінні з порядковими номерами рядків. Як це зробити найпростішим чином? PE$Number <- 1:nrow(PE). До речі, якщо ми виконаємо команду  PE$Ones <- 1, ми отримаємо просто стовпчик, що заповнено одиницями.