【问题标题】:R repeat in column based on value in rowR根据行中的值在列中重复
【发布时间】:2021-04-11 15:22:51
【问题描述】:

我有一个如下的数据框:

Name    School   Weight Days
Antoine Bach     0.03   5
Antoine Ken      0.02   7
Barbara Franklin 0.04   3

我想获得如下输出:

Name    School   1    2    3    4    5    6    7
Antoine Bach     0.03 0.03 0.03 0.03 0.03 NA   NA
Antoine Ken      0.02 0.02 0.02 0.02 0.02 0.02 0.02
Barbara Franklin 0.04 0.04 0.04 NA   NA   NA   NA

可重现的样本数据:

df <- tribble(
  ~Name,    ~School,   ~Weight, ~Days,
  "Antoine", "Bach",     0.03,   5,
  "Antoine", "Ken",      0.02,   7,
  "Barbara", "Franklin", 0.04,   3
)

【问题讨论】:

    标签: r dataframe repeat long-integer


    【解决方案1】:

    使用 data.table,您可以通过 repeating 每行的 WeightDays 次数来创建长版本,然后 dcasting 使用新的 rowid 转换为宽格式变量作为列。

    library(data.table)
    setDT(df)
    
    dcast(df[, .(rep(Weight, Days)), .(Name, School)], 
          Name + School ~ rowid(V1))
    
    # Name   School    1    2    3    4    5    6    7
    # 1: Antoine     Bach 0.03 0.03 0.03 0.03 0.03   NA   NA
    # 2: Antoine      Ken 0.02 0.02 0.02 0.02 0.02 0.02 0.02
    # 3: Barbara Franklin 0.04 0.04 0.04   NA   NA   NA   NA
    

    您也可以repWeightDays 的数量,然后代表NA 足够的次数来完成该行。

    max_days <- max(df$Days) 
    
    df[, as.list(rep(c(Weight, NA), c(Days, max_days - Days))), 
       .(Name, School)]
    
    # Name   School   V1   V2   V3   V4   V5   V6   V7
    # 1: Antoine     Bach 0.03 0.03 0.03 0.03 0.03   NA   NA
    # 2: Antoine      Ken 0.02 0.02 0.02 0.02 0.02 0.02 0.02
    # 3: Barbara Franklin 0.04 0.04 0.04   NA   NA   NA   NA
    

    【讨论】:

    • 非常好的解决方案。我从来没有机会熟悉data.table 包。你对从哪里开始有什么建议吗?例如文档或其他来源可能。提前谢谢你。
    • 我同意上面的请求。即使我还没有找到专门针对这个包的教程/书。我只知道它超级快
    • their pageFrank Narf's R tutorial 上大量使用 data.table 的 vingettes 都很好
    • This 来自一位创作者的演讲的 powerpoint 非常适合复习基础知识。还有 this tutorial 并排提供了 dplyr 和 data.table 的方法
    • @IceCreamToucan 非常感谢。我不知道该怎么感谢你。我一定会检查他们所有的。祝你一切顺利。
    【解决方案2】:

    您可以使用pmap_dfr 跨行应用函数,然后将结果列表行绑定到 tibble 对象。该函数会将参数与列名匹配,其余行值将在省略号 ... 中捕获。

    library(purrr)
    library(dplyr)
    
    pmap_dfr(df, function(Weight, Days, ...) c(..., setNames(rep(Weight, Days), 1:Days))) %>% 
      mutate(across(3:last_col(), as.numeric))
    

    因为向量在 R 中是原子的,c() 将强制该行中的所有内容成为字符。因此,mutate 会将新创建的列转换回数字。

    setNames用于命名新创建的列,需要按行绑定。

    输出

      Name    School     `1`   `2`   `3`   `4`   `5`   `6`   `7`
      <chr>   <chr>    <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
    1 Antoine Bach      0.03  0.03  0.03  0.03  0.03 NA    NA   
    2 Antoine Ken       0.02  0.02  0.02  0.02  0.02  0.02  0.02
    3 Barbara Franklin  0.04  0.04  0.04 NA    NA    NA    NA   
    

    注意:pmap_dfr 来自 purrr 包,mutateacrosslast_col 都来自 dplyr

    工作原理

    当您以上述方式使用pmap 时,命名函数参数将匹配到具有相同名称的列。因此,WeightsDays 作为函数参数与每一行中具有相同名称的那些列匹配

    ... 收集仍传递给函数但在函数中未使用(按名称)的剩余列。本质上,省略号在您的情况下收集 NameSchool

    由于 NameSchool 已经有了名称,它们首先传递给 c() 以保持您的列顺序。此外,我们还结合了其他值并为它们命名。单行的输出是这样的:

         Name    School         1         2         3         4         5         6 
    "Antoine"    "Bach"    "0.03"    "0.03"    "0.03"    "0.03"    "0.03"        NA 
            7 
           NA 
    

    pmap 的输出是一个列表。 _dfr 是将这些列表元素行绑定(因此称为r)到数据框/tibble(因此称为df)的特定函数。

    【讨论】:

    • 我真的很喜欢你的解决方案。事实上,purrr 中的函数始终是我进行逐行操作的首选,我经常使用pmapc(...) 来收集多个参数。但是在这里我不能完全理解我们可以将函数传递给c(...) 的方式,以及它应该是怎样的方式。请你给我解释一下,我很想知道。
    • @AnoushiravanR 当然,没问题。我已经更详细地更新了这个解决方案。希望能提供更多关于它是如何工作的见解。有关更多信息和另一个示例,请参阅页面底部的 ?pmap_dfr 文档中的示例序列,从注释“# Use ... to吸收未使用的输入列表.l”开始
    • 非常感谢您的详尽解释。我只是想知道为什么下面的代码不起作用。因为我只是简单地用一个公式替换了一个匿名函数:df %&gt;% pmap_dfr(., ~ c(..., setNames(rep(Weight, Days), 1:Days)))
    • 在不可见函数中的列名之前使用 .x$,它应该可以工作。用类似的方式替换 ellipsss
    • @AnilGoyal 非常感谢。我非常感谢我们的讨论。他们非常乐于助人和富有。
    【解决方案3】:

    一个tidyverse 解决方案。

    • 首先我们tidyr::nest这两列。结果列将是一个名为 d 的列表列,用于 dummy。
    • 然后我们使用rep 函数将d 变异为weightsdays 次的向量。此迭代使用purrr::map 完成。 注意:此阶段不需要map_dbl,因为它将在下一步中取消嵌套。
    • 我们还将这个参数包装到setNames 中,这样d 就变成了一个命名列表(名称符合预期)。对于名称,使用seq 函数。
    • 最后我们将tidyr::unnest_widerd 列放入前面步骤中名称已保存在列表中的列中
    library(dplyr)
    library(tidyr)
    library(purrr)
    
    df %>% nest(d = c(Weight, Days)) %>%
      mutate(d = map(d, ~setNames( rep(.x$Weight, .x$Days), seq(1, .x$Days, 1)))) %>%
      unnest_wider(d)
    
    # A tibble: 3 x 9
      Name    School     `1`   `2`   `3`   `4`   `5`   `6`   `7`
      <chr>   <chr>    <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
    1 Antoine Bach      0.03  0.03  0.03  0.03  0.03 NA    NA   
    2 Antoine Ken       0.02  0.02  0.02  0.02  0.02  0.02  0.02
    3 Barbara Franklin  0.04  0.04  0.04 NA    NA    NA    NA 
    

    【讨论】:

    • Anil 我刚刚发现了一个非常有趣的想法。我们可以在对mutate 的调用中使用映射函数,而无需命名输出列名。我已经知道我们可以使用map_dfrmap_dfc 中的列表来控制命名,但这个想法对我来说很新,我发现它非常有用。
    • 你能解释一下吗?
    • 非常感谢,您当然也值得一票。
    【解决方案4】:

    您可以使用以下代码获得所需的输出:

    library(dplyr)
    library(tidyr)
    
    df %>% 
      select(Weight, Days) %>%
      uncount(Days, .remove = FALSE) %>%
      group_by(Days) %>%
      mutate(id = row_number()) %>%
      pivot_wider(Days, names_from = id, values_from = Weight) %>%
      right_join(df, by = "Days") %>%
      relocate(Name, School) %>%
      ungroup() %>%
      select(-c(Weight, Days))
    
    # A tibble: 3 x 9
      Name    School     `1`   `2`   `3`   `4`   `5`   `6`   `7`
      <chr>   <chr>    <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
    1 Antoine Bach      0.03  0.03  0.03  0.03  0.03 NA    NA   
    2 Antoine Ken       0.02  0.02  0.02  0.02  0.02  0.02  0.02
    3 Barbara Franklin  0.04  0.04  0.04 NA    NA    NA    NA 
    
    

    数据:

    df <- tribble(
      ~Name,    ~School,   ~Weight, ~Days,
      "Antoine", "Bach",     0.03,   5,
      "Antoine", "Ken",      0.02,   7,
      "Barbara", "Franklin", 0.04,   3
    )
    
    

    更新 既然我们亲爱的朋友们正确地建议使用来自purrr 包的pmapmap,这是另一个变体,猜猜知道会很酷:

    library(purrr)
    
    df %>%
      mutate(map2_dfr(Weight, Days, ~ set_names(rep(.x, .y), 1:.y))) %>%
      select(-c(Weight, Days))
    
    # A tibble: 3 x 9
      Name    School     `1`   `2`   `3`   `4`   `5`   `6`   `7`
      <chr>   <chr>    <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
    1 Antoine Bach      0.03  0.03  0.03  0.03  0.03 NA    NA   
    2 Antoine Ken       0.02  0.02  0.02  0.02  0.02  0.02  0.02
    3 Barbara Franklin  0.04  0.04  0.04 NA    NA    NA    NA 
    
    

    【讨论】:

      【解决方案5】:

      我喜欢tidyr::uncount 制作x 每行的副本数。我们可以转动更长的时间,不计其数,然后再更宽地转动。

      library(tidyr)
      my_data %>%
        pivot_longer(Weight) %>%
        uncount(Days, .id = "colnum") %>%
        dplyr::select(-name) %>%
        pivot_wider(names_from = colnum, values_from = value)
      
      
      # A tibble: 3 x 9
        Name    School     `1`   `2`   `3`   `4`   `5`   `6`   `7`
        <chr>   <chr>    <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
      1 Antoine Bach      0.03  0.03  0.03  0.03  0.03 NA    NA   
      2 Antoine Ken       0.02  0.02  0.02  0.02  0.02  0.02  0.02
      3 Barbara Franklin  0.04  0.04  0.04 NA    NA    NA    NA   
      

      【讨论】:

      • uncount 确实是个很棒的功能
      猜你喜欢
      • 2023-03-21
      • 2020-12-08
      • 2016-07-11
      • 2021-05-13
      • 2016-07-27
      • 1970-01-01
      • 2020-09-22
      • 2023-03-13
      • 1970-01-01
      相关资源
      最近更新 更多