Как организовать большие программы R?

Когда я беру проект R любой сложности, мои скрипты быстро становятся длинными и запутанными.

Какие методы я могу принять, чтобы мой код всегда был с удовольствием работать? Я думаю о таких вещах, как

  • Размещение функций в исходных файлах
  • Когда выкладывать что-то в другой исходный файл
  • Что должно быть в главном файле
  • Использование функций в качестве организационных единиц (стоит ли это, учитывая, что R затрудняет доступ к глобальному состоянию)
  • Практика отступов/прерываний строк.
    • Лечить (например, {?
    • Поместите вещи как)} на 1 или 2 строки?

В основном, каковы ваши эмпирические правила для организации больших R-скриптов?

Ответ 1

Стандартным ответом является использование пакетов - см. руководство Написание R-расширений, а также различные учебники в Интернете.

Он дает вам

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

Просто запуск source() над кодом работает для действительно коротких фрагментов. Все остальное должно быть в пакете - даже если вы не планируете публиковать его, поскольку вы можете писать внутренние пакеты для внутренних репозиториев.

Что касается части "как отредактировать", руководство R Internals имеет отличные стандарты кодирования R в разделе 6. В противном случае я склонен использовать значения по умолчанию в Режим ESS Emacs.

Обновление 2008-Aug-13: Дэвид Смит только что написал о Руководство по стилю Google R.

Ответ 2

Мне нравится добавлять различные функциональные возможности в свои собственные файлы.

Но мне не нравится система R-пакета. Это довольно сложно использовать.

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

util = new.env()

util$bgrep = function [...]

util$timeit = function [...]

while("util" %in% search())
  detach("util")
attach(util)

Это все в файле util.R. Когда вы его используете, вы получаете среду "util", чтобы вы могли называть util$bgrep() и т.д.; но, кроме того, вызов attach() делает это так просто bgrep(), и такая работа выполняется напрямую. Если вы не поместили все эти функции в свою собственную среду, они бы загрязнили пространство имен верхнего уровня интерпретатора (тот, который показывает ls()).

Я пытался имитировать систему Python, где каждый файл является модулем. Это было бы лучше, но это кажется ОК.

Ответ 3

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

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

Как мелкие/крупнозернистые вы делаете свои функции различными, и существуют различные эмпирические правила: например, 15 строк кода или "функция должна отвечать за выполнение одной задачи, которая идентифицируется по ее имени" и т.д. Ваш пробег будет отличаться. Поскольку R не поддерживает call-by-reference, я обычно различаюсь тем, что мои функции слишком мелкие, когда речь идет о передаче кадров данных или подобных структур вокруг. Но это может быть сверхкомпенсацией для некоторых глупых ошибок производительности, когда я впервые начал с R.

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

Если вы собираетесь развернуть свои функции, вам нужно начать думать о пакетах. Я не разворачиваю R-код на производстве или для повторного использования другими по различным причинам (кратко: org культура предпочитает другие langauges, проблемы с производительностью, GPL и т.д.). Кроме того, я стараюсь постоянно совершенствовать и добавлять в свои коллекции исходные файлы, и я бы предпочел не заниматься пакетами, когда я вношу изменения. Поэтому вы должны проверить другие ответы, связанные с пакетом, например, Dirk's, для более подробной информации об этом фронте.

Наконец, я думаю, что ваш вопрос не обязательно относится к Р. Я бы действительно рекомендовал прочитать Code Complete от Steve McConnell, который содержит много мудрости о таких проблемах и методах кодирования в целом.

Ответ 4

Я согласен с советом Дирка! IMHO, организация ваших программ от простых скриптов до документированных пакетов, для программирования в R, например, переключение с Word на TeX/LaTeX для записи. Я рекомендую взглянуть на очень полезный Создание R-пакетов: Учебник от Фридриха Лейша.

Ответ 5

