Книга «Искусство программирования на R. Погружение в большие данные»

image Привет, Хаброжители! Многие пользователи используют R для конкретных задач — тут построить гистограмму, там провести регрессионный анализ или выполнить другие отдельные операции, связанные со статистической обработкой данных. Но эта книга написана для тех, кто хочет разрабатывать программное обеспечение на R. Навыки программирования предполагаемых читателей этой книги могут лежать в широком спектре — от профессиональной квалификации до «Я проходил курс программирования в колледже», но ключевой целью является написание кода R для конкретных целей. (Глубокое знание статистики в общем случае не обязательно.)

Несколько примеров читателей, которые могли бы извлечь пользу из этой книги:

  • Аналитик (допустим, работающий в больнице или в правительственном учреждении), которому приходится регулярно выдавать статистические отчеты и разрабатывать программы для этой цели.
  • Научный работник, занимающийся разработкой статистической методологии — новой или объединяющей существующие методы в интегрированные процедуры. Методологию нужно закодировать, чтобы она могла использоваться в сообществе исследователей.
  • Специалисты по маркетингу, судебному сопровождению, журналистике, издательскому делу и т. д., занимающиеся разработкой кода для построения сложных графических представлений данных.
  • Профессиональные программисты с опытом разработки программного обеспечения, назначенные в проекты, связанные со статистическим анализом.
  • Студенты, изучающие статистику и обработку данных.


Таким образом, эта книга не является справочником по бесчисленным статистическим методам замечательного пакета R. На самом деле она посвящена программированию и в ней рассматриваются вопросы программирования, редко встречающиеся в других книгах о R. Даже основополагающие темы рассматриваются под углом программирования. Несколько примеров такого подхода:

  • В этой книге встречаются разделы «Расширенные примеры». Обычно в них представлены полные функции общего назначения вместо изолированных фрагментов кода, основанных на конкретных данных. Более того, некоторые из этих функций могут пригодиться в вашей повседневной работе с R. Изучая эти примеры, вы не только узнаете, как работают конкретные конструкции R, но и научитесь объединять их в полезные программы. Во многих случаях я привожу описания альтернативных решений и отвечаю на вопрос: «Почему это было сделано именно так?»
  • Материал излагается с учетом восприятия программиста. Например, при описании кадров данных я не только утверждаю, что кадр данных в R представляет собой список, но и указываю на последствия этого факта с точки зрения программирования. Также в тексте R сравнивается с другими языками там, где это может быть полезно (для читателей, владеющих этими языками).
  • Отладка играет важнейшую роль в программировании на любом языке, однако в большинстве книг о R эта тема практически не упоминается. В этой книге я посвятил средствам отладки целую главу, воспользовался принципом «расширенных примеров» и представил полностью проработанные демонстрации того, как происходит отладка программ в реальности.
  • В наши дни многоядерные компьютеры появились во всех домах, а программирование графических процессоров (GPU) производит незаметную революцию в области научных вычислений. Все больше приложений R требует очень больших объемов вычислений, и параллельная обработка стала актуальной для программистов на R. В книге этой теме посвящена целая глава, в которой помимо описания механики также приводятся расширенные примеры.
  • Отдельная глава рассказывает о том, как использовать информацию о внутренней реализации и других аспектах R для ускорения работы кода R.
  • Одна из глав посвящена интерфейсу R с другими языками программирования, такими как C и Python. И снова особое внимание уделяется расширенным примерам и рекомендациям по выполнению отладки.


Отрывок. 7.8.4. Когда следует использовать глобальные переменные?


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

Использование глобальных переменных в R более распространено, чем можно было бы ожидать. Как ни удивительно, R очень широко использует глобальные переменные в своей внутренней реализации (и в коде C, и в функциях R). Так, оператор суперприсваивания << — используется во многих библиотечных функциях R (хотя обычно для записи в переменную, находящуюся всего одним уровнем выше в иерархии переменных). В многопоточном коде и коде графического процессора, используемом для написания быстрых программ (см. главу 16), обычно широко задействованы глобальные переменные, обеспечивающие основной механизм взаимодействия между параллельными исполнителями.

А теперь для конкретности вернемся к более раннему примеру из раздела 7.7:

f <- function(lxxyy) { # lxxyy — список, содержащий x и y
    ...
    lxxyy$x <- ...
    lxxyy$y <- ...
    return(lxxyy)
}
# Задать x и y
lxy$x <- ...
lxy$y <- ...
lxy <- f(lxy)
# Использовать новые x и y
... <- lxy$x
... <- lxy$y


Как упоминалось ранее, этот код может стать громоздким, особенно если x и y сами являются списками.

С другой стороны, взгляните на альтернативную схему с использованием глобальных переменных:

f <- function() {
     ...
     x <<- ...
     y <<- ...
}

# Задать x и y
x <-...
y <-...
f() # Здесь x и y изменяются
# Использовать новые x и y
... <- x
... <- y


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

По этим причинам — для упрощения и снижения громоздкости кода — мы решили использовать глобальные переменные вместо возвращения списков в коде DES, приведенном ранее. Рассмотрим этот пример чуть подробнее.

Использовались две глобальные переменные (обе представляют собой списки, содержащие разную информацию): переменная sim связана с библиотечным кодом, а переменная mm1glbls связана с кодом конкретного применения M/M/1. Начнем с sim.

