将 dplyr 函数 group_by() 与 cut() 一起使用

Posted

技术标签:

【中文标题】将 dplyr 函数 group_by() 与 cut() 一起使用【英文标题】:Using dplyr function group_by() with cut() 【发布时间】:2021-07-20 01:21:51 【问题描述】:

我有一组房地产数据。我正在尝试在市场组(标记为 DOM_Groups)上创建一个新的天数列,并将它们分组为 15 天的间隔(即 0-14、15-29 等)。然后我尝试通过观察次数和每个 15 天组的平均售价来 summarize() 这些分组。

我正在使用cut() 函数试图将我的 DOM_Groups 分成这 15 天的间隔。在我导入的基本电子表格中,包含上市天数的列在每个单元格中都有一个唯一的观察值,并且该列中的数据是数字整数...没有小数,没有负数。

当我运行以下代码时,tibble 输出没有正确分组,它包含一个带小数的负数,这在我的数据集中不存在。我不知道该怎么做才能纠正这个问题。

gibbsMkt %>% 
  mutate(DOM_Groups = cut(DOM, breaks = 15, dig.lab = 2)) %>% 
  filter(Status == "SOLD") %>% 
  group_by(DOM_Groups) %>% 
  summarize(numDOM = n(),
            avgSP = mean(`Sold Price`, na.rm = TRUE))

The tibble output I get is this:


DOM_Groups        numDOM   avgSP
  <fct>              <int>   <dbl>
1 (-0.23,16]            74 561675.
2 (16,31]               18 632241.
3 (31,47]               11 561727.
4 (47,63]                8 545862.
5 (63,78]                7 729286.
6 (78,94]                6 624167.
7 (1.4e+02,1.6e+02]      2 541000 
8 (1.6e+02,1.7e+02]      1 535395 

另外,对于 tibble 中的第 7 行和第 8 行,最大的数字是 164,所以我也不明白为什么要将这些行转换为科学计数法。

当我使用 Excel 数据透视表时,我得到了想要在 R 中重现的输出,如下所示:

如何使用正确的代码在 R 中重现这一点?

【问题讨论】:

这些只是标签,并不意味着你有负数:levels(cut(rpois(1000, 1), breaks = 15))breaks = 15 意味着你会得到 15 个区间,而不是将数据切割成 15 个单位的区间 抱歉,这不是粗鲁,但这并不能帮助我理解如何使用正确的代码在 R 中从 Excel 重新创建数据透视表。由于我想要 15 天的小组,我该如何完成?另外,为什么小标题显示的是负数? 【参考方案1】:

cut(x, breaks = 15) 表示x 将被切割成 15 个间隔——它无法猜测您想要从 0 开始到 150 结束的 15 个单位间隔。这是在?cut 的文档中:

breaks 两个或多个唯一切割点的数字向量或单个数字(大于或等于 2)给出 x 将被切割成的区间数。

您需要为每个间隔定义自己的开始和结束,例如:

seq(0, max(x), 15)
# [1]   0  15  30  45  60  75  90 105 120 135 150
cut(x, seq(0, max(x), 15))

但是,如果设置正确,您可以同时定义间隔和制作标签。

set.seed(1)
x <- floor(runif(500, 0, 164))
from <- seq(0, max(x), 15)
to <- from + 15 - 1

labs <- sprintf('%s-%s', from, to)
# [1] "0-14"    "15-29"   "30-44"   "45-59"   "60-74"   "75-89"   "90-104"  "105-119" "120-134" "135-149" "150-164"

data.frame(table(cut(x, c(from, Inf), right = FALSE)), labels = labs)
#         Var1 Freq  labels
# 1     [0,15)   35    0-14
# 2    [15,30)   57   15-29
# 3    [30,45)   45   30-44
# 4    [45,60)   44   45-59
# 5    [60,75)   57   60-74
# 6    [75,90)   55   75-89
# 7   [90,105)   33  90-104
# 8  [105,120)   47 105-119
# 9  [120,135)   40 120-134
# 10 [135,150)   39 135-149
# 11 [150,Inf)   48 150-164

