Синтаксис языка
Операторы
Присваивание (<-, <<-, =)
Основной оператор присваивания в R — это оператор <-
. Например, x <- 5
. Если до этого объект не был создан, то таким образом мы создадим объект x
, в котором содержится значение 5
. Если до этого объект существовал, то ему будет присвоено новое значение (перезаписано). С формальной точки зрения, оператор <-
связывает значение и символьную запись (название), с помощью которой можно обращаться к значению.
Оператор <-
служит, наверное, одним из самых надёжных визуальных маркеров, позволяющих отличить код на R от кода на других языках. Такая форма оператора присваивания обусловлена историческим контекстом: в период разработки и использования в Bell Labs языка S (предшественника R) код писали на клавиатурах, на которых была одна клавиша для печати знака <-
.
Несмотря на визуальную понятность оператора, он нередко подвергается критике, в частности, за то, что даёт возможность ошибиться. Так, неправильно поставленный пробел меняет присвоение на сравнение: выражения x <- 5
и x < -5
различаются одним неправильно поставленным пробелом, однако содержательно они совершенно разные.
Оператор =
аналогичен оператору <-
и может быть использован для левостороннего присваивания. Одновременно оператор =
используется при указании значений аргументов функций. Большинство руководств по стилю рекомендуют именно такое использование операторов <-
и =
: первый для присваивания, а второй — при использовании функций.
Оператор <<-
также является оператором левостороннего присваивания, как и <-
, однако используется редко и в основном в функциях. Основное отличие: с помощью оператора <<-
значения присваиваются в родительском окружении. Это используется, чтобы созданный во время выполнения функции промежуточный объект вывести за пределы области видимости функции и сохранить в родительском окружении. Родительским окружением может быть как глобальное окружение (чаще всего), так и какое-либо иное окружение, в котором вызывается функция (подробнее см. Выполнение выражений):
f1 <- function(x) {
tmp1 <<- paste('the result of <<- operator in f1()')
tmp2 <- x * 2
return(tmp2)
}
print(f1(2))
## [1] 4
print(tmp1)
## [1] "the result of <<- operator in f1()"
Вместо оператора <<-
для изменения значений объектов в различных окружениях рекомендуется использовать функцию assign()
. В этой функции с помощью аргумента envir
можно указать, в каком окружении происходит присвоение. Окружением может быть как пользовательское окружение, так и родительское (parent.env()
)/глобальное (.GlobalEnv
):
f2 <- function(x) {
assign(x = 'tmp1', value = paste('the result of assign() in f2()'), envir = .GlobalEnv)
tmp2 <- x * 2
return(tmp2)
}
print(f2(2))
## [1] 4
print(tmp1)
## [1] "the result of assign() in f2()"
Несмотря на то, что операторы присваивания позволяют множественное присваивание, а операторы ->
и ->>
— ещё и правостороннее присваивание, выражения вида x = y = z = 5
или 5 <- x -> y
лучше не использовать, так как они уменьшают читабельность и прозрачность кода.
В редких случаях в левой части выражения присвоения при левостороннем присваивании может стоять не имя объекта, а вызов функции. Как правило, это касается функций, несущих определённую информацию об объекте. Наиболее часто встречающийся пример — создание или присвоение названий колонок в таблице:
# создаём и выводим на печать таблицу с колонками var1, var2
new_df <- data.frame(var1 = 1:3, var2 = letters[1:3])
print(new_df)
## var1 var2
## 1 1 a
## 2 2 b
## 3 3 c
# меняем названия колонок на col1, col2 и выводим таблицу
names(new_df) <- c('col1', 'col2')
print(new_df)
## col1 col2
## 1 1 a
## 2 2 b
## 3 3 c
В настоящее время методы и выражения вида names<-
характерны скорее для достаточно старого кода, написанного на базовом R.
Сравнение и логические операторы
Операторы сравнения или логические операторы обычно используются при необходимости указать какие-то условия для выбора элементов вектора или таблицы, а также в конструкциях if/else
(см. Условные операторы).
Операторы сравнения:
Логические операторы:
-
!
— отрицание, например,!is.na(x)
; -
&
,&&
—И
,бинарное И
(оператор&
поэлементно объединяет элементы двух множеств, оператор&&
объединяет только первые слева элементы каждого множества), например, объединяем результаты двух логических проверок:
## [1] FALSE TRUE
## [1] FALSE
Доступ к данным
Как правило, объекты в R — это таблицы, списки или сложные объекты с большим количеством вложенных элементов. Для того чтобы извлечь какой-то элемент или переписать его значение, используются операторы доступа. Здесь мы просто перечислим и дадим общее представление об этих операторах, подробнее операторы доступа к данным, особенно операторы [
и [[
, будут рассмотрены в разделе про манипуляции с данными.
$ и @
Оператор $
используется для обращения к именованному элементу списка, колонке датафрейма и прочим элементам структуры объектов. Вызов выглядит следующим образом: <object_name>$<element_name>
. В выражении можно использовать несколько операторов, особенно часто это необходимо при работе со сложными вложенными списками и объектами S3- или R6-классов. Оператор $
обладает также полезной особенностью: название элемента можно вводить не полностью, но введённая часть должна однозначно определять требуемый элемент. Выведем первые шесть строк (функция head()
) датафрейма cars
и отдельно первые шесть значений колонки speed
с полным и частичным указанием названия колонки:
head(cars)
## speed dist
## 1 4 2
## 2 4 10
## 3 7 4
## 4 7 22
## 5 8 16
## 6 9 10
head(cars$speed)
## [1] 4 4 7 7 8 9
head(cars$sp)
## [1] 4 4 7 7 8 9
Оператор @
используется аналогично оператору $
при работе с объектами S4-класса (доступ к слотам), однако требует полного названия требуемого элемента.
[ и [[
Оператор [
в определённой мере аналогичен оператору $
, однако обладает более гибким функционалом. Так, с помощью оператора можно выделять элементы по их полному названию, по номеру или вектору номеров в списке элементов, по логическому условию:
## [1] 2 6
Для некоторых объектов оператор [
переопределён и имеет дополнительные аргументы.
Оператор [[
похож на операторы [
и $
, однако на вход принимать может только одно значение — номер позиции или название элемента (по умолчанию название должно быть полным):
tmp <- c(v1 = 'alpha', v2 = 'beta')
tmp['v1']
## v1
## "alpha"
tmp[['v1']]
## [1] "alpha"
Доступ к объекту в пространстве имён (::, :::)
Операторы ::
и :::
используются для доступа к объектам из какого-то определённого пространства имён (подробнее про пространства имён см. Окружения пакетов и пространство имён). Так, ::
нередко используется для вызова функций любого установленного, но не подключённого пакета. Оператор :::
обеспечивает доступ вообще к любым неэкспортированным объектам пакетов, не только к функциям.
Чаще всего потребность в этих операторах возникает тогда, когда какая-то функция из специального пакета используется однократно и подключать весь пакет нет необходимости. Например, установка пакета из github-репозитория с помощью пакета remotes
: remotes::install_github("ropensci/plotly")
. Также ::
или :::
используются в ситуациях, когда в нескольких пакетах встречаются одинаковые названия функций или объектов. Например, fromJSON()
встречается одновременно в пакетах jsonlite
, RJSONIO
и rjson
.
В целом при использовании этих операторов надо отдавать себе отчёт, что они усложняют код для восприятия, при этом одновременно делают его более прозрачным, так как явно указано, какая функция и какого пакета используется.
Иерархия операторов
Операторы организованы в иерархию. Это позволяет сочетать их в разных выражениях. При этом некоторые результаты выполнения некоторых выражений могут быть весьма неожиданными, если забывать про иерархию. Так, результатом 3 ^ 1:3
будет 3, потому что оператор возведения в степень (^
) выполняется первым и только потом применяется оператор последовательности (:
).
Список операторов по убыванию приоритета выполнения:
Оператор | Значение |
---|---|
:: , :::
|
доступ к значениям в пространстве имён |
$ , @
|
доступ к элементу объекта/слоту по названию |
[ , [[
|
доступ к элементу объекта/слоту по индексу |
^ |
возведение в степень |
- , +
|
унарные плюс и минус |
: |
оператор последовательности (короткая запись для seq() ) |
%any% |
специальные операторы (включая %% и %/% ) |
* , /
|
умножение и деление |
+ , -
|
сложение и вычитание |
< , > , <= , >= , == , !=
|
логическое сравнение |
! |
логическое отрицание |
& , &&
|
логическое И
|
| , ||
|
логическое ИЛИ
|
~ |
знак формулы |
-> , ->>
|
правое присваивание |
<- , <<-
|
левое присваивание |
= |
левое присваивание |
? , ??
|
справка |
Конвейеры
%>%
Оператор конвейера %>%
не входит в базовые библиотеки R и принадлежит пакету magrittr
, однако весьма распространён, в частности, широко используется в пакетах экосистемы tidyverse
. Оператор %>%
используется для того, чтобы связать в последовательность несколько операций, когда в последующее выражение передаётся результат предыдущего. В результате сокращается количество кода и/или повышается читабельность. Например, три идентичных вычисления, первое использует оператор конвейера, второе — вложенные выражения, а третье просто каждую операцию производит в отдельном выражении:
library(magrittr)
x <- 12 ^ 2 %>% sqrt %>% -2
x <- sqrt(12^2) - 2
x <- 12
x <- x ^ 2
x <- sqrt(x)
x <- x - 2
Так как все операторы по сути являются функциями, то и оператор конвейера точно такая же самостоятельная функция. Как следствие, использование конвейеров влечёт за собой дополнительные расходы ресурсов, пусть и незначительные, что может быть нежелательно при программировании процессов, требующих высокой производительности.
|>
Оператор конвейера |>
совсем недавно вошел в base R. Написание взято от классического символа конвейера, используемого в bash/awk и подобных языках. Также |>
используется в некоторых других высокоуровневых языках, например, в F#
. Операторы %>%
и |>
одинаковы по смыслу, но всё же неравнозначны. Собственно, поэтому команда R Core и не стала использовать %>%
для своего конвейера.
Основное различие заключается в том, что они выполняются в разное время: %>%
применяется при выполнении выражений, в рантайме; оператор |>
обрабатывается интерпретатором на этапе чтения кода, и только потом происходит выполнение конвейера выражений. Такая организация делает применение |>
надёжным и не вносящим дополнительных расходов.
Абстрактное синтаксическое дерево (подробнее см. здесь) хорошо иллюстрирует логику выполнения выражения с вложенными функциями и в виде конвейера:
lobstr::ast(g(f(x)))
## █─g
## └─█─f
## └─x
lobstr::ast(x |> f() |> g())
## █─g
## └─█─f
## └─x
Вызов функций
Классический вызов
Функции составляют основу языка R — все операции совершаются с использованием функций, будь то действие с объектом, вывод графика или печать значения объекта.
Вызов функции выглядит следующим образом: function_name(arglist)
. Например:
mean(x = 1:10)
## [1] 5.5
В приведённом примере mean
— это название функции, а x
— название аргумента функции. Функция в примере вычисляет среднее значение по вектору значений аргумента x
.
Полный список аргументов функции можно посмотреть в справке по функции. Список аргументов функции импорта csv-файлов выглядит следующим образом (можно посмотреть в справке или воспользоваться функцией args()
):
args(read.csv)
## function (file, header = TRUE, sep = ",", quote = "\"", dec = ".",
## fill = TRUE, comment.char = "", ...)
## NULL
При вызове функции аргументы можно задать разными способами. Первый способ — по названию, то есть надо однозначно указывать название аргумента и его значение: read.csv(file = 'myfile.csv')
. Второй способ — позиционный, когда значения аргументов перечислены в функции в том же порядке, как они объявлены в документации: read.csv( 'myfile.csv', FALSE, ';')
.
В приведённом примере документации функции read.csv()
у ряда аргументов уже заданы значения, например, header = TRUE
. Эти значения называются значения аргументов по умолчанию, и если при вызове функции для этих аргументов не указаны другие значения, то используются именно заданные по умолчанию. Это позволяет корректно выполнять выражение read.csv(file = 'myfile.csv')
(или read.csv('myfile.csv')
), несмотря на то, что значение задано только для одного аргумента, file
.
В самом конце списка аргументов функции read.csv()
используется ключевое слово ...
(dot-dot-dot, три точки, эллипсис). Эта конструкция позволяет создавать функции с произвольным количеством аргументов или же использовать дополнительные аргументы в уже написанной функции. В примере ниже функция возвращает значение первого из указанных дополнительно аргументов:
f1 <- function(x, ...) {
x <- x^2
return(..1)
}
f1(x = 3, 5, 9)
## [1] 5
Следует помнить, что если ...
указаны в середине списка аргументов, то для всех последующих аргументов позиционное указание значений становится невозможно и необходимо явно задавать пару аргумент = значение
.
Вызов анонимных функций
Вызов анонимных функций в R выглядит следующим образом: вместо названия функции мы пишем function(x) { some operations with x }
. Часто анонимные функции используются в сочетании с функциями семейства *pply
(подробнее в разделе по созданию функций):
sapply(X = 1:5, FUN = function(x) x + 1)
## [1] 2 3 4 5 6
С относительно недавнего времени эту запись можно немного сократить и вместо function(x)
использовать \(x)
. Символ \
был выбран в данном случае как напоминающий символ \(\lambda\), используемый для обозначения лямбда-выражений (аналоги анонимных функций в R) в функциональных языках.
Служебные символы
Конец выражения
Для маркировки конца выражения в R используется ;
. Однако парсер интерпретатора читает код построчно. Это значит, что если не писать более одного выражения на одной строке, то указывать ;
нет необходимости, достаточно переноса строки. В результате вот такие два выражения идентичны:
# два выражения на одной строке
x <- 5; y <- 3
x + y
## [1] 8
# два выражения отдельно
x <- 5
y <- 3
x + y
## [1] 8
Фигурные скобки
Фигурные скобки используются в тех ситуациях, когда необходимо несколько выражений объединить в один блок. Чаще всего это необходимо при объявлении функций, в циклах и в некоторых случаях при использовании условных операторов (if … else/ifelse/switch, см. Условные операторы). Результатом выполнения блока будет результат последнего выражения:
result <- {
y <- 2 * 3
z <- y ^ 2
x <- y + z
}
print(result)
## [1] 42
В том случае, когда тело функции или цикла составляет одно выражение, фигурные скобки можно опустить. В таком случае выражение после function()
/for
на той же строке или одна следующая строка считается как относящиеся к функции/циклу:
for (i in 1:3)
print(i)
## [1] 1
## [1] 2
## [1] 3
Комментарии
В R для комментирования используется символ #
. Если в какое-то место на строке поставить знак комментария, то вся оставшаяся часть строки будет считаться закомментированной. Исключение — если знак комментария находится внутри выражения, окруженного кавычками:
x <- 5 # это комментарий
x <- "# а вот это уже не комментарий"
Обычно строку комментария ставят перед комментируемым выражением. Однако если комментарий короткий, то можно, как и в примере выше, писать комментарий после комментируемого выражения, на той же строке.
На данный момент R не поддерживает комментирование блоками, как это можно делать в других языках с помощью выражения вида /* commented lines of code */
, поэтому каждую строчку надо комментировать отдельно.
За пределами базового R, но в сопутствующих пакетах, знак #
может иметь иные значения, например, в RMarkdown он используется для создания заголовков и внутренних ссылок.
Кавычки
В R используются три вида кавычек: кавычки (“), апостроф (’) и обратный апостроф (`). Простые кавычки и апостроф взаимозаменяемы, в коде можно использовать и то, и другое, в том числе и комбинированно. Единственное, следует выдерживать стилистическую однородность и использовать какой-нибудь тип как основной, а второй — только в тех ситуациях, когда надо написать сложное выражение. Например, такие ситуации возникают при написании запроса к SQL-базе данных: в SQL, как правило, апострофы используются для текстовых значений и дат, а кавычки — для названий таблиц: dbGetQuery(con, "select * from mytable where dt >= '2018-01-01'")
.
Обратный апостроф используется при необходимости определённым образом выделить символьную запись. Например, обратные апострофы используются в том случае, когда колонка датафрейма или иной объект имеет пробелы или другие символы в названии, названа с использованием кириллицы или другого нелатинского шрифта и т. д. Например:
`это кириллица` <- 'объект с кириллическим названием и пробелом'
print(`это кириллица`)
## [1] "объект с кириллическим названием и пробелом"
Помимо обращения к объектам со специфичным названием, обратный апостроф может использоваться при создании собственных операторов:
`%~%` <- function(x, y)
paste("new operator's result:", x, "^", y, "=", x ^ y)
2 %~% 3
## [1] "new operator's result: 2 ^ 3 = 8"
Также обратный апостроф используется в rmarkdown-разметке для выделения фраз или создания коротких исполняемых выражений в самом тексте, а также для создания отдельных чанков кода. Например, так в тексте выглядит выражение `x <- 2 * 9`: x <- 2 * 9
.
Зарезервированные слова
Как и во многих других языках, в R часть слов является зарезервированными, то есть за ними закреплено определённое значение (функционал), и пользователь не имеет возможности использовать их в качестве имён объектов. Основные зарезервированные слова (согласно документации R Foundation) можно посмотреть с помощью ?Reserved
:
if else repeat while function for in next break
TRUE FALSE NULL Inf NaN
NA NA_integer_ NA_real_ NA_complex_ NA_character_
1 ..2 etc ... ..
Слова, которые уже обозначают функции используемых пакетов или глобальных переменных, не рекомендуется использовать в качестве названий объектов.