File Coverage

blib/lib/Statistics/Running/Tiny.pm
Criterion Covered Total %
statement 127 135 94.0
branch 14 20 70.0
condition 5 8 62.5
subroutine 28 30 93.3
pod 20 25 80.0
total 194 218 88.9


line stmt bran cond sub pod time code
1             package Statistics::Running::Tiny;
2              
3 2     2   70031 use 5.006;
  2         13  
4 2     2   11 use strict;
  2         4  
  2         53  
5 2     2   11 use warnings;
  2         4  
  2         170  
6              
7             our $VERSION = '0.03';
8              
9             use overload
10 2         18 '+' => \&concatenate,
11             '==' => \&equals,
12             '""' => \&stringify,
13 2     2   2435 ;
  2         1982  
14              
15 2     2   179 use constant SMALL_NUMBER_FOR_EQUALITY => 1E-10;
  2         4  
  2         3439  
16              
17             # creates an obj. There are no input params
18             sub new {
19 7     7 1 103 my $class = $_[0];
20              
21 7   100     41 my $parent = ( caller(1) )[3] || "N/A";
22 7         41 my $whoami = ( caller(0) )[3];
23              
24 7         49 my $self = {
25             # these are internal variables to store mean etc. or used to calculate Kurtosis
26             'M1' => 0.0,
27             'M2' => 0.0,
28             'M3' => 0.0,
29             'M4' => 0.0,
30             'SUM' => 0.0,
31             'ABS-SUM' => 0.0,
32             'MIN' => 0.0,
33             'MAX' => 0.0,
34             'N' => 0, # number of data items inserted
35             };
36 7         18 bless($self, $class);
37 7         21 $self->clear();
38 7         17 return $self
39             }
40             # push Data: a sample and process/update mean and all other stat measures
41             sub add {
42 505     505 1 992 my $self = $_[0];
43 505         640 my $x = $_[1];
44              
45 505         746 my $aref = ref($x);
46              
47 505 100       876 if( $aref eq '' ){
    50          
48             # a scalar input
49 502         694 my ($delta, $delta_n, $delta_n2, $term1);
50 502         732 my $n1 = $self->{'N'};
51 502 100       756 if( $n1 == 0 ){ $self->{'MIN'} = $self->{'MAX'} = $x }
  4         10  
52             else {
53 498 100       911 if( $x < $self->{'MIN'} ){ $self->{'MIN'} = $x }
  13         23  
54 498 100       867 if( $x > $self->{'MAX'} ){ $self->{'MAX'} = $x }
  104         154  
55             }
56 502         691 $self->{'SUM'} += $x; # add x to the total SUM
57 502         700 $self->{'ABS-SUM'} += abs($x); # add abs-value x to the total ABS-SUM
58 502         674 $self->{'N'} += 1; # increment sample size push in
59 502         683 my $n0 = $self->{'N'};
60              
61 502         727 $delta = $x - $self->{'M1'};
62 502         710 $delta_n = $delta / $n0;
63 502         630 $delta_n2 = $delta_n * $delta_n;
64 502         711 $term1 = $delta * $delta_n * $n1;
65 502         705 $self->{'M1'} += $delta_n;
66             $self->{'M4'} += $term1 * $delta_n2 * ($n0*$n0 - 3*$n0 + 3)
67             + 6 * $delta_n2 * $self->{'M2'}
68 502         994 - 4 * $delta_n * $self->{'M3'}
69             ;
70             $self->{'M3'} += $term1 * $delta_n * ($n0 - 2)
71 502         857 - 3 * $delta_n * $self->{'M2'}
72             ;
73 502         892 $self->{'M2'} += $term1;
74             } elsif( $aref eq 'ARRAY' ){
75             # an array input
76 3         8 foreach (@$x){ $self->add($_) }
  302         493  
77             } else {
78 0         0 die "add(): only ARRAY and SCALAR can be handled (input was type '$aref')."
79             }
80             }
81             # copies input(=src) Running obj into current/self overwriting our data, this is not a clone()!
82             sub copy_from {
83 1     1 1 5 my $self = $_[0];
84 1         2 my $src = $_[1];
85 1         12 $self->{'M1'} = $src->M1();
86 1         3 $self->{'M2'} = $src->M2();
87 1         2 $self->{'M3'} = $src->M3();
88 1         3 $self->{'M4'} = $src->M4();
89 1         3 $self->set_N($src->get_N());
90             }
91             # clones current obj into a new Running obj with same values
92             sub clone {
93 1     1 1 3 my $self = $_[0];
94 1         4 my $newO = Statistics::Running::Tiny->new();
95 1         4 $newO->{'M1'} = $self->M1();
96 1         3 $newO->{'M2'} = $self->M2();
97 1         3 $newO->{'M3'} = $self->M3();
98 1         3 $newO->{'M4'} = $self->M4();
99 1         3 $newO->set_N($self->get_N());
100 1         3 return $newO
101             }
102             # clears all data entered/calculated including histogram
103             sub clear {
104 9     9 1 15 my $self = $_[0];
105 9         41 $self->{'M1'} = 0.0;
106 9         16 $self->{'M2'} = 0.0;
107 9         15 $self->{'M3'} = 0.0;
108 9         15 $self->{'M4'} = 0.0;
109 9         13 $self->{'MIN'} = 0.0;
110 9         16 $self->{'MAX'} = 0.0;
111 9         11 $self->{'SUM'} = 0.0;
112 9         16 $self->{'ABS-SUM'} = 0.0;
113 9         13 $self->{'N'} = 0;
114             }
115             # return the mean of the data entered so far
116 4     4 1 28 sub mean { return $_[0]->{'M1'} }
117 3     3 1 8 sub sum { return $_[0]->{'SUM'} }
118 0     0 1 0 sub abs_sum { return $_[0]->{'ABS-SUM'} }
119 4     4 1 14 sub min { return $_[0]->{'MIN'} }
120 4     4 1 13 sub max { return $_[0]->{'MAX'} }
121             # get number of total elements entered so far
122 18     18 1 56 sub get_N { return $_[0]->{'N'} }
123             sub variance {
124 4     4 1 7 my $self = $_[0];
125 4         6 my $m = $self->{'N'};
126 4 50       23 if( $m == 1 ){ return 0 }
  0         0  
127 4         21 return $self->{'M2'}/($m-1.0)
128             }
129 4     4 1 12 sub standard_deviation { return sqrt($_[0]->variance()) }
130             sub skewness {
131 3     3 1 5 my $self = $_[0];
132 3         7 my $m = $self->{'M2'};
133 3 50       7 if( $m == 0 ){ return 0 }
  3         49  
134             return sqrt($self->{'N'})
135 0         0 * $self->{'M3'} / ($m ** 1.5)
136             ;
137             }
138             sub kurtosis {
139 4     4 1 8 my $self = $_[0];
140 4         7 my $m = $self->{'M2'};
141 4 50       12 if( $m == 0 ){ return 0 }
  4         24  
142             return $self->{'N'}
143 0         0 * $self->{'M4'}
144             / ($m * $m)
145             - 3.0
146             ;
147             }
148             # concatenates another Running obj with current
149             # returns a new Running obj with concatenated stats
150             # input objs are not modified.
151             sub concatenate {
152 2     2 1 10 my $self = $_[0]; # us
153 2         3 my $other = $_[1]; # another Running obj
154              
155 2         6 my $combined = Statistics::Running::Tiny->new();
156              
157 2         5 my $selfN = $self->get_N();
158 2         3 my $otherN = $other->get_N();
159 2         4 my $selfM2 = $self->M2();
160 2         5 my $otherM2 = $other->M2();
161 2         5 my $selfM3 = $self->M3();
162 2         3 my $otherM3 = $other->M3();
163              
164 2         4 my $combN = $selfN + $otherN;
165 2         6 $combined->set_N($combN);
166            
167 2         3 my $delta = $other->M1() - $self->M1();
168 2         4 my $delta2 = $delta*$delta;
169 2         4 my $delta3 = $delta*$delta2;
170 2         3 my $delta4 = $delta2*$delta2;
171              
172 2         5 $combined->{'M1'} = ($selfN*$self->M1() + $otherN*$other->M1()) / $combN;
173              
174 2         14 $combined->{'M2'} = $selfM2 + $otherM2 +
175             $delta2 * $selfN * $otherN / $combN;
176            
177 2         16 $combined->{'M3'} = $selfM3 + $otherM3 +
178             $delta3 * $selfN * $otherN * ($selfN - $otherN)/($combN*$combN) +
179             3.0*$delta * ($selfN*$otherM2 - $otherN*$selfM2) / $combN
180             ;
181            
182 2         13 $combined->{'M4'} = $self->{'M4'} + $other->{'M4'}
183             + $delta4*$selfN*$otherN * ($selfN*$selfN - $selfN*$otherN + $otherN*$otherN) /
184             ($combN*$combN*$combN)
185             + 6.0*$delta2 * ($selfN*$selfN*$otherM2 + $otherN*$otherN*$selfM2)/($combN*$combN) +
186             4.0*$delta*($selfN*$otherM3 - $otherN*$selfM3) / $combN
187             ;
188              
189 2         5 $combined->{'SUM'} = $self->{'SUM'} + $other->{'SUM'};
190 2         5 $combined->{'ABS-SUM'} = $self->{'ABS-SUM'} + $other->{'ABS-SUM'};
191 2 50       8 $combined->{'MIN'} = $self->{'MIN'} < $other->{'MIN'} ? $self->{'MIN'} : $other->{'MIN'};
192 2 50       12 $combined->{'MAX'} = $self->{'MAX'} > $other->{'MAX'} ? $self->{'MAX'} : $other->{'MAX'};
193            
194 2         14 return $combined;
195             }
196             # appends another Running obj INTO current
197             # current obj (self) IS MODIFIED
198             sub append {
199 0     0 1 0 my $self = $_[0]; # us
200 0         0 my $other = $_[1]; # another Running obj
201 0         0 $self->copy_from($self+$other);
202             }
203             # equality only wrt to stats BUT NOT histogram
204             sub equals {
205 4     4 1 13 my $self = $_[0]; # us
206 4         7 my $other = $_[1]; # another Running obj
207             return
208 4   66     8 $self->get_N() == $other->get_N() &&
209             $self->equals_statistics($other)
210             }
211             sub equals_statistics {
212 5     5 1 19 my $self = $_[0]; # us
213 5         7 my $other = $_[1]; # another Running obj
214             return
215 5   33     11 abs($self->M1()-$other->M1()) < Statistics::Running::Tiny::SMALL_NUMBER_FOR_EQUALITY &&
216             abs($self->M2()-$other->M2()) < Statistics::Running::Tiny::SMALL_NUMBER_FOR_EQUALITY &&
217             abs($self->M3()-$other->M3()) < Statistics::Running::Tiny::SMALL_NUMBER_FOR_EQUALITY &&
218             abs($self->M4()-$other->M4()) < Statistics::Running::Tiny::SMALL_NUMBER_FOR_EQUALITY
219             }
220             # print object as a string, string concat/printing is overloaded on this method
221             sub stringify {
222 3     3 1 15 my $self = $_[0];
223 3         7 return "N: ".$self->get_N()
224             .", mean: ".$self->mean()
225             .", range: ".$self->min()." to ".$self->max()
226             .", sum: ".$self->sum()
227             .", standard deviation: ".$self->standard_deviation()
228             .", kurtosis: ".$self->kurtosis()
229             .", skewness: ".$self->skewness()
230             }
231             # internal methods, no need for anyone to know or use externally
232 4     4 0 10 sub set_N { $_[0]->{'N'} = $_[1] }
233 20     20 0 54 sub M1 { return $_[0]->{'M1'} }
234 16     16 0 43 sub M2 { return $_[0]->{'M2'} }
235 16     16 0 53 sub M3 { return $_[0]->{'M3'} }
236 12     12 0 46 sub M4 { return $_[0]->{'M4'} }
237              
238             1;
239             __END__