【问题标题】:Choosing different amount of elements from each group in R从 R 中的每个组中选择不同数量的元素
【发布时间】:2017-12-31 13:06:14
【问题描述】:

我正在参加 Kaggle Instacart 比赛,但我对 R 很陌生,遇到了一些我无法弄清楚的事情。

我有一个包含 4 列的数据集。第一列是订单 ID (id1)。第二列是产品 ID (id2)。第三列是我想从订单 id1 中选择产品 id2 的概率,我们可以将其视为排名,因此总是选择较高的概率而不是较小的概率。最后,第四列是我想从给定订单中选择的产品数量(订单的一个特征)。例如,我这里有数据框 df 的前 12 行:

        id1        id2       prob       num
1        17      13107   0.4756982        3
2        17      21463   0.3724126        3
3        17      38777   0.3534422        3
4        17      21709   0.3364623        3
5        17      47766   0.3364623        3
6        17      39275   0.3165896        3
7        34      16083   0.4093785        4
8        34      39475   0.3892882        4
9        34      47766   0.3892882        4
10       34       2596   0.3837562        4
11       34      21137   0.3762758        4
12       34      47792   0.3737032        4

我们可以看到,从 id1 = 17 我想选择 3 个元素,而对于 id1 = 34 我想选择 4 个元素。结果应该是

ID1     ID2
 17     13107, 21463, 38777
 34     16083, 39475, 47766, 2596

或类似的东西。

目前我已经尝试使用

df %>% group_by(id1) %>% top_n(n = num)

但我得到了错误

Selecting by num
Error in is_scalar_integerish(n) : object 'num' not found

有人知道我会怎么做吗?

谢谢

【问题讨论】:

  • 您已经有了答案,但您知道,top_n 只接受标量作为输入。您还需要定义要在其中排名的变量。

标签: r dataframe


【解决方案1】:

在基础 R 中,您可以在按 ID 和 split 拆分的数据帧列表上使用 Map 来应用 head 来为每个 ID 选择相应的行数。选定的行数是通过将tapply 输入感兴趣的列并使用head 选择第一个值来提供的。使用do.callrbind 返回具有相应行的data.frame。

do.call(rbind, Map(head, split(dat, dat$id1), tapply(dat$num, dat$id1, head, 1)))
      id1   id2      prob num
17.1   17 13107 0.4756982   3
17.2   17 21463 0.3724126   3
17.3   17 38777 0.3534422   3
34.7   34 16083 0.4093785   4
34.8   34 39475 0.3892882   4
34.9   34 47766 0.3892882   4
34.10  34  2596 0.3837562   4

返回第一个 dat$num 元素的命名列表稍微简单一些,然后列表中的名称对应于 id1。

Map(head, split(dat$id2, dat$id1), tapply(dat$num, dat$id1, head, 1))
$`17`
[1] 13107 21463 38777

$`34`
[1] 16083 39475 47766  2596

数据

dat <-
structure(list(id1 = c(17L, 17L, 17L, 17L, 17L, 17L, 34L, 34L, 
34L, 34L, 34L, 34L), id2 = c(13107L, 21463L, 38777L, 21709L, 
47766L, 39275L, 16083L, 39475L, 47766L, 2596L, 21137L, 47792L
), prob = c(0.4756982, 0.3724126, 0.3534422, 0.3364623, 0.3364623, 
0.3165896, 0.4093785, 0.3892882, 0.3892882, 0.3837562, 0.3762758, 
0.3737032), num = c(3L, 3L, 3L, 3L, 3L, 3L, 4L, 4L, 4L, 4L, 4L, 
4L)), .Names = c("id1", "id2", "prob", "num"), class = "data.frame", row.names = c("1", 
"2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"))

【讨论】:

  • 感谢您的帮助,代码很好。由于我给定任务的输出格式更好,因此接受了 docendo discimus 建议,但您的解决方案总体上也同样有效!
【解决方案2】:

您可以将分组数据直接导入summarise 语句:

