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   2566 use Text::NumericData::Calc qw(linear_value formula_function expression_function);
  7         18  
  7         501  
10 7     7   59 use Text::NumericData;
  7         15  
  7         152  
11 7     7   1668 use Text::ASCIIPipe;
  7         4186  
  7         218  
12 7     7   3849 use sort 'stable';
  7         4404  
  7         47  
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 57 my $class = shift;
40 18         70 my $config = shift;
41 18         103 my $self = $class->SUPER::new($config);
42 18         42 $self->{in_file} = shift;
43 18 100 100     121 if(defined $config->{interpolate} and $config->{interpolate} eq 'spline')
    100 100        
44             {
45 2         1109 require Math::Spline;
46 2         4758 $self->{intmethod} = $spline;
47             }
48             elsif(defined $config->{interpolate} and not $config->{interpolate})
49             {
50 1         7 $self->{intmethod} = $none;
51             }
52 15         58 else{ $self->{intmethod} = $linear; }
53              
54             $self->{config}{pipemode} = $config->{pipemode}
55 18 50       54 if(defined $config->{pipemode});
56              
57 18 100       51 if(defined $self->{in_file})
58             {
59 6         19 $self->read_all();
60             }
61 18         163 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 66 my $self = shift;
69 35         135 $self->SUPER::init();
70 35 50       158 $self->{config}{indexformat} = '%6E' unless defined $self->{config}->{indexformat};
71 35 50       119 $self->{config}{extrapol} = 1 unless defined $self->{config}{extrapol};
72 35         71 $self->{data} = [];
73 35         69 $self->{records} = 0;
74 35         63 $self->{data_index} = [];
75 35         57 $self->{sorted_data} = [];
76 35         67 $self->{raw_header} = [];
77 35         59 $self->{buffer} = ""; #containing the raw lines read by readhead (the'd be lost from stdin otherwise)
78 35         56 $self->{splines} = [];
79 35         82 $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 46 my ($self, $infile, $justhead) = @_;
95 17         44 $self->init(); #do we really want full init?
96 17 100       48 $self->{in_file} = $infile if defined $infile;
97              
98 17         29 my $handle;
99              
100 17 100 33     69 if(ref $self->{in_file}){ $handle = $self->{in_file}; }
  11 50       23  
