使用 group by 或聚合函数删除变量

Posted

技术标签:

【中文标题】使用 group by 或聚合函数删除变量【英文标题】:Removing variables with group by or aggregate functions 【发布时间】:2021-11-07 06:41:40 【问题描述】:

我正在尝试从按分类变量分组的观察子集中删除异常值。这样我就可以绘制没有异常值的箱线图,并获得新数据集的 t-stat。

我尝试使用 data.table 进行“分组”和使用列表进行聚合。但是,考虑到整个数据集,总是会删除异常值。不是来自每个子集。

这是数据集的一部分。有 40 个列变量和 62 个观测值

> dput(head(dat, 30))
structure(list(Treatment = structure(c(2L, 2L, 2L, 2L, 2L, 2L, 
2L, 2L, 2L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L), .Label = c("SHAM+vehicle", "TAC+vehicle", 
"TAC+relaxin", "TAC+Enalapril"), class = "factor"), Comparison = c("TAC(4hrs)+vehicle", 
"TAC(4hrs)+vehicle", "TAC(4hrs)+vehicle", "TAC(4hrs)+vehicle", 
"TAC(4hrs)+vehicle", "TAC(4hrs)+vehicle", "TAC(4hrs)+vehicle", 
"TAC(4hrs)+vehicle", "TAC(4hrs)+vehicle", "TAC(4hrs)+relaxin", 
"TAC(4hrs)+relaxin", "TAC(4hrs)+relaxin", "TAC(4hrs)+relaxin", 
"TAC(4hrs)+relaxin", "TAC(4hrs)+relaxin", "TAC(4hrs)+relaxin", 
"TAC(4hrs)+relaxin", "TAC(4hrs)+relaxin", "SHAM(10hrs)+vehicle", 
"SHAM(10hrs)+vehicle", "SHAM(10hrs)+vehicle", "SHAM(10hrs)+vehicle", 
"SHAM(10hrs)+vehicle", "SHAM(10hrs)+vehicle", "SHAM(10hrs)+vehicle", 
"SHAM(10hrs)+vehicle", "SHAM(10hrs)+vehicle", "TAC(10hrs)+vehicle", 
"TAC(10hrs)+vehicle", "TAC(10hrs)+vehicle"), Mode = c("Prevention", 
"Prevention", "Prevention", "Prevention", "Prevention", "Prevention", 
"Prevention", "Prevention", "Prevention", "Prevention", "Prevention", 
"Prevention", "Prevention", "Prevention", "Prevention", "Prevention", 
"Prevention", "Prevention", "Intervention", "Intervention", "Intervention", 
"Intervention", "Intervention", "Intervention", "Intervention", 
"Intervention", "Intervention", "Intervention", "Intervention", 
"Intervention"), `Adiponectin/Acrp30` = c(1300000, 650000, 650000, 
650000, 1300000, 1300000, 1300000, 1300000, 1300000, 650000, 
650000, 650000, 650000, 650000, 1300000, 1300000, 1300000, 1300000, 
650000, 650000, 650000, 650000, 1300000, 650000, 650000, 1300000, 
1300000, 650000, 1300000, 650000), CRP = c(10666575, 3785850, 
3876595, 6287075, 5612955, 4544670, 9467470, 5632695, 8817655, 
4273610, 3560300, 10077690, 6504345, 4233480, 5425300, 2193250, 
6704455, 7838805, 5144890, 3636160, 4183640, 8913940, 3345130, 
4063455, 3823415, 8426135, 5877360, 5499595, 6996230, 2830510
), `Cystatin C` = c(565000, 565000, 565000, 565000, 565000, 565000, 
565000, 565000, 565000, 565000, 565000, 565000, 565000, 565000, 
565000, 565000, 565000, 565000, 565000, 565000, 565000, 565000, 
565000, 565000, 565000, 565000, 565000, 565000, 565000, 565000
), `Endoglin/CD105` = c(5460.36, 2405.94, 2613.33, 1249.04, 3545.37, 
2152.72, 1769.2, 695.94, 956.65, 1958.48, 3842.39, 3963.14, 1288.27, 
1046.94, 1097.09, 2377.61, 1858.56, 513.67, 1200.51, 2246.9, 
2907.68, 1632.56, 892.39, 988.96, 746.25, 682.59, 327.2, 1601.98, 
361.54, 692.6), Endostatin = c(29667.6, 22750.32, 21733.44, 23829.04, 
20203.12, 14614.88, 17822.56, 23132.24, 20265.84, 17495.76, 27424.16, 
17635.44, 22257.68, 34155.44, 16857.52, 18949.6, 25434.64, 22701.36, 
18186.16, 24013.12, 14673.92, 14092.4, 26438.4, 18384.4, 19220.96, 
18781.52, 19844.08, 23242.96, 23037.2, 22040.24), `FABP4/A-FABP` = c(2389.37, 
1143.58, 862.57, 376.15, 1368.68, 649.46, 370.47, 243.43, 378.48, 
605.82, 1458.3, 588.77, 616.45, 390.36, 403.54, 603.54, 804.06, 
244.41, 1025.16, 602.67, 948.18, 292.27, 260.56, 259.61, 243.58, 
240.89, 314.22, 395.73, 304.18, 836.27), `Fas (APO-1)` = c(24.57, 
10.13, 11.63, 1.25, 14.74, 1.25, 1.25, 1.25, 1.25, 1.25, 14.63, 
6.95, 1.25, 1.25, 1.25, 1.25, 2.5, 1.25, 15.27, 5.68, 8.22, 1.25, 
1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 4.42), `FGF-21` = c(136.07, 
233.66, 63.28, 99.6, 190.43, 54.54, 141.27, 104.86, 136.07, 131.03, 
155.04, 75.54, 130.17, 191.02, 264.49, 97.75, 216.12, 204.42, 
431.37, 62.15, 90.38, 47.5, 74.84, 144.45, 88.4, 181.26, 232.14, 
128.01, 129.74, 771.73), `FGF-23` = c(244.06, 108.41, 140.06, 
168.71, 113.96, 129.91, 274.24, 135.03, 277.9, 168.71, 216.2, 
220.28, 207.95, 216.2, 129.91, 164.1, 111.2, 228.33, 276.07, 
159.42, 199.54, 145.01, 263.1, 238.22, 195.27, 124.7, 207.95, 
145.01, 51.94, 212.09)........

代码如下

dat_o = dat
setDT(dat_o)

for (j in col_names)
  
  dat_o[, (j) := lapply(.SD, function(x) ifelse(!x %in% boxplot.stats(dat_o[[j]])$out, x, NA)), 
              by = Comparison, .SDcols = j]
  


#aggregate function
aggregate(dat_o[[j]], by=list(dat_o$Comparison), 
            FUN= function(x) ifelse(!x %in% boxplot.stats(dat_o[[j]])$out, x, NA))

问题出在哪里?感谢您解决这个问题的见解和新颖的想法。

【问题讨论】:

GedaraHome,任何答案都解决了您的问题吗?如果仍有问题,也许edit您的问题会提供更多详细信息来解释还需要什么。谢谢! 感谢您的回答@r2evans。我在尝试理解编码的同时测试可能性。抱歉,测试它们需要一些时间。但我会尽快回复 您可以发布示例数据吗?请使用dput(dat) 的输出编辑问题。或者,如果 dput(head(dat, 20)) 的输出太大。请注意,这是dat,而不是dat_o 我已经添加了数据集。感谢您的合作和时间 【参考方案1】:

您在function(x) 中使用dat_o[[..]] 始终使用整个框架,而不仅仅是您打算使用的子集/组。另外,不需要使用for 循环,我们可以使用.SDcols。我会用mtcars演示:

library(data.table)
MT <- as.data.table(mtcars)
cols <- c("hp", "wt", "qsec")
MT[, (cols) := lapply(.SD, function(z) fifelse(z %in% boxplot.stats(z)$out, z[NA], z)),
    .SDcols = cols][]
#       mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb
#     <num> <num> <num> <num> <num> <num> <num> <num> <num> <num> <num>
#  1:  21.0     6 160.0   110  3.90 2.620 16.46     0     1     4     4
#  2:  21.0     6 160.0   110  3.90 2.875 17.02     0     1     4     4
#  3:  22.8     4 108.0    93  3.85 2.320 18.61     1     1     4     1
#  4:  21.4     6 258.0   110  3.08 3.215 19.44     1     0     3     1
#  5:  18.7     8 360.0   175  3.15 3.440 17.02     0     0     3     2
#  6:  18.1     6 225.0   105  2.76 3.460 20.22     1     0     3     1
#  7:  14.3     8 360.0   245  3.21 3.570 15.84     0     0     3     4
#  8:  24.4     4 146.7    62  3.69 3.190 20.00     1     0     4     2
#  9:  22.8     4 140.8    95  3.92 3.150    NA     1     0     4     2
# 10:  19.2     6 167.6   123  3.92 3.440 18.30     1     0     4     4
# 11:  17.8     6 167.6   123  3.92 3.440 18.90     1     0     4     4
# 12:  16.4     8 275.8   180  3.07 4.070 17.40     0     0     3     3
# 13:  17.3     8 275.8   180  3.07 3.730 17.60     0     0     3     3
# 14:  15.2     8 275.8   180  3.07 3.780 18.00     0     0     3     3
# 15:  10.4     8 472.0   205  2.93 5.250 17.98     0     0     3     4
# 16:  10.4     8 460.0   215  3.00    NA 17.82     0     0     3     4
# 17:  14.7     8 440.0   230  3.23    NA 17.42     0     0     3     4
# 18:  32.4     4  78.7    66  4.08 2.200 19.47     1     1     4     1
# 19:  30.4     4  75.7    52  4.93 1.615 18.52     1     1     4     2
# 20:  33.9     4  71.1    65  4.22 1.835 19.90     1     1     4     1
# 21:  21.5     4 120.1    97  3.70 2.465 20.01     1     0     3     1
# 22:  15.5     8 318.0   150  2.76 3.520 16.87     0     0     3     2
# 23:  15.2     8 304.0   150  3.15 3.435 17.30     0     0     3     2
# 24:  13.3     8 350.0   245  3.73 3.840 15.41     0     0     3     4
# 25:  19.2     8 400.0   175  3.08 3.845 17.05     0     0     3     2
# 26:  27.3     4  79.0    66  4.08 1.935 18.90     1     1     4     1
# 27:  26.0     4 120.3    91  4.43 2.140 16.70     0     1     5     2
# 28:  30.4     4  95.1   113  3.77 1.513 16.90     1     1     5     2
# 29:  15.8     8 351.0   264  4.22 3.170 14.50     0     1     5     4
# 30:  19.7     6 145.0   175  3.62 2.770 15.50     0     1     5     6
# 31:  15.0     8 301.0    NA  3.54 3.570 14.60     0     1     5     8
# 32:  21.4     4 121.0   109  4.11 2.780 18.60     1     1     4     2
#       mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb

仅供参考:我使用z[NA] 而不仅仅是NA,因为fifelse 强制要求yes=no= 参数必须严格属于相同的class;一个孤立的NA 在技术上属于logical 类(NA 至少有六种类型,仅供参考),但z[NA] 将始终返回满足fifelse 所需的适当NA 类。 (dplyr::if_else 也是如此。我认为base::ifelse 有点马虎......也许更宽容......因为不执行这一点,但如果你没有预料到或准备好了。)

(此方法也可以应用于基本方法或dplyr 方法。)

【讨论】:

嗨@r2evans,你就是那个男人!这段代码就像魔术一样工作。我必须添加 group by 子句,因为它已在您的子句中提交。一旦添加,它就可以完美运行。花了一段时间才理解它是如何工作的,实际上仍然没有完全理解。不过,谢谢! 您还应该在此处添加一个重要说明,即运算符 : = 在 R 中不再使用。它仅保留在 data.table 包中! @MarekFiołka,这不是完全正确的,并且没有严格需要添加注释:(1)OP标记为data.table,我包括@ 987654346@在答案中; (2)dplyr在元编程中使用:=,如dplyr.tidyverse.org/articles/programming.html所示。虽然 base R 没有使用它,但它不仅仅用于data.table 你最初获得了 +15 代表,然后当 OP 改变主意时,它被带走了(这看起来像是你失去了积分);由于您收到了赞成票,因此您对整个答案的评价为 +10。我只能猜测为什么 OP 选择更改接受的答案,但这是他们的特权。 好的。现在我明白了。这让我有点吃惊。好吧,PO决定。我是堆栈溢出的新手,我还不清楚所有内容。【参考方案2】:

基础 R

这是ave 的一种方式。请注意,ave 返回一个与被分组的向量相同类的向量,在本例中是一个数字向量,因此在子集中它被强制为逻辑。

i <- with(dat, ave(j, Comparison, FUN = function(x)
  !x %in% boxplot.stats(x)$out
))
dat[as.logical(i), ]

data.table

诀窍是,像上面一样,在j 上创建一个逻辑索引,按Comparison 分组,然后在该索引上创建一个子集。但索引的创建方式不同。

library(data.table)

dat_o <- dat
setDT(dat_o)

# This returns a logical index
dat_o[, sapply(.SD, function(x) !x %in% boxplot.stats(x)$out), 
      by = Comparison, .SDcols = 'j'][[2]]

现在使用索引进行子集。

dat_o[dat_o[, sapply(.SD, function(x) !x %in% boxplot.stats(x)$out), 
      by = Comparison, .SDcols = 'j'][[2]], ]

nrow(dat_o)
#[1] 200

但它并没有改变 data.table,它只选择了 TRUE 行。必须将结果分配回dat_o

dat_o <- dat_o[dat_o[, sapply(.SD, function(x) !x %in% boxplot.stats(x)$out), 
                     by = Comparison, .SDcols = 'j'][[2]], ]
nrow(dat_o)
#[1] 192

测试数据创建代码。

set.seed(2021)
n <- 100
x <- rnorm(n)
y <- rnorm(n, mean = 20)
x[sample(n, 3)] <- 11:13
y[sample(n, 3)] <- 101:103
boxplot.stats(x)$out
#[1] 13 12 11
boxplot.stats(y)$out
#[1]  17.29928  17.31704 102.00000 101.00000 103.00000

Comparison <- rep(c("A", "B"), each = n)
j <- c(x, y)
dat <- data.frame(Comparison, j)

【讨论】:

嗨瑞巴拉达斯,谢谢你的建议。我试过了,它适用于您在此处生成的数据集。但是,当我尝试将我的数据集与多个列变量一起使用时,它会将整个数据集替换为“NA”值。我使用的采用的代码如下for (j in col_names) dat_o &lt;- dat_o[dat_o[, lapply(.SD, function(x) ifelse(!x %in% boxplot.stats(dat_o[[j]])$out, x, NA)), by = Comparison, .SDcols = j][[2]],] 我很抱歉在故障排除方面很天真 顺便说一句。我使用 ifelse 函数而不是建议的函数,因为当它成为一个列变量的异常值时,我不想删除整行 @GedaraHome 看到我的comment 的问题。【参考方案3】:

也许我还会添加我的解决方案。 首先,让我们生成带有异常值的数据。

library(tidyverse)

nrow=100
ncol=10

df = tibble(group = rep(1:ncol, each=nrow) %>% factor(),
            x = sample(c(-20:20, rnorm(nrow*ncol)), nrow*ncol)) 

df %>% ggplot(aes(group, x, fill=group))+
  geom_boxplot()

现在让我们做一个小聪明的f2 函数,将我们的异常数据转换为NA

f2 = function(data) ifelse(data$x %in% boxplot.stats(data$x)$out, NA, data$x)

是时候使用我们聪明的f2函数了

df %>% group_by(group) %>% 
  nest() %>% 
  mutate(data = map(data, f2)) %>% 
  unnest(data) %>% 
  ggplot(aes(group, data, fill=group))+
  geom_boxplot()

它看起来非常优雅和简单。 或者,也许您想计算这些准备好的数据的统计数据(没有异常值)?没有更简单的了。见下文。

fstat = function(x) tibble(
  mean = mean(x, na.rm = TRUE),
  sd = sd(x, na.rm = TRUE),
  median = median(x, na.rm = TRUE)
)

df %>% group_by(group) %>% 
  nest() %>% 
  mutate(data = map(data, f2),
         stat = map(data, fstat)) %>% 
  unnest(stat)

输出

# A tibble: 10 x 5
# Groups:   group [10]
   group data            mean    sd   median
   <fct> <list>         <dbl> <dbl>    <dbl>
 1 1     <dbl [100]>  0.0140  0.886  0.0513 
 2 2     <dbl [100]>  0.0398  1.11  -0.00458
 3 3     <dbl [100]> -0.00975 1.22   0.00258
 4 4     <dbl [100]>  0.0179  1.01  -0.0242 
 5 5     <dbl [100]>  0.0859  0.928  0.160  
 6 6     <dbl [100]> -0.0374  1.01  -0.00938
 7 7     <dbl [100]> -0.0451  0.945 -0.0277 
 8 8     <dbl [100]>  0.0330  1.06  -0.0535 
 9 9     <dbl [100]>  0.103   0.964  0.0577 
10 10    <dbl [100]>  0.112   1.08   0.0610 

【讨论】:

此代码按说明工作。并且更容易理解。谢谢@Marek Fiolka

以上是关于使用 group by 或聚合函数删除变量的主要内容,如果未能解决你的问题,请参考以下文章

聚合函数或 GROUP BY 子句

在 group by 期间,我需要采用一个未在 group by 中使用的变量,我也不想采用它的聚合函数(我想要它原样)

pandas使用groupby函数按照多个分组变量进行分组聚合统计使用agg函数计算分组的多个统计指标(grouping by multiple columns in dataframe)

查询没有重复和聚合函数或 GROUP BY 子句问题。 - 重复

15group by子句与聚合函数

为啥 CROSS APPLY 与列和聚合函数需要 Group by