Автор: Неха Анвер, Статистика без границ.

Эта статья содержит

  • Краткий обзор концепции apply() и общего варианта использования
  • apply() против sapply() с примерами

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

Обзор apply()

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

В приведенном ниже примере кода мы выполним несколько простых операций со встроенным набором данных R под названием Orange. Этот набор данных фиксирует рост апельсиновых деревьев.

  • Сначала мы загрузим набор данных в нашу среду с помощью команды data.
## Load in data
data('Orange')
head(Orange)

Вывод

#   Tree  age circumference
# 1    1  118            30
# 2    1  484            58
# 3    1  664            87
# 4    1 1004           115
# 5    1 1231           120
# 6    1 1372           142
  • Далее мы разработаем новую функцию. Эта новая функция будет вычислять значение circumference каждого дерева, деленное на наибольшее значение circumference в данных.
  • Сначала мы выполним это вычисление, используя цикл for, а затем вычислим те же значения, используя apply().

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

max_circ <- max(Orange$circumference) # maximum circumference
pct_circ <- list()  ## create an empty list to store values
for(i in 1:nrow(Orange)) { # loop over each row
  
  # Divide each value by the max and multiply by 100
  pct_circ[i] <- (Orange$circumference[i] / max_circ) * 100
  
} # End for loop
pct_circ

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

apply(data, Margin, Function)

Первый аргумент ожидает массив или матрицу. Второй аргумент может быть 1, 2 или вектором индексов. Этот аргумент сообщает функции, должна ли операция применяться к строкам (1), вниз по столбцу (2) или к определенной ячейке. Последний аргумент ожидает имя функции, которая будет применена. В нашем случае, поскольку нет встроенной функции, которая может вычислить данное число как процент от другого числа, мы напишем встроенную функцию:

pct_func <- function(x) {
  result <- x / max_circ   # x is a vector/matrix
  result <- result * 100 # convert from decimal to %
  return(result)
}

Теперь, когда у нас есть готовая функция, нам просто нужно указать правильные входные данные для функции apply(). Помните, что он ожидает, что данные будут фреймом данных или матрицей. Мы предоставим столбец окружности (столбец 3) в качестве нашей входной матрицы. При индексировании столбца 3 кадра данных Orange нам нужно будет указать дополнительный аргумент drop=F, чтобы наша матрица сохраняла свои размеры.

apply(Orange[,3,drop=F], 1, pct_func)

Вывод

# output from console
# 1         2         3         4         5         6         7         8         9        10 
# 14.01869  27.10280  40.65421  53.73832  56.07477  66.35514  67.75701  15.42056  32.24299  51.86916 
# 11        12        13        14        15        16        17        18        19        20 
# 72.89720  80.37383  94.85981  94.85981  14.01869  23.83178  35.04673  50.46729  53.73832  64.95327 
# 21        22        23        24        25        26        27        28        29        30 
# 65.42056  14.95327  28.97196  52.33645  78.03738  83.64486  97.66355 100.00000  14.01869  22.89720 
# 31        32        33        34        35 
# 37.85047  58.41121  66.35514  81.30841  82.71028

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

применить () против sapply ()

Созданная на основе функции apply(), sapply() использует более гибкие типы ввода и идеально подходит для векторных операций. sapply() принимает список, вектор или DataFrame в качестве входных данных и возвращает матрицу или вектор той же длины в качестве выходных данных. Общий синтаксис функции sapply():

sapply(data, function)

Обратите внимание, что здесь нет аргумента margin. Это связано с тем, что функция sapply() по умолчанию применяет функцию к каждому элементу данных. Из-за такого поведения по умолчанию аргумент margin не нужен.

Следуя тому же примеру, что и выше, давайте вычислим длину окружности в процентах от наибольшей окружности в нашем наборе данных, используя sapply(). Обратите внимание, что, поскольку входные данные не должны обязательно быть матрицей с пригодными для использования размерами, мы можем просто указать имя столбца «окружность» и получить объект списка той же длины, что и на выходе.

sapply(Orange$circumference, pct_func)
#Output:
# [1]  14.01869  27.10280  40.65421  53.73832  56.07477  66.35514  67.75701  15.42056  32.24299
# [10]  51.86916  72.89720  80.37383  94.85981  94.85981  14.01869  23.83178  35.04673  50.46729
# [19]  53.73832  64.95327  65.42056  14.95327  28.97196  52.33645  78.03738  83.64486  97.66355
# [28] 100.00000  14.01869  22.89720  37.85047  58.41121  66.35514  81.30841  82.71028

Если бы мы захотели, мы могли бы применить pct_func к столбцу age в нашем наборе данных, мы могли бы так:

sapply(Orange[,c('circumference', 'age')], pct_func)
#Output: 
# circumference       age
# [1,]      14.01869  55.14019
# [2,]      27.10280 226.16822
# [3,]      40.65421 310.28037
# [4,]      53.73832 469.15888
# [5,]      56.07477 575.23364
# [6,]      66.35514 641.12150
# [7,]      67.75701 739.25234
# [8,]      15.42056  55.14019
# [9,]      32.24299 226.16822
# [10,]      51.86916 310.28037
# [11,]      72.89720 469.15888
# [12,]      80.37383 575.23364
# [13,]      94.85981 641.12150
# [14,]      94.85981 739.25234
# [15,]      14.01869  55.14019
# [16,]      23.83178 226.16822
# [17,]      35.04673 310.28037
# [18,]      50.46729 469.15888
# [19,]      53.73832 575.23364
# [20,]      64.95327 641.12150
# [21,]      65.42056 739.25234
# [22,]      14.95327  55.14019
# [23,]      28.97196 226.16822
# [24,]      52.33645 310.28037
# [25,]      78.03738 469.15888
# [26,]      83.64486 575.23364
# [27,]      97.66355 641.12150
# [28,]     100.00000 739.25234
# [29,]      14.01869  55.14019
# [30,]      22.89720 226.16822
# [31,]      37.85047 310.28037
# [32,]      58.41121 469.15888
# [33,]      66.35514 575.23364
# [34,]      81.30841 641.12150
# [35,]      82.71028 739.25234

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

  • Хотите узнать больше о статистике без границ? Подпишитесь на нас в Twitter и LinkedIn и посетите наш веб-сайт.
  • Хотите стать волонтером в проектах или внести свой вклад в этот блог? Отправьте нам электронное письмо по адресу [email protected].

Познакомьтесь с автором

Неха Анвер

Неха — специалист по науке о данных с опытом работы в сфере финансового консультирования. Она работает волонтером в «Статистика без границ» уже более года, а совсем недавно присоединилась к команде SWB по маркетингу и коммуникациям. В свободное время Неха любит путешествовать, читать художественную литературу и проводить время на свежем воздухе.