Функции и циклы

Запись занятия

Условные операторы

if…else

Поведение условных операторов if и else в R аналогично другим языкам - если (if) условие верно, то выполняется первое выражение, если же неверно (else), то второе. Условие else является необязательным, также в коде вполне может встретиться несколько конструкций с if, без последующего else.

Условие ветвления может быть задано как выражением, в результате которого возвращается единичное логическое TRUE/FALSE, либо же объектом с логическим значение (результат проведенной отдельно проверки). Если в условие передан численный объект, то он будет преобразован в логическое значение (0 в FALSE, все остальное в TRUE). При комбинации нескольких логических проверок, их объединяют через бинарные логические операторы && или ||.

Выражения, которые выполняются для if … else желательно заключать в фигурные скобки, даже если это однострочное выражение (и обязательно, если это несколько выражений). Также следует помнить, что оператор else должен быть на той же строчке, на которой закрывается фигурная скобка оператора if. В противном случае эти два оператора будут проинтерпретированы как независимые, и R вернет ошибку.

alarm <- 2 + 2 == 5
print(alarm)
## [1] FALSE
if (alarm) {
  message('well, there are no problems')
} else {
  warning('Your math is broken!!!111')
}
## Warning: Your math is broken!!!111

ifelse

Конструкция if...else обладает одним ограничением — она не векторизована. То есть, для того, чтобы проверить, допустим, каждый элемент вектора, необходимо использовать циклы. Либо же обратиться к конструкции ifelse():

x <- 1:5
ifelse(x %% 2 == 0, 'even', 'odd')
## [1] "odd"  "even" "odd"  "even" "odd"

В примере мы создаем вектор от 1 до 5, и потом, с помощью ifelse(), проверяем каждый элемент, если элемент делится на 2 без остатка, то возвращаем even, иначе же - odd. Как правило ifelse() используется для модификации значений в колонках датасетов и любых других местах, когда надо не просто по условию выполнить какое-то выражение из пары альтернатив, а быстро модифицировать вектор значений.

switch (Advanced)

Функция switch() - очень удобный вариант ветвления в ситуациях нескольких альтернатив. Согласно документации первым аргументом функции выступает строковое или численное значение. Стоит учитывать, что в зависимости от типа (строка или число) несколько меняется поведение функции. Так, если в первым аргументом передается численное значение, то функция возвращает альтернативу под таким номером, из указанного вторым и следующими аргументами списка альтернатив. Если значение первого аргумента превышает количество альтернатив, то функция ничего не вернет (точнее, вернет NULL):

switch(3,
       "Amber",
       "Westeros",
       "Westworld",
       "Cadia")
## [1] "Westworld"
print(switch(3,
             "Amber",
             "Westeros"))
## NULL

Если в функцию передается строковое значение (и это наиболее частое применение), то второй и следующие аргументы - это также список альтернатив, но альтернатив именованных. Это необходимо, так как строковое значение не может быть использовано как номер альтернативы в списке. Список альтернатив должен быть именован в соответствии с допустимыми (или желаемыми) вариантами значений первого аргумента. Также можно указать одну альтернативу без имени, которая будет взвращаться по умолчанию, то есть, когда в первом аргументе будет значение, не совпадающее с названиями именованных альтернатив. Функция switch() чаще всего используется при создании функций. В примере ниже функция poles_of_existence() возвращает вектор имен ключевых элементов вселенной “Хроник Амбера” Роджера Желязны.

# объявляем функцию
poles_of_existence <- function(pole) {
  switch(
    pole,
    Chaos = c('Serpent', 'The Logrus', 'Suhuy', 'Courts of Chaos'),
    Order = c('Unicorn', 'The Pattern', 'Dworkin Barimen', 'Amber'),
    'unknown'
  )
}

# вызываем имена и объекты Хаоса
poles_of_existence('Chaos')
## [1] "Serpent"         "The Logrus"      "Suhuy"           "Courts of Chaos"
# пробуем другой аргумент, которого нет в списке имен альтернатив
poles_of_existence('Westeros')
## [1] "unknown"

В редких случаях в функцию передается элемент сортированного вектора (фактора). В таком случае происходит конвертация типа значения в character и функция работает как со строковым значениям, несмотря на уровни фактора. Одновременно пользователю, при интерактивной работе, возвращается предупреждение. Впрочем, это достаточно редкий и вырожденный случай, который следует избегать.

