Случайная выборка из столбца в фрейме данных с использованием фильтрации информации из другого фрейма данных в R

#r #matching

#r #сопоставление

Вопрос:

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

По сути, моя проблема заключается в следующем: у меня есть два набора данных (для простоты назовем их dataset A и dataset B), которые состоят из ряда общих столбцов, которые включают социально-демографические характеристики для каждого отдельного человека / наблюдения / строки. Что мне нужно, так это для каждого наблюдения / строки в наборе данных A, я должен выбрать случайное наблюдение из набора данных B, которое имеет соответствующие характеристики в отношении ключевых социально-демографических переменных. Для иллюстрации я подготовил простой пример:

 library("dplyr")

A = data.frame(nuts2 = c(1, 1, 2, 2, 3, 3), gender = c(1, 2, 1, 2, 1, 2))
B = data.frame(nuts2 = c(rep(1,10), rep(2,10), rep(3,10)), gender=c(rep(1, 5), rep(2, 5), rep(1, 5), rep(2, 5), rep(1, 5), rep(2, 5)))

A <- A[sample(1:nrow(A)), ] %>% mutate(id = seq(1:nrow(A)))
B <- B[sample(1:nrow(B)), ] %>% mutate(id = seq(1:nrow(B)))
  

Я пытаюсь избегать forloops, потому что, похоже, это считается плохой практикой в R, поэтому я попытался создать функцию и использовать apply для ее запуска при каждом наблюдении. Предполагая, что мы хотим сопоставить случайный идентификатор наблюдения из B с теми же значениями gender и nuts2, что и наблюдение в A, мой код был следующим:

 matching_fun <- function(x) {
  donor <- B %>% filter(gender == A$gender amp; nuts2 == A$nuts2) %>% sample_n(1) 
  donor_id <- donor$id
  return(donor_id)
}

A$donor_id <- apply(A, 1, matching_fun)
  

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

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

Заранее благодарим вас за любую поддержку / комментарии / критику.

ПРИМЕЧАНИЕ: в моих наборах данных около двух миллионов наблюдений в каждом, и мне придется использовать это в нескольких тестах. Следовательно, вычислительная эффективность имеет определенную степень важности.

Ответ №1:

Вот data.table подход

 setDT(A); setDT(B)
A[B, on = .(nuts2, gender)][, .(id = i.id[[sample(.N, 1L)]]), by = .(nuts2, gender)]
  

Вывод

 > set.seed(1L)
> A[B, on = .(nuts2, gender)][, .(id = i.id[[sample(.N, 1L)]]), by = .(nuts2, gender)]
   nuts2 gender id
1:     1      2  1
2:     2      2 16
3:     2      1  4
4:     1      1  6
5:     3      2 25
6:     3      1 22
> A[B, on = .(nuts2, gender)][, .(id = i.id[[sample(.N, 1L)]]), by = .(nuts2, gender)]
   nuts2 gender id
1:     1      2  8
2:     2      2 10
3:     2      1 24
4:     1      1  5
5:     3      2 25
6:     3      1 29
> A[B, on = .(nuts2, gender)][, .(id = i.id[[sample(.N, 1L)]]), by = .(nuts2, gender)]
   nuts2 gender id
1:     1      2  8
2:     2      2  3
3:     2      1  4
4:     1      1 18
5:     3      2 25
6:     3      1 13
  

Обновить

Я переформатировал код, чтобы помочь вам понять его логику.

 library(data.table)
setDT(A); setDT(B)
A[
  B, 
  on = .(nuts2, gender)
][
  , .(id = id[[1L]], donor_id = i.id[[sample(.N, 1L)]]), 
  by = .(nuts2, gender)
]
  

Результат выглядит следующим образом

    nuts2 gender id donor_id
1:     2      1  1       15
2:     1      2  2        2
3:     3      2  5        8
4:     2      2  3        6
5:     1      1  4        9
6:     3      1  6       22
  

Приведенный выше код по сути работает так же, как этот dplyr конвейер ниже, только более эффективным способом.

 left_join(A, B, by = c("nuts2", "gender")) %>% rename(id = id.x, donor_id = id.y) %>% group_by(nuts2, gender) %>% slice_sample(n = 1L)
  

Если у вас все еще возникают проблемы с пониманием этого, см. Иллюстрацию ниже:

 Let A and B be the dataframes as follows:
       A                   B
nuts2 gender id     nuts2 gender  id
    1      1  1         1      1   2
    2      1  3         1      1   7
                        2      1  13
                        2      1   4
                        2      1   6
What you want to do is:

First, match these groups
       A                   B
nuts2 gender id     nuts2 gender  id
    1      1  1         1      1   2
                        1      1   7
---------------     ----------------
    2      1  3         2      1  13
                        2      1   4
                        2      1   6

Second, slice a sample for each group in B
       A                   B
nuts2 gender id     nuts2 gender  id
    1      1  1         1      1   2
                        1      1   7 <--- RNG gives you this
