У меня есть два фрейма данных: df1
и df2
. df2
состоит из строк из df1
. Для каждой строки df2
я хочу, чтобы ее индекс находился в df1
.
Например :
df1 <- data.frame(animal=c('koala', 'hedgehog', 'sloth', 'panda'),
country=c('Australia', 'Italy', 'Peru', 'China'),
avg_sleep_hours=c(21, 18, 17, 10))
df2 <- data.frame(animal=c('koala', 'sloth', 'panda', 'panda'),
country=c('Australia', 'Peru', 'China', 'China'),
avg_sleep_hours=c(21,17,10,10))
я хочу получить
1 3 4 4
Я искал в Интернете, но не нашел удовлетворительного ответа, поэтому написал свой собственный код.
Я знаю, что findIdxRow
может вернуть несколько чисел, если строка df2
повторяется в df1
, но она не появится в моих данных, поэтому я не стал тратить время на это.
findIdxRow <- function(row, df)
{
n <- nrow(df)
is_equal <- sapply(1:n, function(i) all(row==df[i,]))
return(which(is_equal))
}
indexes <- sapply(1:nrow(df2), function(i) findIdxRow(df2[i,],df1))
Этот код работает, но мне интересно, есть ли более короткий способ его написания.
Соединение, которое представляет собой высокооптимизированный способ связать два фрейма данных, будет гораздо более производительным, чем ваш исходный подход или подход match
, если размер ваших данных превышает тривиальный (~ 500 строк). В моем тестировании подход match
работал приемлемо для немного больших данных, но работал намного медленнее, чем подходы merge
/left_join
для n > 1000. Подходы соединения продолжают работать довольно хорошо, даже если ваши данные состоят из миллионов строк. Если вам нужна высокая производительность при работе с большими данными, duckdb
, data.table
, collapse
и arrow
, и это лишь некоторые из них, могут обеспечить дальнейшее улучшение.
В базе Р:
# (is there a better way to keep the `df2` order?)
a <- merge(df2 |> transform(index_orig = 1:nrow(df2)),
df1 |> transform(index = 1:nrow(df1)))
a[order(a$index_orig),]$index
Или с помощью dplyr:
library(dplyr)
df2 |>
left_join(df1 |> mutate(index = row_number())) |>
pull(index)
Joining with `by = join_by(animal, country, avg_sleep_hours)`
[1] 1 3 4 4
# fake data
set.seed(42)
n <- 1E3
library(dplyr)
df1 <- data.frame(
animal = ids::adjective_animal(n),
country = ids::proquint(n, n_words = 1))
df2 <- df1 |>
slice_sample(n = n)
tictoc::tic()
df2 |>
left_join(df1 |> mutate(index = row_number())) |>
pull(index)
tictoc::toc()
tictoc::tic()
a <- merge(df2 |> transform(index_orig = 1:nrow(df2)),
df1 |> transform(index = 1:nrow(df1)))
a[order(a$index_orig),]$index
tictoc::toc()
tictoc::tic()
match(interaction(df2),
interaction(df1))
tictoc::toc()
tictoc::tic()
findIdxRow <- function(row, df)
{
n <- nrow(df)
is_equal <- sapply(1:n, function(i) all(row==df[i,]))
return(which(is_equal))
}
indexes <- sapply(1:nrow(df2), function(i) findIdxRow(df2[i,],df1))
tictoc::toc()
Спасибо за сравнение времени выполнения. Но в своем коде вы сравниваете свое решение с моим (findIdxRow), что действительно очень медленно. Но не с решением по совпадению/взаимодействию. Я пытался провести это сравнение, но не могу запустить ваше решение, у меня ошибка: Ошибка в n()
. Я пытаюсь решить эту проблему, но поскольку я не до конца понимаю ваш код, это довольно сложно.
Понятно, раньше я загружал Plyr, чтобы попробовать некоторые решения из Интернета. Теперь это работает, и да, это намного быстрее, чем два других способа сделать это.
Вам нужен dplyr
, а не plyr
— plyr уже много лет выведен из эксплуатации, и это приведет к путанице, поскольку у него много одинаковых имен функций. Попробуйте install.packages("dplyr")
^ Спасибо за замечание, я сделал совершенно неправильное сравнение. match
— это значительное улучшение по сравнению с исходным подходом, но оно все же медленнее, чем подходы с базовым соединением или соединением dplyr.
Мне жаль возвращаться снова. Я проверил решение merge
, оно не работает. Я проверил это на вашем примере с n = 10, и он не дает тот же вектор, что и другие. Элементы одинаковы, но не в том же порядке. Я обнаружил, что сортировка строк слиянием (даже если для параметра сортировки установлено значение False, по крайней мере, в моих тестах) источник statology.org/merge-vs-join-in-r
Спасибо, что проверили — я забыл, что база merge
не сохраняет порядок. Я добавил здесь шаг, чтобы вернуть все в порядок. Это немного замедляет его, но несущественно в сравнении.
интересный бенчмаркинг, +1! Я думаю, мы можем использовать match
вместо строк, что должно быть быстрее, чем join
.
В базе R вы можете использовать match
и interaction
:
match(interaction(df2),
interaction(df1))
# [1] 1 3 4 4
Однако обратите внимание, что этот подход, вероятно, лучше всего использовать с небольшими данными и, вероятно, будет неэффективен с большими кадрами данных.
Спасибо за ответ, не знал эту функцию interaction
это хорошее использование interaction
, +1, особенно когда набор данных небольшой. Он резко замедляется, когда размер набора данных увеличивается, поскольку уровни факторов резко увеличиваются.
@ThomasIsCoding, спасибо! И да, я не думал о скорости и полагаю, что это становится довольно громоздким в вычислительном отношении по мере увеличения количества столбцов и строк. Я отредактировал свой вопрос, включив в него небольшой отказ от ответственности. Спасибо за совет!
Вы можете использовать match
поверх paste
, как показано ниже.
match(do.call(paste, df2), do.call(paste, df1))
set.seed(42)
n <- 5E3
library(dplyr)
df1 <- data.frame(
animal = ids::adjective_animal(n),
country = ids::proquint(n, n_words = 1)
df2 <- df1 |>
slice_sample(n = n)
f1 <- \() {
a <- merge(
df2 |> transform(index_orig = 1:nrow(df2)),
df1 |> transform(index = 1:nrow(df1))
)
a[order(a$index_orig), ]$index
}
f2 <- \() {
df2 |>
left_join(df1 |> mutate(index = row_number()), by = join_by(animal, country)) |>
pull(index)
}
f3 <- \() {
match(do.call(paste, df2), do.call(paste, df1))
}
microbenchmark(
f1(),
f2(),
f3(),
unit = "relative",
check = "equal"
)
который показывает
Unit: relative
expr min lq mean median uq max neval
f1() 21.351795 16.785351 15.511564 15.514184 14.906641 9.398281 100
f2() 3.882332 3.063971 2.929195 2.893312 2.848455 2.389725 100
f3() 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 100
Большое спасибо. Но в конце определения )
отсутствует df1
.
См. также R соответствует более чем двум условиям и возвращает значение ответа