在给定日期内排名最近的学生分数 - 30天窗口

时间:2018-04-02 22:27:00

标签: r dplyr data.table rank

以下是我的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的框架,但在使用排名之前,我正在努力想出一种方法来获取所有学生在给定时间点的所有最近得分。

6 个答案:

答案 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。因此,它会查找ix的每一行,并找到匹配项,将值从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了解ijbyon.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)]

答案 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
  • 我们添加了DateName
  • 的所有缺失组合
  • 然后我们为last_score_date创建一个列,当得分不是NA时,等于Date
  • 通过填写NAs得分已成为最新得分
  • 我们过滤掉了NA并且仅保留了具有&lt;的分数。 30天
  • 这是我们按日期列出的有效分数表
  • 从那里可以轻松添加排名
  • 并且原始表上的最终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")]