Я действительно не знаю о вашем двойном for
цикле, но есть пара гораздо более эффективных способов решения этого типа проблемы.
Векторизация - это то, что R делает очень хорошо.Фактически, гораздо лучше, что методы грубой силы, которые являются естественными в некоторых языках, все еще могут работать в R, но значительно медленнее.
Примечание: у R * for
циклов раньше было меньшеэффективнее, чем сейчас, так много людей все еще решительно не одобряют их использование в пользу функций из семейства apply
.Два момента: этот факт больше не соответствует действительности;и это другой тип циклической конструкции, о котором я говорю здесь.Поэтому, когда я не одобряю циклы for
в этом случае, это в пользу векторизации математики, а не apply
ее.
Вот некоторые данные:
my_list <- list(
c(1, 12, 23, 34, 38),
c(2, 12, 21, 38, 47, 56, 71),
c(14, 22, 81, 88, 91, 94)
)
Я продемонстрирую на одном векторе этого списка:
v <- my_list[[1]]
v
Я интерпретирую то, что вы сказали, как v[i+1] - v[i]
для каждого i
в последовательности индексов (кроме 1, посколькуv[0]
не определено в R).Чтобы сделать это как вектор, это "начать со всех чисел, кроме первого, затем вычесть все числа, кроме последнего" .
v[-1]
# [1] 12 23 34 38
v[-length(v)]
# [1] 1 12 23 34
v[-1] - v[-length(v)]
# [1] 11 11 11 4
Это эффективно
c(12, 23, 34, 38) - c(1, 12, 23, 34)
c(12-1, 23-12, 34-23, 38-34)
Теперь, когда мы знаем, как сделать это эффективно один раз , давайте упростим эту операцию и сопоставим ее с каждым вектором в списке.В R есть функция, которая делает это для нас:
diff(v)
# [1] 11 11 11 4
, но в случае, если ваши будущие потребности включают более конкретные (не общие) операции, мы могли бы написать нашу собственную функцию для этой конкретной операции:
my_func <- function(vec) vec[-1] - vec[-length(vec)]
Теперь классическое использование одной из функций отображения: lapply
применяет одну функцию к каждому элементу list
и возвращает list
одинаковой длины с возвращаемыми значениями.
Примечание: когда мне нужно выбрать между for
и lapply
(например), я спрашиваю себя, не волнуюсь о расчете по каждому элементу (например, в этом случае, где я хочуdiff
вектора), или, если меня просто интересует побочный эффект (например, создание чего-либо, сохранение файлов).Если первое, то lapply
или его родственник уместен;если последнее, часто for
петли.Это не на 100% эвристика, но в целом это довольно хорошо.
lapply(my_list, my_func)
# [[1]]
# [1] 11 11 11 4
# [[2]]
# [1] 10 9 17 9 9 15
# [[3]]
# [1] 8 59 7 3 3
(аналогично, lapply(my_list, diff)
работает.) Существуют аналогичные функции *apply*
с немного различными преимуществами, требованиями иограничения.(Есть также несколько обучающих программ, которые уже входят в это, и SO не предназначен, чтобы быть учебным сайтом.)
Я действительно не рекомендую использовать циклы for
здесь, частично для lapply
, частично для векторизации, но чтобы помочь вам понять, почему ваша реализация не сработала:
- , если вам нужно перебрать каждый элемент списка:
- , предпочтительно не жесткий код
1:29
, вместо этого используйте что-то, что зависит от самого вектора, например length(my_list)
, поэтому 1:length(my_list)
может показаться подходящим (как вы правильно используете во втором цикле), но... - случилось, что этот список в какой-то момент имеет длину 0, но
for (i in 1:0)
делает , а не делает то, на что можно было бы надеяться.Чтобы было ясно, я хотел бы надеяться, что это ничего не сделает, но 1:0
разрешает в вектор, длину 2, значения 1 и 0 (и это просто неправильно в большинстве случаев, которые используют этот контроль потока).Я рекомендую заменить for (i in 1:length(my_list))
на for (i in seq_along(my_list))
или for (i in seq_len(length(my_list)))
(seq_along
предоставляет индексы для вектора / списка, он не будет давать чисел, если его список имеет длину 0; а seq_len
разумно дает вектор 0-длины, еслиего аргумент равен 0. Оба можно найти в ?seq
.)
- , когда
i
равно 1, а j
равно 2, вы храните list(12-1)
в res[1]
;когда j равен 3, вы перезаписываете res[1]
с помощью list(23-12)
, поэтому вы потеряли свои предыдущие вычисления в векторе 1. Именно поэтому каждый элемент в вашем списке имеет длину 1. - ваш внутренний цикл (
j
) проходит до конца вектора (length(my_list[[i]])
);в этот момент my_list[[i]][j+1]
указывает за конец вектора, поэтому он разрешается в NA
(попробуйте my_list[[1]][999999]
), поэтому все значения в res
равны NA
.Чтобы исправить это, используйте 1:(length(my_list[[i]])-1)
или, предпочтительно, seq_length(my_list[[i]])[-1]
, чтобы отбросить первое (поэтому мы сделаем (j) - (j-1)
вместо (j+1) - (j)
). - Если вы должны сохранить логику индексации
(j+1) - (j)
, тогда используйте что-то вроде seq_along(my_list[[i]])[-length(my_list[[i]])]
или head(seq_along(my_list[[i]]),n=-1)
, где n=-1
означает все, кроме последнего.
Это исправленная версия вашего кода:
resouter <- list()
for (i in seq_along(my_list)) {
resinner <- numeric(0)
for (j in seq_along(my_list[[i]])[-1]) {
resinner[j] <- my_list[[i]][j] - my_list[[i]][j-1]
}
resouter[[i]] <- resinner[-1] # since j starts at 2, first one is always NA
}
resouter
# [[1]]
# [1] 11 11 11 4
# [[2]]
# [1] 10 9 17 9 9 15
# [[3]]
# [1] 8 59 7 3 3
Но я думаю, что lapply(my_list, my_func)
или даже lapply(my_list, diff)
гораздо более лаконичны (и быстрее).