Ниже показано, как выглядит мой dataframe / data.table. Столбец rank
- это мое вычисляемое поле желанный.
library(data.table)
df <- fread('
Name Score Date Rank
John 42 1/1/2018 3
Rob 85 12/31/2017 2
Rob 89 12/26/2017 1
Rob 57 12/24/2017 1
Rob 53 08/31/2017 1
Rob 72 05/31/2017 2
Kate 87 12/25/2017 1
Kate 73 05/15/2017 1
')
df[,Date:= as.Date(Date, format = "%m/%d/%Y")]
Я пытаюсь рассчитать рейтинг каждого студента в каждый данный момент времени в данных в течение 30-дневных окон. Для этого мне нужно получить самые последние оценки всех студентов в данный момент времени, а затем передать функцию ранжирования.
В 1-м ряду, начиная с 1/1/2018
, у John
есть еще два участника за последние 30 дней: Роб с самым последним результатом 85
в 12/31/2017
И Кейт с самым последним результатом 87
в 12/25/2017
, и обе эти даты попадают в пределы 1/1/2018 - 30
Дневное окно. Джон получает рейтинг 3
с самым низким показателем 42
. Если только один ученик попадает в категорию date(at a given row) - 30 day window
, то ему присваивается ранг 1.
В 3-м ряду дата - 12/26/2017
. Итак, оценка Роба на 12/26/2017
- 89
. Есть только один случай, когда другой ученик попадает во временное окно 12/26/2017 - 30
дней, и это самый последний результат (87
) Кейт на 12/25/2017
. Таким образом, в пределах временного окна (12/26/2017) - 30
оценка Роба 89
выше, чем оценка Кейт 87
, и, следовательно, Роб получает рейтинг 1
.
Я думал об использовании фреймворка отсюда Эффективный способ подсчета промежуточных результатов за последние 365 дней, но изо всех сил пытался придумать способ получить все последние оценки всех учеников в данный момент времени, прежде чем использовать ранг.
@Frank Привет, Фрэнк, я думал, что по состоянию на 31 декабря последний результат Роба составляет 85, что уступает 87 баллам Кейт на 25 декабря (что соответствует промежутку между 31 декабря и 30 днями).
Я придумал следующее частичное решение, но столкнулся с проблемой - возможно ли, что два человека будут встречаться с одной и той же датой?
если нет, взгляните на следующий фрагмент кода:
library(tidyverse) # easy manipulation
library(lubridate) # time handling
# This function can be added to
get_top <- function(df, date_sel) {
temp <- df %>%
filter(Date > date_sel - months(1)) %>% # look one month in the past from given date
group_by(Name) %>% # and for each occuring name
summarise(max_score = max(Score)) %>% # find the maximal score
arrange(desc(max_score)) %>% # sort them
mutate(Rank = 1:n()) # and rank them
temp
}
Теперь вам нужно найти имя в таблице на заданную дату и вернуть его рейтинг.
Решение, использующее data.table
, хотя не уверен, что это наиболее эффективное использование:
df[.(iName=Name, iScore=Score, iDate=Date, StartDate=Date-30, EndDate=Date),
.(Rank=frank(-c(iScore[1L], .SD[Name != iName, max(Score), by=.(Name)]$V1),
ties.method = "first")[1L]),
by=.EACHI,
on=.(Date >= StartDate, Date <= EndDate)]
1) Внешние квадратные скобки выполняют неэквивалентное соединение в пределах диапазона дат (т.е. 30 дней назад и самая последняя дата для каждой строки). Попробуйте сравнить вывод ниже с входными данными:
df[.(iName=Name, iScore=Score, iDate=Date, StartDate=Date-30, EndDate=Date),
c(.(RowGroup=.GRP),
.SD[, .(Name, Score, Date, OrigDate, iName, iScore, iDate, StartDate, EndDate)]),
by=.EACHI,
on=.(Date >= StartDate, Date <= EndDate)]
2) .EACHI
должен выполнять вычисления j
для каждой строки i
.
3) Внутри j
iScore[1L]
- это оценка для текущей строки, .SD[Name != iName]
означает получение оценок, не соответствующих ученику в текущей строке. Затем мы используем max(Score)
для каждого студента из этих студентов в течение 30 дней.
4) Соедините все эти оценки и вычислите рейтинг для оценки текущей строки, принимая во внимание ничьи, беря первую ничью.
см. ?data.table
, чтобы понять, что означает i
, j
, by
, on
и .EACHI
.
Я бы добавил столбец OrigDate и нашел бы те, которые соответствуют последней дате.
df[, OrigDate := Date]
df[.(iName=Name, iScore=Score, iDate=Date, StartDate=Date-30, EndDate=Date),
.(Name=iName, Score=iScore, Date=iDate,
Rank=frank(-c(iScore[1L],
.SD[Name != iName, Score[OrigDate==max(OrigDate)], by=.(Name)]$V1),
ties.method = "first")[1L]),
by=.EACHI,
on=.(Date >= StartDate, Date <= EndDate)]
Спасибо за полезное объяснение. Вместо того, чтобы использовать max (Score) для каждого ученика в течение 30-дневного окна, моя цель - использовать самый последний результат этих учеников. Таким образом, по состоянию на 01.01.2018, 42 балла Джона сравниваются с 85 баллом Роба, который является самым последним (31 декабря) по состоянию на 1 января 2018 года, а не максимальным (баллом) 89, который не является последним, как это произошло 12 декабря 2018 г. 26. 42 балла Джона также сравниваются с 87 баллами Кейт, что является наивысшим баллом, а также самым последним баллом Кейт (12/25) по сравнению с 1 января 2018 года. Какую правку вы рекомендуете?
library(data.table)
library(magrittr)
setorder(df, -Date)
fun <- function(i){
df[i:nrow(df), head(.SD, 1), by = Name] %$%
rank(-Score[Date > df$Date[i] - 30])[1]
}
df[, rank := sapply(1:.N, fun)]
Это можно сделать, присоединив к df
те ряды df
, которые отстают в пределах 30 дней от него или на ту же дату и имеют более высокие или равные оценки. Затем для каждой исходной строки и имени объединенной строки получите имя объединенной строки, которое является самым последним. Количество оставшихся соединенных строк для каждой из исходных строк df
является рангом.
library(sqldf)
sqldf("with X as
(select a.rowid r, a.*, max(b.Date) Date
from df a join df b
on b.Date between a.Date - 30 and a.Date and b.Score >= a.Score
group by a.rowid, b.Name)
select Name, Date, Score, count(*) Rank
from X
group by r
order by r")
давая:
Name Date Score Rank
1 John 2018-01-01 42 3
2 Rob 2017-12-31 85 2
3 Rob 2017-12-26 89 1
4 Rob 2017-12-24 57 1
5 Rob 2017-08-31 53 1
6 Rob 2017-05-31 72 2
7 Kate 2017-12-25 87 1
8 Kate 2017-05-15 73 1
Кажется, это работает:
ranks = df[.(d_dn = Date - 30L, d_up = Date), on=.(Date >= d_dn, Date <= d_up), allow.cart=TRUE][,
.(LatestScore = last(Score)), by=.(Date = Date.1, Name)]
setorder(ranks, Date, -LatestScore)
ranks[, r := rowid(Date)]
df[ranks, on=.(Name, Date), r := i.r]
Name Score Date Rank r
1: John 42 2018-01-01 3 3
2: Rob 85 2017-12-31 2 2
3: Rob 89 2017-12-26 1 1
4: Rob 57 2017-12-24 1 1
5: Rob 53 2017-08-31 1 1
6: Rob 72 2017-05-31 2 2
7: Kate 87 2017-12-25 1 1
8: Kate 73 2017-05-15 1 1
... используя last
, поскольку декартово соединение, кажется, сортирует, и нам нужно последнее измерение.
Как работает присоединение к обновлению
Префикс i.
означает, что это столбец из i
в соединении x[i, ...]
, а назначение :=
всегда находится в x
. Таким образом, он ищет каждую строку i
в x
и там, где обнаруживаются совпадения, копируя значения из i
в x
.
Другой способ, который иногда бывает полезен, - это поиск строк x
в i
, что-то вроде df[, r := ranks[df, on=.(Name,Date), x.r]]
, и в этом случае x.r
все еще находится в таблице ranks
(теперь в позиции x
относительно соединения).
Есть также...
ranks = df[CJ(Name = Name, Date = Date, unique=TRUE), on=.(Name, Date), roll=30, nomatch=0]
setnames(ranks, "Score", "LatestScore")
# and then use the same last three lines above
Я не уверен в эффективности одного по сравнению с другим, но думаю, это зависит от количества Имен, частоты измерений и того, как часто совпадают дни измерений.
Собирался опубликовать зверя вроде группировки df[, rank := df[.(iName = Name, iDate1 = Date - 30, iDate2 = Date), on = .(Date >= iDate1, Date <= iDate2), by = .EACHI, .SD[order(x.Date), .SD[.N], by = Name][, frank(-Score)[Name == iName]]] $V1][]
по .EACHI
, но ваш гораздо более лаконичен.
r: = i.r, кажется, волшебным образом очищает и сортирует соединение между df и ranks on =. (Name, Date). Что такое i.r?
Решение tidyverse
(dplyr
+ tidyr
):
df %>%
complete(Name,Date) %>%
group_by(Name) %>%
mutate(last_score_date = `is.na<-`(Date,is.na(Score))) %>%
fill(Score,last_score_date) %>%
filter(!is.na(Score) & Date-last_score_date <30) %>%
group_by(Date) %>%
mutate(Rank = rank(-Score)) %>%
right_join(df)
# # A tibble: 8 x 5
# # Groups: Date [?]
# Name Date Score last_score_date Rank
# <chr> <date> <int> <date> <dbl>
# 1 John 2018-01-01 42 2018-01-01 3
# 2 Rob 2017-12-31 85 2017-12-31 2
# 3 Rob 2017-12-26 89 2017-12-26 1
# 4 Rob 2017-12-24 57 2017-12-24 1
# 5 Rob 2017-08-31 53 2017-08-31 1
# 6 Rob 2017-05-31 72 2017-05-31 2
# 7 Kate 2017-12-25 87 2017-12-25 1
# 8 Kate 2017-05-15 73 2017-05-15 1
Date
и Name
last_score_date
, равный Date
, когда оценка не является NA.данные
library(data.table)
df <- fread('
Name Score Date
John 42 01/01/2018
Rob 85 12/31/2017
Rob 89 12/26/2017
Rob 57 12/24/2017
Rob 53 08/31/2017
Rob 72 05/31/2017
Kate 87 12/25/2017
Kate 73 05/15/2017
')
df[,Date:= as.Date(Date, format = "%m/%d/%Y")]
Оценка Роба по состоянию на 31 декабря также должна быть 89, что дает ему 1-е место во 2-м ряду, верно?