File Coverage

blib/lib/App/St.pm
Criterion Covered Total %
statement 78 91 85.7
branch 29 52 55.7
condition 11 16 68.7
subroutine 18 18 100.0
pod 11 15 73.3
total 147 192 76.5


line stmt bran cond sub pod time code
1             package App::St;
2              
3 9     9   156027 use strict;
  9         25  
  9         365  
4 9     9   51 use warnings;
  9         19  
  9         12217  
5              
6             #use bignum;
7              
8             our $VERSION = '1.1.3';
9              
10             sub new {
11 8     8 1 1056 my ($class, %opt) = @_;
12              
13 8   50     66 my $delimiter = $opt{'delimiter'} || "\t";
14 8   100     50 my $format = $opt{'format'} || '%.2f';
15              
16 8 50       35 if ($delimiter =~ /^\\[a-z]$/) {
17 0 0       0 $delimiter = $delimiter eq '\t' ? "\t"
    0          
18             : $delimiter eq '\n' ? "\n"
19             : die "Invalid delimiter: '$delimiter'\n";
20             }
21              
22 8 50       71 if ($format =~ m{( \s* \% [\s+-]? [0-9]*\.?[0-9]* [deEfgGi] \s* )}x) {
23 8         32 $format = $1;
24             } else {
25 0         0 die "Invalid format: '$format'\n";
26             }
27              
28 8         252 return bless {
29             %opt,
30             N => 0,
31             sum => 0,
32             sum_square => 0,
33             mean => 0,
34             stddev => 0,
35             stderr => 0,
36             min => undef,
37             q1 => 0,
38             median => 0,
39             q3 => 0,
40             max => undef,
41             M2 => 0,
42             delimiter => $delimiter,
43             format => $format,
44             data => [],
45             }, $class;
46             }
47              
48             sub validate {
49 91     91 1 11004 my ($self, $num) = @_;
50              
51 91         467 return ($num =~ m{^
52             [+-]?
53             (?: \. ? [0-9]+
54             | [0-9]+ \. [0-9]*
55             | [0-9]* \. ? [0-9]+ [Ee] [+-]? [0-9]+
56             )
57             $}x);
58             }
59              
60             sub process {
61 60     60 1 227 my ($self, $num) = @_;
62              
63 60 50       131 die "Invalid input '$num'\n" if !$self->validate($num);
64              
65 60         136 $self->{N}++;
66              
67 60         83 $self->{sum} += $num;
68              
69 60 100 100     273 $self->{min} = $num if (!defined $self->{min} or $num < $self->{min});
70 60 100 100     233 $self->{max} = $num if (!defined $self->{max} or $num > $self->{max});
71              
72 60         109 my $delta = $num - $self->{mean};
73              
74 60         108 $self->{mean} += $delta / $self->{N};
75 60         118 $self->{M2} += $delta * ($num - $self->{mean});
76              
77 60 100       125 push( @{ $self->{data} }, $num ) if $self->{keep_data};
  30         65  
78              
79 60         110 return;
80             }
81              
82             sub N {
83 16     16 1 45 return $_[0]->{N};
84             }
85              
86             sub sum {
87 3     3 1 15 return $_[0]->{sum};
88             }
89              
90             sub min {
91 3     3 1 15 return $_[0]->{min};
92             }
93              
94             sub max {
95 3     3 1 12 return $_[0]->{max};
96             }
97              
98             sub mean {
99 3     3 1 11 my ($self,%opt) = @_;
100              
101 3         9 my $mean = $self->{mean};
102              
103 3 100       13 return $opt{formatted} ? $self->_format($mean)
104             : $mean;
105             }
106              
107             sub quartile {
108 7     7 0 2775 my ($self,$q,%opt) = @_;
109 7 50       39 if ($q !~ /^[01234]$/) {
110 0         0 die "Invalid quartile '$q'\n";
111             }
112 7         35 return $self->percentile($q / 4 * 100, %opt);
113             }
114              
115             sub median {
116 1     1 1 2 my ($self,%opt) = @_;
117 1         7 return $self->percentile(50, %opt);
118             }
119              
120             sub variance {
121 5     5 0 11 my ($self,%opt) = @_;
122              
123 5         9 my $N = $self->{N};
124 5         7 my $M2 = $self->{M2};
125              
126 5 50       22 my $variance = $N > 1 ? $M2 / ($N - 1) : undef;
127              
128 5 50       16 return $opt{formatted} ? $self->_format($variance)
129             : $variance;
130             }
131              
132             sub stddev {
133 4     4 1 11 my ($self,%opt) = @_;
134              
135 4         13 my $variance = $self->variance();
136              
137 4 50       13 my $stddev = defined $variance ? sqrt($variance) : undef;
138              
139 4 100       16 return $opt{formatted} ? $self->_format($stddev)
140             : $stddev;
141             }
142              
143             sub stderr {
144 1     1 1 3 my ($self,%opt) = shift;
145              
146 1         2 my $stddev = $self->stddev();
147 1         3 my $N = $self->N();
148              
149 1 50       3 my $stderr = defined $stddev ? $stddev/sqrt($N) : undef;
150              
151 1 50       8 return $opt{formatted} ? $self->_format($stderr)
152             : $stderr;
153             }
154              
155             sub percentile {
156 12     12 0 2099 my ($self, $p, %opt) = @_;
157              
158 12         25 my $data = $self->{data};
159              
160 12 50 33     47 if (!$self->{keep_data} or scalar @{$data} == 0) {
  12         52  
161 0         0 die "Can't get percentile from empty dataset\n";
162             }
163              
164 12 50 33     68 if ($p < 0 or $p > 100) {
165 0         0 die "Invalid percentile '$p'\n";
166             }
167              
168 12 100       45 if (!$self->{_is_sorted_}) {
169 3         8 $data = [ sort {$a <=> $b} @{ $data } ];
  53         81  
  3         17  
170 3         9 $self->{data} = $data;
171 3         10 $self->{_is_sorted_} = 1;
172             }
173              
174 12         39 my $N = $self->N();
175 12         35 my $idx = ($N - 1) * $p / 100;
176              
177 12 100       58 my $percentile =
178             int($idx) == $idx ? $data->[$idx]
179             : ($data->[$idx] + $data->[$idx+1]) / 2;
180              
181 12 50       70 return $opt{formatted} ? _format($percentile)
182             : $percentile;
183             }
184              
185             sub result {
186 1     1 0 5 my $self = shift;
187              
188 1         3 my %result = (
189             N => $self->N(),
190             sum => $self->sum(),
191             mean => $self->mean(),
192             stddev => $self->stddev(),
193             stderr => $self->stderr(),
194             min => $self->min(),
195             max => $self->max(),
196             variance => $self->variance(),
197             );
198              
199 1 50       4 if ($self->{keep_data}) {
200 1         5 %result = (%result,
201             (
202             q1 => $self->quartile(1),
203             median => $self->median(),
204             q3 => $self->quartile(3),
205             )
206             );
207             }
208              
209             # the following is a hack to accept multiple percentiles/quartiles
210 1 50       7 if ( exists $self->{percentile} ) {
211             my $percentile = ref $self->{percentile} eq 'ARRAY'
212 0         0 ? [ map { $self->percentile($_) } @{ $self->{percentile} } ]
  0         0  
213 0 0       0 : $self->percentile( $self->{percentile} );
214              
215 0         0 %result = (
216             %result,
217             percentile => $percentile
218             );
219             }
220              
221 1 50       3 if (exists $self->{quartile}) {
222             my $quartile = ref $self->{quartile} eq 'ARRAY'
223 0         0 ? [ map { $self->quartile($_) } @{ $self->{quartile} } ]
  0         0  
224 0 0       0 : $self->quartile( $self->{quartile} );
225              
226 0         0 %result = (
227             %result,
228             quartile => $quartile,
229             );
230             }
231              
232 1         7 return %result;
233             }
234              
235             sub _format {
236 2     2   7 my ($self,$value,%opt) = @_;
237              
238 2         3 my $format = $self->{format};
239              
240 2         18 return sprintf( $format, $value );
241             }
242              
243             1;
244              
245             __END__