Приношу извинения, если это неправильно отформатировано или мне не хватает какой-либо полезной информации. Я пытаюсь запустить цикл for с вложенным оператором if для нескольких больших наборов данных. Код:
ID_index <- data.frame()
for (x in 1:length(peaks)) {
z <- ((mass_combo - peaks[x])/mass_combo)*10^6
if (length(which(abs(z) < 1)) > 0) {
ID_index[nrow(ID_index)+1, 1] <- peaks[x]
ID_index[nrow(ID_index), 2] <- toString(which(abs(z) < 1))
}
}
Векторные пики представляют собой вектор длиной ~130 000 числовых компонент. Вектор Mass_combo — это числовой вектор, содержащий более 150 000 000 компонентов. Я пытаюсь индексировать, находится ли каждое экспериментальное значение (пики) в пределах заданной погрешности (z) вектора возможных теоретических значений (mass_combo). Теоретические значения были предварительно рассчитаны путем взятия всех возможных комбинаций компонентов с помощью функции expand.grid.
У меня этот цикл работал примерно 2 часа, и цикл прошел всего 5700 итераций. Пример ввода и вывода:
peaks <- c(178.1161, 182.0530, 186.1223)
mass_combo <- c(154.1161, 166.1161, 178.1161, 190.1161, 202.1161, 214.1161, 226.1161, 170.053,
182.053, 194.053, 206.053, 709.0452, 721.0452, 182.0530, 194.0530, 186.1223,
198.1223, 210.1223)
ID_index
V1 V2
178.1161 3
182.0530 9, 14
186.1223 16
Есть ли лучший метод, который я могу использовать для индексации и определения совпадений теоретических значений для каждого экспериментального результата? Любая помощь или предложения будут с благодарностью приняты.





