Избегайте l oop, когда l oop имеет приращение - PullRequest
1 голос
/ 07 февраля 2020

В R я стараюсь систематически избегать циклов "for" и вместо этого использую семейство lapply().
Но как это сделать, если итерация содержит шаг приращения?

Например: это Можно ли получить тот же результат, что и ниже, с помощью подхода lapply?

a <- c()
b <- c()
set.seed(1L) # required for reproducible data
for (i in 1:10){
  a <- c(a, sample(c(0,1), 1))
  b <- c(b, (paste(a, collapse = "-")))
}
data.frame(a, b)


> data.frame(a, b)
>    a                   b
> 1  0                   0
> 2  1                 0-1
> 3  0               0-1-0
> 4  0             0-1-0-0
> 5  1           0-1-0-0-1
> 6  0         0-1-0-0-1-0
> 7  0       0-1-0-0-1-0-0
> 8  0     0-1-0-0-1-0-0-0
> 9  1   0-1-0-0-1-0-0-0-1
> 10 1 0-1-0-0-1-0-0-0-1-1

РЕДАКТИРОВАТЬ Мой вопрос был очень сильно отредактирован. Приведенный ниже новый пример гораздо более нагляден: все-таки использовать семейство lapply, если каждая итерация рассчитывается исходя из предыдущей?

a <- c()
b <- c()
for (i in 1:10){
  a <- c(a, sample(c(0,1), 1))
  b <- c(b, (paste(a, collapse = "-")))
}
data.frame(a, b)

> data.frame(a, b)
   a                   b
1  0                   0
2  1                 0-1
3  0               0-1-0
4  1             0-1-0-1
5  1           0-1-0-1-1
6  1         0-1-0-1-1-1
7  1       0-1-0-1-1-1-1
8  0     0-1-0-1-1-1-1-0
9  1   0-1-0-1-1-1-1-0-1
10 1 0-1-0-1-1-1-1-0-1-1

Ответы [ 4 ]

4 голосов
/ 07 февраля 2020

Для полноты картины есть также функция accumulate() из пакета purrr.

Итак, основываясь на ответах Сотос и ThomasIsCoding :

df <- data.frame(a = 1:10)
df$b <- purrr::accumulate(df$a, paste, sep = "-")
df
    a                    b
1   1                    1
2   2                  1-2
3   3                1-2-3
4   4              1-2-3-4
5   5            1-2-3-4-5
6   6          1-2-3-4-5-6
7   7        1-2-3-4-5-6-7
8   8      1-2-3-4-5-6-7-8
9   9    1-2-3-4-5-6-7-8-9
10 10 1-2-3-4-5-6-7-8-9-10

Разница с Reduce() в том, что

  • в том, что accumulate() сам по себе является глаголом функции (нет требуется дополнительный параметр accumulate = TRUE)
  • и дополнительные аргументы, такие как sep = "-", могут быть переданы в сопоставленную функцию, что может помочь избежать создания анонимной функции.

РЕДАКТИРОВАТЬ

Если я правильно понимаю, что OP редактирует вопрос, ОП спрашивает, можно ли заменить for l oop, итеративно вычисляющий результат, на lapply().

This Сложно ответить за меня. Вот некоторые мысли и наблюдения:

Первый , accumulate() все еще будет работать:

set.seed(1L) # required for reproducible data
df <- data.frame(a = sample(0:1, 10L, TRUE))
df$b <- purrr::accumulate(df$a, paste, sep = "-")
df                 
   a                   b
1  0                   0
2  1                 0-1
3  0               0-1-0
4  0             0-1-0-0
5  1           0-1-0-0-1
6  0         0-1-0-0-1-0
7  0       0-1-0-0-1-0-0
8  0     0-1-0-0-1-0-0-0
9  1   0-1-0-0-1-0-0-0-1
10 1 0-1-0-0-1-0-0-0-1-1

Это возможно, потому что вычисление a можно извлечь из l oop, так как оно не зависит от b.

ИМХО, accumulate() и Reduce() делают то, что ищет OP, но не называется lapply(): они берут результат предыдущей итерации и объединяют его с фактическим значением, например

Reduce(`+`, 1:3)

возвращает сумму 1, 2 и 3 путем итеративного вычисления (((0 + 1) + 2) + 3). Это можно визуализировать с помощью параметра accumulate

Reduce(`+`, 1:3, accumulate = TRUE)
[1] 1 3 6

Секунда , между for l * 1134 существует существенная разница * и функции семейства lapply(): lapply(X, FUN, ...) требует вызова функции FUN для каждого элемента X. Таким образом, применяются общие правила для функций.

Когда мы пересаживаем тело l oop в анонимную функцию в lapply()

a <- c()
b <- c()
set.seed(1L) # required for reproducible data
lapply(1:10, function(i) {
  a <- c(a, sample(c(0,1), 1))
  b <- c(b, (paste(a, collapse = "-")))
})

, мы получаем

[[1]]
[1] "0"

[[2]]
[1] "1"

[[3]]
[1] "0"

[[4]]
[1] "0"

[[5]]
[1] "1"

[[6]]
[1] "0"