df %>% group_by(id1) %>% summarise(id2 = toString(id2[seq_len(first(num))]))
## A tibble: 2 x 2
#    id1                       id2
#  <int>                     <chr>
#1    17       13107, 21463, 38777
#2    34 16083, 39475, 47766, 2596

在此语句中,id2[seq_len(first(num))] 用于提取每个组的第一个 num,创建一个从 1 到 num 的序列,该序列用于对前 X 个 id2 值进行子集化。

toString 为每个 id1 组创建一个字符串。


这是另一个使用 aggregate 的基本 R 选项:

aggregate(id2 ~ id1, FUN=toString, subset(df, ave(id1, id1, FUN=seq_along) <= num))
#  id1                       id2
#1  17       13107, 21463, 38777
#2  34 16083, 39475, 47766, 2596

请注意,我假设数据已经通过递减概率排序(如示例中所示)。

【讨论】:

  • 你不是在你的回答中假设数据是根据概率排序的吗?
【解决方案3】:

你可以用@lmo的数据试试这个

dat%>%group_by(id1)%>%arrange(-prob)%>%dplyr::summarise(ID2=paste(id2[1:unique(num)],collapse=","))

【讨论】:

  • arrange(-prob) 添加到您的答案中。不要假设数据是有序的。
【解决方案4】:

data.table:

library(data.table)
setDT(df)[order(-prob), .(id2 = toString(head(id2, first(num)))), by = id1]
   id1                       id2
1:  17       13107, 21463, 38777
2:  34 16083, 39475, 47766, 2596

这里,df 被强制转换为 data.table,按概率递减排序。对于id1 中的每个组,num 最上面的值被挑选出来并聚合到一个字符串中。

这将返回 id2 作为字符。如果需要继续处理,将id2 值单独保存在一个列表中可能会很有用:

setDT(df)[order(-prob), .(id2 = list(head(id2, first(num)))), by = id1]

数据

df <- fread(
"rn        id1        id2       prob       num
1        17      13107   0.4756982        3
2        17      21463   0.3724126        3
3        17      38777   0.3534422        3
4        17      21709   0.3364623        3
5        17      47766   0.3364623        3
6        17      39275   0.3165896        3
7        34      16083   0.4093785        4
8        34      39475   0.3892882        4
9        34      47766   0.3892882        4
10       34       2596   0.3837562        4
11       34      21137   0.3762758        4
12       34      47792   0.3737032        4")

【讨论】:

    【解决方案5】:

    每个 ID 有一行可能看起来不错,但列表列通常最终难以处理;这不是“整洁”。这是一个简单的dplyr 管道,它坚持有意义的动词:按组分隔、过滤行、重新组合在一起。

    df %>%
      group_by(id1) %>%
      filter(seq_along(num) <= num) %>%
      ungroup() %>%
      select(id1, id2)
    
    # A tibble: 7 x 2
        id1   id2
      <int> <int>
    1    17 13107
    2    17 21463
    3    17 38777
    4    34 16083
    5    34 39475
    6    34 47766
    7    34  2596
    

    【讨论】:

    • @d.b 当 num 对一个组为零时,它仍然会返回一行。使用seq_len(num[[1]])
    • 这是一个运行良好的代码。就我而言,我想在之后立即编写一个 .csv 文件,但我认为在许多情况下,我猜这段代码会更好地工作。由于已经接受的答案给出了我要求的输出,因此我将其保留为已接受的答案,因为它看起来更公平,但我也非常喜欢这个解决方案
    • @d-b 我同意他的选择。另一个答案给了他他要求的输出,而我的给了他我认为会更好的输出。但由于他正在写入 CSV,因此请求的方式可能是正确的选择。我很高兴我的回答很有用。
    猜你喜欢
    • 2018-12-13
    • 2014-06-19
    • 1970-01-01
    • 2014-07-08
    • 1970-01-01
    • 2013-12-12
    • 2023-01-11
    • 1970-01-01
    • 2014-03-22
    相关资源
    最近更新 更多