【问题标题】:How to count rows with conditional after grouping in data.table在data.table中分组后如何计算有条件的行
【发布时间】:2017-06-21 02:34:53
【问题描述】:

我有以下数据框:

dat <- read_csv(
  "s1,s2,v1,v2
   a,b,10,20
   a,b,22,NA
   a,b,13,33
   c,d,3,NA
   c,d,4.5,NA
   c,d,10,20"
)

dat
#> # A tibble: 6 x 4
#>      s1    s2    v1    v2
#>   <chr> <chr> <dbl> <int>
#> 1     a     b  10.0    20
#> 2     a     b  22.0    NA
#> 3     a     b  13.0    33
#> 4     c     d   3.0    NA
#> 5     c     d   4.5    NA
#> 6     c     d  10.0    20

我想做的是

  1. 根据v1 值过滤行
  2. s1s2 分组
  3. 统计每组的总行数
  4. 计算每个组中v2 不是NA 的行。

例如v1_filter &gt;= 0 我们得到这个:

s1 s2 total_line non_na_line
a  b     3          2
c  d     3          1

通过v1_filter &gt;= 10,我们得到了这个:

s1 s2 total_line non_na_line
a  b     2          1
c  d     1          1

如何使用 data.table 或 dplyr 实现这一目标? 实际上,dat 中有大约 3100 万行。所以我们需要 一种快速的方法。

我被这个困住了

 library(data.table)
 dat <- data.table(dat)

 v1_filter = 0
 dat[, v1 >= v1_filter, 
     by=list(s1,s2)]

【问题讨论】:

  • 这感觉就像它需要一个例子来说明你已经尝试过什么以及出了什么问题,否则它就有可能成为一个“做我的工作”的问题。扩展 dat %&gt;% group_by(s1, s2) %&gt;% count() 是一个简单的 dplyr 答案,但我不知道它的性能如何。
  • @JonathanCarroll 谢谢,但你的 dplyr 不考虑non_na_line
  • 数据表中的DT[i,j,k]代表i代表选择,j代表操作,k代表分组。你把选择放在第二位,其实应该是第一名,第二名应该是count(.N)
  • @SymbolixAU 感谢您的指出。我在我的 OP 中修改为 &gt;=10
  • @neversaint 这就是“扩展”评论相关的地方。 dat %&gt;% group_by(s1, s2) %&gt;% filter(!is.na(v2)) %&gt;% count().

标签: r data.table dplyr


【解决方案1】:

使用sum 应该会有所帮助。对逻辑向量进行操作,它将每个TRUE 视为1FALSE 视为0,因此您可以轻松地做到这一点:

dat %>%
    group_by(s1, s2) %>%
    summarise(total_lines = n(),
              non_na_line = sum(!is.na(v2)))

# A tibble: 2 x 4
# Groups:   s1 [?]
     s1    s2 total_lines non_na_line
  <chr> <chr>       <int>       <int>
1     a     b           3           2
2     c     d           3           1

您可以轻松地在group_bysummarise 之间添加过滤器,以获得您想要的。请记住,summarise 只会保留您分组依据的列。

基准测试

不管怎样,我运行了一个快速基准测试,其中包含一些与您的大小相似的测试数据。

s1charMix <- rep(letters[seq(from = 1, to = 10)], length.out = 30000000)
s2charMix <- rep(letters[seq(from = 11, to = 20)], length.out = 30000000)
s1chars <- sample(s1charMix, 30000000)
s2chars <- sample(s2charMix, 30000000)
v1Nums <- runif(30000000, min = 0, max = 20)
nomissing <- sample(1:200000,1)
int.mix <- rbinom(30000000 - nomissing, 30, 0.3)
nalist <- rep(NA, nomissing)
v2NumsNA <- sample(x = c(int.mix, nalist), 30000000)
df <- data_frame(s1 = s1chars, s2 = s2chars, v1 = v1Nums, v2 = v2NumsNA)

这应该大致复制您建议的数据的大小和类型:

df

# A tibble: 30,000,000 x 4
      s1    s2         v1    v2
   <chr> <chr>      <dbl> <int>
 1     d     s  9.2123603     7
 2     b     q 16.6638639    11
 3     g     o 18.3682028    11
 4     g     s  0.8779067     9
 5     a     s  0.0719127    10
 6     b     q 16.8809193    12
 7     h     q 15.4382455     6
 8     e     k  2.3565489    11
 9     h     p 16.4508811     9
10     d     n  2.7283823    11
# ... with 29,999,990 more rows

df %>%
    filter(is.na(v2))

# A tibble: 116,924 x 4
      s1    s2         v1    v2
   <chr> <chr>      <dbl> <int>
 1     d     r 13.1448988    NA
 2     b     o  0.2703848    NA
 3     b     t 18.8319385    NA
 4     a     s 11.6448437    NA
 5     j     m  0.5388760    NA
 6     i     k  8.7098427    NA
 7     d     s  6.1149735    NA
 8     h     p  2.5552694    NA
 9     g     r  0.9057442    NA
