R получить индексы соответствующей строки в фрейме данных

У меня есть два фрейма данных: 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))

Этот код работает, но мне интересно, есть ли более короткий способ его написания.


2
72
3

Ответы:

Соединение, которое представляет собой высокооптимизированный способ связать два фрейма данных, будет гораздо более производительным, чем ваш исходный подход или подход 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