Практика кодирования в R: каковы преимущества и недостатки разных стилей?
Недавние вопросы, касающиеся использования require versus:: подняли вопрос о том, какие стили программирования используются при программировании в R, и каковы их преимущества/недостатки. Просматривая исходный код или просматривая в сети, вы видите много разных стилей.
Основные тенденции в моем коде:
-
Я много играю с индексами (и вложенными индексами), что иногда приводит к довольно неясному коду, но, как правило, намного быстрее, чем другие решения.
например: x[x < 5] <- 0
вместо x <- ifelse(x < 5, x, 0)
-
Я стараюсь использовать функции, чтобы не перегружать память временными объектами, которые мне нужно очистить. Особенно с функциями, управляющими большими наборами данных, это может быть реальной нагрузкой. например: y <- cbind(x,as.numeric(factor(x)))
вместо y <- as.numeric(factor(x)) ; z <- cbind(x,y)
-
Я пишу много пользовательских функций, даже если я использую код только один раз, например. сопло. Я считаю, что он сохраняет его более понятным, не создавая объектов, которые могут оставаться лежащими.
-
Я избегаю циклов любой ценой, так как я считаю, что векторизация будет намного чище (и быстрее)
Тем не менее, я заметил, что мнения по этому поводу различаются, и некоторые люди склонны отступать от того, что они назовут моим "Perl" способом программирования (или даже "Lisp", причем все эти скобки, мой код. Я бы не пошел так далеко, хотя).
Что вы считаете хорошей практикой кодирования в R?
Каков ваш стиль программирования и как вы видите его преимущества и недостатки?
Ответы
Ответ 1
То, что я делаю, будет зависеть от того, почему я пишу код. Если я пишу анализ данных script для моего исследования (дневная работа), я хочу что-то, что работает, но это читаемый и понятный месяц или даже годы спустя. Мне неважно, как много времени вычислять. Векторизация с помощью lapply
et al. может привести к обфускации, чего я бы хотел избежать.
В таких случаях я использовал бы циклы для повторяющегося процесса, если lapply
заставил меня перепрыгнуть через обручи, чтобы построить соответствующую анонимную функцию, например. Я бы использовал ifelse()
в вашей первой пуле, потому что, по крайней мере, на мой взгляд, намерение этого вызова легче понять, чем подмножество + замена версии. С моим анализом данных я больше забочусь о том, чтобы получить правильные вещи, чем обязательно, при вычислении времени - всегда есть выходные и ночи, когда я не в офисе, когда могу выполнять большие задания.
Для ваших других пуль; Я бы склонен не к inline/nest вызовам, если они не были очень тривиальными. Если я четко изложу шаги, я считаю, что код легче читать и, следовательно, с меньшей вероятностью содержать ошибки.
Я пишу пользовательские функции все время, особенно если я буду называть код, эквивалентный функции, повторно в цикле или подобном. Таким образом, я инкапсулировал код из основного анализа данных script в его собственный файл .R
, который помогает разглядеть анализ анализа отдельно от того, как выполняется анализ. И если функция полезна, я использую ее для других проектов и т.д.
Если я пишу код для пакета, я мог бы начать с того же отношения, что и мой анализ данных (знакомство), чтобы получить то, что я знаю, работает, и только потом пойти на оптимизацию, если я хочу улучшить время вычисления.
Единственное, что я стараюсь избегать, - это слишком умный, когда я кодирую, что бы я ни кодировал. В конечном счете, я никогда не был таким умным, как я думаю, что я нахожусь, и если я все делаю просто, я стараюсь не падать на лицо так часто, как мог, если бы я пытался быть умным.
Ответ 2
Я пишу функции (в автономных файлах .R
) для различных фрагментов кода, которые концептуально делают одно. Это держит вещи короткими и милыми. Я нашел отладку несколько проще, потому что traceback()
дает вам, какая функция вызвала ошибку.
Я тоже стараюсь избегать циклов, кроме случаев, когда это абсолютно необходимо. Я чувствую себя несколько грязным, если я использую цикл for()
.:) Я очень стараюсь делать все в векторе или с помощью семейства приложений. Это не всегда лучшая практика, особенно если вам нужно объяснить код другому человеку, который не так свободно говорит о применении или векторизации.
Что касается использования require
vs ::
, я стараюсь использовать оба. Если мне нужна только одна функция из определенного пакета, я использую ее через ::
, но если мне нужно несколько функций, я загружаю весь пакет. Если есть конфликт в именах функций между пакетами, я пытаюсь запомнить и использовать ::
.
Я пытаюсь найти функцию для каждой задачи, которую я пытаюсь достичь. Я считаю, что кто-то передо мной подумал об этом и сделал функцию, которая работает лучше, чем все, что я могу придумать. Иногда это срабатывает, иногда не так много.
Я пытаюсь написать свой код, чтобы я мог его понять. Это означает, что я много комментирую и создаю куски кода, чтобы они как-то следовали идее того, чего я пытаюсь достичь. Я часто перезаписываю объекты по мере продвижения функции. Я думаю, что это обеспечивает прозрачность задачи, особенно если вы ссылаетесь на эти объекты позже в функции. Я думаю о скорости, когда вычислительное время превышает мое терпение. Если функция так долго заканчивается, что я начинаю просматривать SO, я вижу, могу ли я ее улучшить.
Я узнал, что хороший редактор синтаксиса с разворачиванием кода и раскраской синтаксиса (я использую Eclipse + StatET) сэкономил много головных болей.
Основываясь на сообщении VitoshKa, я добавляю, что я использую capitalizedWords (sensu Java) для имен функций и fullstop.delimited для переменных. Я вижу, что у меня может быть другой стиль для аргументов функции.
Ответ 3
Соглашения об именах чрезвычайно важны для читаемости кода. Вдохновленный внутренним стилем R S4, я использую:
- camelCase для глобальных функций и объектов (например, doSomething, getXyyy, upperLimit)
- функции начинаются с глагола
- не экспортируемые и вспомогательные функции всегда начинаются с "."
- локальные переменные и функции выполняются маленькими буквами и в синтаксисе "_" (do_something, get_xyyy). Это позволяет легко различать локальные и глобальные и, следовательно, приводит к более чистым кодам.
Ответ 4
Для жонглирования данных я стараюсь использовать как можно больше SQL, по крайней мере, для основных вещей, таких как средние значения GROUP BY. Мне нравится R много, но иногда это не только интересно понять, что ваша исследовательская стратегия была недостаточно хороша, чтобы найти еще одну функцию, скрытую в еще одном пакете. Для моих случаев SQL-диалекты не отличаются друг от друга, и код действительно прозрачен. В большинстве случаев порог (когда начинать использовать синтаксис R) достаточно интуитивно для обнаружения. например.
require(RMySQL)
# selection of variables alongside conditions in SQL is really transparent
# even if conditional variables are not part of the selection
statement = "SELECT id,v1,v2,v3,v4,v5 FROM mytable
WHERE this=5
AND that != 6"
mydf <- dbGetQuery(con,statement)
# some simple things get really tricky (at least in MySQL), but simple in R
# standard deviation of table rows
dframe$rowsd <- sd(t(dframe))
Поэтому я считаю это хорошей практикой и действительно рекомендую использовать базу данных SQL для ваших данных для большинства случаев использования. Я также изучаю TSdbi и экономя временные ряды в реляционной базе данных, но пока не могу судить об этом.