R语言 是否有table()的有效替代方法?

iaqfqrcu  于 2023-01-28  发布在  其他
关注(0)|答案(7)|浏览(157)

我使用以下命令:

table(factor("list",levels=1:"n")

与“列表”:(例如)a = c(1,3,4,4,3)levels = 1:5,也要考虑到2和5。对于真正大的数据集,我的代码似乎非常无效。
有人知道一个隐藏的库或代码片段,使它更快吗?

bvjxkvbb

bvjxkvbb1#

我们可以从collapse中使用fnobs,这将是高效的

library(collapse)
fnobs(df, g = df$X1)

base R中,tabulate的效率高于table

tabulate(df$X1)
 [1]  9  6 15 13 11  9  7  9 11 10
jecbmhm3

jecbmhm32#

我们也可以使用janitor::tabyl

library(janitor)

df %>%
  tabyl(X1) %>%
  adorn_totals()

    X1   n percent
     1   9    0.09
     2   6    0.06
     3  15    0.15
     4  13    0.13
     5  11    0.11
     6   9    0.09
     7   7    0.07
     8   9    0.09
     9  11    0.11
    10  10    0.10
 Total 100    1.00
mi7gmzs6

mi7gmzs63#

这并不是你要找的,但也许你可以用这个:

library(dplyr)
set.seed(8192)

df <- data.frame(X1 = sample(1:10, 100, replace = TRUE))

df %>% 
  count(X1)

退货

X1  n
1   1  9
2   2  6
3   3 15
4   4 13
5   5 11
6   6  9
7   7  7
8   8  9
9   9 11
10 10 10

如果需要计算更多数字(包括缺失的数字),可以使用

library(tidyr)
library(dplyr)

df2 <- data.frame(X1 = 1:12)

df %>% 
  count(X1) %>% 
  right_join(df2, by="X1") %>% 
  mutate(n = replace_na(n, 0L))

得到

X1  n
1   1  9
2   2  6
3   3 15
4   4 13
5   5 11
6   6  9
7   7  7
8   8  9
9   9 11
10 10 10
11 11  0
12 12  0
dfuffjeb

dfuffjeb4#

TL;DR赢家是base::tabulate
总而言之,基本目标是性能,所以我准备了一个microbenchmark的所有提供的解决方案。我使用小和大向量,两种不同的场景。对于collapse包在我的机器上,我必须下载最新的Rcpp包1.0.7(以抑制崩溃)。甚至由我添加Rcpp solution是慢于base::tabulate

suppressMessages(library(janitor))
suppressMessages(library(collapse))
suppressMessages(library(dplyr))
suppressMessages(library(cpp11))

# source https://stackoverflow.com/questions/31001392/rcpp-version-of-tabulate-is-slower-where-is-this-from-how-to-understand
Rcpp::cppFunction('IntegerVector tabulate_rcpp(const IntegerVector& x, const unsigned max) {
    IntegerVector counts(max);
    for (auto& now : x) {
        if (now > 0 && now <= max)
            counts[now - 1]++;
    }
    return counts;
}')

set.seed(1234)

a = c(1,3,4,4,3)
levels = 1:5
df <- data.frame(X1 = a)

microbenchmark::microbenchmark(tabulate_rcpp = {tabulate_rcpp(df$X1, max(df$X1))},
                               base_table = {base::table(factor(df$X1, 1:max(df$X1)))},
                               stats_aggregate = {stats::aggregate(. ~ X1, cbind(df, n = 1), sum)},
                               graphics_hist = {hist(df$X1, plot = FALSE, right = FALSE)[c("breaks", "counts")]},
                               janitor_tably = {adorn_totals(tabyl(df, X1))},
                               collapse_fnobs = {fnobs(df, df$X1)},
                               base_tabulate = {tabulate(df$X1)},
                               dplyr_count = {count(df, X1)})
#> Unit: microseconds
#>             expr      min        lq       mean    median        uq       max
#>    tabulate_rcpp    2.959    5.9800   17.42326    7.9465    9.5435   883.561
#>       base_table   48.524   59.5490   72.42985   66.3135   78.9320   153.216
#>  stats_aggregate  829.324  891.7340 1069.86510  937.4070 1140.0345  2883.025
#>    graphics_hist  148.561  170.5305  221.05290  188.9570  228.3160   958.619
#>    janitor_tably 6005.490 6439.6870 8137.82606 7497.1985 8283.3670 53352.680
#>   collapse_fnobs   14.591   21.9790   32.63891   27.2530   32.6465   417.987
#>    base_tabulate    1.879    4.3310    5.68916    5.5990    6.6210    16.789
#>      dplyr_count 1832.648 1969.8005 2546.17131 2350.0450 2560.3585  7210.992
#>  neval
#>    100
#>    100
#>    100
#>    100
#>    100
#>    100
#>    100
#>    100

df <- data.frame(X1 = sample(1:5, 1000, replace = TRUE))

microbenchmark::microbenchmark(tabulate_rcpp = {tabulate_rcpp(df$X1, max(df$X1))},
                               base_table = {base::table(factor(df$X1, 1:max(df$X1)))},
                               stats_aggregate = {stats::aggregate(. ~ X1, cbind(df, n = 1), sum)},
                               graphics_hist = {hist(df$X1, plot = FALSE, right = FALSE)[c("breaks", "counts")]},
                               janitor_tably = {adorn_totals(tabyl(df, X1))},
                               collapse_fnobs = {fnobs(df, df$X1)},
                               base_tabulate = {tabulate(df$X1)},
                               dplyr_count = {count(df, X1)})
#> Unit: microseconds
#>             expr      min        lq       mean    median        uq       max
#>    tabulate_rcpp    4.847    8.8465   10.92661   10.3105   12.6785    28.407
#>       base_table   83.736  107.2040  121.77962  118.8450  129.9560   184.427
#>  stats_aggregate 1027.918 1155.9205 1338.27752 1246.6205 1434.8990  2085.821
#>    graphics_hist  209.273  237.8265  274.60654  258.9260  300.3830   523.803
#>    janitor_tably 5988.085 6497.9675 7833.34321 7593.3445 8422.6950 13759.142
#>   collapse_fnobs   26.085   38.6440   51.89459   47.8250   57.3440   333.034
#>    base_tabulate    4.501    6.7360    8.09408    8.2330    9.2170    11.463
#>      dplyr_count 1852.290 2000.5225 2374.28205 2145.9835 2516.7940  4834.544
#>  neval
#>    100
#>    100
#>    100
#>    100
#>    100
#>    100
#>    100
#>    100

reprex package(v2.0.0)于2021年8月1日创建

wa7juj8i

wa7juj8i5#

使用aggregate的基本R期权(从@Martin Gal借用df

> aggregate(. ~ X1, cbind(df, n = 1), sum)
   X1  n
1   1  9
2   2  6
3   3 15
4   4 13
5   5 11
6   6  9
7   7  7
8   8  9
9   9 11
10 10 10

另一个选项是使用hist

> hist(df$X1, plot = FALSE, right = FALSE)[c("breaks", "counts")]
$breaks
 [1]  1  2  3  4  5  6  7  8  9 10

$counts
[1]  9  6 15 13 11  9  7  9 21
drnojrws

drnojrws6#

这里还有一个:summarytools
数据来自Martin Gal!非常感谢:

library(summarytools)

set.seed(8192)
df <- data.frame(X1 = sample(1:10, 100, replace = TRUE))

summarytools::freq(df$X1, cumul=FALSE)

输出:

Freq   % Valid   % Total
----------- ------ --------- ---------
          1      9      9.00      9.00
          2      6      6.00      6.00
          3     15     15.00     15.00
          4     13     13.00     13.00
          5     11     11.00     11.00
          6      9      9.00      9.00
          7      7      7.00      7.00
          8      9      9.00      9.00
          9     11     11.00     11.00
         10     10     10.00     10.00
       <NA>      0                0.00
      Total    100    100.00    100.00
0wi1tuuw

0wi1tuuw7#

如果需要table()更快的替代方法,包括交叉制表,则collapse::qtab()(自v1.8.0(2022年5月)起可用)是可靠且明显更快的替代方法。fcount()也可用于单变量情况,并返回data.frame。

library(collapse) # > v1.8.0, and > 1.9.0 for fcount()
library(microbenchmark)
v = sample(10000, 1e6, TRUE)

microbenchmark(qtab(v, sort = FALSE), fcount(v), tabulate(v), times = 10)
Unit: milliseconds
                  expr      min       lq     mean   median       uq      max neval
 qtab(v, sort = FALSE) 1.911707 1.945245 2.002473 1.963654 2.027942 2.207891    10
             fcount(v) 1.885549 1.906746 1.978894 1.932310 2.103997 2.138027    10
           tabulate(v) 2.321543 2.323716 2.333839 2.328206 2.334499 2.372506    10

v2 = sample(10000, 1e6, TRUE)
microbenchmark(qtab(v, v2), qtab(v, v2, sort = FALSE), table(v, v2), times = 10)
Unit: milliseconds
                      expr       min        lq      mean   median        uq      max neval
               qtab(v, v2)  45.61279  51.14840  74.16168  60.7761  72.86385 157.6501    10
 qtab(v, v2, sort = FALSE)  41.30812  49.66355  57.02565  51.3568  54.69859 118.1289    10
              table(v, v2) 281.60079 282.85273 292.48119 286.0535 288.19253 349.5513    10

话虽如此,就C代码而言,tabulate()的速度几乎和它的速度一样快,但它有一个明确的警告,那就是它根本不散列值,而是确定最大值并分配一个该长度的结果向量,将其用作计算值的表。

v[10] = 1e7L # Adding a random large value here
length(tabulate(v))
[1] 10000000
length(table(v))
[1] 10001
length(qtab(v))
[1] 10001

因此,您得到的结果向量中包含699万个零,而您的性能会下降

microbenchmark(qtab(v, sort = FALSE), fcount(v), tabulate(v), times = 10)
Unit: milliseconds
                  expr      min       lq     mean   median       uq       max neval
 qtab(v, sort = FALSE) 1.873249 1.900473 1.966721 1.923064 2.064186  2.126588    10
             fcount(v) 1.829338 1.850330 1.926676 1.880199 2.021013  2.057667    10
           tabulate(v) 4.207789 4.357439 5.066296 4.417012 4.558216 10.347744    10

有鉴于此,qtab()实际上确实散列了每个值并实现了这样的性能,这一事实相当了不起。

相关问题