Циклы

Наряду с условными операторами, циклы в R аналогичны циклы в других языках программирования. Три основных вида циклов - for, while и repeat. Циклы задаются с помощью оператора цикла, последовательности или условия, ограничивающих работу цикла и, собственно, выполняемого выражения. Если это одна строка, то выражение можно не заключать в фигурные скобки, во всех прочих случаях фигурные скобки необходимы.

Циклы традиционно редко используются в R, в немалой части это вызвано спецификой использования памяти во время выполнения выражений в цикле, точнее, не очень эффективным кодом. Для циклов существуют альтернативы - векторизованные вычисления и неявные циклы, а так же, собственно оптимизация кода путем преаллокации памяти или параллелизации.

for

Цикл for, который используется чаще прочих, использует заданную последовательность, по которой и итерируется. Последовательностью может быть как численный ряд, так и строковый вектор, например, названий файлов при импорте и обработке большого количества файлов в одном цикле. После выполнения цикла используемый итератор имеет значение последнего элемента цикла. В том же случае, если последовательность нулевой длины, то цикл не отрабатывает.

for (i in letters[1:3]) {
  cat('letter', i, '\n')
}
## letter a 
## letter b 
## letter c
cat('i =', i)
## i = c


while (Advanced)

Циклы while и repeat используются намного реже. Если в цикле for количество циклов определяется длиной заданной последовательности, то в while количество циклов может быть бесконечным, до тех пор, пока поставленное условие будет верным.

Для цикла надо задать начальное значение счетчика циклов, задать условие для этого счетчика и не забыть дополнить тело цикла увеличением счетчика при каждой итерации. Либо же добавить любое другое изменение значения счетчика, которое может привести срабатыванию условия. Второй вариант цикла while - это сначала создать объект с логическим значением TRUE и его поставить у условие, а потом прописать в теле цикла, что при определенных условиях значение сменится на FALSE, что и приведет к остановке цикла.

Выведем первые три элемента вектора letters с помощью цикла while.

i <- 1
while (i < 4) {
  my_l <- letters[i]
  cat('letter', my_l, '\n')
  i <- i + 1
}
## letter a 
## letter b 
## letter c
cat('i =', i)
## i = 4

Как правило, while нужен тогда, когда надо подсчитать количество попыток до какого-то результата, либо же неизвестно, сколько попыток модет потребоваться. Самый показательный пример - сбор данных с POST-запросом, когда сервер может не отвечать, соединение может рваться, и так далее.

repeat (Advanced)

Цикл repeat схож с циклом while, только он выполняется до тех пор, пока пока при выполнении выражения не будет достигнут желаемый результат и не будет вызвана команда прерывания цикла. На том же примере с буквами:

i <- 1
repeat {
    my_l <- letters[i]
    if (i == 4) {
      break()
    } else {
      cat('letter', my_l, '\n')
      i <- i + 1
    }
}
## letter a 
## letter b 
## letter c
cat('i =', i)
## i = 4


Прерывание циклов (Advanced)

В какие-то моменты возникает необходимость прервать цикл или же пропустить последующеи действия и начать новую итерацию цикла. Для этих целей используют функции break() и next(). Выше в цикле repeat мы уже использовали break(), вот еще один пример цикла с прерыванием:

for (i in letters[1:10]) {
  cat(i, '\n')
  if (i == 'c')
    break()
}
## a 
## b 
## c

Эффективнее всего функции прерывания в вложенных циклах - если прервать выполнение вложенного цикла, то родительский цикл не будет прерван и продолжит итерироваться.

Неявные циклы, семейство *pply

Циклами в их привычном большинству программистов виде в R пользуются не очень часто. Как правило, это ситуации, когда неизвестно количество возможных циклов или, наоборот, их ничтожное количество (несколько названий файлов). В большинстве же случаев пользуются так называемыми неявными циклами — функциями семейства *pply.

Самая распространённая и покрывающая большинство задач функция семейства — это lapply(). Первый аргумент функции — набор элементов, над которыми должно быть произведено какое-то действие. Буква l в названии маркирует, что функция обычно применяется к спискам (l = list), однако на практике используются и векторы, и списки и т. д. Фактически первый аргумент в функции lapply() схож с итератором в цикле for. Второй аргумент — это собственно функция, которая должна быть применена к элементам вектора/списка из первого аргумента.

