Процесс построения кода и встроенные функции - PullRequest
5 голосов
/ 28 февраля 2012

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

Ниже приведены три способа кодирования чего-либо:

Сначала вот пример данных:

stackexample <- c(52,50,45,49.5,50.5,12,10,14,11.5,12,110,108,106,101,104)
dim(stackexample)<- c(5,3)

Метод первый: выполнить математику в функции без определения каких-либо объектов

 ertimesIVCV1 <- function (x) 
{ (solve(var(log((x[-nrow(x),])/(x[-1,])))))%*%
  ((1+(log(x[1,]/(x)[nrow(x),])))^(1/nrow(x))-1)}

ertimesIVCV1(stackexample)

Метод второй: определить объекты в функции, а затем манипулировать этими объектами

    ertimesIVCV2 <- function (x) 
{ IVCV <- solve(var(log((x[-nrow(x),])/(x[-1,]))));
  retsexcess <- (1+(log(x[1,]/(x)[nrow(x),])))^(1/nrow(x))-1;
  IVCV%*%retsexcess}

ertimesIVCV2(stackexample)

Метод третий: определите несколько функций и вызовите эти функции в «сводной» функции

IVCV <- function (x) {solve(var(log((x[-nrow(x),])/(x[-1,]))))}
retsexcess <- function(x) (1+(log(x[1,]/(x)[nrow(x),])))^(1/nrow(x))-1
ertimesIVCV3 <- function (x) {IVCV(x)%*%retsexcess(x)}

ertimesIVCV3(stackexample)

Так что все выдают один и тот же ответ:

           [,1]
[1,]  1.4430104
[2,] -0.1365155
[3,] 11.8088378

но, как вы можете видеть, три разных подхода.

Существует ли такая вещь, как оптимальное количество встроенных функций, или мы всегда должны пытаться явно перечислить все математические вычисления? Сколько уровней функций внутри функций является оптимальным? Является ли какой-либо метод выше по вычислительной скорости? Есть ли эмпирическое правило к этому? Как вы подходите к этому? Любые комментарии или предложения или ссылки будут приветствоваться и спасибо!

Ржаной

Ответы [ 3 ]

6 голосов
/ 29 февраля 2012

ИМХО, эффективность скорости должна быть последней ваших проблем при написании кода, особенно если вы новичок. Вместо этого вы должны сосредоточиться на простоте, удобочитаемости и модульности. Не поймите меня неправильно, эффективность - это отличная вещь, и вы найдете много способов сделать ваш код быстрее, когда это необходимо, но это не должно быть приоритетом само по себе.

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

IVCV <- function(stack) {

## This function computes [...] IVCV stands for [...]
## Inputs:
##    - stack: a matrix where each column [...]
## Output: a matrix [...]

   n <- nrow(stack) # stack size
   stack.ratios  <- stack[-n, ] / stack[-1, ]
   log.ratios    <- log(stack.ratios)
   ivcv          <- solve(var(log.ratios))

   return(ivcv)
}

ExcessReturn <- function(stack) {

## This function computes [...] IVCV stands for [...]
## Inputs:
##    - stack: a matrix where each column [...]
## Output: a matrix [...]

   n <- nrow(stack) # stack size
   total.ratio   <- stack[1, ] / stack[n, ]
   excess.return <- (1 + log(total.ratio)) ^ (1 / n) - 1

   return(excess.return)
}

ExcessReturnTimesIVCV <- function(stack) {

## This function computes [...] IVCV stands for [...]
## Inputs:
##    - stack: a matrix where each column [...]
## Output: a vector [...]

    return(IVCV(stack) %*% ExcessReturn(stack))
}

1) да, разбейте ваш код на маленькие функции. Это лучше для удобочитаемости, гибкости и обслуживания. Это также облегчает модульное тестирование, где вы можете создавать тесты для каждого элементарного фрагмента кода.

2) задокументировать функцию, включив комментарии о ее описании / входах / выходах внутри тела функции. Таким образом, после создания функции пользователь может видеть ее описание как часть распечатки функции (например, просто наберите ExcessReturnTimesIVCV в GUI).

3) разбить сложность на несколько утверждений. Прямо сейчас, все ваши три предложения трудно понять, так как в каждой строке происходит слишком много вещей. Заявление должно делать простую вещь, чтобы его можно было легко прочитать. Создание большего количества объектов вряд ли замедлит ваш процесс, и это значительно упростит отладку.

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

5) размещать комментарии, особенно там, где 3) и 4) недостаточно, чтобы сделать код понятным. В моем примере я решил использовать переменную n. Я пошел против рекомендации, что имена переменных должны быть описательными, но это должно было сделать код немного легче и придать выражениям типа stack[-n, ] / stack[-1, ] некоторую приятную симметрию. Поскольку n - это плохое имя, я добавил комментарий, объясняющий его значение. Я мог бы также добавить больше комментариев в код, если бы знал, что на самом деле делают функции.

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

a) одно утверждение в строке, без точек с запятой.

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

в) последовательная привязка: будьте осторожны с тем, как вы используете фигурные скобки для определения блоков, в противном случае у вас могут возникнуть проблемы в режиме сценария. См. Раздел 8.1.43 R Inferno (отличный справочник.)

Удачи!

6 голосов
/ 29 февраля 2012

Если целью является экономия времени, то ответ с предложенными примерами - «кого это волнует?».Затраты на вызовы функций не определяют эффективность.Возможно, вам следует сосредоточиться на других вопросах, таких как понимание пользователя и способность поддерживать код.

 require(rbenchmark)
 benchmark(replications=100, ver1= ertimesIVCV1(stackexample),
 ver2=ertimesIVCV2(stackexample),
 ver3 = ertimesIVCV3(stackexample) )
# ------------------
  test replications elapsed relative user.self sys.self user.child sys.child
1 ver1          100   0.030 1.000000      0.03        0          0         0
2 ver2          100   0.030 1.000000      0.03        0          0         0
3 ver3          100   0.031 1.033333      0.03        0          0         0
4 голосов
/ 29 февраля 2012

Не согласен с DWin (хотя на самом деле это не так, я просто добавляю другой вариант). Если целью является ваша экономия времени, то у нас есть несколько случаев. Если вы что-то делаете один раз, то я согласен с тем, "кого это волнует?" Делайте все, что вы хотите / что вы думаете в то время, вероятно, метод 1 или 2.

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

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

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