[,1] [,2] [,3] [,4]
[1,] 1 6 11 16
[2,] 2 7 12 17
[3,] 3 8 13 18
[4,] 4 9 14 19
[5,] 5 10 15 20
Переменные
Векторы
Какие бывают типы векторов?
Неявное и явное приведение типов
Индексация векторов: по номеру индекса, логическим вектором
Логические операторы: И, ИЛИ, НЕ
Пропущенные значения:
Поиск пропущенных значений
Исключение пропущенных значений из вектора
Important
Важно! Значения в векторе могут быть только одного типа
Матрицы
Списки
Датафреймы
Установка пакетов
Условия, циклы (и особенности их использования)
Векторизация как концепт, заменяющий циклы
Матрицы и списки (lists) являются расширением векторов, в свою очередь датафреймы объединяют свойства матриц и списков.
Поэтому важно рассмотреть матрицы и списки перед разбором датафреймов.
Почти такое же понимание как и в линейной алгебре.
Создадим матрицу (укажем числа и количество строк):
Можно сказать, что матрица - вектор, имеющий размерность 2 (строки и столбцы). Матрица, как и вектор, может содержать данные только одного типа. Бывают логические, числовые и строковые матрицы (последние редко).
Для индексации используем квадратные скобки, матрицы имеют два измерения, следовательно, надо прописывать оба: matrix[rows, columns]
Поэлементное извлечение
Извлечь целую строку или столбец
Можно индексировать целыми векторами:
[,1] [,2] [,3] [,4]
[1,] 1 6 11 16
[2,] 2 7 12 17
[3,] 3 8 13 18
[,1] [,2]
[1,] 6 11
[2,] 7 12
Правила индексации матриц нам очень понадобятся при работе с датафреймами
Также, как и при работе с векторами, часть элементов матрицы можно переписать.
Вспомним как для векторов:
[,1] [,2] [,3] [,4]
[1,] 1 100 100 100
[2,] 2 100 100 100
[3,] 3 100 100 100
[4,] 4 9 14 19
[5,] 5 10 15 20
Матрицы, как тип данных, используются при создании тепловых карт.
Список в R может содержать данные разного типа, даже другие списки. Список можно создать функцией list()
.
[[1]]
[[1]][[1]]
[1] 1 2 3 4 5
[[1]][[2]]
[1] "JoJo"
[[1]][[3]]
[1] TRUE
[[2]]
[1] "this" "is" "complex list"
Обычно сложные вложенные списки никто намеренно не создает, но такой список может возникнуть например после парсинга сайтов или терминов генной онтологии.
Парсинг - сбор и структурирование данных с последующим анализом.
У сложных списков удобно посмотреть структуру, используя функцию str()
List of 2
$ :List of 3
..$ : int [1:5] 1 2 3 4 5
..$ : chr "JoJo"
..$ : logi TRUE
$ : chr [1:3] "this" "is" "complex list"
Можно создавать именованные списки:
Индексация списков устроена несколько сложнее, чем векторов и матриц.
Для начала попробуем индексацию по номеру
Получился список длиной 1. Как же извлечь элемент списка не в виде списка?
Иллюстрация:
Чтобы извлечь элемент списка не в виде списка можно использовать две квадратные скобки sample_list[[1]]
.
Также можно использовать индексацию по имени: используя знак $
или имя элемента в квадратных скобках в кавычках.
Удобнее всего создать новый элемент с помощью знака $
Датафреймы - прямоугольные таблицы, которые могут содержать данные разного типа. Нам понадобится много работать с датафреймами.
Попробуем создать датафрейм: функция data.frame()
df <- data.frame(names = c('Alexandra', 'Vlad', 'Ekaterina'),
year = c(5, 3, 6),
tasks = c(TRUE, FALSE, TRUE))
df
names year tasks
1 Alexandra 5 TRUE
2 Vlad 3 FALSE
3 Ekaterina 6 TRUE
Столбцы в датафрейме могут быть разных типов, однако в рамках одного столбца данные должны быть одного типа.
Индексация датафреймов объединяет принципы индексации матриц и списков.
Используя квадратные скобки и индекс строк, колонок.
Поэлементное извлечение:
Извлечение конкретных строк и столбцов
Tip
При расстановке пробелов пользуемся правилами пунктуации: пробел ставится после запятой.
С помощью знака $
или можно с помощью квадратных скобок
Похоже на обращение к существующей, но пишем новое название и заполняем значениями.
names year tasks email
1 Alexandra 5 TRUE email1
2 Vlad 3 FALSE email2
3 Ekaterina 6 TRUE email3
Подробнее про работу с датафреймами еще поговорим в лекции по tidyverse
.
Пакеты с CRAN-а скачиваются командой install.packages('название пакета')
.
После того, как мы скачали пакет, его необходимо подгружать каждый раз при работе командой library(название пакета)
Иногда можно подгрузить одну функцию из пакета, используя оператор ::
С github-a или других репозиториев. Используется пакет remotes
.
С биокондактора. Сначала нужно установить сам bioconductor - менеджер биологических пакетов.
Далее устанавливаем нужный пакет, хранящийся в биокондакторе:
if
Синтаксис условной конструкции: if (condition) true_action
Если выражение содержит несколько строчек, то необходимо использовать фигурные скобки.
if
, else
Для описания действий в случае не выполнения условия используется оператор else
if (condition) true_action else false_action
random_number <- sample(-5:5, 1) # чтобы извлечь случайное число
# из набора чисел от -5 до 5
random_number
[1] 2
if (random_number > 0) {
print('Положительное число')
} else {
print('Отрицательное число или ноль')
}
[1] "Положительное число"
Поскольку команды в R исполняются построчно, важно, чтобы оператор else
был на той же строке, что и закрывающая фигурная скобка if
.
if
Оператор if
на вход принимает только одно число. Что делать, если нам нужно проверить на какое-то условие целый вектор?
Вот это работать не будет (начиная с версии R 4.0):
Error in if (-3:3 > 0) print("Положительное число"): условие длиной > 1
Для операции над векторами используем функцию ifelse()
из base R или if_else()
из библиотеки dplyr
.
if_else()
из библиотеки dplyrОтличается чуть большей строгостью. Например, нельзя смешивать данные разных типов. Синтаксис такой же, как и в обычном ifelse()
.
[1] "Четное" "Нечетное" "Четное" "Нечетное" "Четное" "Нечетное"
[7] "Четное" "Нечетное" "Четное" "Нечетное"
Что произойдет, если мы используем разный тип данных для выражений выполненного условия и невыполненного?
Значения, в случае, если условие выполнилось, и если не выполнилось, должны быть одного типа.
case_when()
Если условий больше двух (или даже больше одного), на помощь приходит функция case_when()
из пакета dplyr
.
[1] "Положительное число" "Положительное число" "Положительное число"
[4] "Положительное число" "Положительное число" "Положительное число"
[7] "Положительное число" "Положительное число" "Положительное число"
[10] "Положительное число"
Здесь тоже работает требование об одинаковом типе данных на значения после выполнения условия.
Про пакет dplyr
поговорим подробнее в лекции 4 про tidyverse
.
Циклы в R не рекомендованы к использованию, однако знать их синтаксис не повредит.
Синтаксис:
Одна из частых ошибок новичков при написании циклов - попасться в ловушку копирования.
Допустим, мы написали цикл, в котором на каждой итерации вектор увеличивается на 1 элемент. В R каждый раз происходит копирование и выделение нового места под вектор и это extremely slow.
Нужно заранее создать вектор нужного размера, чтобы выделить для него память и не создавать копий (подробнее здесь, здесь).
Плохо:
Лучше создать вектор нужного размера перед циклом:
Рекомендуется использовать seq_along()
, чтобы наверняка избежать проблемы с пустым вектором.
Правильно конечно с точки зрения циклов, так как есть ряд альтернативных рекомендуемых подходов вместо них.
Tip
Совет от Соника: не использовать циклы вообще👍
Вторая распространенная ошибка: проблема с 1:length(vector)
Суть в том, что мы можем получить неожиданный результат, если вектор пустой, поскольку оператор :
работает и в убывающую сторону (то есть получается 1:0).
Похоже, что мы ожидали не этого. Чтобы этого избежать, вместо 1:length()
используем seq_along()
.
Вообще правильный подход - использовать векторизацию вместо циклов.
Векторизация - это применение какой-либо функции над каждым элементом вектора.
Можно записать идею в математической нотации:
Пусть f() - векторизованная функция. Тогда \(y = f(x)\) означает, что мы применяем функцию f() к каждому элементу вектора x
, на выходе получаем вектор y
такой же длины как вектор x
.
[1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427
[9] 3.000000 3.162278
Фраза векторизованная функция означает, что операции производятся сразу над каждым элементом вектора.
Большинство базовых функций в R векторизованы по умолчанию, и если они применимы к одному элементу, то скорее всего будут применимы и к целому вектору. Например, уже разобранные операторы возведения в степень ^
, квадратный корень, логарифм и тд.
[1] 1 4 9 16 25 36 49 64 81 100
[1] 0.000000 1.000000 1.584963 2.000000 2.321928 2.584963 2.807355 3.000000
[9] 3.169925 3.321928
Не являются векторизованными: mean()
, sum()
. Как думаете, почему?
Например, нам нужно посчитать средние значения в списке, а функция mean()
не векторизована. Что делать? Использовать функции семейства *apply()
.
list_values <- list(rnorm(10), rnorm(10, mean = 5, sd = 2),
rnorm(10, mean = 10, sd = 3))
list_values
[[1]]
[1] 2.07983997 0.30414912 0.77383315 -0.63807175 0.28889034 0.23361007
[7] -0.09532000 0.66944603 0.08062912 -1.00874211
[[2]]
[1] 3.907073 2.732515 2.606927 5.805492 4.360885 3.701539 6.144990 5.664932
[9] 7.398804 7.892041
[[3]]
[1] 8.188229 6.341163 7.984042 10.407791 11.736316 11.862250 8.177342
[8] 6.138101 15.692003 16.317226
rnorm()
генерирует числа из нормального распределения со среднем 0 и стандартным отклонением 1 (по умолчанию).
Подписывайтесь на телеграм-канал о статистике: