如何在 R 中使用嵌套数据创建条件组标签?

How to create conditional group tags with nested data in R?

我的数据是这样的:

我有 5 个不同级别的嵌套数据:

我的 df 中的每一行(LVL 1 条目除外)都链接到父级(更高级别)。例如:时代广场 -> 曼哈顿 -> 纽约 -> 美国 -> 国家

对于每个Name,都有一个对应的n_values列,表示数据条目的数量。

我的目标:我想组成>=8个数据条目的组。对于具有 n_values <8 的组,我想将它们与上一级的 Parent 列合并。这个新的分配应该用一个新的变量 new_group.

来表达

首先从较低级别开始很重要!例如,“时代广场”只有 2 个数据条目,因此我们希望将这些条目与父“曼哈顿”合并。 Manhattan 现在有 3+2=5 个数据条目。这仍然是 <8,所以我们将这 5 个条目与下一个父“纽约”合并,后者现在有 16+5=21 个条目,所以我们很好。

我试过这样写一个循环:

for (i in 5:1){
df %>% filter(Level==i) %>% group_by(ID) %>% summarize(n = n())

但是,我未能将该信息与原始数据合并以创建我想要的数据集。有人可以帮忙吗?

数据:

structure(list(ID = c(19,12,3,41,50,6,77,83,9,105,11),
                     Parent = c(NA,19,12,3,41,12,19,77,77,19,105),
                     Level = c(1,2,3,4,5,3,2,3,3,2,3),
                     Name = c("Countries","USA","New York","Manhattan","Times Square",
                              "Boston","UK","London","Oxford","Canada","Vancouver"),
                     n_values = c(NA,17,16,3,2,13,12,7,8,9,8)),
                class = "data.frame",
                row.names = c(NA, -11L))

假设您的数据存储在名为 df 的数据框中。最直接的方法是首先将 table 的行按“Level”降序排列,然后将“new_group”设置为“Name”的值。我们还将在名为“new_values”的列中跟踪 per-group 总数。然后遍历行,直到遇到带有 new_values < 8 的行,此时该行的“new_group”更改为其父行的行,并且其“父级”也更新以匹配其父级的“亲”。那时,行循环重新开始。当没有“new_group”具有 new_values < 8:

时,外循环终止
library(tidyverse)

df_sorted <- df %>% 
  arrange(desc(Level)) %>% 
  mutate(new_group = Name) %>% 
  group_by(new_group) %>% 
  mutate(new_values = sum(n_values)) %>% 
  ungroup

while (any(df_sorted$new_values < 8, na.rm = T)) {
  
  for (i in 1:nrow(df_sorted)) {
    
    if (df_sorted$new_values[i] < 8) {
      
      to_id <- df_sorted$Parent[i]
      to_row <- which(df_sorted$ID == to_id)
      
      df_sorted$new_group[i] <- df_sorted$Name[to_row]
      df_sorted$Parent[i] <- df_sorted$Parent[to_row]

      df_sorted <- df_sorted %>% 
        group_by(new_group) %>% 
        mutate(new_values = sum(n_values)) %>% 
        ungroup
      
      break # terminate the for loop immediately and return to the outer while loop
    }
  }
}

      ID Parent Level Name         n_values new_group new_values
   <dbl>  <dbl> <dbl> <chr>           <dbl> <chr>          <dbl>
 1    50     12     5 Times Square        2 New York          21
 2    41     12     4 Manhattan           3 New York          21
 3     3     12     3 New York           16 New York          21
 4     6     12     3 Boston             13 Boston            13
 5    83     19     3 London              7 UK                19
 6     9     77     3 Oxford              8 Oxford             8
 7    11    105     3 Vancouver           8 Vancouver          8
 8    12     19     2 USA                17 USA               17
 9    77     19     2 UK                 12 UK                19
10   105     19     2 Canada              9 Canada             9
11    19     NA     1 Countries          NA Countries         NA

编辑: 下面的版本添加了一个“touched”列来跟踪循环中已修改的行,还添加了一些对 NA 值的检查。对于上面使用的数据集,它产生与以前版本相同的结果。它在下面的数据集上似乎也能正常工作。

df <- structure(list(ID = c(19,12,3,41,50,6,77,83,9,105,11), Parent = c(NA,19,12,3,41,12,19,77,77,19,105), Level = c(1,2,3,4,5,3,2,3,3,2,3), Name = c("Countries","USA","New York","Manhattan","Times Square", "Boston","UK","London","Oxford","Canada","Vancouver"), n_values = c(NA,0,0,3,2,0,12,7,8,9,8)), class = "data.frame", row.names = c(NA, -11L))

df_sorted <- df %>% 
  arrange(desc(Level)) %>% 
  mutate(new_group = Name) %>% 
  group_by(new_group) %>% 
  mutate(
    new_values = sum(n_values), 
    touched = is.na(n_values) | n_values >= 8
  ) %>% 
  ungroup

while (any(!df_sorted$touched)) {
  
  for (i in 1:nrow(df_sorted)) {
    
    if (df_sorted$new_values[i] < 8 & !is.na(df_sorted$Parent[i]) & any(!df_sorted$touched)) {
      
      to_id <- df_sorted$Parent[i]
      to_row <- which(df_sorted$ID == to_id)
      
      df_sorted$new_group[i] <- df_sorted$Name[to_row]
      df_sorted$Parent[i] <- df_sorted$Parent[to_row]
      df_sorted$touched[i] <- TRUE
      
      df_sorted <- df_sorted %>% 
        group_by(new_group) %>% 
        mutate(new_values = sum(n_values, na.rm = T)) %>% 
        ungroup
      
      break # terminate the for loop immediately and return to the outer while loop
    }
  }
}

      ID Parent Level Name         n_values new_group new_values touched
   <dbl>  <dbl> <dbl> <chr>           <dbl> <chr>          <dbl> <lgl>  
 1    50     NA     5 Times Square        2 Countries          5 TRUE   
 2    41     NA     4 Manhattan           3 Countries          5 TRUE   
 3     3     NA     3 New York            0 Countries          5 TRUE   
 4     6     NA     3 Boston              0 Countries          5 TRUE   
 5    83     19     3 London              7 UK                19 TRUE   
 6     9     77     3 Oxford              8 Oxford             8 TRUE   
 7    11    105     3 Vancouver           8 Vancouver          8 TRUE   
 8    12     NA     2 USA                 0 Countries          5 TRUE   
 9    77     19     2 UK                 12 UK                19 TRUE   
10   105     19     2 Canada              9 Canada             9 TRUE   
11    19     NA     1 Countries          NA Countries          5 TRUE