【问题标题】:How can I reshape data from long to wide如何从长到宽重塑数据
【发布时间】:2017-11-04 20:37:51
【问题描述】:

** 注释后添加的示例数据**

我有什么:

pmts <- data.frame(stringsAsFactors=FALSE,
           name = c("johndoe", "johndoe", "janedoe", "foo", "foo", "foo"),
           pmt_amount = c(550L, 550L, 995L, 375L, 375L, 375L),
           pmt_date = c("9/1/16", "11/1/16", "12/15/16", "1/5/17", "3/5/17", "5/5/17")
)

#>      name pmt_amount pmt_date
#> 1 johndoe        550   9/1/16
#> 2 johndoe        550  11/1/16
#> 3 janedoe        995 12/15/16
#> 4     foo        375   1/5/17
#> 5     foo        375   3/5/17
#> 6     foo        375   5/5/17

我想要实现的目标:

read.table(header = T, text = 
"name    pmt_amount  first_pmt   second_pmt  third_pmt
johndoe    550        9/1/16       11/1/16    NA
  janedoe    995        12/15/16       NA       NA
  foo       375        1/5/17       3/5/17   5/5/17"
)

#>      name pmt_amount first_pmt second_pmt third_pmt
#> 1 johndoe        550    9/1/16    11/1/16      <NA>
#> 2 janedoe        995  12/15/16       <NA>      <NA>
#> 3     foo        375    1/5/17     3/5/17    5/5/17

** 更新结束**

我有一个包含不同产品付款信息的大型数据集。其中一些产品具有全额支付选项以及二付和三付选项。我需要创建 First_Payment、Second_Payment 和 Third_Payment 字段,如果只有一个或两个付款,则在相应字段中填充 NA。

我已经尝试了几个选项,到目前为止我最好的解决方法是:

pmts %>%
  group_by(Email, Name, Amount, Form.Title) %>%
  summarise(First_Payment = min(Payment.Date),
           Second_Payment = median(Payment.Date),
           Last_Payment = max(Payment.Date)) -> pmts

这显然不理想,因为为 2-pay 计划制定付款日期,我必须指示最终用户忽略此字段,只查看第 1 和第 3 字段。

我也试着用这样的部分排序来总结:

n <- length(pmts$Payment.Date)
sort(pmts$Payment.Date,partial=n-1)[n-1]

但是,如果此人没有三笔付款,则它将从整个数据集中取 n-1 个日期并应用于所有其他字段。

理想情况下,如果是全额付款,First_Payment 字段将包含日期,而第 2/3 字段将显示 NA。 2-pay 将有第一个和第二个日期,第三个字段将显示 NA。最后,3 支付将包含所有 3 个日期。

这里的最终用户不是超级精通数据的,所以我试图让它尽可能容易解释。任何建议将不胜感激。谢谢!

【问题讨论】:

标签: r dplyr tidyr


【解决方案1】:

使用 data.table 这是一个简单的单行代码

library(data.table) #v1.9.8+
dcast(setDT(pmts), name + pmt_amount ~ rowid(pmt_amount))
# Using 'pmt_date' as value column. Use 'value.var' to override
#       name pmt_amount        1       2      3
# 1:     foo        375   1/5/17  3/5/17 5/5/17
# 2: janedoe        995 12/15/16      NA     NA
# 3: johndoe        550   9/1/16 11/1/16     NA

dcast 从 long 转换为 wide 并接受表达式。 rowid 只是为每个 pmt_amount 添加一个行计数器。

【讨论】:

  • 谢谢!我正在尝试使用它,但是当我完全按照此处复制代码时 - 它给了我一个包含 2,028 个变量的数据框,而不是您的示例中生成的 5 个变量。知道这里会发生什么吗? > dcast(setDT(pmts), Email + Amount ~ rowid(Amount)) -> pmts2 使用 Payment.Date 作为值列:使用 value.var 覆盖。
  • @JamesSnay 它为您提供变量的数量,因为您在某个 name / pmt_amount 组合中拥有的日期数量越大。
  • 没有一个名称附有超过 5 个付款/付款日期。这就是你说的吗?如果是这样,我可以放置一个过滤器来限制创建的字段数量吗?
  • 好的,试试dcast(setDT(pmts), name + pmt_amount ~ rowid(name, pmt_amount)) perhps。或者展示一个更好的示例,让我们可以重现您所看到的内容。
  • 漂亮!我在 rowid 中添加了名称和产品,它就像一个魅力。非常感谢!
【解决方案2】:

您可以为此使用tidyr

library(dplyr)
library(tidyr)

pmts <- tibble(
  name = c("johndoe", "johndoe", "janedoe", "foo", "foo", "foo"),
  pmt_amount = c(550L, 550L, 995L, 375L, 375L, 375L),
  pmt_date = lubridate::mdy(c("9/1/16", "11/1/16", "12/15/16", "1/5/17", "3/5/17", "5/5/17"))
)

pmts
#> # A tibble: 6 x 3
#>      name pmt_amount   pmt_date
#>     <chr>      <int>     <date>
#> 1 johndoe        550 2016-09-01
#> 2 johndoe        550 2016-11-01
#> 3 janedoe        995 2016-12-15
#> 4     foo        375 2017-01-05
#> 5     foo        375 2017-03-05
#> 6     foo        375 2017-05-05

pmts_long <- pmts %>% 
  group_by(name) %>% 
  arrange(name, pmt_date) %>% 
  mutate(pmt = row_number()) %>% 
  ungroup() %>% 
  complete(name, nesting(pmt)) %>% 
  fill(pmt_amount, .direction = "down")

pmts_long
#> # A tibble: 9 x 4
#>      name   pmt pmt_amount   pmt_date
#>     <chr> <int>      <int>     <date>
#> 1     foo     1        375 2017-01-05
#> 2     foo     2        375 2017-03-05
#> 3     foo     3        375 2017-05-05
#> 4 janedoe     1        995 2016-12-15
#> 5 janedoe     2        995         NA
#> 6 janedoe     3        995         NA
#> 7 johndoe     1        550 2016-09-01
#> 8 johndoe     2        550 2016-11-01
#> 9 johndoe     3        550         NA

pmts_wide <- pmts_long %>% 
  gather("key", "val", -name, -pmt_amount, -pmt) %>% 
  unite(pmt_number, key, pmt) %>% 
  spread(pmt_number, val)

pmts_wide
#> # A tibble: 3 x 5
#>      name pmt_amount pmt_date_1 pmt_date_2 pmt_date_3
#> *   <chr>      <int>     <date>     <date>     <date>
#> 1     foo        375 2017-01-05 2017-03-05 2017-05-05
#> 2 janedoe        995 2016-12-15         NA         NA
#> 3 johndoe        550 2016-09-01 2016-11-01         NA

【讨论】:

  • 谢谢!这非常有效。但是,这是我过度简化示例数据的错,我需要在其中添加“Form.Title”和“Email”字段(如我显示的原始 group_by 代码 sn-p 所示)。我尝试将它们添加到我认为它们会出现在您编写的代码中的位置,但我遇到了错误。您能否说明这些字段在代码中的位置?
猜你喜欢
  • 1970-01-01
  • 2012-12-01
  • 2011-01-16
  • 1970-01-01
  • 2021-04-23
  • 2015-05-04
  • 1970-01-01
相关资源
最近更新 更多