#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, спасибо за ваш ответ. У меня не было упомянутого вами пакета. Спасибо за вашу помощь.