Глава 4 Функции
В настоящей главе мы кратко познакомимся с функциональным программированием и метапрограммированием, которые являются одними из основных техник программирования на R.
4.1 Функциональное программирование
4.1.1 Функции
Функции в R можно использовать для структурирования кода на логически завершенные, автономные фрагменты кода, каждый из которых выполняет конкретную задачу. Синтаксис функции выглядит следующим образом:
functionName = function(parameter1, parameter2, ...){
...
return(result)
...
last_value = ...
}
Функция создается c помощью ключевого слова function
, за которым в круглых скобках заключается произвольное количество параметров (столько, сколько вам нужно: от нуля и более). С помощью этих параметров вы сможете передавать внутрь функции значения переменных. Созданной функции необходимо дать имя, используя оператор присвоения <-
или =
.
Возврат значения функции осуществляется двумя способами:
Если не указано иное, то будет возвращен результат вычисления последнего выражения, выполненного внутри функции (
last_value
в примере выше)Результат можно вернуть принудительно в любом месте функции, передав его в выражение
return()
.
Выражение
return()
работает аналогично ключевому словуbreak
в циклах: оно прерывает выполнение функции и осуществляет выход из нее. Как правило, return() используется, если возврат значения надо сделать где-то посередине или в начале функции. Однако я реккомендую использовать его всегда, поскольку это помогает читателю вашей функции быстро определить, что же именно возвращается из функции
Как правило, функции оказываются полезны, если:
- Вы дублируете один и тот же код в разных частях программы
- Ваш код становится слишком длинным, при этом присутствует очевидная этапность решения задачи, позволяющая разбить программу на автономные блоки
- У вас есть фрагмент кода, который выполняет вспомогательную (второстепенную функцию), и не относится непосредственно к основной логике программы.
Предположим, у нас есть линия, заданная координатами четырех точек, и нам надо вычислить длины каждого из трех отрезков. Без использования функции мы запишем это так:
x = rnorm(4)
y = rnorm(4)
d12 = sqrt((x[1] - x[2]) ^ 2 + (y[1] - y[2]) ^ 2)
d23 = sqrt((x[2] - x[3]) ^ 2 + (y[2] - y[3]) ^ 2)
d31 = sqrt((x[3] - x[4]) ^ 2 + (y[3] - y[4]) ^ 2)
cat(d12, d23, d31)
## 0.6288496 2.757015 0.2309297
В правой части этих выражений стоит один и тот же код, который я скопировал и вставил, а далее заменил названия переменных. Это плохо сразу по двум причинам: дублирование фрагментов программы и возрастание вероятности опечаток в скопированой копии. Улучшить код можно, введя функцию вычисления расстояний:
distance = function(x1, y1, x2, y2) {
sqrt((x1 - x2) ^ 2 + (y1 - y2) ^ 2)
}
d12 = distance(x[1], y[1], x[2], y[2])
d23 = distance(x[2], y[2], x[3], y[3])
d31 = distance(x[3], y[3], x[4], y[4])
cat(d12, d23, d31)
## 0.6288496 2.757015 0.2309297
Функция всегда возвращает один объект: значение, вектор, список и т.д. Например, мы можем сделать функцию следующего уровня, рассчитывающая сразу все расстояния для множества точек:
distances = function(x, y) {
n = length(x)
distance(x[1:(n-1)], y[1:(n-1)], x[2:n], y[2:n])
}
distances(x, y)
## [1] 0.6288496 2.7570155 0.2309297
Можно пойти еще дальше, и сделать функцию, выполняющую вычисление длины линии, заданной координатами:
line_length = function(x, y) {
sum(distances(x, y))
}
line_length(x, y)
## [1] 3.616795
Обратите внимание на то, как мы используем одну ранее написанную функцию при создании другой функции! Это весьма распространенная практика: одна и та же функция может быть как самостоятельно полезной (вызываться непосредственно в программе полтьзователя), так и применяться для решения задач внутри других функций. При этом иногда даже относительно простые фрагменты кода имеет смысл оформлять в виде функций, так как это может улучшить читаемость программы и пояснить смысл выполняемых операций. Так, например, line_length(x, y)
в более явном виде обозначает операцию вычисления длины по координатам, нежели sum(distances(x, y))
. В то же время, здесь важно не переусердствовать и оформлять короткие фрагменты кода в виде функций только если они будут применяться вами неоднократно.
Если вам нужно вернуть из функции несколько объектов, имеющих разный тип или смысл, заключите их в список и дайте каждому элементу списка “говорящее” имя. Например, помимо периметра, мы можем вернуть также извилистость линии (отношение длины линии к длине отрезка, соединяющего ее первую и последнюю точку):
4.1.2 Функционалы
Данные (в том числе географические) практически всегда носят множественный характер и организованы в определенные структуры (см. главу 2). Эта особенность данных выдвигает логичное желание иметь процедуры, которые можно применять к полному набору данных, а не к его отдельным компонентам. Это и есть процедуры векторизованных вычислений.
Предположим, вам необходимо что-то вычислить для каждой строки таблицы, при этом порядок вычисления зависит от содержимого ячеек данной строки. Вы можете организовать подобные вычисления с помощью циклов, однако в R существуют специальные функции семейста apply
, которые позволяют решать подобные задачи более элегантно и с высокой скоростью:
Функция | Назначение |
---|---|
apply() |
применить функцию ко всем строкам или столбцам матрицы |
lapply() |
применить функцию к каждому компоненту вектора или списка и получить результат также в виде списка (l — list) |
sapply() |
применить функцию к каждому компоненту вектора или списка и получить результат в виде вектора (s — simplify) |
vapply() |
аналогична vapply, но требует явного задания типа данных возвращаемого вектора, за счет чего работает быстрее (v — velocity) |
mapply() |
применить функцию к каждому компоненту нескольких векторов или списков и вернуть результат в виде списка (m — multivariate) |
rapply() |
применить функцию рекурсивно ко всем элементам переданного списка и вернуть результат в аналогичной структур (r — recursive) |
tapply() |
применить функцию ко всем компонентам вектора или списка, сгруппировав их по значению переданного фактора |
Функции семейства
apply
, принимающие на вход списки, могут работать и с фреймами данных. В этом случае фрейм внутри функции будет преобразован с помощью функцииas.list()
в список, элементами которого являются столбцы (переменные) входного фрейма данных. Данные при этом не потеряются, их типы тоже не изменятся.
Базовая функция apply()
имеет следующие аргументы:
-
X
— массив любой размерности (включая вектор) -
MARGIN
— измерения по которым необходимо вести вычисления. Для матрицы1
означает строку,2
означает столбец,c(1, 2)
будет означать, что вычисления производятся по всем комбинациям строк и столбцов -
FUN
— функция, которая будет применяться к каждому элементу указанных измерений -
...
— список аргументов, которые надо передать в функциюFUN
(в этом случае массив должен передаваться обязательно в первый аргумент)
Другие функции семейства apply
в приложении к фреймам данных будут работать со столбцами (переменными), интерпретируя их как элементы списка. Наиболее часто из них используются lapply()
, sapply()
и vapply()
. В отличие от apply()
, они уже не принимаеют номера измерений и работают только с элементами переданного списка.
Рассмотрим применение функций данного семейства на примере анализа основных социально-экономических характеристик столиц субъектов Северо-Западного округа за 2015 год:
library(readxl)
(df = read_excel("data/sevzap.xlsx", col_types = c('text', rep('numeric', 17))))
## # A tibble: 10 × 18
## city pop birth death labor salary livspace doctors hosp assets business
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 Петр… 277. 13 12.3 72.3 36268. 24.4 75 17 2.47e5 15856
## 2 Сыкт… 259. 14.5 10.4 84.1 39790 22.8 79.5 17 2.17e5 10068
## 3 Арха… 358. 11.8 11.6 97.9 40303. 22.7 82.3 19 2.79e5 13016
## 4 Нарь… 24.5 18.1 7.6 12.7 69884. 23.6 65.2 2 1.05e5 704
## 5 Воло… 313. 16.2 11.4 91 31483 24.7 60.9 18 8.27e5 21931
## 6 Кали… 460. 13.3 13.4 126. 34142 27.7 69.8 25 3.27e5 38013
## 7 Мурм… 302. 12.4 11.9 96.9 53240. 23.8 68.8 15 7.24e5 16041
## 8 Вели… 222. 13.8 13.8 74.1 32377. 24.1 78.9 14 1.59e5 9583
## 9 Псков 208. 13.2 13.7 59.6 27405. 25 59 13 1.31e5 8752
## 10 Санк… 5226. 13.6 11.9 2055. 44187 23.6 73.8 112 4.10e6 374999
## # … with 7 more variables: minerals <dbl>, manufact <dbl>, engaswat <dbl>,
## # construct <dbl>, apart <dbl>, retail <dbl>, invest <dbl>
В данной таблице каждый столбец представляет независимую переменную со своими единицами измерения, поэтому ее необходимо оставить в “широкой” форме, не преобразуя в длинную. Используя apply
, можно быстро получить максимальные значения каждой переменной:
apply(df[-1], 2, max)
## pop birth death labor salary livspace doctors hosp
## 5225.7 18.1 13.8 2055.3 69883.5 27.7 82.3 112.0
## assets business minerals manufact engaswat construct apart retail
## 4102243.8 374999.0 NA 1978634.0 173292.0 397229.0 3031.0 1144607.0
## invest
## 521293.0
Что равносильно вызову sapply:
sapply(df[-1], max)
## pop birth death labor salary livspace doctors hosp
## 5225.7 18.1 13.8 2055.3 69883.5 27.7 82.3 112.0
## assets business minerals manufact engaswat construct apart retail
## 4102243.8 374999.0 NA 1978634.0 173292.0 397229.0 3031.0 1144607.0
## invest
## 521293.0
В качестве функции можно использовать не только стандартные, но и пользовательские функции. Например, нам может быть интересно не максимальное значение показателя, а его отношение к среднему значению среди всех городов. Здесь уже одной функцией не обойдешься, так как нужно каждый столбец поделить на среднее значение по нему. Для этого определим небольшую пользовательскую функцию непосредственно при вызове sapply()
:
(normalized = sapply(df[-1], function(X) { round(X / mean(X, na.rm = TRUE), 2) }))
## pop birth death labor salary livspace doctors hosp assets business
## [1,] 0.36 0.93 1.04 0.26 0.89 1.01 1.05 0.67 0.35 0.31
## [2,] 0.34 1.04 0.88 0.30 0.97 0.94 1.11 0.67 0.30 0.20
## [3,] 0.47 0.84 0.98 0.35 0.99 0.94 1.15 0.75 0.39 0.26
## [4,] 0.03 1.29 0.64 0.05 1.71 0.97 0.91 0.08 0.15 0.01
## [5,] 0.41 1.16 0.97 0.33 0.77 1.02 0.85 0.71 1.16 0.43
## [6,] 0.60 0.95 1.14 0.46 0.83 1.14 0.98 0.99 0.46 0.75
## [7,] 0.39 0.89 1.01 0.35 1.30 0.98 0.96 0.60 1.02 0.32
## [8,] 0.29 0.99 1.17 0.27 0.79 0.99 1.11 0.56 0.22 0.19
## [9,] 0.27 0.94 1.16 0.22 0.67 1.03 0.83 0.52 0.18 0.17
## [10,] 6.83 0.97 1.01 7.42 1.08 0.97 1.03 4.44 5.76 7.37
## minerals manufact engaswat construct apart retail invest
## [1,] 0.06 0.05 0.52 0.13 0.45 0.23 0.11
## [2,] 0.00 0.28 0.43 0.09 0.31 0.21 0.14
## [3,] 0.00 0.06 0.49 0.09 0.17 0.17 0.15
## [4,] 4.12 0.05 0.03 0.08 0.05 0.02 0.52
## [5,] NA 0.15 0.63 0.09 0.52 0.23 0.13
## [6,] 0.82 0.83 0.68 0.28 1.24 0.38 0.53
## [7,] NA 0.23 0.42 0.07 0.02 0.29 0.75
## [8,] NA 0.42 0.45 0.08 0.32 0.17 0.30
## [9,] NA 0.08 0.22 0.04 0.27 0.17 0.10
## [10,] 1.00 7.87 6.13 9.05 6.65 8.13 7.28
Полученный объект является матрицей. Таким образом, можно видеть, что функционалы бывают полезны не только для агрегирования таблиц, но и для преобразования данных, когда структура таблицы остается прежней.
В приведенном выше коде мы сознательно исключили первый столбец, поскольку он является текстовым. Можно сделать более мощную и универсальную функцию, которая будет нормировать все числовые столбцы таблицы, а текстовые оставлять в оригинале. Для этого проверку типа данных надо внести внутрь функции. Поскольку код функции при этом вырастает, целесообразно определить ее заранее. Поскольку в этом случае часть векторов будет символьной, а не числовой, необходимо применять функцию lapply()
, которая вернет список из векторов, а не матрицу и таким образом сохранит типы каждого столбца:
library(dplyr)
normalize = function(X) {
if (is.numeric(X))
round(X / mean(X, na.rm = TRUE), 2)
else X
}
(normalized_df = df |> lapply(normalize) |> as_tibble())
## # A tibble: 10 × 18
## city pop birth death labor salary livspace doctors hosp assets business
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 Петроз… 0.36 0.93 1.04 0.26 0.89 1.01 1.05 0.67 0.35 0.31
## 2 Сыктыв… 0.34 1.04 0.88 0.3 0.97 0.94 1.11 0.67 0.3 0.2
## 3 Арханг… 0.47 0.84 0.98 0.35 0.99 0.94 1.15 0.75 0.39 0.26
## 4 Нарьян… 0.03 1.29 0.64 0.05 1.71 0.97 0.91 0.08 0.15 0.01
## 5 Вологда 0.41 1.16 0.97 0.33 0.77 1.02 0.85 0.71 1.16 0.43
## 6 Калини… 0.6 0.95 1.14 0.46 0.83 1.14 0.98 0.99 0.46 0.75
## 7 Мурман… 0.39 0.89 1.01 0.35 1.3 0.98 0.96 0.6 1.02 0.32
## 8 Велики… 0.29 0.99 1.17 0.27 0.79 0.99 1.11 0.56 0.22 0.19
## 9 Псков 0.27 0.94 1.16 0.22 0.67 1.03 0.83 0.52 0.18 0.17
## 10 Санкт-… 6.83 0.97 1.01 7.42 1.08 0.97 1.03 4.44 5.76 7.37
## # … with 7 more variables: minerals <dbl>, manufact <dbl>, engaswat <dbl>,
## # construct <dbl>, apart <dbl>, retail <dbl>, invest <dbl>
В качестве альтернативы функциям apply
можно также воспользоваться вычислениями посредством функций семейства map
из пакета purrr
(еще один пакет из tidyverse). Эти функции работают аналогично sapply()
:
-
map()
возвращает список. -
map_lgl()
возвращает вектор логических значений. -
map_int()
возвращает вектор целочисленных значений. -
map_dbl()
возвращает вектор чисел с плавающей точкой. -
map_chr()
возвращает вектор строк.
Например, вышеприведенные операции можно осуществить средствами purrr вот так:
library(purrr)
map_dbl(df[-1], max)
## pop birth death labor salary livspace doctors hosp
## 5225.7 18.1 13.8 2055.3 69883.5 27.7 82.3 112.0
## assets business minerals manufact engaswat construct apart retail
## 4102243.8 374999.0 NA 1978634.0 173292.0 397229.0 3031.0 1144607.0
## invest
## 521293.0
df |> map(normalize) |> as_tibble()
## # A tibble: 10 × 18
## city pop birth death labor salary livspace doctors hosp assets business
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 Петроз… 0.36 0.93 1.04 0.26 0.89 1.01 1.05 0.67 0.35 0.31
## 2 Сыктыв… 0.34 1.04 0.88 0.3 0.97 0.94 1.11 0.67 0.3 0.2
## 3 Арханг… 0.47 0.84 0.98 0.35 0.99 0.94 1.15 0.75 0.39 0.26
## 4 Нарьян… 0.03 1.29 0.64 0.05 1.71 0.97 0.91 0.08 0.15 0.01
## 5 Вологда 0.41 1.16 0.97 0.33 0.77 1.02 0.85 0.71 1.16 0.43
## 6 Калини… 0.6 0.95 1.14 0.46 0.83 1.14 0.98 0.99 0.46 0.75
## 7 Мурман… 0.39 0.89 1.01 0.35 1.3 0.98 0.96 0.6 1.02 0.32
## 8 Велики… 0.29 0.99 1.17 0.27 0.79 0.99 1.11 0.56 0.22 0.19
## 9 Псков 0.27 0.94 1.16 0.22 0.67 1.03 0.83 0.52 0.18 0.17
## 10 Санкт-… 6.83 0.97 1.01 7.42 1.08 0.97 1.03 4.44 5.76 7.37
## # … with 7 more variables: minerals <dbl>, manufact <dbl>, engaswat <dbl>,
## # construct <dbl>, apart <dbl>, retail <dbl>, invest <dbl>
4.2 Метапрограммирование
Метапрограммирование — это техника программирования, при которой программный код может генерировать другой код. Широкая поддержка и интенсивное использование метапрограммирования – одна из удивительных черт R, которая ставит его особняком на фоне многих других языков, включая Python. Метапрограммирование позволяет во многих случаях сделать код более компактным и подойти к решению задачи элегантным путем.
Вы уже сталкивались с метапрограммированием, когда работали с функциями dplyr
. Например, сравните следующие два способа извлечь столбец из фрейма данных:
df["salary"]
## # A tibble: 10 × 1
## salary
## <dbl>
## 1 36268.
## 2 39790
## 3 40303.
## 4 69884.
## 5 31483
## 6 34142
## 7 53240.
## 8 32377.
## 9 27405.
## 10 44187
select(df, salary)
## # A tibble: 10 × 1
## salary
## <dbl>
## 1 36268.
## 2 39790
## 3 40303.
## 4 69884.
## 5 31483
## 6 34142
## 7 53240.
## 8 32377.
## 9 27405.
## 10 44187
В обоих случаях мы получили один и тот же результат. В первом случае было указано название столбца в кавычках. Во втором мы передали название столбца без кавычек, точно так же как было передано название фрейма данных. Однако фрейм данных с названием df
был создан перед вызовом select
. Вас не смущает, что объекта salary
в нашем скрипте не существует, а мы его передаем в качестве аргумента функции? Мы ведь не создавали такой переменной в программе. Как же функция догадывается о том, что его надо интерпретировать не как переменную, а как название столбца?
Для начала разберемся с тем, что происходит в программном коде:
-
select()
представляет собой вызов (call) функции -
df
иsalary
представляют собой символы, обозначающие объекты в программе. -
"salary"
представляет собой константу
R — это функциональный язык программирования. Любая программа на R состоит из вызовов функций, которые применяются к символам и константам. Привычные нам арифметические операции на самом деле тоже являются вызовами функций. Это выглядит довольно неожиданно:
a = 78 # стандартная запись
`=`(a, 78) # функциональная запись
a + 4 # стандартная запись
## [1] 82
`+`(a, 4) # функциональная запись
## [1] 82
df['salary'] # стандартная запись
## # A tibble: 10 × 1
## salary
## <dbl>
## 1 36268.
## 2 39790
## 3 40303.
## 4 69884.
## 5 31483
## 6 34142
## 7 53240.
## 8 32377.
## 9 27405.
## 10 44187
`[`(df, 'salary') # функциональная запись
## # A tibble: 10 × 1
## salary
## <dbl>
## 1 36268.
## 2 39790
## 3 40303.
## 4 69884.
## 5 31483
## 6 34142
## 7 53240.
## 8 32377.
## 9 27405.
## 10 44187
Таким образом, бинарный оператор в R представляет собой вызов функции с двумя аргументами.
Программный код a + 4
с точки зрения R является выражением (expression). Выражение может состоять вообще из одного символа, то есть a
— это тоже выражение. Когда интерпретатор доходит до выражения a + 4
, он выполняет следующее:
-
Оценка (evaluation) выражения
a
. Результатом оценки является константа78
-
Вызов (call) функции
+
, которая складывает константы 78 и 4
Не все символы и выражения в программе необходимо оценивать. Некоторые из них необходимо квотировать, то есть использовать в качестве имени объекта. Квотацию можно условно рассматривать как простановку кавычек вокруг выражения. Для обозначения квотации в явном виде используются обратные кавычки: `
Мы уже сталкивались с квотацией при вызове функции сложения: `+`(a, 4)
. В данном случае квотация была нужна чтобы +
интерпретировался как имя функции.
Явная квотация бывает необходима, когда объекты R имеют недопустимые имена, например начинаются с цифры или содержат пробелы. Для начала рассмотрим искусственный пример:
`f + 17` = 87 # создаем объект с именем f + 17
f + 17 # ошибка: переменной f не существует
## Error in eval(expr, envir, enclos): объект 'f' не найден
`f + 17` # обращаемся к объекту путем явной квотации
## [1] 87
Теперь более жизнеспособный пример: данные о населении федеральных округов:
library(readr)
(okruga = read_csv('data/okruga.csv'))
## # A tibble: 8 × 7
## `№` Регион `2005` `2010` `2011` `2012` `2013`
## <dbl> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 Центральный 4341 3761 3613 3651 3570
## 2 2 Северо-Западный 3192 3088 2866 2877 2796
## 3 3 Южный федеральный 1409 1446 1436 1394 1321
## 4 4 Северо-Кавказский 496 390 397 395 374
## 5 5 Приволжский 3162 2883 2857 2854 2849
## 6 6 Уральский 1681 1860 1834 1665 1624
## 7 7 Сибирский 2575 2218 2142 2077 1941
## 8 8 Дальневосточный 871 870 821 765 713
Обратите внимание на обратные кавычки вокруг названий столбцов. Они проставлены потому что числа в среде R по умолчанию оцениваются, и не могут использоваться в качестве символов для объектов. Чтобы разрешить это, используется принудительная квотация. Как мы уже знаем, чтобы обратиться к такому объекту, надо использовать обратные кавычки:
$2010 # ошибка: 2010 - константа, которая оценивается
okruga## Error: <text>:1:8: неожиданная числовая константа
## 1: okruga$2010
## ^
okruga$`2010` # правильно: `2010` -- символ, полученный путем квотации
## [1] 3761 3088 1446 390 2883 1860 2218 870
Теперь вернемся к примеру с использованием функции select()
. Данная функция оценивает первый аргумент (фрейм данных) и квотирует все оставшиеся аргументы, которые отвечают за названия столбцов. Это позволяет избежать использования кавычек и использовать символы для наименования объектов. Чтобы понять, использует ли функция оценку или квотацию ее аргументов, необходимо ознакомиться с ее справкой.
Когда функция применяет квотацию аргументов, говорят что осуществляется нестандартная оценка (NSE – non-standard evaluation). Если аргументы функции оцениваются, то происходит стандартная оценка (SE – standard evaluation)
Иногда бывает необходимо использовать строковые названия столбцов (например, если они записаны у вас в переменные). В таком случае можно использовать функцию select_()
(с подчеркиванием в конце), которая будет выполнять оценку ее аргументов вместо квотации:
f1 = 'salary'
f2 = 'birth'
select_(df, f1, f2) # ОК
## # A tibble: 10 × 2
## salary birth
## <dbl> <dbl>
## 1 36268. 13
## 2 39790 14.5
## 3 40303. 11.8
## 4 69884. 18.1
## 5 31483 16.2
## 6 34142 13.3
## 7 53240. 12.4
## 8 32377. 13.8
## 9 27405. 13.2
## 10 44187 13.6
select_(df, salary, birth) # ошибка: функция пыьтается оценить несуществующие объекты salary и birth
## Error in compat_lazy_dots(.dots, caller_env(), ...): объект 'salary' не найден
Однако кавычки в данном случае усложняют внешний вид программы и затрудняют написание ее текста. Если вы передаете константы в качестве аргументов, они будут работать в обоих случаях:
select(df, 'salary', 'birth')
## # A tibble: 10 × 2
## salary birth
## <dbl> <dbl>
## 1 36268. 13
## 2 39790 14.5
## 3 40303. 11.8
## 4 69884. 18.1
## 5 31483 16.2
## 6 34142 13.3
## 7 53240. 12.4
## 8 32377. 13.8
## 9 27405. 13.2
## 10 44187 13.6
select_(df, 'salary', 'birth')
## # A tibble: 10 × 2
## salary birth
## <dbl> <dbl>
## 1 36268. 13
## 2 39790 14.5
## 3 40303. 11.8
## 4 69884. 18.1
## 5 31483 16.2
## 6 34142 13.3
## 7 53240. 12.4
## 8 32377. 13.8
## 9 27405. 13.2
## 10 44187 13.6
Функции dplyr имеют парные к ним функции с символом подчеркивания на конце, которые производят оценку, а не квотацию аргументов.
Иногда выражения бывает необходимо создать, а оценивать уже потом. Для этого существуеют объекты выражений, которые создаются с помощью функции expression()
:
(d = expression(b^2 - 4*a)) # создаем выражение
## expression(b^2 - 4 * a)
a = 2
b = 7
eval(d) # оцениваем значение выражения
## [1] 41
Все символы в объекте выражения по умолчанию квотируются и выражение хранится в статичном виде до тех пор пока не будет произведена его оценка (подстановка значений переменных вместо их символов).
Выражения используются в формулах, используемых для статистического анализа в R
4.3 Краткий обзор
Для просмотра презентации щелкните на ней один раз левой кнопкой мыши и листайте, используя кнопки на клавиатуре:
Презентацию можно открыть в отдельном окне или вкладке браузере. Для этого щелкните по ней правой кнопкой мыши и выберите соответствующую команду.
4.4 Контрольные вопросы и упражнения
4.4.1 Вопросы
- Что такое функция?
- Какое ключевое слово ипользуется для создания функции?
- В каких случаях целесообразно применение функций?
- Сколько аргументов может принимать функция?
- Можно ли из одной функции вызывать другую функцию?
- Как осуществить принудительный выход из функции с возвратом результата?
- Что необходимо сделать, если надо передать несколько объектов из функции?
- Для чего нужны функционалы семейства apply? В каких задачах они бывают полезны?
- Перечислите функции семейства apply, назовите их отличия и сферы применения.
- Какая функция семейства apply позволяет обабатывать заданные измерения?
- Какой объект первым передается в функцию, подставляемую в параметр
FUN
, если применяется lapply к фрейму данных? - Назовите аналоги функций apply из пакета purrr
- Что такое метапрограммирование?
- Из каких объектов состоят выражения в R?
- Что из себя по на самом деле представляют бинарные операторы в R?
- Как обратиться к объекту, символ которого не является допустимым именем переменной?
- Что такое оценка и квотация выражения? Для чего они используются?
- Как понять, будет ли используемая вами функция квотрировать или оценивать ее аргументы?
- Как называются функции dplyr, осуществляющие оценку, а не квотацию аргументов?
- Как создать и оценить объект выражения в R?
4.4.2 Упражнения
-
Напишите функцию
is_leap(year)
, которая определяет, является ли указанный год високосным. Протестируйте ее в вашем скрипте, используя чтение года из консолииПодсказка: високосным считается год, кратный 400, либо кратный 4, но не кратный 100.
-
Функция Тоблера показывает зависимость скорости пешего маршрута (в км/ч) от угла наклона на местности. Предположим, в вашем распоряжении имеется матрица профиля рельефа, в которой в одном столбце указано расстояние от начала маршрута, а во втором — абсолютная отметка точки. Напишите функцию
hiking_time(profile)
, которая вычисляет время прохождения маршрута на основе переданной ей матрицы. Используйте для тестирования функции маршрут из 10 точек с шагом в 1 км и случайным разбросом высот в диапазоне от 500 до 1000 метров (равномерное распределение).Подсказка: угол наклона на каждом участке считается постоянным. Для вычисления экспоненты используйте встроенную функцию
exp()
. Создайте на основе данных по Москве с сайта pogodaiklimat таблицу Excel с повторяемостью различных направлений ветра. Не преобразовывая структуру данных, вычислите на ее основе с помощью
lapply()
преобладающее направление для каждого месяца. Представьте результат как фрейм данных.-
В текущей лекции мы работали с данными по характеристикам центров субъектов СЗФО. Напишите функцию
get_extremes(df)
, которая определяет названия переменных, по которым каждая строчка фрейма данных (в нашем случае — город) занимает максимальное и минимальное положение относительно среднего значения по всем городам. Например, Петрозаводск имеет максимальный рейтинг по показателю doctors (1.05) — количество врачей на 10000 чел и минимальный по показателю manufact (0.05) — продукции обрабатывающей промышленность (в млн руб). Результирующая таблица должна содержать первое по счету поле, а также поля minvar и maxvar:Region minvar maxvar Петрозаводск doctors manufact ... ...
Подсказка: для начала вам надо нормировать значения всех переменных с помощью
lapply()
. Затем нужно определить номер столбца, имеющего максимальное и минимальное значений для каждой строки таблицы. Используйте для этого функцииwhich.min()
иwhich.max()
возвращающие индекс максимального и минимального элемента вектора, в комбинации с функциейcolnames()
, возвращающей названия переменных. Применяйте функциюapply()
, которая умеет ходить по строкам.
Самсонов Т.Е. Визуализация и анализ географических данных на языке R. М.: Географический факультет МГУ, lubridate::year(Sys.Date()) . DOI: 10.5281/zenodo.901911
|