【问题标题】:How to create bin frequency table where bin size varies by group如何创建 bin 频率表,其中 bin 大小因组而异
【发布时间】:2019-01-18 00:28:24
【问题描述】:

我正在尝试创建一个 bin 频率表,其中有多个分组列,但更重要的是,bin 大小因分组列之一而异。让我举例说明:

set.seed(42)
ID <- as.factor(c(rep("A",20),rep("B",22)))
date <- as.factor(c(rep("C",12),rep("D",8),rep("E",10),rep("F",12)))
group <- as.factor(c(rep("G",6),rep("H",6),rep("G",8),rep("G",6),rep("H",4),rep("G",6),rep("H",6)))
val <- round(rnorm(42,20,10),0)

df <- data.frame(ID,date,group,val)

可以使用我从this 帖子中编辑的一些代码生成按 ID、日期和组的 val 频率表:

br <- c(0,10,30,100)
frqtab <- aggregate(val~ID+date+group,df,FUN=function(x) table(cut(x, br)))

但是,我想为组内的每个因素设置不同的 bin 大小,例如G 组可以保留brG &lt;- c(0,10,30,100),H 组可以是brH &lt;- c(0,10,50,100)。我想,我可以写一些ifelse 函数,但这会很混乱,特别是因为我的真实数据有很多组。任何帮助将不胜感激!

【问题讨论】:

  • 您对垃圾箱大小的标准是什么?
  • 我需要每个 bin 组三个尺寸,例如0-10、10-20 和 >20。这需要根据组内的每个因素而有所不同。

标签: r group-by data.table aggregate frequency


【解决方案1】:

这是一个可能的解决方案:

# example data
set.seed(42)
ID <- as.factor(c(rep("A",20),rep("B",22)))
date <- as.factor(c(rep("C",12),rep("D",8),rep("E",10),rep("F",12)))
group <- as.factor(c(rep("G",6),rep("H",6),rep("G",8),rep("G",6),rep("H",4),rep("G",6),rep("H",6)))
val <- round(rnorm(42,20,10),0)

df <- data.frame(ID,date,group,val)

# using the function you provided
f = function(br, df) {aggregate(val~ID+date+group,df,FUN=function(x) table(cut(x, br)))}

library(tidyverse)

# create a look up table
# (specify the breaks for each group)
look_up = data_frame(group_id = c("G","H"),
                     br = list(c(0,10,30,100), c(0,10,50,100)))

df_upd = df %>%
  group_by(group_id = group) %>%          # duplicate group column and group by it
  nest() %>%                              # nest data
  left_join(look_up, by="group_id") %>%   # join look up table to get corresponding breaks
  mutate(d = map2(br, data, ~f(.x, .y)))  # apply function

# see results
df_upd$d

# [[1]]
#   ID date group val.(0,10] val.(10,30] val.(30,100]
# 1  A    C     G          0           5            1
# 2  A    D     G          1           4            1
# 3  B    E     G          1           3            2
# 4  B    F     G          1           5            0
# 
# [[2]]
#   ID date group val.(0,10] val.(10,50] val.(50,100]
# 1  A    C     H          0           6            0
# 2  B    E     H          1           3            0
# 3  B    F     H          0           5            0

我决定使用您提供的函数,它显然包括对列名的中断。因此,当您对不同的组有不同的中断时,输出不能包含在一个数据框中,因为会有列名冲突。

在一个数据框中获取所有内容的唯一方法是更改​​函数以产生更“整洁”的输出:

library(tidyverse)

# updated function
f = function(br, df) {
  df %>%
  mutate(g = cut(val, br)) %>%
  na.omit() %>%
  count(g, ID, date, group) %>%
  complete(g, nesting(ID, date, group), fill=list(n=0)) }

# same lookup table
look_up = data_frame(group_id = c("G","H"),
                     br = list(c(0,10,30,100), c(0,10,50,100)))

# apply your function
df %>%
  group_by(group_id = group) %>%          
  nest() %>%                              
  left_join(look_up, by="group_id") %>%   
  mutate(d = map2(br, data, ~f(.x, .y))) %>%
  unnest(d) %>%
  select(-group_id) %>%
  arrange(group, date, ID)   # for visualisation purposes only

# # A tibble: 21 x 5
#   g        ID    date  group     n
#   <chr>    <fct> <fct> <fct> <dbl>
# 1 (0,10]   A     C     G         0
# 2 (10,30]  A     C     G         5
# 3 (30,100] A     C     G         1
# 4 (0,10]   A     D     G         1
# 5 (10,30]  A     D     G         4
# 6 (30,100] A     D     G         1
# 7 (0,10]   B     E     G         1
# 8 (10,30]  B     E     G         3
# 9 (30,100] B     E     G         2
# 10 (0,10]  B     F     G         1
# # ... with 11 more rows

【讨论】:

  • 感谢@AntoniosK!有什么方法可以将 df_upd 转换为更用户友好的格式?目前,它是一个 2 x 4 的数据框,其中填充了列表对象。理想情况下,df_upd$d 将是一个数据框。
  • 正如我在解决方案末尾提到的,您希望每个组有不同的列名。你如何想象你说的那个数据框?列应该是什么样子?
  • 对不起,我对你的代码太着迷了,以至于我没有阅读你帖子的结尾。我可以想象一个宽而长的表格格式:ID date valG.(0,10] valG.(10,30] valG.(30,100] valH.(0,10] valH.(10,50] valH.(50,100]ID date group.bin val。对于后者, group.bin 将包含每个组和 bin 大小组合的因子级别。宽格式有点麻烦,但可以适合我已经拥有的以前的数据,并减少我必须对现有代码进行的调整量。这是一个现实的目标吗?
  • 刚刚更新了我的答案 :) 检查你是否喜欢这种格式。
【解决方案2】:

Antonios K 回答的“整洁”部分的 data.table 版本:

df[, data.table(table(bin = cut(val, 
  breaks = c(0, 10, if (group == "G") 30 else 50, 100)
))), by=.(ID, date, group)]

    ID date group      bin N
 1:  A    C     G   (0,10] 0
 2:  A    C     G  (10,30] 5
 3:  A    C     G (30,100] 1
 4:  A    C     H   (0,10] 0
 5:  A    C     H  (10,50] 6
 6:  A    C     H (50,100] 0
 7:  A    D     G   (0,10] 1
 8:  A    D     G  (10,30] 4
 9:  A    D     G (30,100] 1
10:  B    E     G   (0,10] 1
11:  B    E     G  (10,30] 3
12:  B    E     G (30,100] 2
13:  B    E     H   (0,10] 1
14:  B    E     H  (10,50] 3
15:  B    E     H (50,100] 0
16:  B    F     G   (0,10] 1
17:  B    F     G  (10,30] 5
18:  B    F     G (30,100] 0
19:  B    F     H   (0,10] 0
20:  B    F     H  (10,50] 5
21:  B    F     H (50,100] 0
    ID date group      bin N

或者写一个辅助函数和一个辅助表:

library(magrittr)
cut_tab = function(x, br) x %>% cut(br) %>% table(bin = . ) %>% data.table

cutDT = data.table(key="group",
  group = c("G", "H"), 
  br = list(c(0, 10, 30, 100), c(0, 10, 50, 100)))

df[, cut_tab(val, br = cutDT[.BY, on=key(cutDT), unlist(x.br)]), by=.(ID, date, group)]

【讨论】:

    猜你喜欢
    • 2015-03-06
    • 1970-01-01
    • 2019-04-23
    • 2020-03-06
    • 1970-01-01
    • 1970-01-01
    • 2020-11-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多