У меня есть эталонный фрейм данных (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 — это отдельная группа людей (каждая строка — это человек) — я хочу сгенерировать для них распределение «значений» на основе распределения характеристик/значений в df1. Все комбинации характеристик в df2 появятся где-то в df1.
Могут ли две строки в df2 соответствовать одной строке в df1?
Да, хотя это весьма маловероятно, это потенциально может произойти.
Моя 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 здесь.) Наконец, я удаляю столбец с номером строки.
Большое спасибо, это работает именно так, как я хотел. Я принял более поздний ответ, так как он работает быстрее - я думаю, это потому, что с моими фактическими данными промежуточная таблица после соединения становится огромной.
Хороший! Небольшой комментарий по эффективности: sample(.N, 1)
должен быть намного быстрее, чем sample(.N)[1]
, по крайней мере, если .N
большой.
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)
) может использоваться для выбора случайной строки (строк) среди совпадений.
это потрясающе.
Это здорово, и очень быстро - большое спасибо! Еще одна вещь, которую мне трудно понять... Если у меня также есть столбец person_ID в df2, который я хотел бы сохранить в результатах, как я могу изменить код, чтобы сохранить его?
Хотя я не написал это явно, часть .( )
соответствует слоту j
в терминах data.table
. Затем взгляните на аргумент j
в ?data.table
: «Пока j
возвращает список [а .()
является сокращенным псевдонимом list()
], каждый элемент списка становится столбцом в результирующем data.table
». Поэтому, если вы хотите вернуть дополнительные столбцы из «d2», просто добавьте их внутрь .( )
, например. добавьте столбец с именем «id»: .(id = id, amount = amount[ri], status = status[ri])
.
В этом контексте, возможно, также стоит ознакомиться с префиксом i.
и префиксом x.
— поищите их в тексте справки. Ваше здоровье.
@ rw2 Просто чтобы вы знали: я добавил указатель на соответствующую открытую проблему data.table
.
Вы можете перепутать строки df1 и взять первое совпадение: df2[, c("amount", "status") := df1[sample(.N)][.SD, on=.(gender, year, code), mult = "first", .(x.amount, x.status)]]
Я думаю, это должно быть более эффективно, чем что-то с by=
Кто-нибудь знает, почему я получаю эту ошибку при попытке выполнить код выше? Ошибка в [.data.frame
(d1, d2, on = .(пол, год, код), { : неиспользуемые аргументы (on = .(пол, год, код), by = .EACHI)
@C.Toni 'd1' должен быть data.table
. Начните с setDT(d1)
.
Являются ли столбцы «характеристик» обоих фреймов данных одинаковыми? Или есть резон работать с df2?