10     b     s 19.8886830    NA
# ... with 116,914 more rows

现在,让我们对 dplyr 操作与 data.table 进行基准测试:

### dplyr
df %>%
    filter(v1 > 10) %>%
    group_by(s1, s2) %>%
    summarise(total_lines = n(),
              non_na_line = sum(!is.na(v2)))

# A tibble: 100 x 4
# Groups:   s1 [?]
      s1    s2 total_lines non_na_line
   <chr> <chr>       <int>       <int>
 1     a     k      150327      149734
 2     a     l      149655      149062
 3     a     m      149794      149200
 4     a     n      149771      149197
 5     a     o      149495      148942
...
> system.time(df %>% filter(v1 > 10) %>% group_by(s1, s2) %>% summarise(total_lines = n(), non_na_line = sum(!is.na(v2))))
   user  system elapsed 
  1.848   0.420   2.290
> system.time(for (i in 1:100) df %>% filter(v1 > 10) %>% group_by(s1, s2) %>% summarise(total_lines = n(), non_na_line = sum(!is.na(v2))))
   user  system elapsed 
187.657  55.878 245.528 

### Data.table
library(data.table)
dat <- data.table(df)
> dat[v1 > 10, .N, by = .(s1, s2)][dat[v1 > 10 & !is.na(v2), .N, by = .(s1, s2)] , on = c("s1", "s2") , nomatch = 0]
 s1 s2      N    i.N
  1:  b  q 149968 149348
  2:  g  o 150411 149831
  3:  h  q 150132 149563
  4:  h  p 150786 150224
  5:  e  o 149951 149353
 ...
> system.time(dat[v1 > 10, .N, by = .(s1, s2)][dat[v1 > 10 & !is.na(v2), .N, by = .(s1, s2)] , on = c("s1", "s2") , nomatch = 0])
   user  system elapsed 
  2.027   0.228   2.271
> system.time(for (i in 1:100) dat[v1 > 10, .N, by = .(s1, s2)][dat[v1 > 10 & !is.na(v2), .N, by = .(s1, s2)] , on = c("s1", "s2") , nomatch = 0])
   user  system elapsed 
213.281  43.949 261.664

TL;DR dplyrdata.table 同样快,如果有的话 dplyr 稍微快一点

【讨论】:

    【解决方案2】:
    > library(readr)
    > dat <- read_csv(
    +   "s1,s2,v1,v2
    +    a,b,10,20
    +    a,b,22,NA
    +    a,b,13,33
    +    c,d,3,NA
    +    c,d,4.5,NA
    +    c,d,10,20"
    + )
    > 
    > dat
    # A tibble: 6 x 4
         s1    s2    v1    v2
      <chr> <chr> <dbl> <int>
    1     a     b  10.0    20
    2     a     b  22.0    NA
    3     a     b  13.0    33
    4     c     d   3.0    NA
    5     c     d   4.5    NA
    6     c     d  10.0    20
    

    因为你有大数据,所以使用 data.table

    > library(data.table)
    data.table 1.10.4
      The fastest way to learn (by data.table authors): https://www.datacamp.com/courses/data-analysis-the-data-table-way
      Documentation: ?data.table, example(data.table) and browseVignettes("data.table")
      Release notes, videos and slides: http://r-datatable.com
    > dat=data.table(dat)
    

    不移除 NA 并保持 V1 过滤器为 0.1

    > dat1=dat[v1>0.1,.N,.(s1,s2)]
    > dat1
       s1 s2 N
    1:  a  b 3
    2:  c  d 3
    

    移除 v2 NA 并将 V1 过滤器保持为 0.1

    > dat2=dat[v1>0.1&is.na(v2)==F,.N,.(s1,s2)]
    > dat2
       s1 s2 N
    1:  a  b 2
    2:  c  d 1
    

    合并两者,保持V1过滤器为0

     > dat[v1 > 0, .N, by = .(s1, s2)][ dat[v1 > 0 & !is.na(v2), .N, by = .(s1, s2)] , on = c("s1", "s2") , nomatch = 0 ]
           s1 s2 N i.N
        1:  a  b 3   2
        2:  c  d 3   1
    

    【讨论】:

    • 谢谢。如何修改您的代码以使输出具有我的 OP 中的形式?
    • @neversaint 加入最后两条语句dat[v1 &gt; 0, .N, by = .(s1, s2)][ dat[v1 &gt; 0 &amp; !is.na(v2), .N, by = .(s1, s2)] , on = c("s1", "s2") , nomatch = 0 ]
    猜你喜欢
    • 2015-03-21
    • 1970-01-01
    • 1970-01-01
    • 2022-11-11
    • 2016-06-11
    • 2022-07-19
    • 2014-04-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多