101 0         0 elsif(not defined $self->{in_file} or $self->{in_file} eq ''){ $handle = \*STDIN; }
102 6 50       276 else{ open($handle, $self->{in_file}) or return 0; }
103              
104 17         41 my $data = 0;
105 17         58 binmode($handle);
106 17         28 my $l;
107 17         35 $self->{buffer} = '';
108              
109 17         27 my $state;
110 17         70 while(defined ($state = Text::ASCIIPipe::fetch($handle, $l)))
111             {
112 167 50       2396 if($state == $Text::ASCIIPipe::line)
113             {
114 167 100       338 if(!$data)
115             {
116 30 50       68 $self->{buffer}.= $l if($justhead);
117 30 100       134 if($self->line_check(\$l))
118             {
119 17 50       63 last if $justhead;
120 17         56 $data = 1;
121             }
122             else
123             {
124 13         78 $self->make_naked($l);
125 13         32 push(@{$self->{raw_header}}, $l);
  13         46  
126             }
127             }
128 167 100       380 if($data)
129             {
130 154         393 my $da = $self->line_data($l);
131 154 50       760 if(defined $da)
132             {
133 154         245 push(@{$self->{data}}, $da);
  154         303  
134 154 50       223 ++$self->{records} if @{$da}; # count non-empty records
  154         569  
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     323 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       152 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 4 my $self = shift;
162 2         4 my $x = shift;
163              
164 2         5 $self->{splines} = [];
165 2         5 $self->{splinex} = undef;
166 2         8 my $i = 0;
167 2         4 my @x;
168             my @data; # the more effective data structe
169 2         5 for my $d (@{$self->{data}})
  2         7  
170             {
171             # collect data, omitting duplicates
172 16 50       33 next if grep {$_ == $d->[$x]} @x;
  60         118  
173 16         42 push(@x, $d->[$x]);
174 16         40 for(my $j = 0; $j <= $#{$d}; ++$j)
  54         114  
175             {
176 38         112 $data[$j][$i] = $d->[$j];
177             }
178 16         28 ++$i;
179             }
180 2 50 33     22 die "Bad spline column index.\n" if($x < 0 or $x > $#data);
181              
182 2         15 $self->{splinex} = $x;
183              
184             # Not caring for undefined pieces of data, does Math::Spline care?
185 2         11 for(my $j = 0; $j <= $#data; ++$j)
186             {
187 5         347 $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 389 my $self = shift;
207 280         407 my $val = shift;
208 280         395 my $x = shift;
209 280 50       572 $x = 0 unless defined $x;
210 280 100 66     952 $self->prepare_splines($x) unless(defined $self->{splinex} and $self->{splinex} == $x);
211              
212 280         626 my @set;
213 280         395 for my $s (@{$self->{splines}})
  280         622  
214             {
215 750         17819 push(@set, $s->evaluate($val));
216             }
217              
218 280         10045 return \@set;
219             }
220              
221             #compute_index(\arrayofcols)
222             sub compute_index
223             {
224 7     7 1 13 my $self = shift;
225 7         12 my $ar = shift;
226 7         13 my $zahl = $self->{config}->{numregex};
227 7 50       19 $ar = [0] unless defined $ar;
228 7         12 foreach my $c (@{$ar}){ $self->{data_index}->[$c] = {};}
  7         17  
  5         16  
229 7         13 foreach my $d (@{$self->{data}})
  7         30  
230             {
231 66         92 foreach my $c (@{$ar})
  66         110  
232             {
233 46 50       356 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 5 my $self = shift;
243 2         4 my $xl = shift;
244 2 50       6 $xl = [0] unless defined $xl;
245             #compute any missing indices
246 2         5 my @l = ();
247 2 50       4 foreach my $x (@{$xl}){ push(@l, $x) unless defined $self->{data_index}->[$x]; }
  2         8  
  2         8  
248 2         9 $self->compute_index(\@l);
249             #now sort all concerned indices
250 2         3 foreach my $x (@{$xl})
  2         5  
251             {
252 2         5 $self->{sorted_data}->[$x] = [];
253 2         3 foreach my $k (sort {$a <=> $b} keys %{$self->{data_index}->[$x]})
  47         128  
  2         27  
254             {
255 20         26 push(@{$self->{sorted_data}->[$x]}, $self->{data_index}->[$x]->{$k});
  20         49  
256             }
257             }
258             }
259              
260              
261             sub write_data
262             {
263 9     9 1 19 my $self = shift;
264 9         21 my $handle = shift;
265 9         14 my $selection = shift;
266 9         18 for(my $i = 0; $i <= $#{$self->{data}}; ++$i)
  83         280  
267             {
268 74         121 print $handle ${$self->data_line($self->{data}->[$i],$selection)};
  74         232  
269             }
270 9         50 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 6 my $self = shift;
278 2         4 my $file = shift;
279 2         3 my $selection = shift;
280 2 50       8 $self->{out_file} = $file if defined $file;
281 2         3 my $handle;
282 2 50 0     8 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         9 binmode($handle);
286              
287 2 50       7 Text::ASCIIPipe::file_begin($handle) if($self->{config}{pipemode});
288             #header
289 2         8 $self->write_head($handle, $selection);
290             #data
291 2         10 $self->write_data($handle, $selection);
292 2 50       9 Text::ASCIIPipe::file_end($handle) if($self->{config}{pipemode});
293 2         21 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 14 my $self = shift; # No real need to shift here, but keeping in style.
301 5         22 return $self->write_head(@_);
302             }
303              
304             sub write_head
305             {
306 7     7 1 10 my $self = shift;
307 7         16 my $handle = shift;
308              
309 7         13 my $selection = shift;
310             # If there is no old raw header, do a new one automaticaly.
311 7 100       9 if(@{$self->{raw_header}})
  7         27  
312             {
313 6         10 foreach my $h (@{$self->{raw_header}})
  6         17  
314             {
315 12         22 print $handle ${$self->comment_line($h)};
  12         76  
316             }
317 6 50       26 if(defined $selection)
318             {
319 0         0 print $handle ${$self->title_line($selection)};
  0         0  
320             }
321             }
322             else
323             {
324 1         4 return $self->write_new_header($handle,$selection);
325             }
326 6         20 return 1;
327             }
328              
329             sub write_new_header
330             {
331 1     1 1 3 my $self = shift;
332 1         1 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         7  
335 1         2 foreach my $c (@{$self->{comments}}){ print $handle ${$self->comment_line(\$c)}; }
  1         3  
  0         0  
  0         0  
336 1         3 print $handle ${$self->title_line($selection)};
  1         5  
337 1         4 return 1;
338             }
339              
340             sub set_of_noint
341             {
342 600     600 1 842 my $self = shift;
343 600         971 my $value = shift;
344 600         1078 my $zahl = $self->{config}->{numregex};
345 600 50       5543 $value = sprintf($self->{config}->{indexformat}, $value) if $value =~ /$zahl/;
346 600         1118 my $x = shift;
347 600 50       1235 $x = 0 unless defined $x;
348 600 100       1298 defined $self->{data_index}->[$x] or $self->compute_index([$x]);
349 600         1476 return $self->{data_index}->[$x]->{$value};
350             }
351              
352             sub set_of
353             {
354 600     600 1 921 my $self = shift;
355             #the easy way
356 600         1224 my $set = $self->set_of_noint(@_);
357             return $set
358 600 100 100     2441 if (defined $set or $self->{intmethod} == $none);
359              
360             #so, we got to do some Work;
361 460         807 my $val = shift;
362 460         664 my $x = shift;
363 460 50       876 $x = 0 unless defined $x;
364 460 100       965 if($self->{intmethod} == $spline)
365             {
366 280         627 return $self->spline_set($val, $x);
367             }
368             #find suitable data value pairs for interpolation
369 180         390 my $sets = $self->neighbours($val, $x);
370 180 50 33     372 return undef unless defined $sets and $#{$sets} == 1;
  180         488  
371             #compute
372 180         324 my @set = ();
373              
374 180         292 for(my $y = 0; $y <= $#{$sets->[0]}; ++$y)
  540         1245  
375             {
376 360 100       663 if($y != $x)
377             {
378 180         754 push(@set, linear_value($val, [$sets->[0][$x], $sets->[1][$x]], [$sets->[0][$y],$sets->[1][$y]]));
379             }
380 180         420 else{ push(@set, $val); }
381             }
382            
383 180         550 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 309 my $self = shift;
432 180         282 my $val = shift;
433 180         292 my $x = shift;
434            
435 180         245 my $n = undef;
436 180         236 my $e = undef;
437              
438 180 50       360 unless($self->{config}{orderedint})
439             {
440 180 100       405 defined $self->{sorted_data}->[$x] or $self->compute_sorted_index([$x]);
441 180 50       297 return undef unless $#{$self->{sorted_data}->[$x]} > 0; #senseless when not at least two points there
  180         500  
442              
443 180         306 foreach my $v (@{$self->{sorted_data}->[$x]})
  180         361  
444             {
445 1044         1372 $n = $e;
446 1044         1383 $e = $v;
447 1044 100 100     2583 if($v->[$x] > $val and defined $n){ last; }
  180         338  
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       392 return undef unless defined $n; #catches both empty array and one-element-array
472 180 50       393 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         486 return [$n,$e];
478             }
479              
480             sub max
481             {
482 1     1 1 3 my $self = shift;
483 1         2 my $formula = shift;
484 1         3 my $ff = expression_function($formula);
485 1 50       4 return undef unless defined $ff;
486 1         3 my $max = undef;
487 1         1 foreach my $d (@{$self->{data}})
  1         4  
488             {
489 4         82 my $nm = &$ff([$d]);
490 4 100 66     20 $max = $nm if !defined $max or $nm > $max;
491             }
492 1         12 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 16 my $self = shift;
544 8         11 my $cols = shift;
545 8         14 my $down = shift;
546 8         14 my $sortcode = 'my $r; ';
547 8         11 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         13 foreach my $prec (@{$cols})
  8         19  
551             {
552             # Ensure only integer values enter evalued code.
553             # Invalid numbers get parsed to zero, as perl does normally.
554 9 50       46 my $c = $prec =~ /^(\d+)/ ? $1 : 0;
555 9         15 my $r = '$r';
556 9         15 my $a = '$a';
557 9         13 my $b = '$b';
558 9 100 66     51 if(defined $down and $down->[++$i])
559             {
560 1         2 $a = '$b';
561 1         2 $b = '$a';
562             }
563 9         37 $sortcode .= "($r= $a\->[$c] <=> $b\->[$c]) ? $r : ";
564             }
565 8         15 $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         928 return eval 'sub {'.$sortcode.'}';
569             }
570              
571             sub sort_data
572             {
573 8     8 1 15 my $self = shift;
574 8         15 my $cols = shift;
575 8         12 my $down = shift;
576 8         12 my $sortfunc = shift;
577 8 50       21 unless(defined $sortfunc)
578             {
579 8         31 $sortfunc = $self->sort_func($cols,$down);
580             }
581 8         33 @{$self->{data}} = sort $sortfunc @{$self->{data}};
  8         26  
  8         228  
582 8         81 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         2 my $config = shift; # {'byrow'=>0, 'bycol'=>0 }
593 1         5 my $files = shift; # list of files to use for [2,1] style refs
594 1         2 my $workarray = shift; # \@A
595 1         2 my $constants = shift; # \@C
596              
597 1         3 my $ff = formula_function($formula);
598 1 50       5 return 0 unless defined $ff;
599              
600 1         448 require Text::NumericData::FileCalc;
601             my $deletelist = Text::NumericData::FileCalc::file_calc
602             (
603             $ff
604             , $config
605             , $self->{data}
606 1         5 , $files
607             , $workarray
608             , $constants
609             );
610 1 50       3 return 0 unless defined $deletelist;
611 1         4 $self->delete_rows($deletelist);
612              
613 1         13 return 1;
614             }
615              
616             # delete indicated rows from data set
617             sub delete_rows
618             {
619 1     1 1 2 my $self = shift;
620 1         2 my $delist = shift;
621 1 50       2 return unless defined $delist;
622 1         2 my @delis = sort {$a <=> $b} @{$delist};
  0         0  
  1         5  
623 1   33     4 while(@delis and $delis[0]<0){ shift(@delis); }
  0         0  
624 1   33     3 while(@delis and $delis[$#delis]>$#{$self->{data}}){ pop(@delis); }
  0         0  
  0         0  
625              
626 1 50       3 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 30 my $self = shift;
676 15 50       28 return @{$self->{data}} ? $#{$self->{data}[0]}+1 : 0;
  15         40  
  15         105  
677             }
678              
679             1;
680              
681             __END__