В качестве результата работы функция lapply() возвращает список, где каждый элемент — результат применения указанной во втором аргументе функции к каждому элементу первого аргумента. В этом заключается одно из отличий от классических циклов, в которых тело цикла всего лишь повторяется определённое количество раз. То есть в классических циклах, в отличие от lapply(), нет возможности создать объект с результатами цикла, и надо изменять созданный за пределами цикла объект.

res <- lapply(1:5, sqrt)
str(res)
## List of 5
##  $ : num 1
##  $ : num 1.41
##  $ : num 1.73
##  $ : num 2
##  $ : num 2.24
unlist(res)
## [1] 1.000000 1.414214 1.732051 2.000000 2.236068

Если же у функции, используемой в lapply(), есть дополнительные аргументы, то они идут последующими аргументами, так как в списке аргументов lapply() заданы ..., дополнительные аргументы.

# создадим список из векторов
my_list <- list(el1 = c(1, 2, 3, 4), 
                el2 = c(1, 2, NA, 4))

# вычислим среднее и укажем, что NA надо пропускать
lapply(my_list, mean, na.rm = TRUE)
## $el1
## [1] 2.5
## 
## $el2
## [1] 2.333333

Несмотря на определённую гибкость и возможность указывать аргументы функции, чаще всего в lapply() используются анонимные функции, в которые в качестве аргумента при выполнении передаётся значения объекта, переданного в первый аргумент (по которому осуществляется итерирование).

res <- lapply(1:5, function(x) {
  z <- x * 2
  z <- sqrt(z)
  z
})
str(res)
## List of 5
##  $ : num 1.41
##  $ : num 2
##  $ : num 2.45
##  $ : num 2.83
##  $ : num 3.16
unlist(res)
## [1] 1.414214 2.000000 2.449490 2.828427 3.162278

Семейство *pply-функций достаточно велико, вот наиболее часто используемые функции (тут я ориентируюсь на список С. Мастицкого):

  • lapply(): l в названии означает list, список. Используется в случаях, когда необходимо применить какую-либо функцию к каждому элементу списка и получить результат также в виде списка. На деле обычно служит более удобным аналогом цикла for.

  • sapply(): s в названии означает simplify, упрощение. Работает как lapply(), только в результате отдаёт именованный вектор.

  • apply(): используется в случаях, когда необходимо применить какую-либо функцию ко всем строкам или столбцам матрицы (или массивов большей размерности).

  • vapply(): v в названии означает velocity, скорость. Аналогична lapply() и sapply(), однако в качестве ещё одного аргумента требует указать тип данных, которые должны быть получены в результате. Это несколько ускоряет работу функции, что и привело к такому названию.

  • mapply(): m в названии означает multivariate, многомерный. Используется в случаях, когда необходимо поэлементно применить какую-либо функцию одновременно к нескольким объектам (например, получить сумму первых элементов векторов, затему сумму вторых элементов векторов и т. д.).

  • rapply(): r в названии означает recursively, рекурсивно. Используется в случаях, когда необходимо применить какую-либо функцию к компонентам вложенного списка.

Некоторые пакеты имеют свои реализации неявных циклов, например, parallel::mcmapply(), которая часто используется для параллелизации кода.

Cоздание функций

В R огромное количество готовых функций, написанных разработчиками ядра или пакетов. Однако нередко бывает необходимо написать собственную функцию. Причин их написания может быть много - не устраивают существующие, хочется убрать повторяющиеся куски из кода, много операций, которые выполняются итеративно и неоднократно и т.д. В таких случаях проще и лучше написать собственную функцию. Есть вполне очевидная рекомендация - если какая-то часть кода будет использоваться больше одного раза, возможно, ее следует обернуть в функцию.

Все функции в R состоят из трех частей имеют следующий общий вид:

my_fun <- function(arg1, arg2) {
  # тело функции, операции, перемножаем переданные значения
  tmp <- arg1 * arg2

  # возвращаем результат
  return(tmp)
}

В этом примере создания функции:

  • Выражение my_fun <- function(arg1, arg2) - это создание объекта-функции под названием my_fun
  • arg1 и arg2 - два аргумента функции (функция может принять два разных значения и произвести над ними какие-то операции)
  • код в фигурных скобках - собственно тело функции, набор операций, которые должны совершаться над переданными значениями.
  • return(tmp) - результат выполнения функции, который будет передан в глобальное окружение.

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


