File Coverage

blib/lib/Text/NumericData/File.pm
Criterion Covered Total %
statement 277 390 71.0
branch 78 176 44.3
condition 24 72 33.3
subroutine 25 33 75.7
pod 22 29 75.8
total 426 700 60.8


line stmt bran cond sub pod time code
1             package Text::NumericData::File;
2              
3             # Document and properly test the pipe stuff!
4             # Think about replacing MaxInterval by something more generic (sorted index according to given expression)
5              
6             #textdata version x
7             #see POD below!
8              
9 7     7   1700 use Text::NumericData::Calc qw(linear_value formula_function expression_function);
  7         16  
  7         472  
10 7     7   49 use Text::NumericData;
  7         16  
  7         168  
11 7     7   1210 use Text::ASCIIPipe;
  7         3838  
  7         210  
12 7     7   2429 use sort 'stable';
  7         3523  
  7         44  
13              
14             # This is just a placeholder because of a past build system bug.
15             # The one and only version for Text::NumericData is kept in
16             # the Text::NumericData module itself.
17             our $VERSION = '1';
18             $VERSION = eval $VERSION;
19              
20             our @ISA = ('Text::NumericData');
21              
22             # interpolation types
23             my $none = 0;
24             my $linear = 1;
25             my $spline = 2;
26              
27             our %help =
28             (
29             interpolate => 'use interpolation inter-/extrapolation for correlation of data sets (choose linear or spline, or 0 for switching it off)'
30             );
31              
32             our %defaults =
33             (
34             interpolate => 'linear'
35             );
36              
37             sub new
38             {
39 18     18 0 53 my $class = shift;
40 18         43 my $config = shift;
41 18         125 my $self = $class->SUPER::new($config);
42 18         50 $self->{in_file} = shift;
43 18 100 100     156 if(defined $config->{interpolate} and $config->{interpolate} eq 'spline')
    100 100        
44             {
45 2         731 require Math::Spline;
46 2         3302 $self->{intmethod} = $spline;
47             }
48             elsif(defined $config->{interpolate} and not $config->{interpolate})
49             {
50 1         3 $self->{intmethod} = $none;
51             }
52 15         68 else{ $self->{intmethod} = $linear; }
53              
54             $self->{config}{pipemode} = $config->{pipemode}
55 18 50       70 if(defined $config->{pipemode});
56              
57 18 100       62 if(defined $self->{in_file})
58             {
59 6         28 $self->read_all();
60             }
61 18         226 return $self;
62             }
63              
64             sub init
65             {
66             #not touching in_file! this is only handled by Read
67             #as is out_file!
68 35     35 1 97 my $self = shift;
69 35         166 $self->SUPER::init();
70 35 50       152 $self->{config}{indexformat} = '%6E' unless defined $self->{config}->{indexformat};
71 35 50       146 $self->{config}{extrapol} = 1 unless defined $self->{config}{extrapol};
72 35         91 $self->{data} = [];
73 35         88 $self->{records} = 0;
74 35         81 $self->{data_index} = [];
75 35         83 $self->{sorted_data} = [];
76 35         77 $self->{raw_header} = [];
77 35         78 $self->{buffer} = ""; #containing the raw lines read by readhead (the'd be lost from stdin otherwise)
78 35         78 $self->{splines} = [];
79 35         98 $self->{splinex} = undef;
80             }
81              
82             sub read_head
83             {
84 0     0 1 0 my $self = shift;
85 0         0 return $self->read_all($_[0], 1);
86             }
87              
88             # Return values mimicking Text::ASCIIPipe::pull_file.
89             # <0: failure
90             # 0: successful, but nothing more to expect
91             # >0: successful, more could be there
92             sub read_all
93             {
94 17     17 1 59 my ($self, $infile, $justhead) = @_;
95 17         62 $self->init(); #do we really want full init?
96 17 100       68 $self->{in_file} = $infile if defined $infile;
97              
98 17         39 my $handle;
99              
100 17 100 33     93 if(ref $self->{in_file}){ $handle = $self->{in_file}; }
  11 50       28  
101 0         0 elsif(not defined $self->{in_file} or $self->{in_file} eq ''){ $handle = \*STDIN; }
102 6 50       211 else{ open($handle, $self->{in_file}) or return 0; }
103              
104 17         49 my $data = 0;
105 17         69 binmode($handle);
106 17         37 my $l;
107 17         44 $self->{buffer} = '';
108              
109 17         39 my $state;
110 17         97 while(defined ($state = Text::ASCIIPipe::fetch($handle, $l)))
111             {
112 167 50       2860 if($state == $Text::ASCIIPipe::line)
113             {
114 167 100       453 if(!$data)
115             {
116 30 50       95 $self->{buffer}.= $l if($justhead);
117 30 100       165 if($self->line_check(\$l))
118             {
119 17 50       58 last if $justhead;
120 17         42 $data = 1;
121             }
122             else
123             {
124 13         97 $self->make_naked($l);
125 13         41 push(@{$self->{raw_header}}, $l);
  13         61  
126             }
127             }
128 167 100       486 if($data)
129             {
130 154         508 my $da = $self->line_data($l);
131 154 50       706 if(defined $da)
132             {
133 154         269 push(@{$self->{data}}, $da);
  154         420  
134 154 50       276 ++$self->{records} if @{$da}; # count non-empty records
  154         721  
135             }
136             }
137             }
138             else
139             {
140             # If there is no prior decision, this enables pipe control codes on possibly following write operations.
141 0 0       0 $self->{config}{pipemode} = 1 unless defined $self->{config}{pipemode};
142             # anything but line and begin is an end
143 0 0       0 last if $state != $Text::ASCIIPipe::begin;
144             }
145             }
146              
147             # Empty files aren't an error, or are they?
148             # All fine: Loop ended with orderly file end marker.
149 17 50 33     270 return 1 if(defined $state and $state != $Text::ASCIIPipe::allend);
150             # Ended on EOF (or some esoteric error we still treat as such),
151             # as there was no allend or error before that, just assume normal end of things.
152 17 50       139 return 0 if(not defined $state);
153             # If we hit allend, we did not stop with an orderly file end,
154             # so must assume we got nothing at all.
155 0         0 return -1; # if($state == allend) is already implied
156             }
157              
158             # After reading Data, intialize interpolation.
159             sub prepare_splines
160             {
161 2     2 0 2 my $self = shift;
162 2         5 my $x = shift;
163              
164 2         8 $self->{splines} = [];
165 2         5 $self->{splinex} = undef;
166 2         4 my $i = 0;
167 2         4 my @x;
168             my @data; # the more effective data structe
169 2         4 for my $d (@{$self->{data}})
  2         6  
170             {
171             # collect data, omitting duplicates
172 16 50       28 next if grep {$_ == $d->[$x]} @x;
  60         108  
173 16         30 push(@x, $d->[$x]);
174 16         22 for(my $j = 0; $j <= $#{$d}; ++$j)
  54         97  
175             {
176 38         74 $data[$j][$i] = $d->[$j];
177             }
178 16         28 ++$i;
179             }
180 2 50 33     12 die "Bad spline column index.\n" if($x < 0 or $x > $#data);
181              
182 2         5 $self->{splinex} = $x;
183              
184             # Not caring for undefined pieces of data, does Math::Spline care?
185 2         8 for(my $j = 0; $j <= $#data; ++$j)
186             {
187 5         291 $self->{splines}[$j] = Math::Spline->new(\@x, $data[$j]);
188             }
189             }
190              
191             sub SplineY
192             {
193 0     0 0 0 my $self = shift;
194 0         0 my $val = shift;
195 0         0 my $x = shift;
196 0         0 my $y = shift;
197 0 0       0 $x = 0 unless defined $x;
198 0 0       0 $y = 1 unless defined $y;
199 0 0 0     0 $self->prepare_splines($x) unless(defined $self->{splinex} and $self->{splinex} == $x);
200              
201 0         0 return $self->{splines}[$y]->evaluate($val);
202             }
203              
204             sub spline_set
205             {
206 280     280 0 460 my $self = shift;
207 280         411 my $val = shift;
208 280         401 my $x = shift;
209 280 50       534 $x = 0 unless defined $x;
210 280 100 66     945 $self->prepare_splines($x) unless(defined $self->{splinex} and $self->{splinex} == $x);
211              
212 280         680 my @set;
213 280         411 for my $s (@{$self->{splines}})
  280         672  
214             {
215 750         18296 push(@set, $s->evaluate($val));
216             }
217              
218 280         10629 return \@set;
219             }
220              
221             #compute_index(\arrayofcols)
222             sub compute_index
223             {
224 7     7 1 15 my $self = shift;
225 7         14 my $ar = shift;
226 7         21 my $zahl = $self->{config}->{numregex};
227 7 50       20 $ar = [0] unless defined $ar;
228 7         17 foreach my $c (@{$ar}){ $self->{data_index}->[$c] = {};}
  7         21  
  5         24  
229 7         14 foreach my $d (@{$self->{data}})
  7         24  
230             {
231 66         111 foreach my $c (@{$ar})
  66         126  
232             {
233 46 50       376 my $key = $d->[$c] =~ /$zahl/ ? sprintf($self->{config}->{indexformat}, $d->[$c]) : $d->[$c];
234 46 50       224 $self->{data_index}->[$c]->{$key} = $d unless defined $self->{data_index}->[$c]->{$key};
235             }
236             }
237             }
238              
239             #(col)
240             sub compute_sorted_index
241             {
242 2     2 1 4 my $self = shift;
243 2         7 my $xl = shift;
244 2 50       6 $xl = [0] unless defined $xl;
245             #compute any missing indices
246 2         6 my @l = ();
247 2 50       4 foreach my $x (@{$xl}){ push(@l, $x) unless defined $self->{data_index}->[$x]; }
  2         6  
  2         11  
248 2         9 $self->compute_index(\@l);
249             #now sort all concerned indices
250 2         6 foreach my $x (@{$xl})
  2         9  
251             {
252 2         9 $self->{sorted_data}->[$x] = [];
253 2         7 foreach my $k (sort {$a <=> $b} keys %{$self->{data_index}->[$x]})
  45         92  
  2         26  
254             {
255 20         33 push(@{$self->{sorted_data}->[$x]}, $self->{data_index}->[$x]->{$k});
  20         60  
256             }
257             }
258             }
259              
260              
261             sub write_data
262             {
263 9     9 1 27 my $self = shift;
264 9         27 my $handle = shift;
265 9         21 my $selection = shift;
266 9         31 for(my $i = 0; $i <= $#{$self->{data}}; ++$i)
  83         331  
267             {
268 74         171 print $handle ${$self->data_line($self->{data}->[$i],$selection)};
  74         326  
269             }
270 9         60 return 1; # real error checking?
271             }
272              
273             #Write(file, columns)
274             #maybe adding limits in future...
275             sub write_all
276             {
277 2     2 1 4 my $self = shift;
278 2         6 my $file = shift;
279 2         4 my $selection = shift;
280 2 50       8 $self->{out_file} = $file if defined $file;
281 2         5 my $handle;
282 2 50 0     9 if(ref $self->{out_file}){ $handle = $self->{out_file}; }
  2 0       5  
283 0         0 elsif(not defined $self->{out_file} or $self->{out_file} eq ''){ $handle = STDOUT; }
284 0 0       0 else{ open($handle, '>', $self->{out_file}) or return 0; }
285 2         10 binmode($handle);
286              
287 2 50       6 Text::ASCIIPipe::file_begin($handle) if($self->{config}{pipemode});
288             #header
289 2         10 $self->write_head($handle, $selection);
290             #data
291 2         10 $self->write_data($handle, $selection);
292 2 50       10 Text::ASCIIPipe::file_end($handle) if($self->{config}{pipemode});
293 2         24 return 1; # real error checking?
294             }
295              
296             # An inconsistency in first release was write_header, not write_head,
297             # as it matches read_head. Providing this wrapper now.
298             sub write_header
299             {
300 5     5 0 17 my $self = shift; # No real need to shift here, but keeping in style.
301 5         28 return $self->write_head(@_);
302             }
303              
304             sub write_head
305             {
306 7     7 1 21 my $self = shift;
307 7         19 my $handle = shift;
308              
309 7         22 my $selection = shift;
310             # If there is no old raw header, do a new one automaticaly.
311 7 100       18 if(@{$self->{raw_header}})
  7         34  
312             {
313 6         16 foreach my $h (@{$self->{raw_header}})
  6         27  
314             {
315 12         35 print $handle ${$self->comment_line($h)};
  12         77  
316             }
317 6 50       33 if(defined $selection)
318             {
319 0         0 print $handle ${$self->title_line($selection)};
  0         0  
320             }
321             }
322             else
323             {
324 1         5 return $self->write_new_header($handle,$selection);
325             }
326 6         26 return 1;
327             }
328              
329             sub write_new_header
330             {
331 1     1 1 2 my $self = shift;
332 1         2 my $handle = shift;
333 1         2 my $selection = shift;
334 1 50       4 print $handle ${$self->comment_line(\$self->{title})} if defined $self->{title};
  1         8  
335 1         4 foreach my $c (@{$self->{comments}}){ print $handle ${$self->comment_line(\$c)}; }
  1         4  
  0         0  
  0         0  
336 1         3 print $handle ${$self->title_line($selection)};
  1         12  
337 1         3 return 1;
338             }
339              
340             sub set_of_noint
341             {
342 600     600 1 952 my $self = shift;
343 600         1123 my $value = shift;
344 600         1238 my $zahl = $self->{config}->{numregex};
345 600 50       6587 $value = sprintf($self->{config}->{indexformat}, $value) if $value =~ /$zahl/;
346 600         1260 my $x = shift;
347 600 50       1313 $x = 0 unless defined $x;
348 600 100       1503 defined $self->{data_index}->[$x] or $self->compute_index([$x]);
349 600         1664 return $self->{data_index}->[$x]->{$value};
350             }
351              
352             sub set_of
353             {
354 600     600 1 1072 my $self = shift;
355             #the easy way
356 600         1461 my $set = $self->set_of_noint(@_);
357             return $set
358 600 100 100     2811 if (defined $set or $self->{intmethod} == $none);
359              
360             #so, we got to do some Work;
361 460         906 my $val = shift;
362 460         726 my $x = shift;
363 460 50       968 $x = 0 unless defined $x;
364 460 100       1051 if($self->{intmethod} == $spline)
365             {
366 280         641 return $self->spline_set($val, $x);
367             }
368             #find suitable data value pairs for interpolation
369 180         471 my $sets = $self->neighbours($val, $x);
370 180 50 33     488 return undef unless defined $sets and $#{$sets} == 1;
  180         656  
371             #compute
372 180         430 my @set = ();
373              
374 180         355 for(my $y = 0; $y <= $#{$sets->[0]}; ++$y)
  540         1612  
375             {
376 360 100       793 if($y != $x)
377             {
378 180         1001 push(@set, linear_value($val, [$sets->[0][$x], $sets->[1][$x]], [$sets->[0][$y],$sets->[1][$y]]));
379             }
380 180         531 else{ push(@set, $val); }
381             }
382            
383 180         744 return \@set;
384             }
385              
386             sub y_noint
387             {
388 0     0 1 0 my $self = shift;
389 0         0 my $val = shift;
390 0         0 my $x = shift;
391 0         0 my $y = shift;
392 0 0       0 $x = 0 unless defined $x;
393 0 0       0 $y = 1 unless defined $y;
394 0         0 my $set = $self->set_of($val,$x);
395 0 0       0 return defined $set ? $set->[$y] : undef;
396             }
397              
398              
399             sub y
400             {
401 0     0 1 0 my $self = shift;
402             #the easy way
403 0         0 my $y = $self->y_noint(@_);
404             return $y
405 0 0 0     0 if (defined $y or $self->{intmethod} == $none);
406              
407             #so, we got to do some Work;
408 0         0 my $val = shift;
409 0         0 my $x = shift;
410 0         0 $y = shift;
411 0 0       0 $x = 0 unless defined $x;
412 0 0       0 $y = 1 unless defined $y;
413 0 0       0 if($self->{intmethod} == $spline)
414             {
415 0         0 return $self->spline_y($val, $x, $y);
416             }
417             #find suitable data value pairs for interpolation
418 0         0 my $sets = $self->neighbours($val, $x);
419 0 0 0     0 return undef unless defined $sets and $#{$sets} == 1;
  0         0  
420             #compute
421             #print STDERR "Sets: ";
422             #for(@{$sets}){ print STDERR "(@{$_}) "; }
423             #print STDERR "\n";
424 0         0 return linear_value($val, [$sets->[0][$x], $sets->[1][$x]], [$sets->[0][$y],$sets->[1][$y]]);
425             }
426              
427             #it should be configurable wheter extrapolation is acceptable
428              
429             sub neighbours
430             {
431 180     180 1 311 my $self = shift;
432 180         375 my $val = shift;
433 180         336 my $x = shift;
434            
435 180         333 my $n = undef;
436 180         323 my $e = undef;
437              
438 180 50       455 unless($self->{config}{orderedint})
439             {
440 180 100       467 defined $self->{sorted_data}->[$x] or $self->compute_sorted_index([$x]);
441 180 50       295 return undef unless $#{$self->{sorted_data}->[$x]} > 0; #senseless when not at least two points there
  180         607  
442              
443 180         347 foreach my $v (@{$self->{sorted_data}->[$x]})
  180         476  
444             {
445 1044         1745 $n = $e;
446 1044         1549 $e = $v;
447 1044 100 100     3063 if($v->[$x] > $val and defined $n){ last; }
  180         434  
448             }
449             }
450             else
451             {
452 0 0 0     0 if(defined $self->{data}[0] and $self->{data}[0][$x] < $val)
453             {
454 0         0 foreach my $v (@{$self->{data}})
  0         0  
455             {
456 0         0 $n = $e;
457 0         0 $e = $v;
458 0 0 0     0 if($v->[$x] > $val and defined $n){ last; }
  0         0  
459             }
460             }
461             else
462             {
463 0         0 foreach my $v (@{$self->{data}})
  0         0  
464             {
465 0         0 $n = $e;
466 0         0 $e = $v;
467 0 0 0     0 if($v->[$x] < $val and defined $n){ last; }
  0         0  
468             }
469             }
470             }
471 180 50       457 return undef unless defined $n; #catches both empty array and one-element-array
472 180 50       474 unless($self->{config}{extrapol})
473             {
474             #check if we have left and right neighbour
475 0 0 0     0 return undef unless (($n->[$x] <= $val and $e->[$x] >= $val) or ($n->[$x] >= $val and $e->[$x] <= $val))
      0        
      0        
476             }
477 180         628 return [$n,$e];
478             }
479              
480             sub max
481             {
482 1     1 1 5 my $self = shift;
483 1         2 my $formula = shift;
484 1         4 my $ff = expression_function($formula);
485 1 50       7 return undef unless defined $ff;
486 1         3 my $max = undef;
487 1         3 foreach my $d (@{$self->{data}})
  1         4  
488             {
489 4         127 my $nm = &$ff([$d]);
490 4 100 66     25 $max = $nm if !defined $max or $nm > $max;
491             }
492 1         11 return $max;
493             }
494              
495             # A little Hack for now...
496             # Return the two indices for the two biggest expression values.
497             # Idea: Between those, the "real" maximum is to be found, perhaps using non-linear interpolation.
498             sub max_interval
499             {
500 0     0 0 0 my $self = shift;
501 0         0 my $formula = shift;
502 0         0 my $ff = expression_function($formula);
503 0 0       0 return undef unless defined $ff;
504 0         0 my @maxval;
505             my @maxidx;
506 0         0 for my $idx (1..$#{$self->{data}})
  0         0  
507             {
508 0         0 my $nm = &$ff([$self->{data}[$idx]]);
509 0 0       0 if(@maxval)
510             {
511 0 0       0 if($nm >= $maxval[0])
    0          
512             {
513 0         0 $maxval[1] = $maxval[0];
514 0         0 $maxval[0] = $nm;
515 0         0 $maxidx[1] = $maxidx[0];
516 0         0 $maxidx[0] = $idx;
517             }
518             elsif($nm > $maxval[1])
519             {
520 0         0 $maxval[1] = $nm;
521 0         0 $maxidx[1] = $idx;
522             }
523             }
524             else
525             {
526 0         0 @maxidx = ($idx, $idx);
527 0         0 @maxval = ($nm, $nm);
528             }
529             }
530 0         0 return @maxidx;
531             }
532              
533             sub min
534             {
535 0     0 1 0 my $self = shift;
536 0         0 my $formula = shift;
537 0         0 $formula = '-('.$formula.')';
538 0         0 return -1*$self->max($formula);
539             }
540              
541             sub sort_func
542             {
543 8     8 0 21 my $self = shift;
544 8         26 my $cols = shift;
545 8         16 my $down = shift;
546 8         20 my $sortcode = 'my $r; ';
547 8         17 my $i = -1;
548             # tested example sortcode:
549             # '{ my $c; ($c= $a->[0] <=> $b->[0]) ? $c : ($c= $a->[1] <=> $b->[1]) ? $c : 0 }'
550 8         18 foreach my $prec (@{$cols})
  8         24  
551             {
552             # Ensure only integer values enter evalued code.
553             # Invalid numbers get parsed to zero, as perl does normally.
554 9 50       69 my $c = $prec =~ /^(\d+)/ ? $1 : 0;
555 9         23 my $r = '$r';
556 9         22 my $a = '$a';
557 9         20 my $b = '$b';
558 9 100 66     52 if(defined $down and $down->[++$i])
559             {
560 1         2 $a = '$b';
561 1         2 $b = '$a';
562             }
563 9         51 $sortcode .= "($r= $a\->[$c] <=> $b\->[$c]) ? $r : ";
564             }
565 8         23 $sortcode .= '0';
566             # Not sure... is it better to eval that function here or to to put the eval into the sort call?
567             #print STDERR "Sort code: $sortcode\n";
568 8         1038 return eval 'sub {'.$sortcode.'}';
569             }
570              
571             sub sort_data
572             {
573 8     8 1 26 my $self = shift;
574 8         20 my $cols = shift;
575 8         15 my $down = shift;
576 8         16 my $sortfunc = shift;
577 8 50       28 unless(defined $sortfunc)
578             {
579 8         33 $sortfunc = $self->sort_func($cols,$down);
580             }
581 8         27 @{$self->{data}} = sort $sortfunc @{$self->{data}};
  8         37  
  8         294  
582 8         100 return $sortfunc; # Possibility to reuse the sort function.
583             }
584              
585             # Heck, this is the gut of txdcalc.
586             # I do wonder if I could make that use this function here...
587             # ... or a line-oriented version, rather.
588             sub calc
589             {
590 1     1 1 2 my $self = shift;
591 1         2 my $formula = shift; # formula string
592 1         1 my $config = shift; # {'byrow'=>0, 'bycol'=>0 }
593 1         2 my $files = shift; # list of files to use for [2,1] style refs
594 1         1 my $workarray = shift; # \@A
595 1         2 my $constants = shift; # \@C
596              
597 1         11 my $ff = formula_function($formula);
598 1 50       6 return 0 unless defined $ff;
599              
600 1         488 require Text::NumericData::FileCalc;
601             my $deletelist = Text::NumericData::FileCalc::file_calc
602             (
603             $ff
604             , $config
605             , $self->{data}
606 1         8 , $files
607             , $workarray
608             , $constants
609             );
610 1 50       5 return 0 unless defined $deletelist;
611 1         5 $self->delete_rows($deletelist);
612              
613 1         14 return 1;
614             }
615              
616             # delete indicated rows from data set
617             sub delete_rows
618             {
619 1     1 1 3 my $self = shift;
620 1         3 my $delist = shift;
621 1 50       3 return unless defined $delist;
622 1         3 my @delis = sort {$a <=> $b} @{$delist};
  0         0  
  1         4  
623 1   33     6 while(@delis and $delis[0]<0){ shift(@delis); }
  0         0  
624 1   33     4 while(@delis and $delis[$#delis]>$#{$self->{data}}){ pop(@delis); }
  0         0  
  0         0  
625              
626 1 50       4 return unless @delis;
627             # This is designed to handle possibly many sparse deletes in one go.
628             # One simple splice() wouldn't do it. Many would be wasteful.
629             # This is a rather stupid approach, but at least linear and
630             # light on index confusion.
631 0         0 my @newdata;
632 0         0 my $row = 0;
633 0         0 while(@delis)
634             {
635 0         0 my $pi = shift(@delis);
636             # splice off the leading part including the to-be-deleted one
637 0         0 my @part = splice(@{$self->{data}}, 0, $pi-$row+1);
  0         0  
638 0         0 $row += @part;
639 0         0 pop(@part);
640 0         0 push(@newdata, @part);
641             # make sure the next index is different
642 0   0     0 while(@delis and $delis[0] == $pi){ shift(@delis); }
  0         0  
643             }
644 0         0 push(@newdata, splice(@{$self->{data}}, 0));
  0         0  
645 0         0 $self->{data} = \@newdata;
646             }
647              
648             sub mean
649             {
650 0     0 1 0 my ($self, $col, $xcol, $begin, $end) = @_;
651 0         0 my $count = 0;
652 0         0 my $sum = 0;
653 0 0       0 defined $self->{sorted_data}->[$xcol] or $self->compute_sorted_index([$xcol]);
654 0         0 foreach my $a (@{$self->{sorted_data}->[$xcol]})
  0         0  
655             {
656 0 0       0 if($a->[$xcol] >= $start)
657             {
658 0 0       0 if($a->[$xcol] <= $end){ $sum += $a->[$col]; ++$count; }
  0         0  
  0         0  
659 0         0 else{ last; }
660             }
661             }
662 0 0       0 return $count > 0 ? $sum/$count : undef;
663             }
664              
665             sub get_sorted_data
666             {
667 0     0 1 0 my $self = shift;
668 0         0 my $x = shift;
669 0 0       0 defined $self->{sorted_data}->[$x] or $self->compute_sorted_index([$x]);
670 0         0 return $self->{sorted_data}->[$x];
671             }
672              
673             sub columns
674             {
675 15     15 1 36 my $self = shift;
676 15 50       92 return @{$self->{data}} ? $#{$self->{data}[0]}+1 : 0;
  15         70  
  15         94  
677             }
678              
679             1;
680              
681             __END__