Заметки по языку 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 каналов.

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

Комментарии (4)


  1. forthuser
    17.12.2021 12:17
    -1

    Как с вашей точки зрения и сделанные 553 решения представленные на R сайта rosettacode.org тоже многое из Вашего арсенала использования R не применили?

    P.S. Может быть полезно озвучивание критического взгляда на представленые решения и более правильные варианты их из выбранного списка задач.

    На данном ресурсе можно в формате использования Wiki изучать, добавлять и редактировать свои решения на интересующем языке


    1. selesnow Автор
      17.12.2021 12:19

      Первый раз об этом сайте слышу, но спасибо за ссылку, интересно.


      1. forthuser
        17.12.2021 12:37

        R в Rosetta Code: Popular Programming Languages сейчас распологается на 51-ом месте.
        image
        Python на 8-ом.


  1. BkmzSpb
    17.12.2021 13:04

    {index0} открывает портал в ад, особенно если над проектом работает больше одного разработчика.