Аргументы функции

Аргументы функции указываются в круглых скобках при определении функции. В теле функции имена аргументов служат своего рода абстрактными названиями для любых объектов, которые переданы в аргументы при вызове функции. Собственно, “передать какое-то значение в аргумент функции” означает, что при выполнении функции над этим объектом будут проведены те операции, которые в коде (теле) функции проводятся над этим аргументом. В принципе, выражения “передать значение в аргумент” тождественно “использовать значение в качестве аргумента”, второе, возможно, даже более корректно.

Простейший пример функции с одним аргументом. Функция вычисляет квадратный корень и округляет результат до второго знака:

# создаем функцию
my_fun <- function(x) {
  z <- sqrt(x)
  z <- round(z, 2)
  z
}

# используем функцию
my_fun(5)
## [1] 2.24

Как правило, все функции имеют свой набор аргументов, однако в редких случаях возможно создание функций вообще без аргументов. В таких случаях функции либо вычисляют и возвращают какое-то определенное значение, либо производят какие-то операции с объектами родительского окружения. Оба эти варианта, следует уточнить, крайне не рекомендуются к использованию, так как либо бессмысленны и усложняют код, либо просто вредны и некорректны с точки зрения R. Редкими примерами осмысленного использования функций без аргументов могут послужить функции getwd(), Sys.time() и им подобные.

Ниже пример функции, которая вычисляет квадратный корень из числа 5 и округляет его до второго знака:

# создаем функцию
my_fun <- function() {
  x <- sqrt(5)
  x <- round(x, 2)
  x
}

# используем функцию
my_fun()
## [1] 2.24


Значения аргументов по умолчанию (Advanced)

Нередко в практике встречаются ситуации, когда один из аргументов функции принимает какое-то определенное значение (или значение из определенного вектора значений) намного чаще, чем все прочие возможные значения. В таких случаях разумно задать значение этому аргументу по умолчанию - то есть, если не указано обратное, то будет использоваться заданное значение. Например, функция sort() имеет значение аргумента decreasing по умолчанию равное FALSE. Соответственно, если не задавать этот аргумент, то функция сортирует вектор по возрастанию. И наоборот, если нужна сортировка по убыванию, следует прямо задать значение аргумента decreasing = TRUE:

sort(1:5)
## [1] 1 2 3 4 5
sort(1:5, decreasing = TRUE)
## [1] 5 4 3 2 1

Если посмотреть в справке описание аргументов функции sort(), то видно, что аргументу x никакое значение не передается, а аргументу decreasing - передается значение FALSE.

args(sort)
## function (x, decreasing = FALSE, ...) 
## NULL

Собственно, таким образом и задаются значения по умолчанию - при объявлении функции аргументу уже передается какое-то значение. Например, функция ниже умножает значение, переданное в качестве первого аргумента, на 2, если значение второго аргумента не указано

# объявляем функцию my_fun, которая перемножает два переданных объекта
# если второй аргумент не указан, то считаем, что он равен 2
my_fun_def <- function(arg1, arg2 = 2) {
  tmp <- arg1 * arg2
  return(tmp)
}

Используем созданную функцию, и в аргумент, у которого есть значение по умолчанию, ничего не передаем (игнорируем его):

# не указываем аргумент
x <- 9
my_fun_def(x)
## [1] 18


Тело функции

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

# объявляем функции
my_fun1 <- function(x) {x ^ 5}
my_fun2 <- function(x) x ^ 5

# вызываем функции
my_fun1(5)
## [1] 3125
my_fun2(5)
## [1] 3125

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


Результат функции

Большинство функций в результате своей работы возвращает один объект. Объектом может быть вектор значений, список, таблица, другая функция и так далее. Для того, чтобы указать, какой именно объект должна вернуть функция, используется return() и, что важно, использовать эту функцию можно в любом месте тела функции. Впрочем, возможен и более лаконичный вариант, когда самой последней строчкой тела функции указывается название возвращаемого объекта. Следует учитывать, что это должно быть именно имя объекта или какое-то выражение, создающее новый объект (*pply-функции, function(), data.frame() и так далее), за исключением операции присвоения:

# используем return(x) в середине кода
my_fun1 <- function(x) {
  x <- x ^ 3
  return(x)
  x <- x * 2
}

