选择每行的前N个项目

4

我在R中有一个数据框,记录了客户对多个不同品牌的排名偏好。数据框的示例如下表所示。实际表格在两个维度上都要大得多(大约80,000 x 30)。

我的表格:

+------+---------+---------+---------+---------+
| User | Brand_A | Brand_B | Brand_C | Brand_D |
+------+---------+---------+---------+---------+
| A    | 1       | NA      | 3       | 2       |
| B    | NA      | NA      | NA      | 1       |
| C    | 3       | 2       | 4       | 1       |
| D    | NA      | 1       | 2       | NA      |
+------+---------+---------+---------+---------+

其中1表示客户将品牌评为“最佳”,NA表示客户未对品牌进行排名。我想创建一个表格,为每个用户选择前3个(或前N个)排名最高的品牌,并输出类似于以下的表格:

+------+---------+---------+---------+
| User | Ranked1 | Ranked2 | Ranked3 |
+------+---------+---------+---------+
| A    | Brand_A | Brand_D | Brand_C |
| B    | Brand_D | NA      | NA      |
| C    | Brand_D | Brand_B | Brand_A |
| D    | Brand_B | Brand_C | NA      |
+------+---------+---------+---------+

假设每个客户的排名都是详尽的,即如果我只使用了一个品牌,则该品牌自动排名第一。
我已经尝试使用for循环来获得所需的输出,但没有成功。我认为我缺少的是一些非常简单的东西。
3个回答

2

一种选择是将数据融化再重塑。使用 data.table 实现这个选项的代码如下:

library(data.table)
dcast(setDT(melt(data, id.vars = "user"))[, rank := paste0("Ranked",value)][!is.na(value),], user ~ rank, value.var = "variable")

#  user Ranked1 Ranked2 Ranked3 Ranked4
#1    A Brand_A Brand_D Brand_C    <NA>
#2    B Brand_D    <NA>    <NA>    <NA>
#3    C Brand_D Brand_B Brand_A Brand_C
#4    D Brand_B Brand_C    <NA>    <NA>

谢谢@MikeH。我安装了data.table,但运行代码后,我收到一个警告消息,告诉我“缺少聚合函数,默认为'length'”。代码仍在运行,但我的输出有一些奇怪的输出列:Ranked1.5,Ranked2.5等。可能是冲突的软件包(我也安装了reshape2)?我不确定。 - user7474113
你尝试过使用 data.table::dcast 吗? - Mike H.
是的,我做了。但结果一样。 - user7474113
你的数据中是否可能存在多个具有相同排名的“品牌”?因为这将导致失败。 - Mike H.
你是对的 @Mike H。当我在数据的较小子集上尝试它时,代码是可以工作的。问题似乎出在我的排名算法(在这一步之前)如何处理平局上。看起来算法会平均平局,然后创建那些“0.5”列。感谢你的坚持! :) - user7474113

1
你可以使用apply来完成它:
df2=data.frame(User=df$User,t(apply(df,1,function(x) names(x)[-1][order(x[-1],na.last=NA)][1:3])))
colnames(df2)=c("User",paste0("Ranked",c(1:3)))

这句话的意思是:这将返回:
User Ranked1 Ranked2 Ranked3
1    A Brand_A Brand_D Brand_C
2    B Brand_D    <NA>    <NA>
3    C Brand_D Brand_B Brand_A
4    D Brand_B Brand_C    <NA>

@MikeH。很棒,我已经编辑了答案以包括你的建议。 - Lamia

1
使用 `tidyverse`...
df <- read.table(header = T, text = '
User Brand_A Brand_B Brand_C Brand_D
A 1 NA 3 2
B NA NA NA 1
C 3 2 4 1
D NA 1 2 NA
')

library(tidyverse)

df %>% 
  gather(brand, rank, -User, na.rm = T) %>% 
  filter(rank < 4) %>% 
  spread(rank, brand, sep = '')

生成...
  User   rank1   rank2   rank3
1    A Brand_A Brand_D Brand_C
2    B Brand_D    <NA>    <NA>
3    C Brand_D Brand_B Brand_A
4    D Brand_B Brand_C    <NA>

谢谢。这个实现似乎与dplyr可能实现的类似?但是,我不确定它是否回答了提出的问题,因为在“spread”之后,我仍然会得到一个具有“品牌”作为列标题而不是每个用户的“Ranked1”,“Ranked2”,“Ranked3”等的表格。如果我错了,请纠正我。 - user7474113
它同时使用了 dplyrtidyr,两者都在 tidyverse 中。我将输出添加到了答案中... 列的名称不完全相同,但结构与您要求的相同。 - CJ Yetman
谢谢。我明白你的意思。使用 dplyrtidyr 函数非常酷。 - user7474113

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接