Заметки по языку R - это серия статей, в которых я собираю наиболее интересные публикации канала R4marketing из рубрики "#заметки_по_R"
.
В прошлый раз мы говорили о нетипичных визуализациях, сегодняшняя подборка состоит из описания приёмов, которые свойственны и горячо любимы пользователям Python, но большинство пользователей R о них не знают.
Для пользователей Python эта статья будет полезна тем, что они найдут реализацию своих любимых приёмов в другом языке, для пользователей R статья будет полезна тем, что они откроют для себя изящные приёмы Python, и смогут перенести их в свои R проекты.
![](https://habrastorage.org/getpro/habr/upload_files/0d2/0f6/f64/0d20f6f6446a0c4f3e2721d0e2e43926.png)
Содержание
Если вы интересуетесь анализом данных возможно вам будут полезны мои telegram и youtube каналы. Большая часть контента которых посвящены языку R.
Декораторы в R
На самом деле декораторы широко применяются в Python и горячо любимы пользователям этого языка, а в R они пока не получили широкого распространения. Тем не менее в R тоже можно реализовывать декорирование функций.
Немного теории:
Декораторы — это, по сути, "обёртки", которые дают нам возможность изменить поведение функции, не изменяя её код.
Визуально изображение ниже помогает понять смысл декораторов.
![](https://habrastorage.org/getpro/habr/upload_files/f86/6c1/37d/f866c137d3932e77987748f8b3039a45.png)
Первоначальная функция - автомобиль. Декоратор добавляет к машине антенну и крыло, но основной функционал машины (перевозка людей) остается неизменным.
Реализация декораторов в R
Базовый скелет декораторов выглядит следующим образом:
deco <- function(f) {
wrapper <- function(...) {
# <код до выполнения основной функции>
res <- f(...)
# <код после выполнения основной функции>
return(res)
}
return(wrapper)
}
Ниже представлен пример декоратора, который выводит время начала и завершения выполнения задекорированной функции:
timer <- function(f) {
wrapper <- function(...) {
# Перед выполнением
op <- options(digits.secs = 6) # увеличиваем точность выводимого времени
print(paste("Ini time:", Sys.time())) # Показываем время начала выполнения
res <- f(...)
# После выполнения
print(paste("End time:", Sys.time())) # Показываем время завершения выполнения
return(res)
}
return(wrapper)
}
Теперь задекорируем функцию cos()
из базового R и используем её задекорированную версию.
# декорируем cos()
cos_timed <- timer(cos)
# используем
cos_timed(3.1416)
# альтернативный укороченный вариант использования
timer(cos)(3.1418)
Пакет tinsel
Мы привели пример декоратора в R, но выглядят приведённые примеры в R всё ещё не так привлекательно как в Python:
# Пример декоратора в Python
@decorator
def f(args):
# <function body>
Добавить синтаксического сахара в реализацию декораторов в R поможет пакет tinsel
. Он позволяет применять декораторы с помощь специального синтаксиса комментариев. Например, что бы задекорировать функцию написанным ранее декоратором timer
, достаточно использовать комментарий #. timer
.
#. timer
say_hi <- function(name) {
return(paste("Hi", name, sep = " "))
}
Эта заметка является неполным передом статьи "Decorators in R".
Множественное присваивание
Множественное присваивание, так же как и декораторы, горячо любимо пользователями Python. Этот приём даёт возможность присвоить одновременно значения сразу нескольким объектам. Множественное присваивание в Python используется например для обмена значений между двумя переменными, не используя при этом третью, временную переменную. Также его удобно использовать в случаях, когда функция возвращает набор значений в виде списка, например summary()
. Тогда вы можете распаковать её результат сразу в несколько переменных, поэтому множественное присваивание также иногда называют распаковочным, параллельным или деструктурирующим.
В базовом R аналога этой операции нет, но как мы помним, в R на любой чих есть готовый паке. Для множественного присваивания можно использовать оператор %<-%
из пакета zeallot
.
Ниже несколько примеров его использования:
# распаковываем список или вектор
c(lat, lng) %<-% list(38.061944, -122.643889)
c(lat, lng) %<-% c(38.061944, -122.643889)
# распаковываем результат выполнения функции
c(min_wt, q1_wt, med_wt, mean_wt, q3_wt, max_wt) %<-% summary(mtcars$wt)
# ещё один пример распаковки функции
coords_list <- function() {
list(38.061944, -122.643889)
}
c(lat, lng) %<-% coords_list()
# используем в паре с lapply
quartet <- lapply(1:4, function(i) anscombe[, c(i, i + 4)])
c(an1, an2, an3, an4) %<-% lapply(quartet, head, n = 3)
# распаковка вложенных списков
c(a, c(b, d), e) %<-% list("begin", list("middle1", "middle2"), "end")
# распаковка даты
c(y, m, d) %<-% Sys.Date()
# меняем местами значения
# без использования временной переменной
c(first, last) %<-% c("Ai", "Genly")
c(first, last) %<-% c(last, first)
Ссылка на заметку.
Списковые включения
Списковое включение – это некий синтаксический сахар, позволяющий упростить генерацию последовательностей. Этот приём, аналогично рассмотренным выше, очень распространён в Python, но в R о нём знают не многие, а используют ещё меньше.
Списковые включения, или как их ещё называют - генераторы списков, в Python выглядят следующим образом: a = [i for i in range(1,15)]
.
В результате вы получите следующий список (не путайте со списками в R, в Python список наиболее похож на вектор из R): [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
.
Пример не самый выразительный, но он демонстрирует в простейшем виде синтаксис списковых включений в Python. (Конкретно этот пример в R можно заменить на a <- 1:14
).
Аналог списковых включений в R
В базовом R пока нет аналога генераторов списков, но они реализованы в пакете comprehenr
.
Пакет включает три основные функции:
to_list()
- генерация списков;to_vec()
- генерация векторов;alter()
- приводит преобразование над объектом, и возвращает объект того же типа, что и входящий, но уже с преобразованными значениями (из приведённых примеров кода, понять смысл этого определения будет проще).
Примеры:
library(comprehenr)
to_vec(for(i in 1:10) if(i %% 2==0) i*i)
to_list(for (x in 1:20) for (y in x:20) for (z in y:20) if (x^2 + y^2 == z^2) c(x, y, z))
colours = c("red", "green", "yellow", "blue")
things = c("house", "car", "tree")
to_vec(for(x in colours) for(y in things) paste(x, y))
# преобразование фактора в текстовый тип
res = alter(for(i in iris) if(is.factor(i)) as.character(i))
# удаление столбцов - факторов
res = alter(for(i in iris) if(is.factor(i)) exclude())
Индексирование с нуля
Значимой разницей в R и Python является индексирование. По умолчанию в Python индексация элементов объектов начинается с нуля, а в R с единицы.
![](https://habrastorage.org/getpro/habr/upload_files/753/513/66e/75351366e596c54062adc935c1e74209.png)
Если вы привыкли к индексации в Python, использовать её в R позволяет пакет index0
.
library(index0)
letters0 <- as.index0(letters)
numbers0 <- as.index0(c(2, 3, 4, 5, 6))
letters0[0]
#> [1] "a"
#> indexed from 0
numbers0[0]
#> [1] 2
#> indexed from 0
letters0[c(1, 2, 4)]
#> [1] "b" "c" "e"
#> indexed from 0
numbers0[c(1, 3)] <- NA
numbers0
#> [1] 2 NA 4 NA 6
#> indexed from 0
Заметка родилась из статьи "Indexing from zero in R".
Обработка исключений (try - except)
В базовом Python обработка исключений зачастую реализуется конструкцией try-except
, которая имеет следующий синтаксис:
try:
~ Тут код который будет выполняться ~
except Exception:
~ Код который будет выполняться в случае возникновения ошибки в блоке try ~
finally:
~ Код который будет выполняться в любом случае, не зависимо от того закончилось выражение try ошибкой или нет ~т ~
Аналогом этой операции в R является конструкция tryCatch()
, которая имеет следующий синтаксис:
tryCatch(expr = {
~ Тут код который будет выполняться ~
},
error = function(err) {
~ Код который будет выполняться в случае возникновения ошибки в блоке expr ~
},
finally = {
~ Код который будет выполняться в любом случае, не зависимо от того закончилось выражение expr ошибкой или нет ~
})
Более подробно изучить конструкцию tryCatch()
можно посмотрев следующее видео:
Классическое объектно ориентированное программирование
Ключевая разница между R и Python заключаются в том, что эти языки используют разные парадигмы программирования:
R - функциональный язык программирования;
Python - объектно ориентированный язык программирования.
По умолчанию в базовом R ООП реализовано на S3 классах и обобщённых функциях. Подробнее об этом можно узнать из статьи "ООП в языке R (часть 1): S3 классы".
Но, так же в R вам доступно и классическое ООП, которое в этом языке реализовано в отдельном пакете - R6
.
Ниже приведён пример кода, построения класса Cat
, включающий в себя несколько свойств и методов.
library(R6)
# создаём класс Cat
Cat <- R6Class(classname = "Cat",
public = list(
name = "Tom",
breed = "Persian",
age = 3,
rename = function(name = NULL) {
self$name <- name
invisible(self)
},
add_year = function(ages = 1) {
self$age <- self$age + ages
invisible(self)
}
)
)
# инициализируем объект класса Cat
tom <- Cat$new()
# используем метод rename
tom$rename('Tommy')
Более подробно про классическое объектно ориентирование программирование в R можно узнать из статьи "ООП в языке R (часть 2): R6 классы".
Логирование (logging)
Модуль logging
поставляется с базовой комплектацией Python, в базовом R подобный функционал мне не известен, но он реализован в пакете lgr
.
Пример создания простейшего логгера в R, с помощью пакета lgr
:
# создаём обычный логгер
lg <- get_logger('simple logger')
# выводим информационное сообщение
lg$info('Is %s', 'test message!')
Подробно узнать о работе с пакетом lgr можно из статьи "Логирование выполнения скриптов на языке R, пакет lgr" или следующего видео:
Работа с табличными данными
В Python вся работа с табличными данными зачастую реализуется средствами библиотеки pandas
. Уэс Мак-Кинни начал разработку pandas под вдохновением от работы с данными на языке R.
В R есть несколько средств манипуляции данными:
Базовый синтаксис
data.frame
Пакеты
dplyr
иtidyr
Пакет
data.table
Тема манипуляции табличными данными очень обширная, и не поместиться в раздел одной статьи, поэтому я более подробно описал примеры манипуляции данных на R и Python в статье "Какой язык выбрать для работы с данными — R или Python? Оба! Мигрируем с pandas на tidyverse и data.table и обратно".
Заключение
В этой статье я продемонстрировал несколько приёмов в R, которые довольно популярны среди пользователей Python, но знакомы далеко не всем пользователям R.
На самом деле я искрене надеюсь, что статья будет полезна как пользователям R, так и пользователям Python, которые планируют ознакомится с возможностями R.
Буду рад видеть вас в рядах подписчиков моего telegram и youtube каналов.
Делитесь в комментариях другими интересными приёмами, которые мигрировали из одного языка в другой.