【问题标题】:How to distribute data for each rows in R?如何为 R 中的每一行分配数据?
【发布时间】:2019-12-08 23:27:24
【问题描述】:

我有一个如下所示的数据集:

df <- tribble(
  ~id,  ~price, ~type, ~number_of_book,        
  "1",    10,     "X",        3,    
  "1",     2,     "X",        1, 
  "1",     5,     "Y",        1,         
  "2",     7,     "X",        4,
  "2",     6,     "X",        1,
  "2",     6,     "Y",        2, 
  "3",     2,     "X",        4,
  "3",     8,     "X",        2,
  "3",     1,     "Y",        4,
  "3",     9,     "Y",        5,
)

现在,我要回答这个问题:对于每个 id 和每个选定的价格组,X 占书籍的百分比,Y 占书籍的百分比?换句话说,每个id和价格组的书籍类型分布是怎样的?

要做到这一点,首先我需要在脑海中将这个数据集可视化:

agg_df <- tribble(
  ~type,     ~id,       ~less_than_two,    ~two-five,  ~five-six, ~more_than_six,     
    "X",      "1",              1,               0,           0,            3,
    "Y",      "1",              0,               1,           0,            0,
    "X",      "2",              0,               0,           1,            4,
    "Y",      "2",              0,               0,           2,            2,
    "X",      "3",              4,               0,           0,            2,
    "Y",      "3",              4,               0,           0,            5,
)

然后,这将是我想要的数据集:

desired_df <- tribble(
  ~type,     ~id,       ~less_than_two,  ~three-five,  ~five-six, ~more_than_six,     
  "X",      "1",            "100%",           "0%",          "0%",       "100%",
  "Y",      "1",              "0%",         "100%",          "0%",         "0%",
  "X",      "2",              "0%",           "0%",       "33.3%",      "66.6%",
  "Y",      "2",              "0%",           "0%",       "66.6%",       "33.3%",
  "X",      "3",             "50%",           "0%",          "0%",      "28.5%",
  "Y",      "3",             "50%",           "0%",          "0%",       "71.4%",
)

这个期望的数据集告诉我,当 id 为“3”并且价格箱超过 6 美元时,有两本 X 类型的书,但有五本 Y 类型的书。所以,这是分布:X(28.5%) 和 Y(71.4%)。

注意:我在这里有一个类似的问题,但现在我无法解决更复杂的操作:How to manipulate (aggregate) the data in R?

如果您能帮助我,我将不胜感激。提前致谢。

【问题讨论】:

  • 对不起,我刚刚更正了数据。
  • @akrun 不幸的是,代码没有给出正确的答案。
  • @akrun 我已经在您的代码中尝试了价格和书籍数量,但仍然没有给出结果。 ://
  • 你能检查一下这是否有帮助吗? df %&gt;% mutate(price_group = c("less_than_two", "three_five", "five_six", "more_than_six")[findInterval(price, c(2, 5, 6), left.open = TRUE) + 1]) %&gt;% group_by(id, type, price_group) %&gt;% summarise(number_of_book = sum(number_of_book)) %&gt;% group_by(id, price_group) %&gt;% mutate(n = number_of_book/sum(number_of_book) * 100) %&gt;% select(-number_of_book) %&gt;% pivot_wider(names_from = price_group, values_from = n)
  • 请注意,findInterval 已在 cmets 中提及

标签: r dplyr tidyverse


【解决方案1】:

我们可以使用findIntervalprice 划分为不同的组,计算number_of_booksum 对于每个idtypeprice_group,然后计算每个id 的比率和price_group。最后,我们使用pivot_wider获取更宽格式的数据。

library(dplyr)

df %>% 
  mutate(price_group = c("less_than_two", "three_five", "five_six", "more_than_six")
                      [findInterval(price, c(2, 5, 6), left.open = TRUE) + 1]) %>% 
  group_by(id, type, price_group) %>%
  summarise(number_of_book = sum(number_of_book)) %>% 
  group_by(id, price_group) %>% 
  mutate(n = number_of_book/sum(number_of_book) * 100) %>% 
  select(-number_of_book) %>% 
  tidyr::pivot_wider(names_from = price_group, values_from = n, 
                     values_fill = list(n = 0))