Мой краткий ответ:

  • Тщательно напишите ваши функции, указав достаточно общие выходы и входы;
  • Ограничить использование глобальных переменных;
  • Использовать объекты S3 и, при необходимости, объекты S4;
  • Поместите функции в пакеты, особенно когда ваши функции вызывают C/Fortran.

Я считаю, что R все больше используется в производстве, поэтому потребность в многоразовом коде больше, чем раньше. Я нахожу интерпретатора гораздо более надежным, чем раньше. Нет сомнения, что R на 100-300x медленнее C, но обычно узкое место сосредоточено вокруг нескольких строк кода, которые могут быть делегированы C/С++. Я думаю, было бы ошибкой делегировать сильные стороны R в манипулировании данными и статистическом анализе на другой язык. В этих случаях ограничение производительности низкое, и в любом случае стоит экономить средства на развитие. Если бы это было время выполнения, мы все будем писать ассемблер.

Ответ 6

Я имел в виду выяснить, как писать пакеты, но не вложил времени. Для каждого из моих мини-проектов я сохраняю все свои низкоуровневые функции в папке с именем "функции/" и отправляю их в отдельное пространство имен, которое я явно создаю.

Следующие строки кода создадут среду с именем "myfuncs" в пути поиска, если она еще не существует (с использованием attach) и заполняет ее функциями, содержащимися в файлах .r в моих "функциях/", (используя sys.source). Я обычно помещаю эти строки в начало моего основного script, предназначенного для "пользовательского интерфейса", из которого вызывается высокоуровневые функции (вызывающие низкоуровневые функции).

if( length(grep("^myfuncs$",search()))==0 )
  attach("myfuncs",pos=2)
for( f in list.files("functions","\\.r$",full=TRUE) )
  sys.source(f,pos.to.env(grep("^myfuncs$",search())))

Когда вы вносите изменения, вы всегда можете переименовать его с теми же строками или использовать что-то вроде

evalq(f <- function(x) x * 2, pos.to.env(grep("^myfuncs$",search())))

для оценки дополнений/модификаций в созданной среде.

Это kludgey, которого я знаю, но избегает быть слишком формальным об этом (но если у вас есть шанс, я поощряю систему пакетов - надеюсь, я буду переносить этот путь в будущем).

Что касается условных обозначений кодирования, это единственное, что я видел в отношении эстетики (я люблю их и свободно следую, но я не использую слишком много фигурных скобок в R):

http://www1.maths.lth.se/help/R/RCC/

Существуют и другие "соглашения" относительно использования [, drop = FALSE] и < - как оператор присваивания, предлагаемый в различных презентациях (обычно основной) при использовании R! конференции, но я не думаю, что любой из них является строгим (хотя [, drop = FALSE] полезен для программ, в которых вы не уверены в ожидаемом вами входе).

Ответ 7

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

Ответ 8

Я также согласен. Для начала используйте функцию package.skeleton(). Даже если вы считаете, что ваш код никогда не будет запущен снова, это может помочь мотивировать вас создать более общий код, который может сэкономить ваше время позже.

Что касается доступа к глобальной среде, то это легко с помощью оператора < -, хотя оно не рекомендуется.

Ответ 9

Не узнав, как писать пакеты, я всегда организовывал подкопирование сценариев. Это похоже на написание классов, но не на участие. Он не является программно элегантным, но я нахожу, что я собираю анализы с течением времени. Как только у меня есть большой раздел, который работает, я часто переношу его на другой script и просто его источник, так как он будет использовать объекты рабочей области. Возможно, мне нужно импортировать данные из нескольких источников, отсортировать их и найти пересечения. Я могу добавить этот раздел в дополнительный script. Однако, если вы хотите распространять свое "приложение" для других людей или использует какой-либо интерактивный ввод, пакет, вероятно, является хорошим путем. В качестве исследователя мне редко приходится распространять код анализа, но мне нужно увеличить или настроить его.

Ответ 10

R подходит для интерактивного использования и небольших скриптов, но я бы не использовал его для большой программы. Я бы использовал основной язык для большей части программирования и переносил его в R-интерфейс.