给定日期内学生的最新分数排名 - 30 天 window
Rank most recent scores of students within a given date - 30 days window
以下是我的 dataframe/data.table 的样子。 rank
列是我的 desired 计算字段。
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 天内数据中每个给定时间点的排名 windows。为此,我需要获取所有学生在给定时间点的最新分数,然后传递排名函数。
在第 1 行,截至 1/1/2018
,John
在过去 30 天内有另外两个竞争对手 window:最近得分为 85
的 Rob 12/31/2017
AND Kate 在 12/25/2017
中的最近得分为 87
,并且这两个日期都在 1/1/2018 - 30
日 Window 之内。约翰获得 3
的排名,最低得分为 42
。如果只有一名学生落入 date(at a given row) - 30 day window
,则排名为 1。
第 3 行的日期是 12/26/2017
。所以 Rob 截至 12/26/2017
的分数是 89
。在 12/26/2017 - 30
天的时间 window 中,只有另一名学生的一个案例是 kate 在 12/25/2017
上的最近成绩(87
)。因此在 (12/26/2017) - 30
的 window 时间内,Rob 的 89
得分高于 Kate 的 87
得分,因此 Rob 获得排名 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
}
现在,您必须在 table 中找到给定日期和 return 排名的名称。
使用 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) 外部方括号在日期范围内进行 non-equi 连接(即每行的 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
就是对i
的每一行进行j
次计算。
3)在j
里面,iScore[1L]
是当前行的分数,.SD[Name != iName]
表示取与当前行学生不对应的分数。然后,我们将 max(Score)
用于 30 天内这些学生中的每个学生 window。
4) 连接所有这些分数并计算当前行分数的排名,同时通过第一个平局来处理平局。
注:
请参阅 ?data.table
了解 i
、j
、by
、on
和 .EACHI
指的是什么。
在 OP 发表评论后进行编辑:
我会添加一个 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)]
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
中晚 30 天内或同一日期且具有更高或相等分数的那些行加入 df
来完成。然后对于每个原始行和连接的行名称,获取最新的连接行名称。每个原始 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.
前缀表示它是 x[i, ...]
连接中 i
的列,赋值 :=
总是在 x
中。因此它在 x
中查找 i
的每一行以及找到匹配项的位置,将值从 i
复制到 x
。
另一种有时有用的方法是在 i
中查找 x
行,例如 df[, r := ranks[df, on=.(Name,Date), x.r]]
,在这种情况下 x.r
仍然来自 [=26] =] table(现在位于相对于连接的 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
我不确定两者的效率,但我想这取决于名称的数量、测量频率以及测量日期重合的频率。
一个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
。
- 通过向下填充 NAs 分数已成为 最新 分数
- 我们过滤掉 NA,只保留小于 30 天的分数
- 这是我们 table 的日期有效分数
- 从那里可以轻松添加排名
- 原始 table 的最终 right_join 给了我们预期的输出
数据
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")]
以下是我的 dataframe/data.table 的样子。 rank
列是我的 desired 计算字段。
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 天内数据中每个给定时间点的排名 windows。为此,我需要获取所有学生在给定时间点的最新分数,然后传递排名函数。
在第 1 行,截至 1/1/2018
,John
在过去 30 天内有另外两个竞争对手 window:最近得分为 85
的 Rob 12/31/2017
AND Kate 在 12/25/2017
中的最近得分为 87
,并且这两个日期都在 1/1/2018 - 30
日 Window 之内。约翰获得 3
的排名,最低得分为 42
。如果只有一名学生落入 date(at a given row) - 30 day window
,则排名为 1。
第 3 行的日期是 12/26/2017
。所以 Rob 截至 12/26/2017
的分数是 89
。在 12/26/2017 - 30
天的时间 window 中,只有另一名学生的一个案例是 kate 在 12/25/2017
上的最近成绩(87
)。因此在 (12/26/2017) - 30
的 window 时间内,Rob 的 89
得分高于 Kate 的 87
得分,因此 Rob 获得排名 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
}
现在,您必须在 table 中找到给定日期和 return 排名的名称。
使用 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) 外部方括号在日期范围内进行 non-equi 连接(即每行的 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
就是对i
的每一行进行j
次计算。
3)在j
里面,iScore[1L]
是当前行的分数,.SD[Name != iName]
表示取与当前行学生不对应的分数。然后,我们将 max(Score)
用于 30 天内这些学生中的每个学生 window。
4) 连接所有这些分数并计算当前行分数的排名,同时通过第一个平局来处理平局。
注:
请参阅 ?data.table
了解 i
、j
、by
、on
和 .EACHI
指的是什么。
在 OP 发表评论后进行编辑:
我会添加一个 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)]
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
中晚 30 天内或同一日期且具有更高或相等分数的那些行加入 df
来完成。然后对于每个原始行和连接的行名称,获取最新的连接行名称。每个原始 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.
前缀表示它是 x[i, ...]
连接中 i
的列,赋值 :=
总是在 x
中。因此它在 x
中查找 i
的每一行以及找到匹配项的位置,将值从 i
复制到 x
。
另一种有时有用的方法是在 i
中查找 x
行,例如 df[, r := ranks[df, on=.(Name,Date), x.r]]
,在这种情况下 x.r
仍然来自 [=26] =] table(现在位于相对于连接的 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
我不确定两者的效率,但我想这取决于名称的数量、测量频率以及测量日期重合的频率。
一个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
。 - 通过向下填充 NAs 分数已成为 最新 分数
- 我们过滤掉 NA,只保留小于 30 天的分数
- 这是我们 table 的日期有效分数
- 从那里可以轻松添加排名
- 原始 table 的最终 right_join 给了我们预期的输出
数据
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")]