Главная страница

А. Б. Шипунов, Е. М. Балдин, П. А. Волкова, А. И. Коробейников, С. А. Назарова


Скачать 3.04 Mb.
НазваниеА. Б. Шипунов, Е. М. Балдин, П. А. Волкова, А. И. Коробейников, С. А. Назарова
Анкорrbook
Дата29.09.2022
Размер3.04 Mb.
Формат файлаpdf
Имя файлаrbook.pdf
ТипДокументы
#705644
страница17 из 19
1   ...   11   12   13   14   15   16   17   18   19
> state.x77[1:2, "Area"]
Alabama
Alaska
50708 566432
является вектором, а не матрицей. Если такое поведение нежела- тельно, то его можно переопределить заданием аргумента drop:
> state.x77[1:2, "Area", drop=FALSE]
Area
Alabama
50708
Alaska
566432
Так как матрицы являются обычными векторами, то их элементы можно индексировать по порядку так же, как если бы измерения отсут- ствовали. Двумерные матрицы при этом индексируются по столбцам;
многомерные — так, что первое измерение меняется наиболее часто, а последнее — наиболее редко:
> m <- matrix(1:6, nrow = 3)
> m
[,1] [,2]
[1,]
1 4
[2,]
2 5
[3,]
3 6
> m[2:4]
[1] 2 3 4
Такая гибкость очень часто приводит к трудноуловимым ошибкам в случае пропуска запятых при индексировании.
Объект типа data.frame является обычным списком из векторов,
поэтому доступ к нему при помощи оператора [ аналогичен доступу к списку.

Функции и аргументы
233
В.2.8. Пустые индексы
Отдельные индексирующие векторы могут быть опущены. В таком случае выбирается все измерение и нет необходимости дополнительно узнавать его размер. Наиболее часто это используется при выборке от- дельных строк или столбцов из матрицы:
> m <- matrix(1:6, nrow = 3)
> m[c(2,3), ]
[,1] [,2]
[1,]
2 5
[2,]
3 6
> m[, 2]
[1] 4 5 6
Пустой индекс можно использовать и с векторами (осторожно!):
>
v <- 1:10
>
v[] <- 0
> v
[1] 0 0 0 0 0 0 0 0 0 0
Заменяет все элементы вектора v на 0, но при этом сохраняет все атрибуты (например, имена отдельных элементов). В некоторых случа- ях это может быть более предпочтительно, чем, скажем, конструкция вида
> v <- rep(0, length(v))
В.3. Функции и аргументы
Функции являются объектами типа function. Единственное их от- личие, скажем, от вектора заключается в наличии в языке дополни- тельного оператора вызова функции (). Создать функцию можно при помощи вызова функции function():
function(arglist) expr
Здесь arglist — список аргументов в виде имен или пар имя = зна- чение
, а expr — объект типа expression, составляющий тело функ- ции. Выполнение функции происходит до вызова функции return(),
аргумент которой становится возвращаемым значением, либо до вы- полнения последнего выражения в теле функции. В последнем случае именно значение последнего выражения будет возвращено из функции.
Возвращаемое значение можно замаскировать при помощи функции invisible()

234
Основы программирования в R
> norm <- function(x) sqrt(x%*%x)
> fib <- function(n)
+ {
+ if (n<=2) {
+ if (n>=0) 1 else 0
+ }
+ else {
+ return(Recall(n-1) + Recall(n-2))
+ }
+ }
Обратите внимание на использование фигурных скобок. Несмотря на то что во многих случаях они могут быть опущены, мы рекоменду- ем их использовать чаще, особенно для функций, циклов и условных конструкций. Наоборот, точка с запятой в конце строки, хотя и допус- кается, но совершенно не обязательна, и лучше ее опускать. Отметим еще использование конструкции Recall() во втором примере. Так как функции суть обычные переменные, то они могут быть переименованы естественным образом:
> fibonacci <- fib; rm(fib)
> fibonacci(10)
Конструкция Recall() позволяет вызвать «текущую функцию» не- зависимо от используемого имени, тем самым сохраняя работоспособ- ность рекурсивных функций даже после переименования.
Аргументы функций можно разделить на обязательные и необяза- тельные. Наличие возможности передавать необязательные аргументы
(опции) является одной из отличительных черт языка R.
Так как функции может быть передан не весь набор аргументов,
то, естественно, должен существовать механизм назначения аргумен- тов
. Так, аргументы могут передаваться по имени (и поскольку их чис- ло конечно, то стандартные правила сокращения имен до уникального префикса работают и здесь). Есть также способ передачи аргументов по их позиции в операторе вызова функции.
Назначение значений аргументам происходит в три этапа:
1. Аргументы, переданные в виде пары имя = значение, имена ко- торых совпадают явно.
2. Аргументы, переданные в виде пары имя = значение, имена ко- торых совпали по уникальному префиксу.
3. Все остальные аргументы по порядку.

Функции и аргументы
235
Ключевое слово «...» (ellipsis) позволяет создавать функции с про- извольным числом аргументов. Его наличие несколько усложняет про- цедуру назначения аргументов, так как происходит разделение аргу- ментов на те, что находятся до троеточия, и располагающихся после.
Если к первым применяются стандартные правила назначения аргумен- тов, то последние можно назначить исключительно по полному имени,
все неназначенные аргументы «съедаются» троеточием и доступны из- нутри функции в виде именованного списка.
Как правило, аргументы функции, идущие после троеточия, в справ- ке представлены в виде пары имя = значение. Кроме того, так как ар- гументы назначаются по имени, то при вызове функции они могут на- ходиться на любой позиции, нет необходимости передавать их послед- ними. Рассмотрим простой пример:
> f <- function(aa, bb, cc, ab, ..., arg1 = 5, arg2 = 10) {
+ print(c(aa, bb, cc, ab, arg1, arg2)); print(list(...))
+ }
> f(arg1 = 7, aa = 1, a = 2, ac = 3, 4, 5, 6)
[1]
1 4
5 2
7 10
$ac
[1] 3
[[2]]
[1] 6
Назначение аргументов здесь происходит так:
1. Назначаются аргументы arg1 и aa, так как их имена совпадают целиком.
2. Значение аргументу aa уже было присвоено на предыдущем ша- ге, поэтому ab может быть назначен по совпадению префикса a,
ставшего уникальным.
3. Вследствие совпадения имени для переданного аргумента ac про- исходит добавление аргумента к списку (. . . ). arg2 получает зна- чение по умолчанию. После этого этапа именованных аргументов не остается.
4. Аргументы bb и cc назначаются по порядку значениями 4 и 5
соответственно. Аргумент 6 добавляется к списку list(...).
При отсутствии оператора троеточия вызов функции оканчивается ошибкой при попытке назначить аргументы на шагах 3 и 4.

236
Основы программирования в R
Функция args(fun) выводит список всех аргументов функции fun.
В заключение отметим еще одну важную особенность, отличающую
R
от многих других языков программирования: аргументы функций вы- числяются «лениво», то есть не в момент вызова функции, а в момент использования (этот факт объясняется просто: язык R является интер- претируемым). В связи с этим надо соблюдать большую осторожность,
скажем, при передаче в качестве аргументов результатов вызовов функ- ций со сторонними эффектами: порядок их вызова может отличаться от ожидаемого.
С другой стороны, такое поведение позволяет в некоторых случаях существенно упростить задание значений по умолчанию для аргумен- тов:
f <- function(X, L = N %/% 2)
{
N <- length(X)
do.something(X, L)
}
Здесь вычисление аргумента L произойдет где-то внутри функции do.something()
. К этому моменту переменной N уже будет назначено значение, и выражение N %/% 2 будет корректно определено.
В.4. Циклы и условные операторы
Как и многие языки программирования, R предоставляет конструк- ции, позволяющие управлять исполнением программы в зависимости от внешних условий: операторы цикла и условия. Хотя, в отличие от
«обычных» языков декларативного программирования (например, C),
они используются существенно реже по причинам, которые будут разо- браны ниже.
Условное выполнение кода производится при помощи оператора if:
if (cond) cons.expr else alt.expr
Здесь cond — логический вектор длины 1, cons.expr, alt.expr —
выражения, которые будут выполнены в случае, если cond истинно или ложно соответственно. Значение NA для условия не допускается. Если длина условия больше 1, то используется только первый элемент век- тора и выдается предупреждение.
Данный факт является источником многочисленных недоразуме- ний: оператор == работает покомпонентно, поэтому в конструкции вида

R как СУБД
237
if (v1 == v2) do.something(v1, v2)
будет происходить сравнение только первых элементов векторов, но не их содержимого целиком. Ожидаемого поведения можно достичь при помощи функций identical() или all.equal() в зависимости от того,
требуется точное или приближенное равенство векторов.
Циклы в R реализуются при помощи операторов while и for. Син- таксис первого следующий:
while (cond) expr
Здесь cond — условие выполнения тела цикла (правила вычисления условия совпадают с таковыми для оператора if), expr — собственно,
тело цикла.
Оператор for позволяет перечислить все элементы последователь- ности:
for (idx in seq) expr
Здесь idx — переменная цикла, seq — перечисляемая последователь- ность, а expr — тело цикла. Последовательность seq вычисляется до первого выполнения тела цикла, ее переопределение внутри тела цикла не влияет на число итераций, аналогично назначение какого-либо зна- чения переменной idx не влияет на следующие итерации цикла. Цикл можно прервать оператором break; закончить текущую итерацию и пе- рейти к следующей — оператором next.
В.5. R как СУБД
Несмотря на то что к R написаны интерфейсы ко многим системам управления базами данных (СУБД) и даже есть специальный пакет sqldf
, который позволяет управлять таблицами данных посредством команд SQL, стоит обратить внимание и на базовые особенности R,
позволяющие превратить его, практически не расширяя, в организатор связанных (реляционных) текстовых баз данных.
Тем, кто знаком с основами языка SQL, могут показаться интерес- ными соответствия между командами и операторами этого языка и ко- мандами R. Некоторые из них приведены в табл. В.1.
Соответствия эти не однозначные и не абсолютные, но хорошо вид- но, что операции SQL могут быть без особых проблем выполнены «из- нутри» R. Единственным серьезным недостатком является то, что мно- гие из этих функций выполняются весьма медленно. Грешит этим и очень важная функция merge(), которая позволяет связывать разные

238
Основы программирования в R
SELECT
[
и subset()
JOIN
merge()
GROUP BY aggregate(), tapply()
DISTINCT
unique()
и duplicated()
ORDER BY order(), sort(), rev()
WHERE
which()
, %in%, ==
LIKE
grep()
INSERT
rbind()
EXCEPT
!
и -
Таблица В.1. Некоторые (примерные) соответствия между операторами и командами SQL и функциями R
таблицы на основании общей колонки («ключа» в терминологии баз данных).
Вот пример пользовательской функции, которая работает быстрее:
> recode <- function(var, from, to)
+ {
+ x <- as.vector(var)
+ x.tmp <- x
+ for (i in 1:length(from)) {x <- replace(x, x.tmp == from[i],
+ to[i])}
+ if(is.factor(var)) factor(x) else x
+ }
Она делает то, чего не умеет делать встроенная функция replace(),—
перекодирование всех значений по определенному правилу:
> replace(rep(1:10,2), c(2,3,5), c("a","b","c"))
[1] "1"
"a"
"b"
"4"
"c"
"6"
"7"
"8"
"9"
"10"
"1"
"2"
"3"
"4"
"5"
[16] "6"
"7"
"8"
"9"
"10"
> recode(rep(1:10,2), c(2,3,5), c("a","b","c"))
[1] "1"
"a"
"b"
"4"
"c"
"6"
"7"
"8"
"9"
"10"
"1"
"a"
"b"
"4"
"c"
[16] "6"
"7"
"8"
"9"
"10"

R как СУБД
239
Как видите, replace() заменил только первые значения, в то время как recode() заменил их все.
Теперь мы можем оперировать несколькими таблицами как одной.
Это очень важно для иерархически организованных данных. Напри- мер, мы можем работать в разных регионах и всюду делать похожие операции (скажем, что-то измерять). Тогда удобнее иметь не одну таб- лицу, а две: в первой будут данные по регионам, а во второй — данные измерений объектов. Для связывания таблиц нужно, чтобы в каждой из них была одна и та же колонка, например номер региона. Вот как это можно организовать:
> locations <- read.table("data/eq-l.txt", h=T, sep=";")
> measurements <- read.table("data/eq-s.txt", h=T, sep=";")
> head(locations)
N.POP
WHERE
SPECIES
1 1 Tverskaja arvense
2 2 Tverskaja arvense
3 3 Tverskaja arvense
4 4 Tverskaja arvense
5 5 Tverskaja pratense
6 6 Tverskaja palustre
> head(measurements)
N.POP DL.R DIA.ST N.REB N.ZUB DL.OSN.Z DL.TR.V DL.BAZ DL.PER
1 1
424 2.3 13 12 2.0 5
3.0 25 2
1 339 2.0 11 12 1.0 4
2.5 13 3
1 321 2.5 15 14 2.0 5
2.3 13 4
1 509 3.0 14 14 1.5 5
2.2 23 5
1 462 2.5 12 13 1.1 4
2.1 12 6
1 350 1.8 9
9 1.1 4
2.0 15
> loc.N.POP <- recode(measurements$N.POP, locations$N.POP,
+ as.character(locations$SPECIES))
> head(cbind(species=loc.N.POP, measurements))
species N.POP DL.R DIA.ST N.REB N.ZUB DL.OSN.Z DL.TR.V DL.BAZ
1 arvense
1 424 2.3 13 12 2.0 5
3.0 2 arvense
1 339 2.0 11 12 1.0 4
2.5 3 arvense
1 321 2.5 15 14 2.0 5
2.3 4 arvense
1 509 3.0 14 14 1.5 5
2.2 5 arvense
1 462 2.5 12 13 1.1 4
2.1 6 arvense
1 350 1.8 9
9 1.1 4
2.0

240
Основы программирования в R
Здесь показано, как работать с двумя связанными таблицами и ко- мандой recode(). В одной таблице записаны местообитания (locations),
а в другой — измерения растений (measurements). Названия видов есть только в первой таблице. Если мы хотим узнать, каким видам какие признаки соответствуют (см. главу про многомерные данные), то на- до слить первую и вторую таблицы. Можно использовать для этого merge()
, но recode() работает быстрее и эффективнее. Надо только помнить о типе данных, чтобы факторы не превратились в цифры.
Ключом в этом случае является колонка N.POP (номер местообитания).
В.6. Правила переписывания. Векторизация
Встроенные операции языка R векторизованы, то есть выполняют- ся покомпонентно. В таком случае достаточно быстро встает вопрос,
каким образом осуществляются операции в случае, если операнды име- ют разную длину (например, при сложении вектора длины 2 и длины
4
). За это отвечают так называемые правила переписывания (recycling rules):
1. Длина результата совпадает с длиной операнда наибольшей дли- ны.
2. Если длина операнда меньшей длины делит длину второго опе- ранда, то такой операнд повторяется (переписывается) столько раз, сколько нужно до достижения длины второго операнда. Пос- ле этого операция производится покомпонентно над операндами одинаковой длины.
3. Если длина операнда меньшей длины не является делителем дли- ны второго операнда (то есть она не укладывается целое число раз в длину большего операнда), то такой операнд повторяется столько раз, сколько нужно для перекрытия длины второго опе- ранда. Лишние элементы отбрасываются, производится операция и выводится предупреждение.
Как следствие этих правил, операции типа сложения числа (то есть вектора единичной длины) с вектором выполняются естественным об- разом:
> 2 + c(3, 5, 7, 11)
[1]
5 7
9 13
> c(1, 2) + c(3, 5, 7, 11)
[1]
4 7
8 13

Правила переписывания. Векторизация
241
> c(1, 2, 3) + c(3, 5, 7, 11)
[1]
4 7 10 12
Warning message:
In c(1, 2, 3) + c(3, 5, 7, 11) :
longer object length is not a multiple of shorter object length
Большинство встроенных функций языка R так или иначе векто- ризованы, то есть выдают «естественный» результат при передаче в качестве аргумента вектора. К этому необходимо стремиться при напи- сании собственных функций, так как это, как правило, является клю- чевым фактором, влияющим на скорость выполнения программы. Раз- берем простой пример (написанный, очевидно, человеком, хорошо зна- комым с языком типа C, но малознакомым с R):
> p <- 1:20
> lik <- 0
> for (i in 1:length(p))
+ {
+ lik <- lik + log(p[i])
+ }
Это же самое действие можно реализовать существенно проще и короче (кроме того, обеспечив корректную работу в случае, если вектор p
имел бы нулевую длину):
> lik <- sum(log(p))
Отметим, что «проще» имеется в виду не только с точки зрения количества строк кода, но и вычислительной сложости: первый образец кода выполняется полностью на интерпретаторе, второй же использует эффективные и быстрые встроенные функции.
Второй образец кода работает потому, что функции log() и sum()
векторизованы. Функция log() векторизована в обычном смысле: ска- лярная функция применяется поочередно к каждому элементу вектора,
таким образом результат log(c(1, 2)) идентичен результату c(log(1),
log(2))
Функция sum() векторизована в несколько ином смысле: она берет на вход вектор и считает что-то, зависящее от вектора целиком. В дан- ном случае вызов sum(x) полностью эквивалентен выражению x[1] +
x[2] + ... + x[length(x)]
Очень часто векторизация кода появляется сама собой за счет нали- чия встроенных функций, правил переписывания для арифметических операций и т. п. Однако (особенно часто это происходит при переписы-

242
Основы программирования в R
вании кода с других языков программирования) код следует изменить для того, чтобы он стал векторизованным. Например, код
> v <- NULL
> v2 <- 1:10
> for (i in 1:length(v2))
+ {
+ if (v2[i] %% 2 == 0)
+ {
+ v <- c(v, v2[i]) # 7 строка
+ }
+ }
плох сразу по двум причинам: он содержит цикл там, где его можно избежать, и, что совсем плохо, он содержит вектор, растущий внутри цикла. Среда R действительно прячет детали выделения и освобожде- ния памяти от пользователя, но это вовсе не значит, что о них не надо знать и их не надо учитывать. В данном случае в седьмой строке про- исходят выделение памяти под новый вектор v и копирование в этот новый вектор элементов из старого. Таким образом, вычислительные затраты этого цикла (в терминах числа копирования элементов) про- порциональны квадрату длины вектора v2!
Оптимальное же решение в данном случае является очень простым:
> v <- v2[v2 %% 2 == 0]
Векторизацию отдельной функции можно произвести при помощи функции Vectorize(), однако не стоит думать, что это решение всех проблем — это исключительно изменение внешнего интерфейса функ- ции, внутри она по-прежнему будет вызываться для каждого элемента по отдельности (хотя в отдельных случаях такого решения оказывается достаточно).
Стандартной проблемой при векторизации является оператор if.
Один из вариантов замены его векторизации был рассмотрен выше, но очень часто подобного рода преобразования невозможны. Например,
1   ...   11   12   13   14   15   16   17   18   19


написать администратору сайта