Чтобы повторять действия в R и при этом не копировать один и тот же код много раз, используются циклы. Существует два основных цикла: цикл for
и цикл while
. На практике чаще используется цикл for
, потому что цикл while
более специфический и более медленный (и программу с while
очень легко зациклить).
Стоит также отметить, что для выполнения стандартных задач в рамках анализа данных в R циклы используются нечасто, так как в R многие операции векторизованы - функции применяются сразу к наборам значений, например, к векторам.
# не нужно проходить по вектору x и возводить каждый элемент в квадрат
x <- c(1, 2, 3, 8)
x ** 2
## [1] 1 4 9 64
Смысл цикла for
: “для каждого элемента в наборе (векторе) сделай что-то”. У нас есть вектор v
и мы хотим вывести его элементы на экран:
v <- c(0, 1, NA, 0, 1)
for (i in v){
print(i)
}
## [1] 0
## [1] 1
## [1] NA
## [1] 0
## [1] 1
То, по чему итерируем (пробегаемся), указывается в круглых скобках, а то, что делаем в ходе цикла – в фигурных.
Для перебора можно задействовать последовательности:
for (n in 1:10){
print(n)
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6
## [1] 7
## [1] 8
## [1] 9
## [1] 10
А теперь совместим цикл и условные конструкции.
Пусть у нас есть вектор оценок студентов.
grades <- c(2, 4, 5, 10, 11, 3, 7)
И мы хотим на каждую оценку выдавать какой-нибудь комментарий. Это можно сделать следующим образом:
for (g in grades){
if (g < 4) print(sprintf("Your mark is %i. Bad.", g)) else {
if (g >= 4 & g < 6) print(sprintf("Your mark is %i. Not bad.", g))
if (g >= 6 & g < 8) print(sprintf("Your mark is %i. Good.", g))
if (g >= 8 & g <= 10) print(sprintf("Your mark is %i. Excellent.", g))
if (g > 10) print(sprintf("Your mark is %i. 'We are the champions - my friend...'", g))}
}
## [1] "Your mark is 2. Bad."
## [1] "Your mark is 4. Not bad."
## [1] "Your mark is 5. Not bad."
## [1] "Your mark is 10. Excellent."
## [1] "Your mark is 11. 'We are the champions - my friend...'"
## [1] "Your mark is 3. Bad."
## [1] "Your mark is 7. Good."
На самом деле с функциями в R мы уже знакомы. Когда мы загружаем базу данных из файла, мы используем функцию read.csv()
, когда определяем максимальное значение в векторе, используем функцию max()
, когда выводим сообщение на экран, используем print()
или cat()
.
Но часто бывает, что встроенных функций R не хватает для полноценной работы, поэтому приходится дописывать некоторые функции самим.
При создании функции нужно учитывать три момента: 1) что функция получает на вход (аргументы функции); 2) что функция делает; 3) что функция возвращает, выдает на выходе (результат).
Для примера напишем свою функцию, которая будет принимать на вход числовой вектор и считать его среднее значение (для простоты будем считать, что пропущенных значений в векторе нет). Создадим функцию my_mean()
.
Внимание: своим функциям нужно давать названия, отличные от названий функций, уже встроенных в R (в случае совпадения R не выдаст ошибку, сохранит функцию с тем же названием, но потом могут возникнуть проблемы, да и самим легко запутаться).
my_mean <- function(x){
avg <- sum(x) / length(x)
return(avg)
}
Сначала мы задаем название функции (как обычно поступаем с переменными), а затем присваиваем ей значение: прописываем, что это будет объект function
, указываем аргумент (или аргументы) функции в круглых скобках и в фигурных скобках перечисляем все то, что должна делать функция. Последняя строчка с return
означает, что функция должна что-то возвращать, в данном случае одно число – среднее значение вектора.
На самом деле, можно обойтись и без return
: R по умолчанию будет возвращать то, что указано в последней строчке в теле функции (в фигурных скобках).
# допустимо
my_mean <- function(x){
avg <- sum(x) / length(x)
avg
}
Теперь можем использовать функцию как обычную команду в R.
v <- c(10, 20, 15, 5)
my_mean(v)
## [1] 12.5
Главное не забыть важную вещь: два действия “функция возвращает какое-то значение” и “функция выводит на экран какое-то значение” – не одно и то же. Функция может просто выводить результат на экран и никуда его не сохранять, а может, наоборот, сохранять результат, но на экран его не выводить.
Например, эта функция просто выводит среднее на экран, не сохраняя его:
my_mean2 <- function(x){
avg <- sum(x) / length(x)
cat(avg)
}
# на экран выводит
my_mean2(v)
## 12.5
# а если сохраним результат в переменную -
# получим пустоту и безысходность
result <- my_mean2(v)
## 12.5
result
## NULL
Можно привести такую аналогию. Если я что-то говорю и не записываю, что говорю – я просто вывожу нечто на экран с помощью cat()
: результат все видят, но обратиться к нему позже не получится. Если я по ходу рассказа делаю некоторые заметки – я возвращаю определенный результат, сохраняю записи, которые позже можно будет прочитать.
Однако никто не мешает сочетать и основной результат, и какие-то побочные действия:
my_mean3 <- function(x){
avg <- sum(x) / length(x)
cat("You have to sleep!")
return(avg)
}
# и текст, не связанный со средним, вывели
# и среднее значение сохранили
res <- my_mean3(v)
## You have to sleep!
res
## [1] 12.5
Как быть, если функция выглядит громоздко, или мы создали слишком много полезных функций, и нам неудобно хранить их в файле с кодом? Можно вынести все необходимые написанные нами функции в отдельный файл с расширением .R
.
Скопируем для примера нашу функцию my_means
в новый файл и назовем его my_functions.R
. Для чистоты эксперимента откроем R заново и попробуем загрузить функцию из этого файла:
source("my_functions.R")
w <- c(0, 3, 7)
# работает, и R знает, откуда эта "не родная" функция взялась
my_mean(w)