Вопрос или проблема
Предположим, есть набор данных с дублированными PersonID и несколькими датами.
PersonID | Дата |
---|---|
1 | 2024-01-01 |
1 | 2024-01-02 |
1 | 2024-01-09 |
1 | 2024-01-15 |
2 | 2024-08-05 |
2 | 2024-08-06 |
3 | 2024-01-07 |
3 | 2024-01-08 |
3 | 2024-01-15 |
Я хочу оставить только первую запись для каждого PersonID и отфильтровать любые записи, которые находятся в пределах 3 дней. Затем для следующей записи, которая сохраняется, удалить все записи, которые находятся в пределах 3 дней после этой записи. И повторять это столько раз, сколько нужно.
Итак, в этом примере останутся только следующие записи:
PersonID | Дата |
---|---|
1 | 2024-01-01 |
1 | 2024-01-09 |
1 | 2024-01-15 |
2 | 2024-08-05 |
3 | 2024-01-07 |
3 | 2024-01-15 |
Есть ли эффективный способ сделать это? Или мне нужно создать столбец поэтапно?
Такую операцию можно выполнить с помощью Reduce
. Например:
day_lag_fitler <- function(x, cutoff = 3) {
Reduce(function(acc, current) {
if (difftime(current, acc$ref, unit="days") < cutoff) {
list(keep=FALSE, ref=acc$ref)
} else {
list(keep=TRUE, ref=current)
}
}, x[-1], init=list(keep=TRUE, ref=x[1]), accumulate = TRUE) |> sapply('[[', "keep")
}
Мы в основном просто храним список ссылочной даты и того, нужно ли сохранять значение, а в конце просто удаляем часть, которая хранится.
А затем, используя dplyr
, вы можете применить это к каждой группе людей:
dd |>
filter(day_lag_fitler(Date), .by=PersonID)
# PersonID Date
# 1 1 2024-01-01
# 2 1 2024-01-09
# 3 1 2024-01-15
# 4 2 2024-01-16
# 5 2 2024-09-01
# 6 3 2024-01-07
# 7 3 2024-01-15
Проверено с:
dd<- structure(list(PersonID = c(1L, 1L, 1L, 1L, 2L, 2L, 3L, 3L, 3L
), Date = structure(list(sec = c(0, 0, 0, 0, 0, 0, 0, 0, 0),
min = c(0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L), hour = c(0L,
0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L), mday = c(1L, 2L, 9L, 15L,
16L, 1L, 7L, 8L, 15L), mon = c(0L, 0L, 0L, 0L, 0L, 8L, 0L,
0L, 0L), year = c(124L, 124L, 124L, 124L, 124L, 124L, 124L,
124L, 124L), wday = c(1L, 2L, 2L, 1L, 2L, 0L, 0L, 1L, 1L),
yday = c(0L, 1L, 8L, 14L, 15L, 244L, 6L, 7L, 14L), isdst = c(0L,
0L, 0L, 0L, 0L, 1L, 0L, 0L, 0L), zone = c("EST", "EST", "EST",
"EST", "EST", "EDT", "EST", "EST", "EST"), gmtoff = c(NA_integer_,
NA_integer_, NA_integer_, NA_integer_, NA_integer_, NA_integer_,
NA_integer_, NA_integer_, NA_integer_)), class = c("POSIXlt",
"POSIXt"), tzone = c("", "EST", "EDT"), balanced = TRUE)), row.names = c(NA,
-9L), class = "data.frame")
Если вы используете tidyverse, вы можете сделать так:
library(tidyverse)
dat %>%
filter(accumulate(Date, ~if(as.numeric(.y - .x, 'days') > 3) .y else .x) == Date,
.by = PersonID)
PersonID Date
1 1 2024-01-01
2 1 2024-01-09
3 1 2024-01-15
4 2 2024-01-16
5 2 2024-09-01
6 3 2024-01-07
7 3 2024-01-15
Ответ или решение
Для эффективной фильтрации набора данных по временным меткам в пределах заданного интервала (в данном случае в три дня) можно использовать пакет dplyr
, который является частью экосистемы tidyverse
в языке программирования R. Это поможет вам избавиться от дублирующихся записей для каждого PersonID
и оставить только те записи, которые соответствуют вашим критериям.
Шаги для решения задачи:
-
Импортируйте необходимые библиотеки.
Убедитесь, что у вас установлены и загружены необходимые пакеты.library(dplyr) library(lubridate)
-
Подготовьте ваши данные.
Создайте набор данных, с которым вы будете работать. Ваши данные могут выглядеть следующим образом:data <- data.frame( PersonID = c(1, 1, 1, 1, 2, 2, 3, 3, 3), Date = as.Date(c('2024-01-01', '2024-01-02', '2024-01-09', '2024-01-15', '2024-08-05', '2024-08-06', '2024-01-07', '2024-01-08', '2024-01-15')) )
-
Определите фильтрацию по временным интервалам.
Для того чтобы произвести фильтрацию, можно использовать функциюaccumulate
из пакетаpurrr
, чтобы отслеживать предыдущие даты и применять фильтр. Вот пример кода:filtered_data <- data %>% arrange(PersonID, Date) %>% group_by(PersonID) %>% filter(accumulate(Date, ~ ifelse(as.numeric(.y - .x, units = "days") > 3, .y, .x)) == Date) %>% ungroup()
Таким образом, вы сможете оставить только первую запись для каждого PersonID
, фильтруя все последующие записи, которые находятся в пределах 3-х дней от предыдущей оставленной даты.
-
Проверьте результаты.
После применения фильтра вы можете просмотреть отфильтрованный набор данных, который должен выглядеть следующим образом:print(filtered_data)
Объяснение кода:
arrange(PersonID, Date)
: Сортирует данные сначала поPersonID
, затем по дате.group_by(PersonID)
: Объединяет данные по каждомуPersonID
, чтобы применять операции на уровне каждой группы.filter(accumulate(...))
: Применяет условие, чтобы проверить, превышает ли разница между текущими и предыдущими датами 3 дня, и оставляет только те значения, которые удовлетворяют этому условию.ungroup()
: Убирает группировку после фильтрации.
Заключение
Используя предложенное решение, вы сможете динамично фильтровать ваши данные по времени, оставляя только нужные записи. Это гарантирует, что вы получите оптимальный результат без создания промежуточных колонок, сохраняя при этом элегантную структуру кода. Пользуясь возможностями пакетов dplyr
и purrr
, вы сможете легко масштабировать это решение для работы с большими наборами данных.
Если у вас возникли вопросы или требуется дополнительная информация, не стесняйтесь задавать их!