Содержание статьи
- О циклах
- Классические циклы
- Циклы на основе
Код:
apply
- Циклы на основе
Код:
foreach
- Вместо выводов
Сегодня мы с тобой раcсмотрим особенности использования штатных циклов в R, а также познакомимся с функцией
Код:
foreach
Код:
foreach
О циклах
Начнем с того, что часто оказывается неприятным сюрпризом для тех, кто переходит на R с классических языков программиpования: если мы хотим написать цикл, то стоит перед этим на секунду задуматься. Дело в том, что в языках для работы с большим объемом данных циклы, как правило, уступают по эффективности специализированным функциям запросов, фильтрации, агрегации и трансформации данных. Это легко запомнить на примере баз данных, где большинство операций производится с помощью языка запросов SQL, а не с помощью циклов.
Чтобы понять, нaсколько важно это правило, давай обратимся к цифрам. Допустим, у нас есть очень простая таблица из двух столбцов
Код:
a
Код:
b
Код:
testDF <- data.frame(a = 1:100000, b = 100000:1)
Код:
for(row in 1:nrow(testDF)) testDF[row, 3] <- testDF[row, 1] + testDF[row, 2] # Ужас!
Код:
dplyr
Код:
testDF <- testDF %>% mutate(c = a + b)
Классические циклы
R поддерживает основные классические спoсобы написания циклов:
-
Код:
for
Код:for
Код:# Напечатаем номера от 1 до 10 for(i in 1:10) print(i) # Напечатаем все строки из вектора strings strings <- c("Один", "Два", "Три") for(str in strings) print(str)
- Чуть менее распространенные
Код:
while
Код:repeat
Код:while
Код:while(cond) expr
Код:repeat
Код:break
Код:repeat expr
Код:
for
Код:
while
Код:
repeat
Циклы на основе
Код:
apply
Код:
apply
Код:
eapply
Код:
lapply
Код:
mapply
Код:
rapply
Код:
sapply
Код:
tapply
Код:
vapply
Код:
apply
Код:
apply(X, MARGIN, FUN, ...)
Код:
X
Код:
MARGIN
Код:
apply
Например, создадим матрицу
Код:
m
Код:
m <- matrix(1:9, nrow = 3, ncol = 3) print(m) [,1] [,2] [,3] [1,] 1 4 7 [2,] 2 5 8 [3,] 3 6 9
Код:
apply
Код:
apply(m, MARGIN = 1, FUN = sum) # Сумма ячеек для каждой строчки [1] 12 15 18 apply(m, MARGIN = 2, FUN = sum) # Сумма ячеек для каждого столбца [1] 6 15 24
Код:
apply
Код:
sum
Код:
apply
Код:
apply(m, MARGIN = 1, # Вызов нашей функции для каждой строчки FUN = function(x) # Определяем нашу функцию прямо в вызове apply { s <- sum(x) # Считаем сумму if (s == 15) # Если сумма равна 15, то поменяем ее на 100 s <- 100 (s) } ) [1] 12 100 18
Код:
lapply
Код:
lapply(X, FUN, ...)
Код:
sapply
Код:
vapply
Код:
lapply
Достаточно распространен такой способ применения
Код:
sapply
Код:
data <- data.frame(co1_num = 1, col2_num = 2, col3_char = "a", col4_char = "b")
Код:
sapply
Код:
sapply
Код:
data.frame
Код:
is.numeric
Код:
sapply(data, is.numeric) co1_num col2_num col3_char col4_char TRUE TRUE FALSE FALSE
Код:
data[,sapply(data, is.numeric)] co1_num col2_num 1 1 2
Код:
apply
Помнишь тот медленный цикл, что мы написали в самом нaчале с помощью
Код:
for
Код:
apply
Применим
Код:
apply
Код:
sum
Код:
apply
Код:
a_plus_b <- apply(testDF, 1,sum) testDF$c <- a_plus_b
Циклы на основе
Код:
foreach
Код:
foreach
Код:
install.packages("foreach") # Установка пакета на компьютер (один раз) library(foreach) # Подключение пакета
Код:
foreach
Код:
foreach
Код:
foreach
Основные причины популярности
Код:
foreach
- синтаксис похож на
Код:
for
-
Код:
foreach
- есть возможность использовать многопоточность и запускать итерации параллельно.
Код:
result <- foreach(i = 1:10) %do% (i*2)
Код:
c
Код:
result <- foreach(i = 1:10, .combine = "c") %do% (i*2)
Код:
+
Код:
result
Код:
result <- foreach(i = 1:10, .combine = "+") %do% (i*2)
Код:
foreach
Код:
a
Код:
b
Код:
result
Код:
result <- foreach(a = 1:10, b = 10:1, .combine = "c") %do% (a+b)
Код:
data.frame
Код:
customFun <- function(param) { data.frame(param = param, result1 = sample(1:100, 1), result2 = sample(1:100, 1)) }
Код:
data.frame
Код:
rbind
Код:
result <- foreach(param = 1:100,.combine = "rbind") %do% customFun(param)
Код:
result
В
Код:
.combine
Код:
foreach
Код:
.multicombine
Код:
.maxcombine
Одно из главных преимуществ
Код:
foreach
Код:
%do%
Код:
%dopar%
- До вызова
Код:
foreach
Код:doParallel
Код:doSNOW
Код:doMC
Код:library(doParallel) # Загружаем библиотеку в память cl <- makeCluster(8) # Создаем «кластер» на восемь потоков registerDoParallel(cl) # Регистрируем «кластер»
Код:system.time({ foreach(i=1:8) %dopar% Sys.sleep(1) }) user system elapsed 0.008 0.005 1.014
Код:stopCluster(cl)
Код:foreach
- Тебе надо явно указать, какие пакеты необходимо загрузить в рабочие потоки с помощью параметра
Код:
.packages
Код:readr
Код:foreach
Код:%do%
Код:library(readr) foreach(i=1:8) %do% write_csv(data.frame(id = 1), paste0("file", i, ".csv"))
Код:%dopar%
Код:library(readr) foreach(i=1:8) %do% write_csv(data.frame(id = 1), paste0("file", i, ".csv")) Error in write_csv(data.frame(id = 1), paste0("file", i, ".csv")) : task 1 failed - "could not find function "write_csv""
Код:readr
Код:.packages
Код:foreach(i=1:8, .packages = "readr") %dopar% write_csv(data.frame(id = 1), paste0("file", i, ".csv"))
- Вывод на консоль в параллельном потоке не отображается на экране. Иногда это может здорово усложнить отладку, поэтому обычно сложный код сначала пишут без параллельности, а потом заменяют
Код:
%do%
Код:%dopar%
Код:sink
Вместо выводов
- При работе с большим объемом данных циклы не всегда оказываются лучшим выбором. Использование специализированных функций для выборки, агрегации и трансформации данных всегда эффективнее циклoв.
- R предлагает множество вариантов реализации циклов. Основное отличие классических
Код:
for
Код:while
Код:repeat
Код:apply
- Использование циклов
Код:
foreach
Официальная документация пакета foreach
Официальный обзор функциональности foreach