# возвращаем x просто последней строчкой
my_fun2 <- function(x) {
  x <- x ^ 3
  x
}

# проверяем
my_fun1(2)
## [1] 8
my_fun2(2)
## [1] 8

Функции всегда возвращают только один объект. Если необходимо, чтобы функция возвращала несколько разных значений или объектов, то в таком случае необходимо их все собрать в список (list()) или таблицу (любой вариант - data.table, data.frame, tibble).

В примере функция возвращает список из двух элементов - первый является результатом перемножения значений аргументов функции (значений, переданных в аргументы), а второй - возведение значения первого аргумента в степень, которую задает значение второго аргумента.

my_f <- function(x, y) {
  mult <- x * y
  pw <- x ^ y
  result <- list(
    x_y_mult = mult,
    x_y_power = pw
  )
  return(result)
}

my_res <- my_f(3, 4)
str(my_res)
## List of 2
##  $ x_y_mult : num 12
##  $ x_y_power: num 81
my_res$x_y_mult
## [1] 12
my_res$x_y_power
## [1] 81

Скрапинг сайта журнала

Сбор информации по рубрике

Каждая рубрика содержит несколько страниц, на которых виде находятся ссылки на статьи и краткая информация по ним. Выше мы научились собирать информацию по одной такой странице, теперь надо научиться определять, сколько вообще таких страниц есть в каждой рубрике и собрать информацию по ним всем.

Получить количество страниц с списками статей мы можем простым выражением - эта информация хранится в ячейке с классом last:

library(rvest)
library(data.table)

# идем на первую страницу рубрики
rubric_url <- 'https://ecsoc.hse.ru/rubric/26590246.html'
rubric_page <- read_html(rubric_url)

# указываем путь к значению и извлекаем значение
last_num <- 
  html_element(rubric_page, xpath = '//div[@class="pageNav"]/span[@class="last"]/a') %>% 
  html_text()
# конвертируем строку в число
last_num <- as.numeric(last_num)
last_num
## [1] 15

После того, как мы получили количество страниц, мы можем написать lapply()-выражение, в котором будем ходить по этим страницам и собирать с них ссылки на статьи и, соответственно, из этих ссылок уже собирать информацию по статьям в таблицу. Логика работы аналогична предыдущему блоку, когда собирали информацию по одной странице.

В веб-верстке, когда есть несколько страниц одного типа, как в нашем случае, менять url-адрес простым изменением номера. То есть, https://ecsoc.hse.ru/rubric/26590246_page=1_sort=author.html для первой страницы, https://ecsoc.hse.ru/rubric/26590246_page=3_sort=author.html для третьей и так далее.

Правда, нередко первую страницу можно указывать и без номера. То есть, 'https://ecsoc.hse.ru/rubric/26590246.html' и https://ecsoc.hse.ru/rubric/26590246_page=1_sort=author.html должны быть идентичны, первый вариант более человекочитаем, второй — более корректен с точки зрения верстки.

Соответственно, в lapply() в первом аргументе указываем, по каким номерам страниц мы будем ходить и собирать информацию. В нашем случае это 1:last_num (что тождественно seq(from = 1, to = last_num, by = 1)). В анонимной функции мы собираем адрес страницы (используя меняющееся на каждой итерации значение из первого аргумента) и импортируем эту страницу. А потом применяем уже написанный нами ранее блок сбора данных о статьях с одной страницы рубрики.

