File Coverage

blib/lib/Statistics/Running/Tiny.pm
Criterion Covered Total %
statement 116 123 94.3
branch 12 16 75.0
condition 5 8 62.5
subroutine 27 28 96.4
pod 18 23 78.2
total 178 198 89.9


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