在不丢弃其他行的情况下改变前 n 行

Mutate top n rows without throwing away the other rows

我下面有以下data.frame。我想创建一个新列 w(用于重量)。 w 对于每个给定日期具有最高 returns 的行业,w 应该等于 1 / n,而对于其余行业,w 应该等于 0。我可以 group_by(date) 并使用 top_n(3, wt = return) 筛选顶级行业,然后使用 mutate(w = 1/n),但我如何 mutate 不丢弃 w = 0 的其他行业?

structure(list(date = structure(c(16556, 16556, 16556, 16556, 
16556, 16556, 16556, 16556, 16556, 16556, 16587, 16587, 16587, 
16587, 16587, 16587, 16587, 16587, 16587, 16587, 16617, 16617, 
16617, 16617, 16617, 16617, 16617, 16617, 16617, 16617), class = "Date"), 
    industry = c("Hlth", "Txtls", "BusEq", "Fin", "ElcEq", "Food", 
    "Beer", "Books", "Cnstr", "Carry", "Clths", "Txtls", "Fin", 
    "Games", "Cnstr", "Meals", "Hlth", "Hshld", "Telcm", "Rtail", 
    "Smoke", "Games", "Clths", "Rtail", "Servs", "Meals", "Food", 
    "Hlth", "Beer", "Trans"), return = c(4.89, 4.37, 4.02, 2.99, 
    2.91, 2.03, 2, 1.95, 1.86, 1.75, 4.17, 4.09, 1.33, 1.26, 
    0.42, 0.29, 0.08, -0.11, -0.45, -0.48, 9.59, 6, 5.97, 5.78, 
    5.3, 4.15, 4.04, 3.67, 3.51, 3.27)), row.names = c(NA, -30L
), class = c("tbl_df", "tbl", "data.frame"))

# A tibble: 30 x 3
   date       industry return
   <date>     <chr>     <dbl>
 1 2015-05-01 Hlth       4.89
 2 2015-05-01 Txtls      4.37
 3 2015-05-01 BusEq      4.02
 4 2015-05-01 Fin        2.99
 5 2015-05-01 ElcEq      2.91
 6 2015-05-01 Food       2.03
 7 2015-05-01 Beer       2   
 8 2015-05-01 Books      1.95
 9 2015-05-01 Cnstr      1.86
10 2015-05-01 Carry      1.75
# ... with 20 more rows

编辑:你会如何处理平局?假设第三名并列。第三名的权重应该在第 3 名和第 4 名之间分配(假设只有 2 个并列),权重为 (1/n)/2。第一名和第二名的权重保持在1/n。

编辑:假设n = 3。每个A1的前3个A2值应该得到权重w 的 1/3 如果没有关系。如果第三名 (T3) 并列,那么我们有 (1st, 2nd, T3, T3) 并且我希望权重为 1/3、1/3、1/6、1/6 以保持总数权重为 1。但这仅适用于第三名。 (1st, T2, T2) 的权重应该是 1/3, 1/3, 1/3。 (T1, T1, T2, T2) 的权重应为 1/3、1/3、1/6、1/6 等

structure(list(A1 = structure(c(1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 
    2L), .Label = c("A", "B"), class = "factor"), A2 = c(1, 3, 3, 
    4, 5, 6, 7, 8, 8)), row.names = c(NA, -9L), class = "data.frame")

df 的输出应该是:

> df
  A1 A2  w
1  A  1  0 
2  A  3  0.1666
3  A  3  0.1666 
4  A  4  0.3333
5  A  5  0.3333
6  B  6  0
7  B  7  0.3333
8  B  8  0.3333
9  B  8  0.3333

我们可以用 ifelse 创建一个条件。根据 'date'、arrange 数据集按 'date' 和 'return' 降序分组后,然后通过创建条件创建 'w' 如果row_number() 小于 'n',则 'return' 除以 'n' 否则 return 0

n <- 3
df1 %>%
   group_by(date) %>%
   arrange(date, -return) %>% 
   mutate(w = ifelse(row_number() <= n, return/n, 0))

如果我们使用 top_n,则在过滤后的数据集中创建列 'w' 并与原始

连接
df1 %>% 
  group_by(date) %>% 
  top_n(return, n = 3) %>% 
  mutate(w = return/n()) %>% 
  right_join(df1)  %>% 
  mutate(w = replace_na(w, 0))

我们可以按 date 分组,然后 sort return 变量获得最后 3 个条目(前 3 个)和 return return/n 否则为 0 .

library(dplyr)
n <- 3
df %>%
  group_by(date) %>%
  mutate(w = ifelse(return %in% tail(sort(return), n), return/n, 0))


