Заметки по языку R | Часть 2: Используем синтаксический сахар и приёмы Python в R

от автора

Заметки по языку R — это серия статей, в которых я собираю наиболее интересные публикации канала R4marketing из рубрики "#заметки_по_R".

В прошлый раз мы говорили о нетипичных визуализациях, сегодняшняя подборка состоит из описания приёмов, которые свойственны и горячо любимы пользователям Python, но большинство пользователей R о них не знают.

Для пользователей Python эта статья будет полезна тем, что они найдут реализацию своих любимых приёмов в другом языке, для пользователей R статья будет полезна тем, что они откроют для себя изящные приёмы Python, и смогут перенести их в свои R проекты.

Содержание

Если вы интересуетесь анализом данных возможно вам будут полезны мои telegram и youtube каналы. Большая часть контента которых посвящены языку R.

Декораторы в R

На самом деле декораторы широко применяются в Python и горячо любимы пользователям этого языка, а в R они пока не получили широкого распространения. Тем не менее в R тоже можно реализовывать декорирование функций.

Немного теории:

Декораторы — это, по сути, «обёртки», которые дают нам возможность изменить поведение функции, не изменяя её код.

Визуально изображение ниже помогает понять смысл декораторов.

Первоначальная функция — автомобиль. Декоратор добавляет к машине антенну и крыло, но основной функционал машины (перевозка людей) остается неизменным.

Реализация декораторов в 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 с единицы.

Если вы привыкли к индексации в 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 каналов.

Делитесь в комментариях другими интересными приёмами, которые мигрировали из одного языка в другой.


ссылка на оригинал статьи https://habr.com/ru/articles/587480/