[[7]]
[1] "0"

[[8]]
[1] "0"

[[9]]
[1] "1"

[[10]]
[1] "1"
data.frame(a, b)
data frame with 0 columns and 0 rows    data.frame(a, b)

Из-за правил области действия a и b внутри функция считается локальной для функции. Не делается никаких ссылок на a и b, определенные вне функции.

Это можно исправить с помощью глобального назначения с использованием глобального назначения оператор <<-:

a <- c()
b <- c()
set.seed(1L) # required for reproducible data
lapply(1:10, function(i) {
  a <<- c(a, sample(c(0,1), 1))
  b <<- c(b, (paste(a, collapse = "-")))
})
data.frame(a, b)
   a                   b
1  0                   0
2  1                 0-1
3  0               0-1-0
4  0             0-1-0-0
5  1           0-1-0-0-1
6  0         0-1-0-0-1-0
7  0       0-1-0-0-1-0-0
8  0     0-1-0-0-1-0-0-0
9  1   0-1-0-0-1-0-0-0-1
10 1 0-1-0-0-1-0-0-0-1-1

Однако global assignment считается плохой практикой программирования и его следует избегать, см., например, 6th Круг Патрик Бернс 'The R Inferno и множество вопросов по SO.

Третий , способ написания l oop увеличивает векторы в л * * 1 137. Это также считается плохой практикой, поскольку требует многократного копирования данных, что может значительно замедлиться с увеличением размера. См., Например, 2-й круг Патрика Бернса 'The R Inferno .

Однако исходный код

a <- c()
b <- c()
set.seed(1L) # required for reproducible data
for (i in 1:10) {
  a <- c(a, sample(c(0,1), 1))
  b <- c(b, (paste(a, collapse = "-")))
}
data.frame(a, b)

можно восстановить - записывается как

a <- integer(10)
b <- character(10)
set.seed(1L) # required for reproducible data
for (i in seq_along(a)) {
  a[i] <- sample(c(0,1), 1)
  b[i] <- if (i == 1L) a[1] else paste(b[i-1], a[i], sep = "-")
}
data.frame(a, b)

Здесь векторы предварительно выделяются с требуемым размером для хранения результата. Элементы для обновления идентифицируются по подписке.

Расчет b[i] по-прежнему зависит только от значения предыдущей итерации b[i-1] и фактического значения a[i] в соответствии с запросом OP.

3 голосов
/ 07 февраля 2020

Другой способ - использовать Reduce с accumulate = TRUE, то есть

df$new <- do.call(rbind, Reduce(paste, split(df, seq(nrow(df))), accumulate = TRUE))

, что дает,

    a                  new
1   1                    1
2   2                  1 2
3   3                1 2 3
4   4              1 2 3 4
5   5            1 2 3 4 5
6   6          1 2 3 4 5 6
7   7        1 2 3 4 5 6 7
8   8      1 2 3 4 5 6 7 8
9   9    1 2 3 4 5 6 7 8 9
10 10 1 2 3 4 5 6 7 8 9 10
1 голос
/ 07 февраля 2020

Другой способ использования Reduce, отличающийся от подхода @ Sotos

df$b <- Reduce(function(...) paste(...,sep = "-"), df$a, accumulate = T)

такой, что

> df
    a                    b
1   1                    1
2   2                  1-2
3   3                1-2-3
4   4              1-2-3-4
5   5            1-2-3-4-5
6   6          1-2-3-4-5-6
7   7        1-2-3-4-5-6-7
8   8      1-2-3-4-5-6-7-8
9   9    1-2-3-4-5-6-7-8-9
10 10 1-2-3-4-5-6-7-8-9-10
1 голос
/ 07 февраля 2020

Вы можете использовать sapply (lapply тоже будет работать, но он возвращает список) и перебирать каждое значение a в df, создавать последовательность и paste значение вместе.

df <- data.frame(a = 1:10)
df$b <- sapply(df$a, function(x) paste(seq(x), collapse = "-"))
df

#    a                    b
#1   1                    1
#2   2                  1-2
#3   3                1-2-3
#4   4              1-2-3-4
#5   5            1-2-3-4-5
#6   6          1-2-3-4-5-6
#7   7        1-2-3-4-5-6-7
#8   8      1-2-3-4-5-6-7-8
#9   9    1-2-3-4-5-6-7-8-9
#10 10 1-2-3-4-5-6-7-8-9-10

Если в данных могут быть нечисловые значения, для которых мы не можем использовать seq подобно

df <- data.frame(a =letters[1:10])

В этих случаях мы можем использовать

df$b <- sapply(seq_along(df$a), function(x) paste(df$a[seq_len(x)], collapse = "-"))
df

#   a                   b
#1  a                   a
#2  b                 a-b
#3  c               a-b-c
#4  d             a-b-c-d
#5  e           a-b-c-d-e
#6  f         a-b-c-d-e-f
#7  g       a-b-c-d-e-f-g
#8  h     a-b-c-d-e-f-g-h
#9  i   a-b-c-d-e-f-g-h-i
#10 j a-b-c-d-e-f-g-h-i-j
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...