---------------     ----------------
    2      1  3         2      1  13
                        2      1   4 <--- RNG gives you this
                        2      1   6 

Then, create a new variable in A with the id in B and call it donor_id
       A                            B
nuts2 gender id donor_id     nuts2 gender  id
    1      1  1        7         1      1   2
                                 1      1   7 <--- RNG gives you this
------------------------     ----------------
    2      1  3        4         2      1  13
                                 2      1   4 <--- RNG gives you this
                                 2      1   6 

However, an equivalent but more efficient way is 

First, join A and B on/by nuts2 and gender. In this way we can determine the population that we want to sample from.
       A                     B
 ---------------         ----------------                         ---------------------- 
|nuts2 gender id|       |nuts2 gender  id|                       |nuts2 gender id.A id.B|
|    1      1  1|       |    1      1   2|   by (nuts2, gender)  |    1      1    1    2|
|    2      1  3|       |    1      1   7|        =======>       |    1      1    1    7|
 ---------------        |    2      1  13|                       |    2      1    3   13|
                        |    2      1   4|                       |    2      1    3    4|
                        |    2      1   6|                       |    2      1    3    6|
                         ----------------                         ---------------------- 

Then, just slice a sample row within each (nuts2, gender) group and rename id.A and id.B as id and donor_id, respectively.
        A   B
               id donor_id
nuts2 gender XXXX     XXXX
    1      1    1        2
    1      1    1        7 <--- RNG gives you this
--------------------------
    2      1    3       13
    2      1    3        4 <--- RNG gives you this
    2      1    3        6
  

Это то, что делает мой код.

Эта часть означает объединение A и B в каждой соответствующей группе nuts2 и gender .

 A[
  B, 
  on = .(nuts2, gender)
]
  

Затем мы вырезаем строку выборки из .N строк в каждой группе nuts2 и gender . .N это зарезервированное слово в data.table пакете; оно дает вам количество строк в каждой группе. Существует i.id , потому что и A, и B имеют столбец id , и data.table автоматическое переименование B id i.id после объединения. Кроме того, нам нужно только id[[1L]] потому id , что s одинаковы в каждой группе.

 A[
  B, 
  on = .(nuts2, gender)
][
  , .(id = id[[1L]], donor_id = i.id[[sample(.N, 1L)]]), 
  by = .(nuts2, gender)
]
  

Комментарии:

1. Привет. Спасибо за ваш ответ. Я пробовал этот код и его, но я не думаю, что полностью понимаю, что происходит. Я отредактировал свой вопрос, чтобы было более ясно, что я ищу. Например, для человека в A с nuts2 == 1 и gender == 1 мне нужен один случайный донор из B с nuts2 == 1 и gender == 1. По сути, то, что я хотел бы получить в итоге, — это просто дополнительный столбец в вызываемом don_id с идентификатороминформация из этой случайной выборки зависит от социально-демографических переменных.

2. Привет @ekoam, большое спасибо за подробное объяснение. Это сделало ситуацию более понятной для меня, и она работает отлично. Ты лучший!

Ответ №2:

Если вы хотите избежать forloops, возможно, вам подойдут циклы if / else:

 library("dplyr")

#set.seed(1)

A = data.frame(nuts2 = c(1, 1, 2, 2, 3, 3), gender = c(1, 2, 1, 2, 1, 2))
B = data.frame(nuts2 = c(rep(1,10), rep(2,10), rep(3,10)), gender=c(rep(1, 5), rep(2, 5), rep(1, 5), rep(2, 5), rep(1, 5), rep(2, 5)))

A <- A[sample(1:nrow(A)), ] %>% mutate(id = seq(1:nrow(A)))
B <- B[sample(1:nrow(B)), ] %>% mutate(id = seq(1:nrow(B)))


  if (is.element(A$gender,B$gender) amp; is.element(A$nuts2, B$nuts2)){
  donor_id <- sample(B$id, 6)
  filter<-A[is.element(A$gender,B$gender) amp; is.element(A$nuts2, B$nuts2),]
  result<-cbind(filter, donor_id)[-3]
  print(result)
  }else{
  print("No matching characteristics")
}
  

Комментарии:

1. Привет. Сначала позвольте мне поблагодарить вас за ваш ответ. Я воспроизвел ваш код в своем примере, и совпадения не работают. Я отредактировал свой пост, чтобы стало более ясно, что я ищу. Например, для человека в A с nuts2 == 1 и gender == 1 мне нужен один случайный донор из B с nuts2 == 1 и gender == 1. По сути, то, что я хотел бы получить в итоге, — это просто дополнительный столбец в вызываемом don_id с идентификатороминформация из этой случайной выборки зависит от социально-демографических переменных.

2. странно … код работает в моей IDE … возможно, некоторые пакеты отсутствуют, например, для функции is.element (пакет «DistributionUtils»)!?

3. Привет @RogiKo35, спасибо за ваш ответ. У меня не было упомянутого вами пакета. Спасибо за вашу помощь.