【问题标题】:R Aggregate over multiple columnsR聚合多列
【发布时间】:2020-09-10 09:27:03
【问题描述】:

我目前正在处理一个包含 75 列和大约 9500 行的大型数据框。此数据框包含 1995 年至 2019 年期间每天对多个观察点的观察结果。

编辑:来自 dput(head(df)) 的打印

> dput(head(df))
structure(list(date = structure(c(9131, 9132, 9133, 9134, 9135, 
9136), class = "Date"), x1 = c(50.75, 62.625, 57.25, 56.571, 
36.75, 39.125), x2 = c(62.25, 58.714, 49.875, 56.375, 43.25, 
41.625), x3 = c(90.25, NA, 70.125, 75.75, 83.286, 98.5), 
    x4 = c(60, 72, 68.375, 65.5, 63.25, 55.875), x5 = c(NA_real_, 
    NA_real_, NA_real_, NA_real_, NA_real_, NA_real_), xn = c(53.25, 
    61.143, 56.571, 58.571, 36.25, 44.375), year = c(1995, 1995, 1995, 1995, 
    1995, 1995), month = c(1, 1, 1, 1, 1, 1), day = c(1, 2, 3, 
    4, 5, 6)), row.names = c(NA, -6L), class = c("tbl_df", "tbl", 
"data.frame"))

数据框看起来像下面的示例:

date             x1      x2     x3       x4       x5     xn     year    month    day
  <date>       <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>
1 1995-01-01    50.8    62.2    90.2    60        NA    53.2    1995      1    1
2 1999-08-02    62.6    58.7    NA      72        NA    61.1    1999      8    2
3 2001-09-03    57.2    49.9    70.1    68.4      NA    56.6    2001      9    3
4 2008-05-04    56.6    56.4    75.8    65.5      NA    58.6    2008      5    4
5 2012-04-05    36.8    43.2    83.3    63.2      NA    36.2    2012      4    5
6 2019-12-31    39.1    41.6    98.5    55.9      NA    44.4    2019      12   31
str(df)
tibble [9,131 x 75] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
 $ date   : Date[1:9131], format: "1995-01-01" "1995-01-02" ...
 $ x1     : num [1:9131] 50.8 62.6 57.2 56.6 36.8 ...
 $ x2     : num [1:9131] 62.2 58.7 49.9 56.4 43.2 ...
   xn
 $ year   : num [1:9131] 1995 1995 1995 1995 1995 ...
 $ month  : num [1:9131] 1 1 1 1 1 1 1 1 1 1 ...
 $ day    : num [1:9131] 1 2 3 4 5 6 7 8 9 10 ...

我的目标是获得每个观察点 xn 每年超过某个限制的所有观察的计数。 到目前为止,我尝试使用 Aggregate 函数来实现这一点。

为了获得每年的平均值,我使用了以下命令:

aggregate(list(df), by=list(year=df$year), mean, na.rm=TRUE)

这很完美,我得到了每个观察点每年的平均值。

为了得到一个站的总和,我使用了以下代码

aggregate(list(x1=df$x1), by=list(year=df$year), function(x) sum(rle(x)$values>120, na.rm=TRUE))

这导致了这个打印:

   year      x1
1  1995      52
2  1996      43
3  1997      44
4  1998      42
5  1999      38
6  2000      76
7  2001      52
8  2002      58
9  2003     110
10 2004      34
11 2005      64
12 2006      46
13 2007      46
14 2008      17
15 2009      41
16 2010      30
17 2011      40
18 2012      47
19 2013      40
20 2014      21
21 2015      56
22 2016      27
23 2017      45
24 2018      22
25 2019      45

到目前为止,一切都很好。我知道我可以通过将 (..,x2=data$x2, x3=data$x3,..xn) 添加到上面代码中的列表参数来扩展代码。我试过了,它们起作用了。

但是我怎样才能一次得到它们呢?

我尝试了以下代码:

aggregate(.~(date, year, month, day), by=list(year=df$year), function(x) sum(rle(x)$values>120, na.rm=TRUE))
Fehler: Unerwartete(s) ',' in "aggregate(.~(date,"
aggregate(.~date+year+month+day, by=list(year=df$year), function(x) sum(rle(x)$values>120, na.rm=TRUE))
Fehler in as.data.frame.default(data, optional = TRUE) : 
  cannot coerce class ‘"function"’ to a data.frame
