Присоединяйтесь к кадрам данных и выбирайте случайную строку при наличии нескольких совпадений

У меня есть эталонный фрейм данных (df1) с тремя столбцами «характеристик» (пол, год, код) и двумя столбцами «значений» (количество, статус). Это выглядит так, но с большим количеством строк:

gender    year    code    amount   status
     M    2011       A        15      EMX
     M    2011       A       123      NOX
     F    2015       B         0      MIX
     F    2018       A        12      NOX
     F    2015       B        11      NOX

У меня есть еще один фрейм данных (df2), в котором есть только три столбца «характеристик». Например:

gender    year   code
     M    2011      A
     M    2011      A
     F    2018      A
     F    2015      B

Для каждой строки в df2 я хочу присвоить «значения» на основе совпадений в «характеристиках» для df1. Там, где есть несколько совпадений, я хочу выбрать пары «значений» случайным образом. Поэтому, когда в df2 есть повторяющиеся «характеристики», они могут оказаться с разными парами «значений», но все они будут иметь точное совпадение в df1. По сути, для каждой комбинации характеристик я хочу, чтобы распределение значений между двумя таблицами совпадало.

Например, последняя строка в 'df2' (пол = F, год = 2015, код = B) соответствует двум строкам в 'df1': третьей строке (количество = 0, статус = MIX) и пятой строке (количество = 11, статус = NOX). Затем одна из этих совпадающих строк должна быть выбрана случайным образом. Для всех подобных случаев множественных совпадений между 'df2' и 'df1' по признаку пола, года и кода следует выбирать случайную строку.


До сих пор мой подход заключался в том, чтобы начать с использования dplyr для выполнения left_join между двумя фреймами данных. Однако это обеспечивает все возможные «значения» для каждой строки в df2, а не случайный выбор. Поэтому мне нужно сгруппировать по характеристикам и выбрать одну. Это создает очень большую промежуточную таблицу и не кажется очень эффективной.

Интересно, есть ли у кого-нибудь предложения по более эффективному методу? Ранее я обнаружил, что присоединение к пакету data.table происходит быстрее, но на самом деле я плохо разбираюсь в пакете. Мне также интересно, должен ли я вообще выполнять соединения или просто использовать функцию sample?

Любая помощь высоко ценится.

Являются ли столбцы «характеристик» обоих фреймов данных одинаковыми? Или есть резон работать с df2?

Sven 12.06.2019 12:49

df2 — это отдельная группа людей (каждая строка — это человек) — я хочу сгенерировать для них распределение «значений» на основе распределения характеристик/значений в df1. Все комбинации характеристик в df2 появятся где-то в df1.

rw2 12.06.2019 13:32

Могут ли две строки в df2 соответствовать одной строке в df1?

sindri_baldur 12.06.2019 13:50

Да, хотя это весьма маловероятно, это потенциально может произойти.

rw2 12.06.2019 13:52
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
4
756
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Моя data.table игра довольно слабая, но вот потенциальное решение, использующее подход, аналогичный описанному выше. Сначала я определяю фреймы данных.

