Конструкция if-else в R

Условные конструкции в R очень похожи на условные конструкции в Python. Есть оператор if, после которого формулируется условие и код, выполняемый в случае, если это условие верно, и есть оператор else, после которого пишется код, выполняемый в противном случае.

Главное отличие заключается в том, что в R для разграничения блоков кода вместо отступов для используются фигурные скобки.

if (condition){
# some code here
} else {
# some code here
}

Фигурные скобки обеспечивают связывание кода в единую программу: если скобки поставлены верно, при запуске строки с if исполняется весь код до конца конструкции, то есть до else включительно.

Напишем код, который выводит на экран сообщение Malo, если оценка в переменной mark меньше 10, и сообщение OK, если оценка не ниже 10.

mark <- 7
if (mark < 10){
  print("Malo")
} else {
  print("OK")
}
## [1] "Malo"

Теперь давайте сделаем наш код более реалистичным – добавим на оценку ограничение сверху. Добавим еще одно условие с if внутри ветки с else, которое будет проверять, не превышает ли оценка значение 10:

mark <- 12
if (mark < 10){
  print("Malo")
} else if (mark > 10){
  print("Stranno")
} else {
  print("OK")
}
## [1] "Stranno"

Обратите внимание: готового оператора elif, как в Python, в R нет, только if плюс else.

В продолжение примера с оценками давайте напишем программу, которая определяет, является ли оценка отличной, хорошей, удовлетворительной или плохой. Реализуем ее через серию условий if:

if (mark >= 8 & mark <= 10){
  print("Отлично")
} 
if (mark >= 6 & mark < 8){
  print("Хорошо")
}
if (mark >= 4 & mark < 6){
  print("Удовлетворительно")
}
if (mark < 4 & mark >= 0){
  print("Плохо")
}
if (mark > 10){
  print("Странно...")
}
## [1] "Странно..."

Важно: последнее условие у нас тоже написано через if, если напишем else, он будет относиться только к последнему условию с if, а не ко всем условиям выше, и программа будет работать не так, как нужно.

Проверка условий на векторах

Для того чтобы проверить выполнение некоторого условия сразу для всех элементов вектора (столбца в таблице), совсем не обязательно писать цикл и проверять условие для каждого элемента. Достаточно воспользоваться функцией ifselse(). На первом месте в аргументах этой функции указывается условие, которое мы проверяем, на втором – значение, которое должно возвращаться в случае, если условие выполняется, на третьем – значение, возвращаемое в случае невыполнения условия. Это функция удобна для получения новых векторов (столбцов в таблице) на основе старых. Получим вместо yes и no набор из 1 и 0:

answers <- c("yes", "no", "no", "yes")
ifelse(answers == "yes", 1, 0)
## [1] 1 0 0 1

Для условий, состоящих из нескольких частей, эта функция тоже подойдет:

v <- c(-0.6, 0.1, 0.5, 0.8, 2, 3)
binary <- ifelse(v > 1 | v < 0, "invalid", "valid")
binary
## [1] "invalid" "valid"   "valid"   "valid"   "invalid" "invalid"

Если на выходе мы хотим получить вектор, состоящий более, чем из двух значений, придется использовать ifelse() несколько раз. Для примера напишем код, который вместо пропущенного значения NA будет выставлять 99, а в остальных случаях записывать 1 для положительных значений и 0 — для отрицательных и нулевых:

w <- c(4, 6, 7, 0, -1, -5, 10, 21, NA)

# is.na(): TRUE для NA, FALSE для остального
ifelse(is.na(w), 99, ifelse(w > 0, 1, 0))
## [1]  1  1  1  0  0  0  1  1 99

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

Цикл for в R

Обычно цикл for используется для повторения операций фиксированное число раз. В R цикл for выполняет ту же задачу, однако не всегда использование этого цикла действительно необходимо. Дело в том, что основной структурой данных в R является вектор, а это значит, что многие операции являются векторизованными: применяя операцию к вектору, мы автоматически применяем ее сразу ко всем его элементам.

Например, для домножения всех элементов вектора на число цикл нам не понадобится:

v <- c(-0.6, 0.1, 0.5, 0.8, 2, 3)
v ** 2
## [1] 0.36 0.01 0.25 0.64 4.00 9.00

То же самое будет работать и с несколькими векторами одинаковой длины:

# попарно складываем все элементы
v1 <- c(4, 7, 8, 9, 0)
v2 <- c(1, 2, 3, -1, 0)
v1 + v2
## [1]  5  9 11  8  0
# считаем математическое ожидание
# по вектору значений и вероятностей
x <- c(0, 3, 6)
p <- c(0.2, 0.5, 0.3)
sum(x * p)
## [1] 3.3

Однако, если мы работаем не с готовыми векторами, цикл for все же понадобится. Например, для перебора файлов в какой-нибудь папке. Представим себе такую ситуацию: у нас есть набор CSV-файлов с одинаковой структурой (один и тот же опрос в разное время, одни и те же индексы по разным странам, результаты выборов в разных регионах), и мы хотим из каждого файла извлечь столбец с одним и тем же названием и построить для него какой-нибудь график. Тут-то и пригодится цикл for.

Давайте для простоты пока не будем брать файлы с данными, а просто создадим вектор с реалистичными названиями файлов:

files <- c("France.csv", 
           "Spain.csv", 
           "Slovenia.csv", 
           "Brazil.csv")

Переберем названия файлов в цикле и выведем их названия на экран (как обычно в циклах, вместо f мы могли вписать любое название):

for (f in files){
  print(f)
}
## [1] "France.csv"
## [1] "Spain.csv"
## [1] "Slovenia.csv"
## [1] "Brazil.csv"

Теперь рассмотрим задачу поинтереснее. Предположим, дальше для данных из каждого файла мы будем строить графики. И мы хотим, чтобы заголовки графиков соответствовали названиям стран. Чтобы это автоматизировать, нам потребуется код, который будет извлекать название страны из названия CSV-файла. Начнем с одного названия, сохраним его как f:

f <- files[1]

Сначала название файла нужно разбить по точке. Для разбиения строки на части в R есть функция strsplit():

strsplit(f, "\\.")
## [[1]]
## [1] "France" "csv"

Почему в функции выше вместо разделителя . у нас указано \\.? Многие функции для работы со строками в R автоматически поддерживают работу с регулярными выражениями для поиска и изменения текста по заданному шаблону (почитать можно здесь и здесь), а в регулярных обозначениях точка является специальным символом. В регулярных выражениях точка означает любой символ. То есть, выражение т.к будет находить слова ток, тик, так, тук или даже т9к. Поэтому, чтобы R понимал точку как точку, ее пришлось экранировать — выделить с помощью \\.

Функция strsplit() возвращает результат в виде списка, заберем из этого списка единственный вектор с индексом 1:

strsplit(f, "\\.")[[1]]
## [1] "France" "csv"

Теперь из этого вектора извлечем первый элемент — название страны и сделаем все буквы заглавными:

name <- toupper(strsplit(f, "\\.")[[1]][1])
name
## [1] "FRANCE"

Осталось применять эту операцию в цикле и посмотреть на результаты:

for (f in files){
  name <- toupper(strsplit(f, "\\.")[[1]][1])
  print(name)
}
## [1] "FRANCE"
## [1] "SPAIN"
## [1] "SLOVENIA"
## [1] "BRAZIL"

Отлично! Запомним эти манипуляции со строками, они нам понадобятся в практической части!