aggregate(. ~ date + year + month + day, data = df,by=list(year=df$year), function(x) sum(rle(x)$values>120, na.rm=TRUE))
Fehler in aggregate.data.frame(lhs, mf[-1L], FUN = FUN, ...) : 
  Argumente müssen dieselbe Länge haben

但不幸的是,它们都不起作用。有人可以告诉我我的错误在哪里吗?

【问题讨论】:

  • 欢迎来到 SO。您能否使您的问题可重现:以对象的形式包含一个最小数据集,例如,如果数据框为 df minimal reproducible example 和 How to Ask
  • 你好安德烈。您是在问如何对输入数据框中的列求和,但仅针对一年中至少有 120 个观测值的那些年份,或者您是否要求对至少有 120 个非一年内没有观察,还是其他什么?
  • @Peter,感谢您的建议。我添加了 dput(head(df))。
  • @LenGreski Hej Len,不,我实际上想知道一年中有多少观测值大于 120。这适用于每个站 xn。

标签: r dataframe


【解决方案1】:

这是一个使用基数R的答案,由于示例数据中没有一个数据高于120,我们设置了一个高于70的标准。

data <- structure(
     list(
          date = structure(c(9131, 9132, 9133, 9134, 9135,
                             9136), class = "Date"),
          x1 = c(50.75, 62.625, 57.25, 56.571,
                 36.75, 39.125),
          x2 = c(62.25, 58.714, 49.875, 56.375, 43.25,
                 41.625),
          x3 = c(90.25, NA, 70.125, 75.75, 83.286, 98.5),
          x4 = c(60, 72, 68.375, 65.5, 63.25, 55.875),
          x5 = c(NA_real_,
                 NA_real_, NA_real_, NA_real_, NA_real_, NA_real_),
          xn = c(53.25,
                 61.143, 56.571, 58.571, 36.25, 44.375),
          year = c(1995, 1995, 1995, 1995,
                   1995, 1995),
          month = c(1, 1, 1, 1, 1, 1),
          day = c(1, 2, 3,
                  4, 5, 6)
     ),
     row.names = c(NA,-6L),
     class = c("tbl_df", "tbl",
               "data.frame"
     ))

首先,我们创建一个包含所有包含x的列的数据子集,并根据值是否大于70将它们设置为TRUE或FALSE。

theCols <- data[,colnames(data)[grepl("x",colnames(data))]]

其次,我们将cbind() 的年份放到逻辑值矩阵中。

x_logical <- cbind(year = data$year,as.data.frame(apply(theCols,2,function(x) x > 70)))

最后,我们对除year 之外的所有列使用聚合并对列求和。

aggregate(x_logical[2:ncol(x_logical)],by = list(x_logical$year),sum,na.rm=TRUE)

...和输出:

  Group.1 x1 x2 x3 x4 x5 xn
1    1995  0  0  5  1  0  0
> 

请注意,通过使用colnames() 来提取aggregate() 函数中以xnrow() 开头的列,我们将其作为一个通用解决方案来处理不同数量的x 位置。

两个 tidyverse 解决方案

同一问题的 tidyverse 解决方案如下。它包括以下步骤。

  1. 使用 mutate()across() 来创建 x 变量的 TRUE / FALSE 版本。请注意,across() 需要 dplyr 1.0.0,该版本目前正在开发中,但将于 5 月 25 日当周发布。

  2. 使用pivot_longer() 让我们可以summarise() 进行多种测量,而无需大量复杂的代码。

  3. 对于每个x 测量,使用pivot_wider() 将数据转换回一列。

...代码是:

devtools::install_github("tidyverse/dplyr") # needed for across()
library(dplyr)
library(tidyr) 
library(lubridate) 
data %>%
     mutate(.,across(starts_with("x"),~if_else(. > 70,TRUE,FALSE))) %>%
        select(-year,-month,-day) %>% group_by(date) %>% 
        pivot_longer(starts_with("x"),names_to = "measure",values_to = "value") %>% 
        mutate(year = year(date)) %>% group_by(year,measure) %>%
        select(-date) %>% 
                summarise(value = sum(value,na.rm=TRUE)) %>%
        pivot_wider(id_cols = year,names_from = "measure",
                    values_from = value)

...以及与我最初发布的 Base R 解决方案相匹配的输出:

`summarise()` regrouping output by 'year' (override with `.groups` argument)
# A tibble: 1 x 7
# Groups:   year [1]
   year    x1    x2    x3    x4    x5    xn
  <dbl> <int> <int> <int> <int> <int> <int>
