Skip to content

R course, Lecture 4

Stanislav edited this page Oct 31, 2013 · 57 revisions

##Функции

Функции в R ленивы но только на уровне передачи в функцию, а не на уровне возврата значения. Функции в R создаются как анонимные функции. Также функции являются объектами первого класса, что за бесплатно дает различные плюшки в виде замыканий, которые в R захватывают весь контекст целиком. (Подробнее о замыканиях)

###Содержание:

###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-тесты).

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 чтобы убить его)

Все есть здесь: ссылка

Clone this wiki locally