Достаточно хорошо документировано, что выращивание объектов в цикле for очень неэффективно и является самым большим источником неэффективности вашего кода.
Вот несколько вещей, которые я бы сделал, чтобы улучшить скорость вашего цикла for:
length(peaks). Используйте это для инициализации вектора правильного размера. Таким образом, вы не будете выращивать его внутри цикла.which(abs(z) < 1) дважды. Один раз в операторе if и еще раз для тех, кто передает ваш условный оператор.peaks. Нет необходимости создавать это в своем цикле for, просто создайте его постфактум.Например,
for (x in seq_along(peaks)) {
z <- ((mass_combo - peaks[x])/mass_combo)*10^6
idx <- which(abs(z) < 1)
if (length(idx) > 0) {
tol_idx[x] <- toString(idx)
}
}
ID_index <- data.frame(peaks, tol_idx)
Отсюда вы можете продолжать совершенствоваться, рассматривая, какие оставшиеся выражения необходимы. Например, вам нужен оператор if?
Если ни один из mass_combo не находится в пределах вашего допуска, то which() вернет пустой вектор, а если передать его toString(), то вернет пустую строку: "". Это то же значение, которое инициализируется vector():
vector(mode = "character", length = 1L)
# [1] ""
which(c(F, F))
# integer(0)
toString(which(c(F, F)))
# [1] ""
Это позволяет вам полностью удалить это условие и повысить производительность:
tol_idx <- vector(mode = "character", length = length(peaks))
for (x in seq_along(peaks)) {
z <- ((mass_combo - peaks[x])/mass_combo)*10^6
tol_idx[x] <- toString(which(abs(z) < 1))
}
ID_index <- data.frame(peaks, tol_idx)
Редактировать
Судя по комментарию @ThomasIsCoding, неопределенность в размере вашего вывода может быть связана только с желанием сохранить совпадающие строки. Я думаю, что более эффективно хранить все наблюдения, а затем фильтровать фрейм данных постфактум:
ID_index <- data.frame(peaks, tol_idx)
# remove rows where tol_idx is ""
ID_index <- ID_index[nzchar(ID_index$tol_idx),]
Контрольный показатель
Ваш пример небольшой, но экономия времени будет зависеть от размера ваших реальных данных. В этом небольшом примере ваш код занимает примерно на 66% больше времени:
microbenchmark::microbenchmark(
`LMc if` = {
tol_idx <- vector(mode = "character", length = length(peaks))
for (x in seq_along(peaks)) {
z <- ((mass_combo - peaks[x])/mass_combo)*10^6
idx <- which(abs(z) < 1)
if (length(idx) > 0) {
tol_idx[x] <- toString(idx)
}
}
ID_index <- data.frame(peaks, tol_idx)
},
`LMc no if` = {
tol_idx <- vector(mode = "character", length = length(peaks))
for (x in seq_along(peaks)) {
z <- ((mass_combo - peaks[x])/mass_combo)*10^6
tol_idx[x] <- toString(which(abs(z) < 1))
}
ID_index <- data.frame(peaks, tol_idx)
},
`TCB at EU` = {
ID_index <- data.frame()
for (x in 1:length(peaks)) {
z <- ((mass_combo - peaks[x])/mass_combo)*10^6
if (length(which(abs(z) < 1)) > 0) {
ID_index[nrow(ID_index)+1, 1] <- peaks[x]
ID_index[nrow(ID_index), 2] <- toString(which(abs(z) < 1))
}
}
},
unit = "relative"
)
Unit: relative
expr min lq mean median uq max neval cld
LMc if 1.218349 1.217380 1.561912 1.220759 1.213983 15.954036 100 ab
LMc no if 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 100 a
TCB at EU 1.712652 1.705199 1.664062 1.702189 1.661490 1.357403 100 b
Если прироста скорости, полученного в результате этих рекомендаций, все еще недостаточно, вы можете рассмотреть возможность параллельной обработки ваших итераций, которые также обеспечат большой прирост производительности.
Привет @LMc Спасибо за отзывы и предложения. Я самоучка, и кажется, что некоторые методы, которые я использовал для написания кода, оказались неэффективными. Вероятно, я этого не заметил, поскольку раньше не работал с такими большими наборами данных. Я приму ваши предложения и постараюсь реализовать их, чтобы улучшить свой код! Метод, предоставленный @ThomasIsCoding, оказался самым быстрым методом, когда я масштабировал объекты до полного mass_combo размера и использовал более крупные входные данные для peaks. Можно ли задать вопрос завтра в этой теме, если у меня будут продолжения?
Это здорово, и я надеюсь, что вы чему-то научились. У @ThomasIsCoding всегда есть быстрые ответы, из которых вы можете многому научиться, внимательно их изучив. Если у вас есть дополнительные сведения, связанные с этим конкретным вопросом или решениями, приведенными здесь, это нормально, но если ваш вопрос немного отличается от содержания этого сообщения, я бы рекомендовал опубликовать новый вопрос.
Учитывая, что вы имеете дело с большими данными, вам, возможно, придется перебирать каждый peaks для получения желаемых индексов (в противном случае векторизованный подход может вызвать некоторые проблемы с нехваткой памяти).
В вашем коде та часть, которая итеративно обновляет фрейм данных путем добавления новых строк, является медленной операцией.
ID_index[nrow(ID_index)+1, 1] <- peaks[x]
ID_index[nrow(ID_index), 2] <- toString(which(abs(z) < 1))
который на самом деле можно оптимизировать, избегая динамического обновления.
set.seed(0)
peaks <- rnorm(1e3, 200)
mass_combo <- rnorm(1e5, 200)
f1 <- \() {
ID_index <- data.frame()
for (x in 1:length(peaks)) {
z <- ((mass_combo - peaks[x]) / mass_combo) * 10^6
if (length(which(abs(z) < 1)) > 0) {
ID_index[nrow(ID_index) + 1, 1] <- peaks[x]
ID_index[nrow(ID_index), 2] <- toString(which(abs(z) < 1))
}
}
ID_index
}
f2 <- \() {
subset(
data.frame(
V1 = peaks,
V2 = unlist(
lapply(
peaks,
function(x) {
toString(which(abs(1 - x / mass_combo) < 1e-6))
}
), FALSE, FALSE
)
),
nzchar(V2)
)
}
microbenchmark(
f1 = f1(),
f2 = f2(),
times = 10L,
check = "equivalent",
unit = "relative"
)
который показывает
Unit: relative
expr min lq mean median uq max neval
f1 2.434582 2.506911 2.504454 2.474551 2.579847 2.497963 10
f2 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 10
Привет @ThomasIsCoding, спасибо за ответ и за предложения. Как я уже говорил @LMc, я довольно дилетант в вопросах оптимизации кода и не имею формального образования. Я ценю ваше предложение и код, который вы рекомендовали. Использование предоставленного вами subset дало самый быстрый код и результаты сопоставления, когда я масштабировал данные до более крупного набора данных (peaks с 30 значениями и mass_combo с полным списком). Я опробую этот код в полном списке peaks, когда завтра приду на работу. Можно ли продолжить эту тему завтра, если у меня возникнут какие-либо проблемы?
@TCBatEU, без проблем. Если ваши последующие вопросы сильно отличаются, я предлагаю вам открыть новую заявку на вопросы.
После некоторой алгебры, чтобы перестроить сравнение, операцию можно реализовать как data.table неэквисоединение, что будет намного быстрее, чем цикл.
Демонстрируем на примере данных:
data.table(x = mass_combo)[,`:=`(min = x - x/1e6, max = x + x/1e6, r = .I)][
data.table(y = peaks), .(peak = y, r), on = .(min < y, max > y)
][,.(idx = .(r)), peak]
#> peak idx
#> <num> <list>
#> 1: 178.1161 3
#> 2: 182.0530 9,14
#> 3: 186.1223 16
Или чтобы получить значения mass_combo вместо индексов, согласно комментариям:
data.table(x = mass_combo)[,`:=`(min = x - x/1e6, max = x + x/1e6)][
data.table(y = peaks), .(peak = y, x), on = .(min < y, max > y)
][,.(mass_combo = .(x)), peak]
#> peak mass_combo
#> <num> <list>
#> 1: 178.1161 178.1161
#> 2: 182.0530 182.053,182.053
#> 3: 186.1223 186.1223
Синхронизация набора данных с приблизительными размерами ваших фактических данных:
peaks <- runif (13e4)
mass_combo <- runif (15e7)
system.time(
ID_index <-
data.table(x = mass_combo)[,`:=`(min = x - x/1e6, max = x + x/1e6, r = .I)][
data.table(y = peaks), .(peak = y, r), on = .(min < y, max > y)
][,.(idx = .(r)), peak]
)
#> user system elapsed
#> 71.22 4.47 25.64
Первые 10 рядов:
ID_index[1:10]
#> peak idx
#> <num> <list>
#> 1: 0.54024270 267797,2733918,3335681,3499464,3800516,4405793,...
#> 2: 0.52608405 263261, 383398,1462884,1510015,2504774,2521597,...
#> 3: 0.41793366 847991,1156169,1213597,1489392,1680001,3838945,...
#> 4: 0.24143285 512967,1296863,3515676,4526957,5952832,6020254,...
#> 5: 0.89169524 285889, 785031, 829258, 966923,1044726,2564670,...
#> 6: 0.09785744 20626427,23309899,23498292,23712990,23870867,35958767,...
#> 7: 0.07118160 681958, 7164888,10311600,11386095,12753834,34130008,...
#> 8: 0.27893975 1378505,2577870,3153625,3320806,5095727,9098257,...
#> 9: 0.01651383 1503323, 5273696,27981236,30451611,42380615,42807730,...
#> 10: 0.61528940 2191889,2570098,2979545,3398146,4041039,4652609,...
Сравните с циклическим подходом к векторам, которые в 10 раз меньше:
peaks <- runif (13e3)
mass_combo <- runif (15e6)
system.time({ # from @ThomasIsCoding
subset(
data.frame(
V1 = peaks,
V2 = unlist(
lapply(
peaks,
function(x) {
toString(which(abs(1 - x / mass_combo) < 1e-6))
}
), FALSE, FALSE
)
),
nzchar(V2)
)
})
#> user system elapsed
#> 920.64 542.20 1463.61
system.time(
data.table(x = mass_combo)[,`:=`(min = x - x/1e6, max = x + x/1e6, r = .I)][
data.table(y = peaks), .(peak = y, r), on = .(min < y, max > y)
][,.(idx = .(r)), peak]
)
#> user system elapsed
#> 5.15 0.26 2.14
Привет @jblood94, спасибо и тебе за предложение. Ваше решение оказалось даже быстрее, чем предыдущие рекомендации, как вы также продемонстрировали. Спасибо за отзыв, я постараюсь реализовать его в своем коде!
Этот код работает очень хорошо при масштабировании до полного набора данных! Он может предоставить индексы для всего за очень короткий период, и я благодарю вас за это. Поскольку это сработало так хорошо, можно ли это изменить, чтобы вместо этого давать индексируемое значение? Итак, в приведенном вами примере вместо указания индекса в ID_index[,2] вместо этого указано индексированное значение?
Получить значения немного проще, чем получить индексы. См. второй блок кода в обновленном ответе.
Спасибо @jblood94, обе версии кода очень полезны! Я ценю дополнение, которое вы внесли в ответ.
Вот еще одно решение на базе R, которое интересно и увлекательно играть, но может подойти только для небольших или средних наборов данных.
Например, с данными peaks и mass_combo в вашем вопросе вы можете использовать outer + which + split, как показано ниже.
with(
as.data.frame(which(abs(1 - outer(peaks, mass_combo, `/`)) < 1e-6, TRUE)),
split(col, row)
)
который дает
$`1`
[1] 3
$`2`
[1] 9 14
$`3`
[1] 16
Описанный выше подход не применим к большим данным, поскольку outer требует больших затрат памяти, и у вас могут возникнуть проблемы с нехваткой памяти, если вы создадите титаническую матрицу. Поэтому НЕ РЕКОМЕНДУЕТСЯ для больших наборов данных.
Привет @ThomasIsCoding, спасибо за это дополнительное предложение! Как вы предупреждали, для большого набора данных это не сработало, но я могу реализовать это для другой части кода, над которым работаю. Спасибо.
Я думаю, вам нужно опустить строки с пустым
tol_idx, т. е."", в соответствии с форматом вывода OP. В любом случае, хороший ответ с обоснованной ссылкой, +1!