# date       industry return     w
#   <date>     <chr>     <dbl> <dbl>
# 1 2015-05-01 Hlth       4.89  1.63
# 2 2015-05-01 Txtls      4.37  1.46
# 3 2015-05-01 BusEq      4.02  1.34
# 4 2015-05-01 Fin        2.99  0   
# 5 2015-05-01 ElcEq      2.91  0   
# 6 2015-05-01 Food       2.03  0   
# 7 2015-05-01 Beer       2     0   
#....

使用 ave

等同于相同逻辑的基本 R
ave(df$return, df$date, FUN = function(x) ifelse(x %in% tail(sort(x), n), x/n, 0))

编辑

如评论中所述,如果出现平局,OP 想要 return (1/n)/2 或除以我们拥有的平局数。

为此,我创建了一个新的更简单的数据框,可以很容易地理解正在发生的事情。

df <- data.frame(A1 = rep(c("A", "B"),c(5, 4)), A2 = 1:9)
df$A2[2] <- 3

如果我们使用它给出的当前代码

df %>%
   group_by(A1) %>%
   mutate(w = ifelse(A2 %in% tail(sort(A2), n), A2/n, 0))

  # A tibble: 9 x 3
  # Groups:   A1 [2]
#  A1       A2     w
# <fct> <int> <dbl>
#1 A         1  0   
#2 A         3  1   
#3 A         3  1   
#4 A         4  1.33
#5 A         5  1.67
#6 B         6  0   
#7 B         7  2.33
#8 B         8  2.67
#9 B         9  3   

这不是我们想要的。为避免这种情况,我们可以再次按 A2 分组,并且仅针对 w!=0 的那些行,我们将其除以 A2.

的出现次数
df %>%
  group_by(A1) %>%
  mutate(w = ifelse(A2 %in% tail(sort(A2), n), A2/n, 0)) %>%
  group_by(A2) %>%
  mutate(w1 = ifelse(w != 0, w/n(), w)) %>%
  ungroup()

# A1       A2     w    w1
#  <fct> <dbl> <dbl> <dbl>
#1 A         1  0     0   
#2 A         3  1     0.5 
#3 A         3  1     0.5 
#4 A         4  1.33  1.33
#5 A         5  1.67  1.67
#6 B         6  0     0   
#7 B         7  2.33  2.33
#8 B         8  2.67  2.67
#9 B         9  3     3   

另一个编辑

原来我们只想为最后一个在场的小组分配 w。此外,每个组中所有 w 的总和应为 1。对于更新的数据集,我们可以做

n <- 3

temp_df <- df %>%
            group_by(A1) %>%
            top_n(n, A2)


 temp_df %>%
     arrange(A1, A2) %>%
     mutate(w = ifelse(A2 == A2[1], 
    (1 - (1/n * sum(A2 != A2[1])))/sum(A2 == A2[1]), 1/n)) %>%
     bind_rows(anti_join(df, temp_df) %>%
                      mutate(w = 0)
    ) %>%
     arrange(A1, A2)


# A1       A2     w
#  <fct> <dbl> <dbl>
#1 A         1 0    
#2 A         3 0.167
#3 A         3 0.167
#4 A         4 0.333
#5 A         5 0.333
#6 B         6 0    
#7 B         7 0.333
#8 B         8 0.333
#9 B         8 0.333

让我们尝试另一种变体,让组的所有值保持相同。

df1 = df
df1$A2[6:9] <- 10


 temp_df <- df1 %>%
             group_by(A1) %>%
             top_n(n, A2)


  temp_df %>%
       arrange(A1, A2) %>%
       mutate(w = ifelse(A2 == A2[1], 
      (1 - (1/n * sum(A2 != A2[1])))/sum(A2 == A2[1]), 1/n)) %>%
       bind_rows(anti_join(df1, temp_df) %>%
                      mutate(w = 0)
       ) %>%
       arrange(A1, A2)


#  A1       A2     w
#  <fct> <dbl> <dbl>
#1 A         1 0    
#2 A         3 0.167
#3 A         3 0.167
#4 A         4 0.333
#5 A         5 0.333
#6 B        10 0.25 
#7 B        10 0.25 
#8 B        10 0.25 
#9 B        10 0.25 

我们的逻辑是 select 前 3 个 A2 值及其组使用 top_n。使用 anti_join 我们得到所有不在前 3 名中的行,并为它们分配一个固定权重 w 作为 0。对于包含在前 3 名中的行,我们得到最后一组行并分配它们将权重分配给非最后一组后剩余的权重。