以下是我的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/2018
的第1行中,John
在过去30天的窗口中还有两个竞争对手:85
中最新得分为12/31/2017
的Rob和Kate在87
中的最新得分为12/25/2017
,这两个日期都属于1/1/2018 - 30
日窗口。 John的得分为3
,得分最低为42
。如果只有一名学生在date(at a given row) - 30 day window
之内,则排名为1.
在第3行中,日期为12/26/2017
。因此,Rob 12/26/2017
的分数为89
。只有一个案例的另一名学生属于12/26/2017 - 30
天的时间窗口,这是87
上kate的最新得分(12/25/2017
)。因此,在(12/26/2017) - 30
的时间窗口内,Rob的得分89
高于Kate得分87
,因此Rob获得等级1
。
我正在考虑使用此处Efficient way to perform running total in the last 365 day window的框架,但在使用排名之前,我正在努力想出一种方法来获取所有学生在给定时间点的所有最近得分。
答案 0 :(得分:5)
这似乎有效:
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
我不确定相对于另一个的效率,但我想这取决于名称的数量,测量的频率以及测量天数重合的频率。
答案 1 :(得分:2)
使用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]
表示取得与当前行中学生不对应的得分。然后,我们在30天窗口内为这些学生的每个学生使用max(Score)
。
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)]
答案 2 :(得分:1)
我想出了以下部分解决方案,遇到了问题 - 是否可能会有两个人在同一个日期发生?
如果没有,请看下面的代码:
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
}
现在,你必须在表格中找到给定日期的名称并返回其排名。
答案 3 :(得分:1)
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)]
答案 4 :(得分:1)
这可以通过加入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
答案 5 :(得分:1)
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
创建一个列,当得分不是NA时,等于Date
。数据强>
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")]