DOM_Groups <- cut(x, c(from, Inf), labs, right = FALSE)
data.frame(table(DOM_Groups))
#    DOM_Groups Freq
# 1        0-14   35
# 2       15-29   57
# 3       30-44   45
# 4       45-59   44
# 5       60-74   57
# 6       75-89   55
# 7      90-104   33
# 8     105-119   47
# 9     120-134   40
# 10    135-149   39
# 11    150-164   48

您的另一个问题是“为什么我得到负数”,正如我所提到的,这并不意味着您的数据中有负数 - 这些只是通过使用 breaks = 15 与您的数据生成的标签。

这些是cut.default中的相关行

if (length(breaks) == 1L) 
  if (is.na(breaks) || breaks < 2L) 
    stop("invalid number of intervals")
  nb <- as.integer(breaks + 1)
  dx <- diff(rx <- range(x, na.rm = TRUE))
  if (dx == 0) 
    dx <- if (rx[1L] != 0) 
      abs(rx[1L])
    else 1
    breaks <- seq.int(rx[1L] - dx/1000, rx[2L] + dx/1000, 
                      length.out = nb)
  
  else 
    breaks <- seq.int(rx[1L], rx[2L], length.out = nb)
    breaks[c(1L, nb)] <- c(rx[1L] - dx/1000, rx[2L] + 
                             dx/1000)
  

使用之前的xbreaks = 15,您可以看到如何引入负数:

breaks <- 15
nb <- as.integer(breaks + 1)
dx <- diff(rx <- range(x, na.rm = TRUE))
if (dx == 0) 
  dx <- if (rx[1L] != 0) 
    abs(rx[1L])
  else 1
  breaks <- seq.int(rx[1L] - dx/1000, rx[2L] + dx/1000, 
                    length.out = nb)
 else 
  breaks <- seq.int(rx[1L], rx[2L], length.out = nb)
  breaks[c(1L, nb)] <- c(rx[1L] - dx/1000, rx[2L] + dx/1000)

breaks
# [1]   -0.16300  10.86667  21.73333  32.60000  43.46667  54.33333  65.20000  76.06667  86.93333  97.80000 108.66667 119.53333 130.40000
# [14] 141.26667 152.13333 163.16300

levels(cut(x, breaks = 15))
# [1] "(-0.163,10.9]" "(10.9,21.7]"   "(21.7,32.6]"   "(32.6,43.5]"   "(43.5,54.3]"   "(54.3,65.2]"   "(65.2,76.1]"   "(76.1,86.9]"  
# [9] "(86.9,97.8]"   "(97.8,109]"    "(109,120]"     "(120,130]"     "(130,141]"     "(141,152]"     "(152,163]"    

【讨论】:

感谢您的详细回复!目前这有点超出我的编码技能水平,所以我将保存它以供将来参考。您知道是否有一种方法可以像 Excel 数据透视表那样显示每个单独观察的计数在列总数中的百分比? @EastBeast 我不知道 excel 数据透视表的外观,您可以使用 paste0(round(proportions(table(DOM_Groups)) * 100), '%') 之类的东西获得百分比【参考方案2】:

这是我的santoku 包的简单解决方案:

library(santoku)
gibbsMkt %>% 
  mutate(DOM_Groups = chop_width(DOM, 15, labels = lbl_dash("-")))

# then proceed as before

如果您想以特定数字开始间隔,您可以使用 start 参数到 chop_width

【讨论】:

嘿,这个包真的很有帮助!谢谢你。有没有办法对组中的最后一个数字设置限制? 如果您想同时控制开始和结束,请执行chop(DOM, seq(start, end, 15), ...)。但我会考虑功能请求。

以上是关于将 dplyr 函数 group_by() 与 cut() 一起使用的主要内容,如果未能解决你的问题,请参考以下文章

R语言dplyr包使用dplyr函数使用group_by函数summarise函数和mutate函数计算分组占比实战

R语言dplyr包使用group_by函数和summarise函数构建频率表实战

在 R 中使用 dplyr 在 group_by 之后应用自定义函数

dplyr:在 group_by 之后汇总内部的管道

R语言dplyr包使用group_by函数arrange函数和filter函数获取每个分组的第一个第N个最后一个记录实战

使用 group_by(多个变量)时的 dplyr 问题