Даже программисты, сдержанно относящиеся к глобальным переменным, согласны с тем, что применение таких переменных может быть оправданно в том случае, если они действительно глобальны — в том смысле, что они широко используются в программе. Все это относится к переменной sim из примера DES: она используется как в коде библиотеки (в schedevnt (), getnextevnt () и dosim ()) и в коде M/M/1 (в mm1reactevnt ()). В этом конкретном примере последующие обращения к sim ограничиваются чтением, но в некоторых ситуациях возможна запись. Типичный пример такого рода — возможная реализация отмены событий. Например, такая ситуация может встретиться при моделировании принципа «более раннее из двух»: планируются два события, и когда одно из них происходит, другое должно быть отменено.

Таким образом, использование sim в качестве глобальной переменной выглядит оправданным. Тем не менее, если бы мы решительно отказались от использования глобальных переменных, sim можно было бы поместить в локальную переменную внутри dosim (). Эта функция будет передавать sim в аргументе всех функций, упоминаемых в предыдущем абзаце (schedevnt (), getnextevnt () и т. д.), и каждая из этих функций будет возвращать измененную переменную sim.
Например, строка 94:

reactevnt(head)


преобразуется к следующему виду:

sim <- reactevnt(head)


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

return(sim)


Нечто похожее можно сделать и с mm1glbls, включив в dosim () локальную переменную с именем (например) appvars. Но если это делается с двумя переменными, то их необходимо поместить в список, чтобы обе переменные можно было вернуть из функции, как в приведенном выше примере функции f (). И тогда возникает громоздкая структура списков внутри списков, о которой говорилось выше, а вернее, списков внутри списков внутри списков.

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

Другая проблема, о которой упоминают критики, встречается при вызове функции из нескольких несвязанных частей программы с разными значениями. Например, представьте, что функция f () вызывается из разных частей программы, причем каждый вызов получает собственные значения x и y вместо одного значения для каждого. Проблему можно решить созданием векторов значений x и y, в которых каждому экземпляру f () в вашей программе соответствует отдельный элемент. Однако при этом будет утеряна простота от использования глобальных переменных.

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

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

Внутри функции dosim () строка

sim <<- list()


может быть заменена строкой

assign("simenv",new.env(),envir=.GlobalEnv)


Она создает новое окружение, на которое ссылается переменная simenv на верхнем уровне. Это окружение служит контейнером для инкапсуляции глобальных переменных, к которым можно обращаться вызовами get () и assign (). Например, строки

if (is.null(sim$evnts)) {
   sim$evnts <<- newevnt


в schedevnt () принимают вид

if (is.null(get("evnts",envir=simenv))) {
  assign("evnts",newevnt,envir=simenv)


Да, это решение тоже получается громоздким, но по крайней мере оно не такое сложное, как списки внутри списков внутри списков. И оно защищает от случайной записи в постороннюю переменную на верхнем уровне. Использование оператора суперприсваивания по-прежнему дает менее громоздкий код, но этот компромисс следует принять во внимание.

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

7.8.5. Замыкания


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

Замыкание представляет собой функцию, которая создает локальную переменную, а затем создает другую функцию, обращающуюся к этой переменной. Описание получается слишком абстрактным, поэтому я лучше приведу пример.

1 > counter
2 function () {
3      ctr <- 0
4      f <- function() {
5          ctr <<- ctr + 1
6          cat("this count currently has value",ctr,"\n")
7      }
8      return(f)
9 }


Проверим, как работает этот код, прежде чем погружаться в подробности реализации:

> c1 <- counter()
> c2 <- counter()
> c1
function() {
        ctr <<- ctr + 1
        cat("this count currently has value",ctr,"\n")
     }

> c2
function() {
        ctr <<- ctr + 1
        cat("this count currently has value",ctr,"\n")
     }

> c1()
this count currently has value 1
> c1()
this count currently has value 2
> c2()
this count currently has value 1
> c2()
this count currently has value 2
> c2()
this count currently has value 3
> c1()
this count currently has value 3


Здесь функция counter () вызывается дважды, а результаты присваиваются c1 и c2. Как и ожидалось, эти две переменные состоят из функций, а именно копий f (). Тем не менее f () обращается к переменной ctr через оператор суперприсваивания, и эта переменная будет переменной с указанным именем, локальной по отношению к counter (), так как она будет первой на пути по иерархии окружений. Она является частью окружения f (), и как таковая упаковывается в то, что возвращается на сторону вызова counter (). Ключевой момент заключается в том, что при разных вызовах counter () переменная ctr будет находиться в разных окружениях (в примере окружения хранились в памяти по адресам 0×8d445c0 и 0×8d447d4). Иначе говоря, разные вызовы counter () будут создавать физически разные экземпляры ctr.

В результате функции c1() и c2() работают как полностью независимые счетчики. Это видно из примера, где каждая функция вызывается по несколько раз.

» Более подробно с книгой можно ознакомиться на сайте издательства
» Оглавление
» Отрывок

Для Хаброжителей скидка 25% по купону — R

По факту оплаты бумажной версии книги на e-mail высылается электронная версия книги.

© Habrahabr.ru