File Coverage

blib/lib/Statistics/Descriptive/PDL.pm
Criterion Covered Total %
statement 167 184 90.7
branch 49 68 72.0
condition 16 31 51.6
subroutine 40 41 97.5
pod 14 14 100.0
total 286 338 84.6


line stmt bran cond sub pod time code
1             package Statistics::Descriptive::PDL;
2            
3             ## no critic (ProhibitExplicitReturnUndef)
4            
5 8     8   773245 use 5.010;
  8         32  
6 8     8   46 use strict;
  8         36  
  8         273  
7 8     8   48 use warnings;
  8         72  
  8         573  
8 8     8   60 use Scalar::Util qw /blessed/;
  8         20  
  8         694  
9 8     8   5082 use POSIX qw /fmod/;
  8         77189  
  8         50  
10            
11             # avoid loading too much, especially into our name space
12 8     8   20092 use PDL::Lite '2.012';
  8         605666  
  8         79  
13             # try to keep running if PDL::Stats is not installed
14             eval 'require PDL::Stats::Basic';
15             my $has_PDL_stats_basic = $@ ? undef : 1;
16             #$has_PDL_stats_basic = 0;
17            
18             # We could inherit from PDL::Objects, but in this case we want
19             # to hide the piddle from the caller to avoid arbitrary changes
20             # being applied to it.
21            
22             our $VERSION = '0.17';
23            
24             our $Tolerance = 0.0; # for compatibility with Stats::Descr, but not used here
25            
26             my @cache_methods = qw /
27             count sum mode median
28             mean standard_deviation skewness kurtosis
29             geometric_mean harmonic_mean
30             max min sample_range
31             iqr
32             sum_sqr_sample_weights
33             /;
34             __PACKAGE__->_make_caching_accessors( \@cache_methods );
35            
36             sub _make_caching_accessors {
37 16     16   50 my ( $pkg, $methods ) = @_;
38            
39             ## no critic
40 8     8   2413 no strict 'refs';
  8         22  
  8         23547  
41             ## use critic
42 16         50 foreach my $method (@$methods)
43             {
44 192         1056 *{ $pkg . "::" . $method } = do
45 192         380 {
46 192         315 my $m = $method;
47             sub {
48 683     683   97096 my $self = shift;
49             return $self->{_cache}{$method}
50 683 100       2754 if defined $self->{_cache}{$method};
51            
52 449         1163 my $piddle = $self->_get_piddle;
53             return undef
54 449 100 66     2090 if !defined $piddle or $piddle->isempty;
55            
56 327         2886 my $call_meth = "_$method";
57 327         1364 my $val = $self->$call_meth;
58            
59 327 100 66     8951 if (blessed $val and $val->isa('PDL')) {
60 246         1283 $val = $val->sclr;
61             }
62 327         1728 return $self->{_cache}{$method} = $val;
63 192         701 };
64             };
65             }
66            
67 16         46 return;
68             }
69            
70             sub new {
71 29     29 1 1026929 my $proto = shift;
72 29   33     234 my $class = ref($proto) || $proto;
73            
74 29         110 my $self = {piddle => undef};
75 29         79 bless $self, $class;
76            
77 29         141 return $self;
78             }
79            
80             sub available_stats {
81 5     5 1 41 my $self = shift;
82 5         75 my @methods = sort (@cache_methods, qw /percentile variance/);
83 5 50       42 return wantarray ? @methods : \@methods;
84             }
85            
86             sub add_data {
87 29     29 1 2076 my $self = shift;
88 29         400 my $data;
89            
90             # have we been passed an ndarray?
91 29 50 33     172 if (blessed $_[0] && $_[0]->isa('PDL')) {
92 0         0 $data = $_[0];
93             }
94             else {
95 29 100       122 $data
96             = ref ($_[0]) eq 'ARRAY'
97             ? $_[0]
98             : \@_;
99 29 50       104 return if !scalar @$data;
100             }
101            
102 29         54 my $piddle;
103 29         104 my $has_existing_data = $self->count;
104            
105 29         113 $self->clear_cache;
106            
107             # Take care of appending to an existing data set
108 29 100       79 if ($has_existing_data) {
109 4         14 $piddle = $self->_get_piddle;
110 4         23 $piddle = $piddle->append (PDL->pdl ($data)->flat);
111 4         816 $self->_set_piddle ($piddle);
112             }
113             else {
114 25         146 $self->_set_piddle (PDL->pdl($data)->flat);
115             }
116            
117 29         3722 return $self->count;
118             }
119            
120             sub get_data {
121 1     1 1 3 my $self = shift;
122 1         5 my $piddle = $self->_get_piddle;
123            
124 1 50       40 my $data = defined $piddle ? $piddle->unpdl : [];
125            
126 1 50       6 return wantarray ? @$data : $data;
127             }
128            
129             sub get_data_as_hash {
130 2     2 1 1262 my $self = shift;
131            
132 2         8 my $piddle = $self->_get_piddle;
133 2 50       8 if (defined $piddle) {
134 2         19 require Statistics::Descriptive::PDL::SampleWeighted;
135 2         11 my $wtd_obj = Statistics::Descriptive::PDL::SampleWeighted->new;
136 2         15 my $wts_piddle = PDL->ones ($piddle->dims);
137 2         205 $wtd_obj->add_data ($piddle->copy, $wts_piddle);
138 2         10 return $wtd_obj->get_data_as_hash;
139             }
140            
141 0 0       0 return wantarray ? () : {};
142             }
143            
144       0 1   sub values_are_unique {}
145            
146             # flatten $data if multidimensional
147             sub _set_piddle {
148 101     101   3091 my ($self, $data) = @_;
149 101         334 $self->{piddle} = PDL->pdl ($data);
150             }
151            
152             sub _get_piddle {
153 906     906   1560 my $self = shift;
154 906         2547 return $self->{piddle};
155             }
156            
157             sub clear_cache {
158 75     75 1 157 my $self = shift;
159 75         245 delete $self->{_cache};
160 75         148 return;
161             }
162            
163             sub _count {
164 29     29   90 my $self = shift;
165 29         77 return $self->_get_piddle->nelem;
166             }
167            
168             sub _sum {
169 2     2   6 my $self = shift;
170 2         8 return $self->_get_piddle->sum;
171             }
172            
173             sub sum_weights {
174 2     2 1 533 my $self = shift;
175 2         9 return $self->count;
176             }
177            
178             sub sum_sqr_weights {
179 4     4 1 12 my $self = shift;
180 4         18 return $self->count;
181             }
182            
183             sub _sum_sqr_sample_weights {
184 3     3   9 my $self = shift;
185 3         11 return $self->sum_sqr_weights;
186             }
187            
188             sub _min {
189 10     10   27 my $self = shift;
190 10         35 return $self->_get_piddle->min;
191             }
192            
193             sub _max {
194 10     10   61 my $self = shift;
195 10         33 return $self->_get_piddle->max;
196             }
197            
198             sub _mean {
199 6     6   17 my $self = shift;
200 6         22 return $self->_get_piddle->average;
201             }
202            
203            
204 5     5 1 17 sub sd {return $_[0]->standard_deviation}
205 1     1 1 4 sub stdev {return $_[0]->standard_deviation}
206            
207             sub _standard_deviation {
208 8     8   20 my $self = shift;
209            
210 8         24 my $piddle = $self->_get_piddle;
211            
212 8         17 my $sd;
213 8         29 my $n = $piddle->nelem;
214 8 100       32 if ($n > 1) {
    50          
215 7 50       24 if ($has_PDL_stats_basic) {
216 7         156 $sd = $piddle->stdv_unbiased;
217             }
218             else {
219 0         0 my $var = (($piddle ** 2)->sum - $n * $self->mean ** 2);
220 0 0       0 $sd = $var > 0 ? sqrt ($var / ($n - 1)) : 0;
221             }
222             }
223             elsif ($n == 1){
224 1         3 $sd = 0;
225             }
226 8         26 return $sd;
227             }
228            
229             sub variance {
230 5     5 1 1407 my $self = shift;
231 5         21 my $sd = $self->standard_deviation;
232 5 100       48 return defined $sd ? $sd ** 2 : undef;
233             }
234            
235             sub _median {
236 10     10   23 my $self = shift;
237 10         30 return $self->_get_piddle->median;
238             }
239            
240            
241             sub _skewness {
242 9     9   22 my $self = shift;
243            
244 9         55 my $piddle = $self->_get_piddle;
245            
246 9         37 my $n = $piddle->nelem;
247            
248 9 100       64 return undef if $n < 3;
249            
250 8 50       247 return $piddle->skew_unbiased
251             if $has_PDL_stats_basic;
252            
253             # do it ourselves
254 0         0 my $mean = $self->mean;
255 0         0 my $sd = $self->standard_deviation;
256 0         0 my $sumpow3 = ((($piddle - $mean) / $sd) ** 3)->sum;
257 0         0 my $correction = $n / ( ($n-1) * ($n-2) );
258 0         0 my $skew = $correction * $sumpow3;
259            
260 0         0 return $skew;
261             }
262            
263             sub _kurtosis {
264 9     9   27 my $self = shift;
265 9         28 my $piddle = $self->_get_piddle;
266            
267 9         33 my $n = $piddle->nelem;
268            
269 9 100       56 return undef if $n < 4;
270            
271 8 50       235 return $piddle->kurt_unbiased
272             if $has_PDL_stats_basic;
273            
274             # do it ourselves
275 0         0 my $mean = $self->mean;
276 0         0 my $sd = $self->standard_deviation;
277 0         0 my $sumpow4 = ((($piddle - $mean) / $sd) ** 4)->sum;
278            
279 0         0 my $correction1 = ( $n * ($n+1) ) / ( ($n-1) * ($n-2) * ($n-3) );
280 0         0 my $correction2 = ( 3 * ($n-1) ** 2) / ( ($n-2) * ($n-3) );
281            
282 0         0 my $kurt = ( $correction1 * $sumpow4 ) - $correction2;
283 0         0 return $kurt;
284             }
285            
286             sub _sample_range {
287 5     5   16 my $self = shift;
288 5   50     20 my $min = $self->min // return undef;
289 5   50     20 my $max = $self->max // return undef;
290 5         16 return $max - $min;
291             }
292            
293            
294             sub _harmonic_mean {
295 5     5   13 my $self = shift;
296 5         16 my $piddle = $self->_get_piddle;
297            
298 5 100       84 return undef if $piddle->which->nelem != $piddle->nelem;
299            
300 4         667 my $hs = (1 / $piddle)->sum;
301            
302 4 100       396 return $hs ? $self->count / $hs : undef;
303             }
304            
305             sub _geometric_mean {
306 4     4   13 my $self = shift;
307 4         16 my $piddle = $self->_get_piddle;
308            
309 4         16 my $count = $self->count;
310            
311 4 50       23 return undef if !$count;
312 4 50       206 return undef if $piddle->where($piddle < 0)->nelem;
313            
314 4         1562 my $exponent = 1 / $self->count();
315 4         26 my $powered = $piddle ** $exponent;
316            
317 4         546 my $gm = $powered->dprodover;
318             }
319            
320             sub _mode {
321 6     6   14 my $self = shift;
322 6         19 my $piddle = $self->_get_piddle;
323            
324 6         39 my $count = $piddle->nelem;
325 6         40 my $unique = $piddle->uniq;
326            
327 6 100 66     7073 return undef if $unique->nelem == $count or $unique->nelem == 1;
328            
329             #if (!($count % $unique->nelem)) {
330             # # might have equal numbers of each value
331             # # need to check for this, but for now return undef
332             # return undef;
333             #}
334            
335 3         21 my $mode = $piddle->mode;
336            
337             # bodge to handle odd values
338 3 50       416 return undef if !$piddle->in($mode)->max;
339            
340 3         353 return $mode;
341             }
342            
343             sub percentiles {
344 1     1 1 5 my ($self, @percentiles) = @_;
345            
346 1         4 my $piddle = $self->_get_piddle;
347            
348             return
349 1 50 33     12 if !defined $piddle || $piddle->nelem == 0;
350            
351 1         4 my @vals = map {$self->percentile($_)} @percentiles;
  2         5  
352            
353 1         4 return @vals;
354             }
355            
356            
357             # caching wrapper
358             # need to convert $p to fraction, or perhaps die if it is between 0 and 1
359             # hard-coded cache percentiles not ideal
360             sub percentile {
361 80     80 1 23046 my ($self, $p) = @_;
362 80         223 my $piddle = $self->_get_piddle;
363            
364             return undef
365 80 100 66     670 if !defined $piddle || $piddle->nelem == 0;
366            
367 76 100       267 return $self->median
368             if $p == 50;
369            
370 60 50 33     259 die "Percentile $p outside range 0..100"
371             if $p < 0 or $p > 100;
372            
373             # allow for other number formats like '005'
374             # needed for cache
375 60         119 $p += 0;
376            
377             return $self->{_cache}{percentile}{$p}
378 60 100       284 if defined $self->{_cache}{percentile}{$p};
379            
380 48         205 my $pctl = $self->_percentile($p);
381            
382 48 100 66     2960 if (blessed $pctl && $pctl->isa('PDL')) {
383 18         152 $pctl = $pctl->sclr;
384             }
385            
386 48 100       176 if (int ($p) == $p) {
387 46         161 $self->{_cache}{percentile}{$p} = $pctl;
388             }
389            
390 48         314 return $pctl;
391             }
392            
393             sub _percentile {
394 18     18   50 my ($self, $p) = @_;
395            
396 18         42 return $self->_get_piddle->pct($p / 100);
397             }
398            
399            
400            
401             sub _iqr {
402 6     6   17 my $self = shift;
403 6         45 $self->percentile(75) - $self->percentile(25);
404             }
405            
406            
407             1;
408            
409             __END__