File Coverage

blib/lib/Statistics/SPC.pm
Criterion Covered Total %
statement 184 206 89.3
branch 64 74 86.4
condition n/a
subroutine 22 24 91.6
pod 0 21 0.0
total 270 325 83.0


line stmt bran cond sub pod time code
1             package Statistics::SPC;
2              
3 5     5   130356 use strict;
  5         12  
  5         211  
4 5     5   26 use Carp;
  5         10  
  5         500  
5 5     5   25 use vars qw($VERSION);
  5         15  
  5         10856  
6             $VERSION = "0.01";
7              
8             =head1 NAME
9              
10             Statistics::SPC - Calculations for Stastical Process Control
11              
12             =head1 DESCRIPTION
13              
14             Creates thresholds based on the variability of all data, # of samples not
15             meeting spec, and variablity within sample sets, all from training data.
16              
17             Note: this is only accurate for data which is normally distributed when
18             the process is under control
19              
20             Recommended usage: at least 15 sample sets, w/ sample size >=2 (5 is good)
21             This module is fudged to work for sample size 1, but it's a better idea
22             to use >= 2
23              
24             Important: the closer the process your are monitoring to how you would
25             like it to be running (steady state), the better the calculated control
26             limits will be.
27              
28             Example: we take 5 recordings of the CPU utilization at random intervals
29             over the course of a minute. We do this for 15 minutes, keeping all
30             fifteen samples. Using this will be able to tell whether or not
31             CPU use is in steady state.
32              
33             =head1 SYNOPSIS
34              
35             my $spc = new Statistics::SPC;
36             $spc->n(5) # set the number of samples per set
37             $spc->Uspec(.50); # CPU should not be above 50% utilization
38             $spc->Lspec(.05); # CPU should not be below 5%
39             # (0 is boring in an example)
40            
41             # Now feed training data into our object
42             $return = $spc->history($history); # "train the system";
43             # $history is ref to 2d array;
44             # $return > 1 means process not likely to
45             # meet the constraints of your specified
46             # upper and lower bounds
47            
48             # now check to see if the the latest sample of CPU util indicates
49             # CPU utilization was under control during the time of the sample
50            
51             $return = $spc->test($data); # check one sample of size n
52             # $return < 0 there is something wrong with your data
53             # $return == 0 the sample is "in control"
54             # $return > 0 there are $return problems with the sample set
55            
56              
57             =head2 Possible problems with a sample set
58              
59             =head3 The range (max - min) is not what we predicted:
60              
61             The range of the data ($self->R) greater than our calculated upper limit on
62             the intra-sample range ($self->UCLR);
63              
64             The range of the data ($self->R) less than our calculated lower limit on the
65             intra-sample range ($self->LCLR);
66              
67             =head3 The average of the sample is not what we predicited:
68              
69             The average of the sample set ($self->Xbar) is greater than our calculated
70             upper limit ($self->UCLXbar)
71              
72             The average of the sample set ($self->Xbar) is less than our calculated
73             upper limit ($self->LCLXbar)
74              
75             =head3 The number of errors is not what we predicited:
76              
77             The number of data that fall outside our specification (i.e. errors)
78             ($self->p) is greater than our calculated upate limit ($self->UCLp)
79              
80             The number of data that fall outside our specification (i.e. errors)
81             ($self->p) is less than our calculated upate limit ($self->LCLp)
82              
83             =cut
84             ############################################################################
85             # Let the code begin
86             ############################################################################
87              
88             my @d2 = (undef, 1, 1.128, 1.693, 2.059, 2.326, 2.534, 2.704, 2.847,
89             2.97, 3.078, 3.173, 3.258, 3.336, 3.407, 3.472, 3.532, 3.588, 3.64,
90             3.689, 3.735, 3.778, 3.819, 3.858, 3.895, 3.931);
91              
92             my @D3 = (undef, 0, 0, 0, 0, 0, 0, 0.076, 0.136, 0.184, 0.223, 0.256,
93             0.283, 0.307, 0.328, 0.347, 0.363, 0.378, 0.391, 0.404, 0.415, 0.425,
94             0.435, 0.443, 0.452, 0.459);
95              
96             my @D4 = (undef, 4, 3.267, 2.575, 2.282, 2.114, 2.004, 1.924, 1.864,
97             1.816, 1.777, 1.744, 1.717, 1.693, 1.672, 1.653, 1.637, 1.622, 1.609,
98             1.596, 1.585, 1.575, 1.565, 1.557, 1.548, 1.541);
99              
100             my $INFINITY = 999999999999999;
101              
102             sub new {
103 5     5 0 173 my $that = shift;
104 5         20 my $self = {};
105            
106 5         16 $self->{n} = undef;
107 5         16 $self->{Xbar} = undef;
108 5         14 $self->{Xbarbar} = undef;
109 5         15 $self->{Rbar} = undef;
110 5         14 $self->{UCLXbar} = undef;
111 5         17 $self->{LCLXbar} = undef;
112 5         13 $self->{UCLR} = undef;
113 5         19 $self->{LCLR} = undef;
114 5         15 $self->{defects} = undef;
115 5         14 $self->{p} = undef;
116 5         53 $self->{pbar} = undef;
117 5         14 $self->{Uspec} = undef;
118 5         11 $self->{Lspec} = undef;
119              
120 5         11 bless($self);
121 5         17 return($self);
122             }
123              
124             sub history {
125             # calculate all of the necessary variables based on the
126             # historical "training" data
127 6     6 0 31 my $self = shift;
128              
129             # get the history
130             # could be from DB or whatever, and do not need to store, just
131             # need to calculate over the "training" data, usu. 15+ samples
132              
133             # we'll force this to be a 2-d array, row: sample, col: sample set data
134 6         13 my $history = shift;
135 6 50       25 unless ( defined $history ) {
136 0         0 warn "history not provided as input";
137 0         0 return -1;
138             }
139            
140 6         13 my $average;
141 6         8 my $defects = 0;
142 6         10 my $history_samples = $#{$history} + 1;
  6         59  
143 6         672 my ($row, $column);
144 0         0 my $sample;
145 0         0 my $sum_defects;
146 0         0 my $sum_average;
147 0         0 my $row_range;
148              
149 6         46 for($row=0; $row < $history_samples; $row++) {
150 90         109 my $min = $INFINITY;
151 90         107 my $max = -1 * $INFINITY;
152 90         1321 for($column=0;$column<$self->n();$column++) {
153 450         24657 $sample = $history->[$row][$column];
154 450 50       971 if ( ! defined $sample ) {
155 0         0 warn "found an undefined sample value in provided history";
156 0         0 return -1;
157             }
158 450 100       1126 if ( $sample > $max ) {
159 144         168 $max = $sample;
160             }
161 450 100       770 if ( $sample < $min ) {
162 108         189 $min = $sample;
163             }
164 450 100       925 if ( $sample > $self->Uspec ) {
165 75         89 $defects += 1;
166             }
167 450 100       897 if ( $sample < $self->Lspec ) {
168 49         57 $defects += 1;
169             }
170 450         993 $average += $sample;
171             }
172 90         169 $sum_defects += $defects/$column;
173 90         128 $defects = 0;
174 90         1102 $sum_average += $average/$column;
175 90         100 $average = 0;
176 90         232 $row_range += $max - $min;
177             }
178 6         49 $self->Xbarbar($sum_average/$history_samples);
179 6         31 $self->Rbar($row_range/$history_samples);
180 6         77 $self->pbar($sum_defects/$history_samples);
181 6         22 $self->UCLXbar(
182             $self->Xbarbar()+$self->Rbar()*3/($d2[$self->n()]*sqrt($self->n()))
183             );
184 6         21 $self->LCLXbar(
185             $self->Xbarbar()-$self->Rbar()*3/($d2[$self->n()]*sqrt($self->n()))
186             );
187 6         22 $self->UCLR($D4[$self->n()] * $self->Rbar());
188 6         23 $self->LCLR($D3[$self->n()] * $self->Rbar());
189 6         17 $self->UCLp(
190             $self->pbar()+3*sqrt($self->pbar()*(1-$self->pbar())/$self->n())
191             );
192 6         23 $self->LCLp(
193             $self->pbar()-3*sqrt($self->pbar()*(1-$self->pbar())/$self->n())
194             );
195              
196 6 100       16 if ( $self->UCLXbar() > $self->Uspec() ) {
197 1         7 return 1;
198             }
199 5 100       14 if ( $self->LCLXbar() < $self->Lspec() ) {
200 1         5 return 1;
201             }
202            
203 4         23 return 0;
204             }
205              
206             sub Rbar {
207 30     30 0 42 my $self = shift;
208 30         44 my $n = shift;
209 30 100       1331 return $self->{Rbar} unless defined($n);
210 6         15 $self->{Rbar} = $n;
211 6         13 return $n;
212             }
213              
214             sub Xbarbar {
215 18     18 0 44 my $self = shift;
216 18         33 my $n = shift;
217 18 100       90 return $self->{Xbarbar} unless defined($n);
218 6         14 $self->{Xbarbar} = $n;
219 6         16 return $n;
220             }
221              
222             sub defects {
223 5     5 0 8 my $self = shift;
224 5         11 my $n = shift;
225 5 50       14 return $self->{defects} unless defined($n);
226 5         9 $self->{defects} = $n;
227 5         8 return $n;
228             }
229              
230             sub pbar {
231 42     42 0 50 my $self = shift;
232 42         48 my $n = shift;
233 42 100       194 return $self->{pbar} unless defined($n);
234 6         23 $self->{pbar} = $n;
235 6         13 return $n;
236             }
237              
238             sub p {
239 16     16 0 644 my $self = shift;
240 16         60 my $n = shift;
241 16 100       76 return $self->{p} unless defined($n);
242 5         9 $self->{p} = $n;
243 5         8 return $n;
244             }
245              
246             sub UCLpbar {
247 0     0 0 0 my $self = shift;
248 0         0 my $n = shift;
249 0 0       0 return $self->{UCLpbar} unless defined($n);
250 0         0 $self->{UCLpbar} = $n;
251 0         0 return $n;
252             }
253              
254             sub LCLpbar {
255 0     0 0 0 my $self = shift;
256 0         0 my $n = shift;
257 0 0       0 return $self->{LCLpbar} unless defined($n);
258 0         0 $self->{LCLpbar} = $n;
259 0         0 return $n;
260             }
261              
262             sub UCLp {
263 12     12 0 24 my $self = shift;
264 12         15 my $n = shift;
265 12 100       59 return $self->{UCLp} unless defined($n);
266 6         643 $self->{UCLp} = $n;
267 6         12 return $n;
268             }
269              
270             sub LCLp {
271 11     11 0 20 my $self = shift;
272 11         24 my $n = shift;
273 11 100       44 return $self->{LCLp} unless defined($n);
274 6         11 $self->{LCLp} = $n;
275 6         11 return $n;
276             }
277              
278             sub LCLXbar {
279 21     21 0 52 my $self = shift;
280 21         26 my $n = shift;
281 21 100       1015 return $self->{LCLXbar} unless defined($n);
282 6         15 $self->{LCLXbar} = $n;
283 6         11 return $n;
284             }
285              
286             sub UCLXbar {
287 22     22 0 32 my $self = shift;
288 22         31 my $n = shift;
289 22 100       118 return $self->{UCLXbar} unless defined($n);
290 6         15 $self->{UCLXbar} = $n;
291 6         16 return $n;
292             }
293              
294             sub Xbar {
295 17     17 0 995 my $self = shift;
296 17         21 my $n = shift;
297 17 100       62 return $self->{Xbar} unless defined($n);
298 5         10 $self->{Xbar} = $n;
299 5         9 return $n;
300             }
301              
302             sub LCLR {
303 11     11 0 25 my $self = shift;
304 11         15 my $n = shift;
305 11 100       43 return $self->{LCLR} unless (defined $n);
306 6         13 $self->{LCLR} = $n;
307 6         10 return $n;
308             }
309              
310             sub UCLR {
311 12     12 0 23 my $self = shift;
312 12         635 my $n = shift;
313 12 100       68 return $self->{UCLR} unless defined($n);
314 6         605 $self->{UCLR} = $n;
315 6         15 return $n;
316             }
317              
318             sub R {
319 16     16 0 2070 my $self = shift;
320 16         20 my $n = shift;
321 16 100       69 return $self->{R} unless defined($n);
322 5         17 $self->{R} = $n;
323 5         9 return $n;
324             }
325              
326             sub n {
327 640     640 0 745 my $self = shift;
328 640         646 my $n = shift;
329 640 100       2441 return $self->{n} unless defined($n);
330 5         38 $self->{n} = $n;
331 5         17 return $n;
332             }
333              
334             sub Uspec {
335 489     489 0 1517 my $self = shift;
336 489         491 my $n = shift;
337 489 100       1825 return $self->{Uspec} unless defined($n);
338 6         13 $self->{Uspec} = $n;
339 6         17 return $n;
340             }
341              
342             sub Lspec {
343 488     488 0 563 my $self = shift;
344 488         1262 my $n = shift;
345 488 100       2309 return $self->{Lspec} unless defined($n);
346 6         15 $self->{Lspec} = $n;
347 6         16 return $n;
348             }
349              
350             sub test {
351 5     5 0 10 my $self = shift;
352 5         7 my $data = shift;
353 5         7 my @data;
354 5         8 push @data, @{$data};
  5         15  
355              
356 5 50       17 if ( ($#data+1) != $self->n ) {
357             # not the right number of sample size
358 0         0 warn "number of samples does not match 'n'";
359 0         0 return -1;
360             }
361              
362 5         9 my $min = $INFINITY;
363 5         11 my $max = -1 * $INFINITY;
364 5         6 my $defects = 0;
365 5         9 my $sum = 0;
366            
367 5         12 for (my $i=0; $i < $self->n; $i++ ) {
368 25 100       54 if ( $data[$i] > $max ) {
369 9         12 $max = $data[$i];
370             }
371 25 100       54 if ( $data[$i] < $min ) {
372 8         12 $min = $data[$i];
373             }
374 25 100       46 if ( $data[$i] < $self->Lspec ) {
375 8         11 $defects += 1;
376             }
377 25 100       52 if ( $data[$i] > $self->Uspec ) {
378 12         54 $defects += 1;
379             }
380 25         59 $sum += $data[$i];
381             }
382 5         20 $self->R($max - $min);
383 5         14 $self->Xbar($sum/$self->n);
384 5         18 $self->defects($defects);
385 5         22 $self->p($defects/$self->n);
386              
387 5         9 my $return = 0;
388 5 100       13 if ( $self->R > $self->UCLR ) {
389 3         6 $return += 1;
390             }
391 5 50       12 if ( $self->R < $self->LCLR ) {
392 0         0 $return += 1;
393             }
394            
395 5 100       14 if ( $self->Xbar > $self->UCLXbar ) {
396 3         7 $return += 1;
397             }
398 5 100       13 if ( $self->Xbar < $self->LCLXbar ) {
399 1         2 $return += 1;
400             }
401              
402 5 100       17 if ( $self->p() > $self->UCLp() ) {
403 4         6 $return += 1;
404             }
405 5 50       28 if ( $self->p() < $self->LCLp() ) {
406 0         0 $return += 1;
407             }
408              
409 5         101 return $return;
410             }
411              
412             1;
413              
414             =head1 AUTHOR
415              
416             Erich S. Morisse
417              
418             =head1 COPYRIGHT and LICENSE
419              
420             Copyright (c) 2007 Erich Morisse
421              
422             This program is free software; you can redistribute it and/or
423             modify it under the terms of the GNU General Public License
424             as published by the Free Software Foundation; either version 2
425             of the License, or (at your option) any later version.
426              
427             This program is distributed in the hope that it will be useful,
428             but WITHOUT ANY WARRANTY; without even the implied warranty of
429             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
430             GNU General Public License for more details.
431              
432             You should have received a copy of the GNU General Public License
433             along with this program; if not, write to the Free Software
434             Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
435             USA.