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))

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

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
72
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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()

Спасибо за сравнение времени выполнения. Но в своем коде вы сравниваете свое решение с моим (findIdxRow), что действительно очень медленно. Но не с решением по совпадению/взаимодействию. Я пытался провести это сравнение, но не могу запустить ваше решение, у меня ошибка: Ошибка в n(). Я пытаюсь решить эту проблему, но поскольку я не до конца понимаю ваш код, это довольно сложно.

Ccile 26.08.2024 18:53

Понятно, раньше я загружал Plyr, чтобы попробовать некоторые решения из Интернета. Теперь это работает, и да, это намного быстрее, чем два других способа сделать это.

Ccile 26.08.2024 18:55

Вам нужен dplyr, а не plyr — plyr уже много лет выведен из эксплуатации, и это приведет к путанице, поскольку у него много одинаковых имен функций. Попробуйте install.packages("dplyr")

Jon Spring 26.08.2024 18:57

^ Спасибо за замечание, я сделал совершенно неправильное сравнение. match — это значительное улучшение по сравнению с исходным подходом, но оно все же медленнее, чем подходы с базовым соединением или соединением dplyr.

Jon Spring 26.08.2024 19:01

Мне жаль возвращаться снова. Я проверил решение merge, оно не работает. Я проверил это на вашем примере с n = 10, и он не дает тот же вектор, что и другие. Элементы одинаковы, но не в том же порядке. Я обнаружил, что сортировка строк слиянием (даже если для параметра сортировки установлено значение False, по крайней мере, в моих тестах) источник statology.org/merge-vs-join-in-r

Ccile 26.08.2024 23:10

Спасибо, что проверили — я забыл, что база merge не сохраняет порядок. Я добавил здесь шаг, чтобы вернуть все в порядок. Это немного замедляет его, но несущественно в сравнении.

Jon Spring 26.08.2024 23:24

интересный бенчмаркинг, +1! Я думаю, мы можем использовать match вместо строк, что должно быть быстрее, чем join.

ThomasIsCoding 26.08.2024 23:54

В базе R вы можете использовать match и interaction:

match(interaction(df2), 
      interaction(df1))

# [1] 1 3 4 4

Однако обратите внимание, что этот подход, вероятно, лучше всего использовать с небольшими данными и, вероятно, будет неэффективен с большими кадрами данных.

Спасибо за ответ, не знал эту функцию interaction

Ccile 26.08.2024 18:20

это хорошее использование interaction, +1, особенно когда набор данных небольшой. Он резко замедляется, когда размер набора данных увеличивается, поскольку уровни факторов резко увеличиваются.

ThomasIsCoding 27.08.2024 00:00

@ThomasIsCoding, спасибо! И да, я не думал о скорости и полагаю, что это становится довольно громоздким в вычислительном отношении по мере увеличения количества столбцов и строк. Я отредактировал свой вопрос, включив в него небольшой отказ от ответственности. Спасибо за совет!

jpsmith 27.08.2024 02:41
Ответ принят как подходящий

Вы можете использовать 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.

Ccile 27.08.2024 13:31

Другие вопросы по теме