#  id    type  less_than_two more_than_six three_five five_six
#  <chr> <chr>         <dbl>         <dbl>      <dbl>    <dbl>
#1 1     X               100         100            0      0  
#2 1     Y                 0           0          100      0  
#3 2     X                 0         100            0     33.3
#4 2     Y                 0           0            0     66.7
#5 3     X                50          28.6          0      0  
#6 3     Y                50          71.4          0      0  

【讨论】:

    【解决方案2】:

    我们可以在'price'列上创建一个带有cut的bin组,按'id'、'grp'分组,通过将'number_of_book'除以'number_of_book'的sum来创建百分比,然后重新整形为“宽”格式

    library(dplyr)
    library(tidyr)
    df %>% 
      group_by(id,grp = cut(price, breaks = c(-Inf, 2, 5, 6, Inf), 
        c('less_than_two', 'three-five', 'five-six', 'more_than_six')), add = TRUE) %>%
      mutate(Perc = 100 *number_of_book/sum(number_of_book)) %>%
      select(-price, -number_of_book) %>%
      mutate(rn = row_number()) %>%
      pivot_wider(names_from = grp, values_from = Perc, values_fill = list(Perc = 0)) %>%
      select(-rn)
    # A tibble: 6 x 6
    # Groups:   id [3]
    #  id    type  more_than_six less_than_two `three-five` `five-six`
    #  <chr> <chr>         <dbl>         <dbl>        <dbl>      <dbl>
    #1 1     X             100             100            0        0  
    #2 1     Y               0               0          100        0  
    #3 2     X             100               0            0       33.3
    #4 2     Y               0               0            0       66.7
    #5 3     X              28.6            50            0        0  
    #6 3     Y              71.4            50            0        0  
    

    【讨论】:

    • Ops,在我的真实数据集中,它仍然没有给出准确的结果。我不知道为什么,但是在我运行代码之后,价格列仍然在数据中。
    • @zineda 如果您检查select(-price, -number_of_book) %&gt;% 行,它将删除price 列。可能是您将price 也包含在分组变量中吗?也有可能您加载了带有select 函数的其他包。在这种情况下,请特别使用 dplyr::select(-price, -number_of_book)
    • @zineda 请检查str(df)str(originaldata)
    • "id" 是整数,"price" 是数字,"type" 是字符,"number_of_book" 是整数。即使我添加了 dplyr:: 我在控制台中得到了这个:添加缺少的分组变量:price
    • @zineda 不清楚,因为我无法重现它
    【解决方案3】:

    也许不是完美的解决方案,但另一种方法是使用case_when 来定义不同的类别:

    library(tidyverse)
    df %>% group_by(id, type, price) %>% 
      mutate(Less2 = case_when(price <= 2 ~ cumsum(number_of_book)),
                                                Three_Five = case_when(price %in% 3:5 ~ cumsum(number_of_book)),
                                                Five_six = case_when(price %in% 5:6 ~ cumsum(number_of_book)),
                                                More_six = case_when(price >6 ~ cumsum(number_of_book))) %>% 
      replace(is.na(.),0) %>% 
      ungroup(.) %>% 
      group_by(id, type) %>% 
      summarise_at(vars(Less2:More_six), ~sum(.)) %>%
      ungroup(.) %>%
      group_by(id) %>%
      mutate_at(vars(Less2:More_six), ~ replace_na(./sum(.), 0)) 
    
    # A tibble: 6 x 6
    # Groups:   id [3]
      id    type  Less2 Three_Five Five_six More_six
      <chr> <chr> <dbl>      <dbl>    <dbl>    <dbl>
    1 1     X       100          0      0      100  
    2 1     Y         0        100    100        0  
    3 2     X         0          0     33.3    100  
    4 2     Y         0          0     66.7      0  
    5 3     X        50          0      0       28.6
    6 3     Y        50          0      0       71.4
    ```
    

    【讨论】:

    • 我会用findInterval..关于你最后的mutate,可以用mutate_at缩短,即mutate_at(vars(Less2:More_six), ~ replace_na(./sum(.), 0))
    • 感谢mutate_at, it makes things much nicer now. I did not know about findInterval`的更新,我会看看;)
    • 同样,您也可以使用summarise_at 来表示这一行,summarise(Less2 = sum(Less2).....
    • 我尝试编写此代码,但运行我的真实数据集需要很长时间。 ://
    • 感谢@RonakShah 的提示。我正在编辑我的答案
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-16
    • 2019-08-20
    • 1970-01-01
    相关资源
    最近更新 更多