-
Notifications
You must be signed in to change notification settings - Fork 0
R course, Lecture 4
##Функции
Функции в R ленивы но только на уровне передачи в функцию, а не на уровне возврата значения. Функции в R создаются как анонимные функции. Также функции являются объектами первого класса, что за бесплатно дает различные плюшки в виде замыканий, которые в R захватывают весь контекст целиком. (Подробнее о замыканиях)
###Содержание:
- Body - тело функции (все что внутри { }).
- Formals - аргументы функции.
- Environment - контекст функции.
- Полезные функции.
###Body
-
body(function)- возвращает тело функции.
>body(lapply)
{
FUN <- match.fun(FUN)
if (!is.vector(X) || is.object(X))
X <- as.list(X)
.Internal(lapply(X, FUN))
}
>body(ls) <- getwd() # подменили тело ls() на getwd()
- Функция возвращает последнее вычисленное выражение.
-
invisible(laststatement)- не будет вывода на консоль при вызове функции.
###Formals
-
formals(function)- возвращает список аргументов функции.
>formals(plot)
$x
$y
$...
>formals(rnorm) <- sample(formals(rnorm), length(formals(rnorm))) # немного вредничанья
- Ellipsis - обозначается как
...способ задания переменного количества переменных. Вначале функция смотрит именованные переменные, потом все до многоточия, остальные будут в...из которого можно получить список путемlist(...)
>f <- function (a, ... , n) {list(...)}
>f(,b=123, 1412, "asfsa", n = 123) #так мы пропустили переменную a,
>#можно было еще написать `f(a=, b=123, 1412, "asfsa", n = 123)
$b
[1] 123
[[3]]
[1] 1412
[[4]]
[1] "asfsa"
Удобно для чего нибудь в стиле:
>f <- function (a,b,c ...) {
>print(a,b)
>plot(...) #передали ... в plot
>return n}
-
missing(f)- проверяет есть ли отсутствующие аргументы (например а из примера выше) -
f <- function (a = 10, b = 234) {}так можно создавать значения по умолчанию -
do.call(f, list)- вызывает функцию от списка её аргументов:
>do.call("c", list(1:10,3:4,letters))
[1] "1" "2" "3" "4" "5" "6" "7" "8" "9" "10" "3" "4" "a" "b" "c"
[16] "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r"
[31] "s" "t" "u" "v" "w" "x" "y" "z"
###Environment
У каждой функции есть локальный контекст где она хранит все свои локальные переменные, он появляется при создании функции, а при выходе из неё сборщик мусора уничтожает контекст. А что же будет если мы внутри функции создадим функцию? Её контекст тоже удалится при выходе из родителя? Ответ нет, если эта функция будет жива. Она сохранит свой контекст и вдобавку к нему контекст предка. Если нам понадобится внешняя переменная, то это и будет первым местом поиска, и так выше и выше по уровням. См. Замыкания
-
environment(f)- возвращает контекст функции. (посмотрите help там все понятно описано)
Для посылки предупреждений.
-
stop("smth")- остановка с ошибкой и выводом текста. -
stopifnot(bool, bool, ...)- остановка с ошибкой, если одно из булевых значений FALSE. -
warning("smth")- вывод предупреждения (по умолчанию не сразу а в конце, можноwarning("smth", immediate. = TRUE)). -
message("say something")- вывод сообщения. -
suppressWarnings({ code } )- код будет без предупреждений. -
warnings()- 50 последних предупреждений.
Для работы с системой.
-
source("filename")- загрузить исхдные файлы (функции и проч.) -
setwd()иgetwd()- получить путь к рабочей папке и назначить его. -
file.exists()- проверить существование файла. -
dir()- перечислить содержимое папки. - `dir.create("name") - создать папку.
-
system("echo 454")иsystem2("ls")- запустить исполняемый файл (exe) или скрипт.
Несколько примеров замыканий:
f <- function(N) {
function(x) x%%N
}
f10 <- f(10)
f2 <- f(5)
f10(5) == 5
f2(5) == 1
cache <- function(N) { #Кэш функция возвращающая функцию
x <- list()
i <- 1
function(value=NULL) { #Помним что функция возвращает последнее выражение?
#Так вот, тут это функция!)
if (is.null(value)) {
return(x)
}
else if (i <= N){
x[[i]] <<- value
i <<- i + 1
}
else {
warning("Out of Memory!")
}
}
}
>buff <- cache(2) #Создали буффер размером 2 любых элементов(лол)
>buff(23) #Добавили туда 23
>buff(iris) #Добавили туда iris
>buff(47)
Warning message: #Это у нас комически кончилась бесконечная память
In buf(234) : Out of Memory!
>buff() #Так можно посмотреть содержимое
Тут дан пример со стеком и environment тоже используется)
Можете попробовать дополнить кэш удалением и прочей бякой.
Функции перечисленные на паре:
-
sapply(vector, fun, ... , simplify = bool)- применяет функцию к вектору и возвращает вектор, многоточие идет в функцию fun. Fun может быть текстом имени, например "mean". -
lapply(list, fun, ... , simplify = bool)- то же самое со списками.
>cummean <- function(v) sapply(seq_along(v), function(i) median(v[seq_len(i)])
>cummean(1:12)
[1] 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.5
-
apply(x, margin, fun, ... , simplify)- принимает массив(включая матрицы), применяет fun по следующей схеме: margin может быть вектор с размерностью до максимального количества измерений массива, все элементы его берутся из (1:n) без повторений (в случае матрицы: (2,1), 1, 2)
n = matrix (rnorm(100, 2, 132), nrow=25) # Матрица 25х4 ~ n(2,132)
apply(x, 2, mean, trim = .2) # Средние значения по столбцам
apply(x, 2, sort) # Матрица с сортированными столбцами
-
outer(v1,v2,fun)- внешнее произведение матриц пример (fun обязательно векторизованная!) -
Vectorize(f,names, ...)- векторизует функцию, names - список аргументов по которым будет произведена векторизация. -
tapply(x, ind, fun, ... , simplify = bool)- x - вектор или список, возвращает фактор с лэйблами из ind и функцией примененной к каждому подсписку/подвектору. Пример:
>x = 1 2 4 39 48 2
>ind = 2 2 3 3 5 2
>tapply(x, ind, mean)
2 3 5
1.666667 21.500000 48.000000
>tapply(x, ind, exp)
$`2`
[1] 2.718282 7.389056 7.389056
$`3`
[1] 5.459815e+01 8.659340e+16
$`5`
[1] 7.016736e+20
-
by(df, ind, fun, ... , simplify = bool)- -
aggregate(df, ind, fun, ... , simplify = bool)- -
ave(df, ind, fun, ... , simplify = bool)-
Unit-тесты писать можно с помощью RUnit или testthat (есть верификация при сборке)
Рассмотрим testthat:
source("file.R") #Берем файл который будем тестить
context("chapter 3") #Контекст что-то типо главы в книге (может быть один на несколько
#файлов, также может быть несколько в одном файле
test_that("some name of our test", { #Тут пишем название и дальше кусок кода который тестим и сами тесты
expect_true(x)
expect_false(x)
expect_is(x, y) #Проверить наследование, например если
#model <- lm(mpg ~ wt, data = mtcars)
#то expect_is(model, "lm") == TRUE
expect_equal(x, y, tol = 10e-5, "Message")
expect_identical(x, y) #должны быть идентичны
expect_equivalent(x, y) #идентичны до названия полей
#expect_equivalent(c("one" = 1, "two" = 2),(1:2)) == TRUE
expect_matches(x, y) #Проверяет подходит ли под регулярное выражение*
expect_output(x, y)
expect_message(x, y)
expect_warning(x, y)
expect_error(x, y)
Потом в консоли пишем test_file(path) если хотим запустить файл тестов или test_dir(path) если всю папку тестов, но тогда все тестовые файлы должны начинаться на test. Можно запускать с добавлением директив reporter = "stop"(останавливается на ошибке), "minimal"(печатает "." - success, "F" - failure, "E" - error) и "summary"(стандартный).
Можно написать autotest(codedirectory, testdirectory) - будет при любых изменениях кода или тестов перезапускать тесты и выводить на печать. Ctrl+C чтобы убить его)
Все есть здесь: ссылка