У меня есть два фрейма данных: 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()
В базе R вы можете использовать match
и interaction
:
match(interaction(df2),
interaction(df1))
# [1] 1 3 4 4
Однако обратите внимание, что этот подход, вероятно, лучше всего использовать с небольшими данными и, вероятно, будет неэффективен с большими кадрами данных.
Вы можете использовать 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