# Define data frames
df1 <- read.table(text= "gender    year    code    amount   status
M    2011       A        15      EMX
M    2011       A       123      NOX
F    2015       B         0      MIX
F    2018       A        12      NOX
F    2015       B        11      NOX", header = TRUE)

df2 <- read.table(text = "gender    year   code
     M    2011      A
     M    2011      A
     F    2018      A
     F    2015      B", header = TRUE)

Затем я устанавливаю начальное число генератора случайных чисел для воспроизводимости и загружаю библиотеку.

# Set RNG seed
set.seed(4)

# Load library
library(data.table)

Затем я конвертирую фреймы данных в таблицы данных.

# Convert to data tables
dt1 <- data.table(df1) 
dt2 <- data.table(df2) 

Здесь я выполняю фактические соединения и т. д. Я делал это 5 раз в цикле, чтобы показать случайность результатов.

for(i in c(1:5)){
  # Add row numbers
  dt3 <- dt2[, rn :=.I
             ][dt1,on = .(gender, year, code)
               ][, .SD[sample(.N)[1]], .(gender, year, code, rn)
                 ][, rn := NULL]

  # Check results
  print(dt3)
}
#>    gender year code amount status
#> 1:      M 2011    A    123    NOX
#> 2:      M 2011    A     15    EMX
#> 3:      F 2015    B      0    MIX
#> 4:      F 2018    A     12    NOX
#>    gender year code amount status
#> 1:      M 2011    A    123    NOX
#> 2:      M 2011    A    123    NOX
#> 3:      F 2015    B     11    NOX
#> 4:      F 2018    A     12    NOX
#>    gender year code amount status
#> 1:      M 2011    A    123    NOX
#> 2:      M 2011    A    123    NOX
#> 3:      F 2015    B     11    NOX
#> 4:      F 2018    A     12    NOX
#>    gender year code amount status
#> 1:      M 2011    A     15    EMX
#> 2:      M 2011    A     15    EMX
#> 3:      F 2015    B     11    NOX
#> 4:      F 2018    A     12    NOX
#>    gender year code amount status
#> 1:      M 2011    A    123    NOX
#> 2:      M 2011    A     15    EMX
#> 3:      F 2015    B      0    MIX
#> 4:      F 2018    A     12    NOX

Created on 2019-06-12 by the reprex package (v0.3.0)

Что я на самом деле делаю, так это добавляю номера строк в таблицу данных, что поможет мне сократить окончательную таблицу данных. Я соединяю таблицы данных, а затем группирую все строки, полученные из одной строки, в dt2 и выбираю одну случайным образом, используя sample. (Этот фрагмент кода заимствован у @akrun здесь.) Наконец, я удаляю столбец с номером строки.

Большое спасибо, это работает именно так, как я хотел. Я принял более поздний ответ, так как он работает быстрее - я думаю, это потому, что с моими фактическими данными промежуточная таблица после соединения становится огромной.

rw2 12.06.2019 14:27

Хороший! Небольшой комментарий по эффективности: sample(.N, 1) должен быть намного быстрее, чем sample(.N)[1], по крайней мере, если .N большой.

sindri_baldur 12.06.2019 14:58
df2 %>%
  mutate(
    amount = pmap_chr(
      .l = df2,
      .f = ~ df1 %>%
        filter(gender == ..1, year == ..2, code == ..3) %>%
        select(amount) %>%
        sample_n(1) %>%
        pull(amount)
    ),
    status = pmap_chr(
      .l = df2,
      .f = ~ df1 %>%
        filter(gender == ..1, year == ..2, code == ..3) %>%
        select(status) %>%
        sample_n(1) %>%
        pull(status)
    )
  )

Это довольно медленно, и я лично избегал бы этого, но это способ сделать это.

Я ожидаю, что это будет эффективно:

df1[, row := .I]
keys <- c("year", "gender", "code")
setkeyv(df1, keys)
setkeyv(df2, keys)

for (rowdf2 in seq_len(nrow(df2))) {
  set(df2, i = rowdf2, j = "rowindf1", value = df1[df2[rowdf2], x.row[sample(.N, 1)]])
}

setkeyv(df1, "row")
df1[df2[, .(rowindf1)]]

Пример вывода:

#    gender year code amount status row
# 1:      M 2011    A    123    NOX   2
# 2:      M 2011    A     15    EMX   1
# 3:      F 2015    B     11    NOX   5
# 4:      F 2018    A     12    NOX   4
Ответ принят как подходящий

Используйте «d2» для поиска строк в «d1» на основе совпадений в «поле», «годе», «коде» (d1[d2, on = .(gender, year, code), ...]). Для каждого совпадения (by = .EACHI) выберите одну строку (sample(.N, 1L)). Используйте это для индексации «суммы» и «статуса».

d1[d2, on = .(gender, year, code),
  {ri <- sample(.N, 1L)
  .(amount = amount[ri], status = status[ri])}, by = .EACHI]

# sample based on set.seed(1)
#    gender year code amount status
# 1:      M 2011    A     15    EMX
# 2:      M 2011    A     15    EMX
# 3:      F 2018    A     12    NOX
# 4:      F 2015    B     11    NOX

Обратите внимание, что существует открытый вопрос по Расширенная функциональность аргумента mult, то есть как обрабатывать случаи, когда несколько строк в x совпадают со строкой в ​​i. В настоящее время допустимы варианты "all" (по умолчанию), "first" или "last". Но если/когда проблема будет реализована, mult = "random" (sample(.N, size = 1L)) может использоваться для выбора случайной строки (строк) среди совпадений.

это потрясающе.

sindri_baldur 12.06.2019 14:55

Это здорово, и очень быстро - большое спасибо! Еще одна вещь, которую мне трудно понять... Если у меня также есть столбец person_ID в df2, который я хотел бы сохранить в результатах, как я могу изменить код, чтобы сохранить его?

rw2 12.06.2019 15:37

Хотя я не написал это явно, часть .( ) соответствует слоту j в терминах data.table. Затем взгляните на аргумент j в ?data.table: «Пока j возвращает список [а .() является сокращенным псевдонимом list()], каждый элемент списка становится столбцом в результирующем data.table». Поэтому, если вы хотите вернуть дополнительные столбцы из «d2», просто добавьте их внутрь .( ), например. добавьте столбец с именем «id»: .(id = id, amount = amount[ri], status = status[ri]).

Henrik 12.06.2019 15:47

В этом контексте, возможно, также стоит ознакомиться с префиксом i. и префиксом x. — поищите их в тексте справки. Ваше здоровье.

Henrik 12.06.2019 16:25

@ rw2 Просто чтобы вы знали: я добавил указатель на соответствующую открытую проблему data.table.

Henrik 12.06.2019 17:25

Вы можете перепутать строки df1 и взять первое совпадение: df2[, c("amount", "status") := df1[sample(.N)][.SD, on=.(gender, year, code), mult = "first", .(x.amount, x.status)]] Я думаю, это должно быть более эффективно, чем что-то с by=

Frank 12.06.2019 18:48

Кто-нибудь знает, почему я получаю эту ошибку при попытке выполнить код выше? Ошибка в [.data.frame(d1, d2, on = .(пол, год, код), { : неиспользуемые аргументы (on = .(пол, год, код), by = .EACHI)

C. Toni 27.07.2020 13:28

@C.Toni 'd1' должен быть data.table. Начните с setDT(d1).

Henrik 27.07.2020 13:34

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