如何返回按列顺序分配给特定行的值?

时间:2019-05-13 21:32:38

标签: r data.table

我有一个看起来像这样的数据框,并延伸了数百万行:

          id       class weight
1:   3930271          77    1.0
2:   3930272          55    0.5
3:   3930272         654    0.5
4:   3930273          66    0.5
5:   3930273          66    0.5
6:   3930274         225    1.0
7:   3930275          66   0.05
7:   3930275          44   0.05
...
...
34'000'000: 

那是因为每个类在同一个ID中出现多次。权重栏衡量专利的每个类别的分数值(拥有2个类别的专利意味着每个类别贡献0.5)。 现在,我想通过在同一行中简单地包含一个patent_id和多个类(从1到最大20)来减少行数。我想要的是这样的:

          id      class1 class2  ... class20  weight
1:   3930271          77      0            0       1
2:   3930272          55    654            0     0.5
3:   3930273          65     66            0     0.5
4:   3930274         225      0            0       1
5:   3930275          66     44           30    0.05
6:   3930276         225     33            0     0.5

某些id不会有20个类,因此在这种情况下,它应该返回0或点。当类具有超过20个值时,没有选择标准的条件,因为与数百万个id相比,它仅发生几次。 一些patent_id将具有20多个类,但是我想排除那些(很少观察到)。 你会怎么做? 我尝试了tidyr的功能传播,但报告了此错误消息

Error: Each row of output must be identified by a unique combination of keys.

那是因为有时class的值会重复出现,但是我需要保持原样。

3 个答案:

答案 0 :(得分:1)

可能有一种更清洁的方式来完成此任务。阅读tidyr :: spread()和collect()以及data.table :: dcast(),melt()和cast()。

使用您提供的示例数据:

sample_data <- data.frame("id" = c(3930271, 3930272, 3930272,
                                   3930273, 3930273, 3930274, 
                                   3930275, 3930275),
                          "class" = c(77, 55, 654, 
                                      66, 66, 225, 
                                      66, 44),
                          "weight" = c(1, 0.5, 0.5, 0.5, 
                                       0.5, 1, 0.05, 0.05))

使用dplyr的summary函数汇总每个id的类值。这将进行过滤,以排除具有超过20个唯一类值的所有ID。

 library(dplyr) #imports the group_by, summarize, filter, select, and bind_cols functions
 library(magrittr) #imports the %>% pipe function
 library(tidyr) #imports the separate function

wide_df_new <- sample_data %>% 
   group_by(id) %>% 
   summarize(class_list = list(class),
             n = n(),
             weight = 1/n) %>%
   filter(n <= 20) %>%
   select(-n)

创建新列名的向量:

 new_col_names <- paste0("class", 1:20)

每个id在数据框sample_data$class_list的单个列中都有其类的列表(列表中的列表)。将此单个列表拆分为新列。

wide_df_new <- separate(wide_df_new , col = class_list, 
                        into = new_col_names, 
                        remove = TRUE,
                        sep = ", ")

从列中删除字符向量工件。这将从所有列中删除所有非数字字符!转换回数字并绑定列以返回到数据框数据结构。

wide_df_new <- lapply(wide_df_new  , function(x) gsub("[^0-9\\.]", "", x)) %>%
lapply(as.numeric) %>% 
bind_cols()

最后但并非最不重要的一点是,用0填充所有NA值。

wide_df_new[is.na(wide_df_new)] <- 0

就像我最初说的那样,可能有一种更清洁的方式来做到这一点。

答案 1 :(得分:0)

这是一个data.table答案,应该可以为您提供所需的输出。诀窍是使用.N中的特殊符号data.table获得编号,这将以动态方式为类创建所需的序列。这是示例数据,请注意我将其转换为data.table

library(data.table)
sample_data <- data.table("id" = c(3930271, 3930272, 3930272,
                               3930273, 3930273, 3930274, 
                               3930275, 3930275),
                      "class" = c(77, 55, 654, 
                                  65, 66, 225, 
                                  66, 44),
                      "weight" = c(1, 0.5, 0.5, 0.5, 
                                   0.5, 1, 0.05, 0.05))

编辑:我刚刚意识到您只希望ID的前20个出现,因此将其添加到我的答案中。或者,您是否要删除ID出现次数超过20次的所有实例?请澄清。

melt_dt<-sample_data[,melt(.SD,measure.vars=c("class"))]
melt_dt[,id_count:=seq_len(.N),by="id"][id_count<=20][,colname_val:=paste0("class",id_count)]

wide_dt<-dcast(melt_dt,id+weight~colname_val, value.var="value",fill=0)

因此,首先只需融化并指定“ class”作为您的测量变量。然后创建一个变量,该变量将计算相同ID的数量。如果只希望出现的前20个,则仅选择少于20个的行,如melted_dt的第二个链所示。最后,将这些数字附加到“类”中,以在名为colname_val的变量中获得所需的命名方案。最后,您可以使用data.table创建宽形dcast并添加fill=0参数来替换NAs。这有帮助吗?让我知道您是否需要我澄清任何事情。祝你好运!

答案 2 :(得分:0)

使用data.table的另一种方法

library(data.table)
sample_data[, ri := paste0("class",seq_len(.N)), by=.(id, weight)]
ans <- dcast(sample_data[ri<=20], id + weight ~ ri, value.var="class")
ans[, names(ans) := lapply(.SD, function(x) replace(x, is.na(x), 0))]
ans