File Coverage

blib/lib/Stream/Aggregate/Stats.pm
Criterion Covered Total %
statement 90 93 96.7
branch 34 56 60.7
condition 7 16 43.7
subroutine 12 12 100.0
pod 0 9 0.0
total 143 186 76.8


line stmt bran cond sub pod time code
1              
2             package Stream::Aggregate::Stats;
3              
4 6     6   432 use strict;
  6         13  
  6         231  
5 6     6   33 use warnings;
  6         10  
  6         1010  
6             # use Scalar::Util;
7             require List::Util;
8             require Exporter;
9              
10             our @ISA = qw(Exporter);
11             our @EXPORT = qw(percentile median mean largest smallest dominant dominantcount standard_deviation numeric_only);
12              
13             our $ps;
14              
15             sub numeric_only
16             {
17 225     225 0 235 my ($field) = @_;
18 225 50       434 die unless $ps;
19 225 100       921 return $ps->{numeric}{$field} if $ps->{numeric}{$field};
20              
21 53 50       124 unless ($ps->{keep}->{$field}) {
22 0         0 $ps->{numeric}{$field} = [];
23             }
24              
25 6     6   31 no warnings;
  6         11  
  6         7467  
26 53 100 66     56 $ps->{numeric}{$field} = [ grep({defined($_) and ($_ <=> 0 or $_+0 eq $_) } @{$ps->{keep}->{$field}}) ];
  164         742  
  53         121  
27             }
28              
29             sub percentile
30             {
31 106     106 0 734 my ($field, $cutoff) = @_;
32 106 50 33     444 die unless $cutoff >= 0 && $cutoff <= 100;
33              
34 106 50       721 return undef unless numeric_only($field);
35              
36 106 50       128 my @sorted = sort { $a <=> $b || $a cmp $b } @{$ps->{numeric}->{$field}};
  156         359  
  106         315  
37              
38 106 100       420 return $sorted[0] unless @sorted > 1;
39              
40             # count the fences, not the fence posts
41 30         62 my $i = (@sorted -1) * $cutoff / 100;
42              
43 30         56 my $rem = $i - int($i);
44              
45             # my $c = @sorted; print "# i = $i, rem = $rem, sorted$c = @sorted\n";
46              
47 30 100       108 return $sorted[$i] unless $rem;
48              
49 16 50       55 return $sorted[$i] unless exists $sorted[$i+1];
50              
51 16 50       53 return $sorted[$i] unless $sorted[$i] > 0;
52 16 50       38 return $sorted[$i] unless $sorted[$i+1] > 0;
53              
54 16         74 return $sorted[$i] * (1-$rem) + $sorted[$i+1] * $rem;
55             }
56              
57             sub standard_deviation
58             {
59 17     17 0 86 my ($field) = @_;
60 17 50       29 return undef unless numeric_only($field);
61 17         18 my $count = scalar(@{$ps->{numeric}->{$field}});
  17         32  
62 17 100       32 return 0 unless $count;
63 15         16 my $mean = List::Util::sum(@{$ps->{numeric}->{$field}}) / $count;
  15         46  
64 15         17 my $x;
65 15         15 for my $item (@{$ps->{numeric}->{$field}}) {
  15         28  
66 59         58 my $diff = $item - $mean;
67 59         96 $x += $diff * $diff;
68             }
69 15         78 return sqrt($x / $count);
70             }
71              
72             sub median
73             {
74 96     96 0 3596 my ($field) = @_;
75 96 50       165 return undef unless numeric_only($field);
76 96         181 return percentile($field, 50);
77             }
78              
79             sub mean
80             {
81 4     4 0 48 my ($field) = @_;
82 4 50       9 return undef unless numeric_only($field);
83 4         10 my $data = $ps->{numeric}{$field};
84 4 50 33     19 return undef unless $data && @$data;
85              
86              
87 4         7 my $count = @$data;
88 4         28 my $sum = List::Util::sum(@$data);
89 4   50     19 my $side = $ps->{sidestats}{$field} || {};
90              
91 4 50       9 if ($side->{count}) {
92 0         0 return ($sum + $side->{sum}) / ($count + $side->{count});
93             } else {
94 4         45 return $sum / $count;
95             }
96             }
97              
98             sub largest
99             {
100 1     1 0 2 my ($field) = @_;
101 1 50       3 return undef unless numeric_only($field);
102 1         1 return List::Util::max(@{$ps->{numeric}->{$field}}, grep(defined $_, $ps->{sidestats}{$field}{max}));
  1         9  
103             }
104              
105             sub smallest
106             {
107 1     1 0 1 my ($field) = @_;
108 1 50       3 return undef unless numeric_only($field);
109 1         2 return List::Util::min(@{$ps->{numeric}->{$field}},grep(defined $_, $ps->{sidestats}{$field}{min}));
  1         11  
110             }
111              
112             # stats "mode"
113             sub dominant
114             {
115 1     1 0 2 my ($field) = @_;
116 1 50       4 die unless $ps;
117 1 50       4 return undef unless $ps->{keep}->{$field};
118 1         2 my %counts;
119 1         1 for my $d (@{$ps->{keep}->{$field}}) {
  1         3  
120 4 50       7 next unless defined $d;
121 4         10 $counts{$d}++;
122             }
123 1         2 my $maxcount = 0;
124 1         1 my $max;
125 1         4 for my $c (keys %counts) {
126 3 100       10 next unless $counts{$c} > $maxcount;
127 2         2 $maxcount = $counts{$c};
128 2         3 $max = $c;
129             }
130 1         4 $ps->{sidestats}{$field}{dominantcount} = $maxcount;
131 1 50       3 return ($max, $maxcount) if wantarray;
132 1         5 return $max;
133             }
134              
135             sub dominantcount
136             {
137 1     1 0 2 my ($field) = @_;
138 1 50       4 die unless $ps;
139 1         3 my $data = $ps->{keep}{$field};
140 1 50 33     7 return undef unless $data && @$data;
141 1         1 my ($max, $maxcount);
142 1 50       5 if (defined $ps->{sidestats}{$field}{dominantcount}) {
143 1         3 $maxcount = $ps->{sidestats}{$field}{dominantcount}
144             } else {
145 0         0 ($max, $maxcount) = dominant($field);
146             }
147 1         1 my $side = $ps->{sidestats}{$field};
148 1   50     8 my $multiplier = (@$data + ($side->{count} || 0)) / @$data; # for discarded data
149 1         4 return $maxcount * $multiplier;
150             }
151              
152             1;
153              
154             __END__