1  1995     0     0     5     1     0     0
> 

...这是另一个答案的编辑版本,也将产生与上述相同的结果。此解决方案在创建超出阈值的逻辑变量之前实现pivot_longer(),因此不需要across() 函数。另请注意,由于此处使用 120 作为阈值,并且没有任何数据符合此阈值,因此总和均为 0。

df_example %>% 
        pivot_longer(x1:x5) %>% 
        mutate(greater_120 = value > 120) %>% 
        group_by(year,name) %>% 
        summarise(sum_120 = sum(greater_120,na.rm = TRUE)) %>%
        pivot_wider(id_cols = year,names_from = "name", values_from = sum_120)

...和输出:

`summarise()` regrouping output by 'year' (override with `.groups` argument)
# A tibble: 1 x 6
# Groups:   year [1]
   year    x1    x2    x3    x4    x5
  <dbl> <int> <int> <int> <int> <int>
1  1995     0     0     0     0     0
> 

结论

像往常一样,在 R 中完成给定任务的方法有很多。根据个人喜好,可以使用 Base R 或 tidyverse 解决问题。 tidyverse 的一个怪癖是像summarise() 这样的一些操作在窄格式整洁数据上比在宽格式数据上更容易执行。因此,在 tidyverse 中工作时,精通tidyr::pivot_longer()pivot_wider() 很重要。

也就是说,随着 dplyr 1.0.0 的生产发布,RStudio 的团队继续添加有助于处理宽格式数据的功能。

【讨论】:

  • @LenGrenski,您的解决方案非常有效。我适应了我的聚合代码,现在看起来像这样: , function(x) sum(rle(x)$values>70, na.rm=TRUE)) ``` 它产生的结果与您的解决方案相同,因此非常完美。非常感谢,周末愉快!
  • @André_1090 - 感谢您的反馈,安德烈。如果您对此方法感兴趣,我刚刚发布了一个 tidyverse 解决方案。
【解决方案2】:

这应该可以解决您的问题

library(tidyverse)
library(lubridate)
df_example <- structure(list(date = structure(c(9131, 9132, 9133, 9134, 9135, 
                                                9136), class = "Date"), x1 = c(50.75, 62.625, 57.25, 56.571, 
                                                                               36.75, 39.125), x2 = c(62.25, 58.714, 49.875, 56.375, 43.25, 
                                                                                                      41.625), x3 = c(90.25, NA, 70.125, 75.75, 83.286, 98.5), 
                             x4 = c(60, 72, 68.375, 65.5, 63.25, 55.875), x5 = c(NA_real_, 
                                                                                 NA_real_, NA_real_, NA_real_, NA_real_, NA_real_), xn = c(53.25, 
                                                                                                                                           61.143, 56.571, 58.571, 36.25, 44.375), year = c(1995, 1995, 1995, 1995, 
                                                                                                                                                                                            1995, 1995), month = c(1, 1, 1, 1, 1, 1), day = c(1, 2, 3, 
                                                                                                                                                                                                                                              4, 5, 6)), row.names = c(NA, -6L), class = c("tbl_df", "tbl", 
                                                                                                                                                                                                                                                                                           "data.frame"))


df_example %>% 
  pivot_longer(x1:x5) %>% 
  mutate(greater_120 = value > 120) %>% 
  group_by(year(date)) %>% 
  summarise(sum_120 = sum(greater_120,na.rm = TRUE))

【讨论】:

  • 不,这不是我想要的。也许我在帖子中拼错了,而不是总和“计数”会更合适。对不起!所以我想计算每年每个站点有多少观测值超过 120。
  • @André_1090 - 实际上,如果我们将 name 添加到 group_by() 以及 pivot_wider() 到此代码,它可以产生您请求的输出。我没有编辑@Bruno 的代码,而是发布了一个更正的版本以及我的答案。此外,根据指定pivot_longer() 的方式,此解决方案中不需要 lubridate year() 函数。 @Bruno 随时编辑您的解决方案,然后我会更新我的答案以参考您的答案。
  • @LenGreski 没关系
猜你喜欢
  • 2015-05-17
  • 2018-06-24
  • 1970-01-01
  • 2019-10-02
  • 1970-01-01
  • 1970-01-01
  • 2021-12-02
  • 2017-08-04
  • 2016-01-22
相关资源
最近更新 更多