Функции и apply-семейство

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

Функция — именованный блок кода, принимающий аргументы и возвращающий результат; основа повторного использования.

Когда один и тот же расчёт нужен много раз, его оформляют в функцию. А чтобы применить функцию к каждому элементу или каждой колонке, в R вместо цикла часто берут компактное apply-семейство.

Функции — это способ дать имя куску логики и перестать его дублировать. Скопированный пять раз расчёт придётся и править в пяти местах; вынесенный в функцию — в одном. Кроме того, R — функциональный язык: здесь функции можно передавать другим функциям как аргументы. Именно на этом построено apply-семейство — вы отдаёте ему функцию, а оно само применяет её ко всем элементам. Понимание этой идеи отличает уверенного пользователя R от новичка, который везде пишет циклы.

Своя функция

Функцию создают ключевым словом function и присваивают имени:

to_celsius <- function(f) {
  (f - 32) * 5 / 9
}
to_celsius(98.6)

Вывод:

[1] 37

Аргумент f — вход, результат последнего выражения возвращается автоматически (явный return не обязателен). Поскольку операции векторные, функция сразу работает и с вектором температур.

Цикл for

Иногда цикл нужен — например, для побочных действий:

for (name in c("Аня", "Борис")) {
  print(paste("Привет,", name))
}

Вывод:

[1] "Привет, Аня"
[1] "Привет, Борис"

sapply и lapply вместо цикла

Чтобы применить функцию к каждому элементу и собрать результаты, берут sapply (возвращает вектор) или lapply (возвращает список):

temps_f <- c(32, 50, 98.6, 212)
sapply(temps_f, to_celsius)

Вывод:

[1]   0  10  37 100

Одна строка заменила цикл: sapply прошёл по вектору и применил функцию к каждому значению.

Как работает под капотом

Семейство apply — это не «магия скорости», а способ выразить намерение «применить функцию ко всем элементам» одной строкой. Внутри R всё равно проходит по элементам, но код становится короче и яснее цикла. Разница в результате: lapply всегда возвращает список (по элементу на вход), а sapply пытается упростить его до вектора или матрицы, если получается. Когда упрощение невозможно, sapply тихо вернёт список — это источник неожиданностей, поэтому для надёжности иногда берут строгий vapply с указанием типа результата.

Частые ошибки

  • Писать цикл там, где хватит векторной операции. to_celsius(temps_f) уже работает со всем вектором — sapply нужен лишь когда функция не векторизована.
  • Ждать от sapply всегда вектор. Если результаты разной длины, вернётся список.
  • Забывать про область видимости. Переменные внутри функции локальны и не «протекают» наружу.

Итог

  • Функция создаётся через function(args) { ... }, последнее выражение возвращается.
  • for подходит для побочных действий.
  • sapply применяет функцию к элементам и упрощает результат, lapply возвращает список.
  • Часто векторная операция избавляет от цикла вовсе.
Проверьте себя
1. Как в R создаётся функция?
Adef f(x):
Bf <- function(x) { ... }
Cfunction f(x) {}
Dcreate.function(x)
2. Чем sapply отличается от lapply?
AНичем
Bsapply пытается упростить результат до вектора, lapply всегда возвращает список
Clapply быстрее
Dsapply работает только с числами
3. Нужен ли явный return в простой функции R?
AДа, всегда обязателен
BНет, возвращается значение последнего выражения
Creturn запрещён
DТолько для чисел