Практика кодирования в R: каковы преимущества и недостатки разных стилей? - PullRequest
34 голосов
/ 10 декабря 2010

Недавние вопросы относительно использования require vs. :: подняли вопрос о том, какие стили программирования используются при программировании на 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)

  • Я пишу много пользовательских функций , даже если я использую код только один раз, например. sapply. Я считаю, что это делает его более читабельным без создания объектов, которые могут лежать вокруг.

  • I избежать циклов любой ценой , так как я считаю, что векторизация намного чище (и быстрее)

Тем не менее, я заметил, что мнения по этому вопросу расходятся, и некоторые люди склонны отступать от того, что они назвали бы моим "Perl" способом программирования (или даже "Lisp"), когда все эти скобки в моем коде летают Я бы не пошел так далеко, хотя).

Что вы считаете хорошей практикой кодирования в R?

Каков ваш стиль программирования, и как вы видите его преимущества и недостатки?

Ответы [ 4 ]

21 голосов
/ 10 декабря 2010

Что я буду делать, будет зависеть от того, почему я пишу код. Если я пишу сценарий анализа данных для своего исследования (дневная работа), я хочу что-то, что работает, но это читается и понятно месяцы или даже годы спустя. Меня не очень заботит время вычислений. Векторизация с помощью lapply и соавт. может привести к запутыванию, которого я хотел бы избежать.

В таких случаях я бы использовал циклы для повторяющегося процесса, если бы lapply заставил меня перепрыгнуть через обручи, например, для создания соответствующей анонимной функции. Я бы использовал ifelse() в вашей первой букве, потому что, по крайней мере, на мой взгляд, намерение этого вызова легче понять, чем версия подмножества + замена. С моим анализом данных я больше озабочен тем, чтобы все было правильно, чем обязательно с временем вычислений - всегда бывают выходные и ночи, когда я не нахожусь в офисе, когда я могу выполнять большие задания.

Для других ваших пуль; Я бы склонялся , а не к встроенным вызовам, если они не были очень тривиальными. Если я четко изложу шаги, я обнаружу, что код легче читать и, следовательно, он с меньшей вероятностью содержит ошибки.

Я пишу пользовательские функции все время, особенно если я собираюсь вызывать код, эквивалентный функции, многократно в цикле или подобном. Таким образом, я инкапсулировал код из основного сценария анализа данных в его собственный файл .R, который помогает отделить цель анализа от того, как выполняется анализ. И если эта функция полезна, я использую ее для других проектов и т. Д.

Если я пишу код для пакета, я мог бы начать с того же подхода, что и мой анализ данных (знакомство), чтобы получить что-то, что, как я знаю, работает, и только потом пойти на оптимизацию, если я хочу улучшить время вычислений.

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

11 голосов
/ 10 декабря 2010

Я пишу функции (в отдельных .R файлах) для различных частей кода, которые концептуально выполняют одну вещь. Это делает вещи короткими и сладкими. Я нашел отладку несколько проще, потому что traceback() показывает, какая функция вызвала ошибку.

Я тоже склонен избегать петель, кроме случаев, когда это абсолютно необходимо. Я чувствую себя немного грязно, если использую петлю for(). :) Я очень стараюсь сделать все векторизованным или с применением семейства. Это не всегда лучшая практика, особенно если вам нужно объяснить код другому человеку, который не так свободно применяет или применяет векторизацию.

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

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

Я пытаюсь написать свой код, чтобы я мог его понять. Это означает, что я много комментирую и создаю куски кода, чтобы они каким-то образом следовали идее того, чего я пытаюсь достичь. Я часто перезаписываю объекты по мере выполнения функции. Я думаю, что это сохраняет прозрачность задачи, особенно если вы ссылаетесь на эти объекты позже в функции. Я думаю о скорости, когда время вычислений превышает мое терпение. Если функция завершается так долго, что я начинаю просматривать SO, я вижу, могу ли я улучшить ее.

Я обнаружил, что хороший редактор синтаксиса со свертыванием кода и окраской синтаксиса (я использую Eclipse + StatET) избавил меня от многих головных болей.

Основываясь на посте VitoshKa, я добавляю, что я использую прописные буквы (sensu Java) для имен функций и fullstop.delimited для переменных. Я вижу, что у меня может быть другой стиль для аргументов функции.

8 голосов
/ 12 декабря 2010

Соглашения об именах чрезвычайно важны для читабельности кода. Вдохновленный внутренним стилем R4 S4, вот что я использую:

  • camelCase для глобальных функций и объектов (таких как doSomething, getXyyy, upperLimit)
  • функции начинаются с глагола
  • не экспортируется, и вспомогательные функции всегда начинаются с "."
  • все локальные переменные и функции написаны строчными буквами и имеют синтаксис "_" (do_something, get_xyyy), что позволяет легко отличать локальные от глобальных и, следовательно, приводит к более чистому коду.
4 голосов
/ 10 декабря 2010

Для манипулирования данными я стараюсь использовать как можно больше 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 и сохраняю временные ряды в реляционной базе данных, но пока не могу судить об этом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...