rubric_articles <- lapply(1:last_num, function(i) {
  # импортируем страницу рубрики  
  rubric_url <- paste('https://ecsoc.hse.ru/rubric/26590246_page=', i, '_sort=author.html', sep = '')
  rubric_page <- read_html(rubric_url)
  
  # собираем информацию по странице рубрики  
  res <- lapply(3:12, function(x) {
    art_xpath <- paste('//div[@class="centercolumn"]/div[', x, ']/small/a[2]', sep = '')
    page_url <- html_element(rubric_page, xpath = art_xpath) %>% 
      html_attr('href')  
    # проверка, что есть ссылка на статью    
    if (!is.na(page_url)) {
      # если ссылка не NA, то импортируем данные по статье      
      page <- read_html(page_url)
      article_info <- data.table(
        url = page_url,
        autor = html_element(page, xpath = '//div[@class="centercolumn"]/h3') %>% html_text2(),
        title = html_element(page, xpath = '//h2[@class="article-header"]') %>% html_text(),
        annotation = html_element(page, xpath = '//div[@class="annot"]') %>% html_text2(),
        keywords = html_element(page, xpath = '//div[@class="keywords"]') %>% html_text2(),
        doi = html_element(page, xpath = '//div[@class="keywords"][2]') %>% html_text2(),
        volume = html_element(page, xpath = '//div[@class="centercolumn"]/div') %>% html_text2()
      )
      return(article_info)
    }
  })
  res <- rbindlist(res)  
  return(res)
})
# собираем результат в табличку
rubric_articles <- rbindlist(rubric_articles)
kableExtra::kable(rubric_articles[1:3])
url autor title annotation keywords doi volume
https://ecsoc.hse.ru/2000-1-1/26592783.html Biggart N. W. Social Organisation and Economic Development Policy makers and scholars have been searching for the magic key to unlock the economic potential of underdeveloped regions. I will argue that there is no single "key", but rather that research in economic sociology shows that development depends on utilizing the historically developed patterns of social organization. Societies do best when they pursue economic activities for which their social structure gives them a comparative advantage. NA NA
  1. Т. 1. № 1. С. 6–12 [содержание номера]
https://ecsoc.hse.ru/2004-5-4/26591399.html Jagd S. French Economics of Convention and Economic Sociology The French Economics of convention tradition has developed to be an influential research tradition situated in the area between economics and sociology. The aim of the paper is to explore some of the themes that may be common to economics of conventions and economic sociology by looking more closely into three recent texts from the economics of convention tradition discussing, in slightly different ways, differences and similarities between economics of convention and economic sociology. It is argued that André Orléan’s point that a common aim could be to ‘denaturalise’ the institutional foundation of markets and of money may be an occasion for economic sociology to focus even more on elaborating on the institutional void created by traditional economic theory. A second point is that economic sociology could benefit from the perspective of a plurality of forms of coordination involved in economic action. NA NA
  1. Т. 5. № 4. С. 22–36 [содержание номера]
https://ecsoc.hse.ru/2000-1-2/26592841.html Stark D. Ambiguous Assets for Uncertain Environments: Heterarchy in Postsocialist Firms This paper questions the disciplinary division of labor whereby economists study value and sociologists study values; and it rejects the pact whereby economists study the economy and sociologists study the social relations in which economies are embedded. One of the core tasks of economic sociology is to develop a sociology of worth. I explore these themes by discussing the emergence of a new organizational form: heterarchy. Heterarchies are characterized by lateral accountability and the organization of diversity. In them we find an active rivalry of heterogeneous principles of evaluation as actors manuver in multiple networks by attempting to hold resources that are legitimated in more than one regime of worth. Examples of such organizational innovations are drawn from a longitudinal study of the intersecting ownership portfolios of firms in postsocialist Hungary and from my recent research on strategic alliances among new media firms in Manhattan’s Silicon Valley. NA NA
  1. Т. 1. № 2. С. 7–34 [содержание номера]

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


Дополнительные материалы

  • Глава про функции в моем учебнике по R

  • Раздел про область видимости функции

Домашние задания

level 1 (IATYTD)

  • напишите функцию, которая добавляет к переданному в аргумент x значению строку x =. Вам потребуется еще функция paste(). То есть:
my_f(5)
## [1] "x = 5"


level 2 (HNTR)

level 3 (HMP)

  • Создайте функцию, которая возвращает среднее и стандартное отклонение вектора, переданного в аргумент x, а также высчитывает медиану и моду.


level 4 (UV)

Напишите функцию, которая принимает на вход название пакета в строковом виде, а на выходе возвращает табличку с колонками: package (название пакета), publish_date (дата публикации), version (версию пакета), reference_manual (ссылку на мануал). Вся информация берется с страницы пакета на сайте CRAN, ссылка на страницу формируется по маске https://CRAN.R-project.org/package=PACKAGENAME, где вместо PACKAGENAME - название пакета.


level 5 (N)

Разберитесь, как и почему работают вот эти выражения. Если знаете объявление функций в Python, попробуйте сделать сравнительный анализ.

x <- 5
y <- 7
my_f1 <- function(x, z) x * y
my_f1(9)
## [1] 63
my_f2 <- function(x) function(z) z * 5
x <- my_f2()
x(5)
## [1] 25