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   2583 use Text::NumericData::Calc qw(linear_value formula_function expression_function);
  7         17  
  7         475  
10 7     7   47 use Text::NumericData;
  7         14  
  7         144  
11 7     7   1610 use Text::ASCIIPipe;
  7         4245  
  7         231  
12 7     7   3942 use sort 'stable';
  7         4333  
  7         42  
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 38 my $class = shift;
40 18         61 my $config = shift;
41 18         101 my $self = $class->SUPER::new($config);
42 18         36 $self->{in_file} = shift;
43 18 100 100     121 if(defined $config->{interpolate} and $config->{interpolate} eq 'spline')
    100 100        
44             {
45 2         979 require Math::Spline;
46 2         4697 $self->{intmethod} = $spline;
47             }
48             elsif(defined $config->{interpolate} and not $config->{interpolate})
49             {
50 1         4 $self->{intmethod} = $none;
51             }
52 15         45 else{ $self->{intmethod} = $linear; }
53              
54             $self->{config}{pipemode} = $config->{pipemode}
55 18 50       52 if(defined $config->{pipemode});
56              
57 18 100       51 if(defined $self->{in_file})
58             {
59 6         16 $self->read_all();
60             }
61 18         130 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 65 my $self = shift;
69 35         135 $self->SUPER::init();
70 35 50       113 $self->{config}{indexformat} = '%6E' unless defined $self->{config}->{indexformat};
71 35 50       103 $self->{config}{extrapol} = 1 unless defined $self->{config}{extrapol};
72 35         70 $self->{data} = [];
73 35         66 $self->{records} = 0;
74 35         79 $self->{data_index} = [];
75 35         60 $self->{sorted_data} = [];
76 35         59 $self->{raw_header} = [];
77 35         55 $self->{buffer} = ""; #containing the raw lines read by readhead (the'd be lost from stdin otherwise)
78 35         59 $self->{splines} = [];
79 35         74 $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 48 my ($self, $infile, $justhead) = @_;
95 17         44 $self->init(); #do we really want full init?
96 17 100       47 $self->{in_file} = $infile if defined $infile;
97              
98 17         28 my $handle;
99              
100 17 100 33     73 if(ref $self->{in_file}){ $handle = $self->{in_file}; }
  11 50       21  
101 0         0 elsif(not defined $self->{in_file} or $self->{in_file} eq ''){ $handle = \*STDIN; }
102 6 50       265 else{ open($handle, $self->{in_file}) or return 0; }
103              
104 17         36 my $data = 0;
105 17         67 binmode($handle);
106 17         36 my $l;
107 17         34 $self->{buffer} = '';
108              
109 17         24 my $state;
110 17         69 while(defined ($state = Text::ASCIIPipe::fetch($handle, $l)))
111             {
112 167 50       1963 if($state == $Text::ASCIIPipe::line)
113             {
114 167 100       311 if(!$data)
115             {
116 30 50       62 $self->{buffer}.= $l if($justhead);
117 30 100       113 if($self->line_check(\$l))
118             {
119 17 50       59 last if $justhead;
120 17         33 $data = 1;
121             }
122             else
123             {
124 13         74 $self->make_naked($l);
125 13         34 push(@{$self->{raw_header}}, $l);
  13         34  
126             }
127             }
128 167 100       333 if($data)
129             {
130 154         357 my $da = $self->line_data($l);
131 154 50       407 if(defined $da)
132             {
133 154         209 push(@{$self->{data}}, $da);
  154         307  
134 154 50       242 ++$self->{records} if @{$da}; # count non-empty records
  154         491  
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     298 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       196 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 19 my $self = shift;
162 2         4 my $x = shift;
163              
164 2         6 $self->{splines} = [];
165 2         5 $self->{splinex} = undef;
166 2         3 my $i = 0;
167 2         5 my @x;
168             my @data; # the more effective data structe
169 2         4 for my $d (@{$self->{data}})
  2         7  
170             {
171             # collect data, omitting duplicates
172 16 50       26 next if grep {$_ == $d->[$x]} @x;
  60         112  
173 16         30 push(@x, $d->[$x]);
174 16         23 for(my $j = 0; $j <= $#{$d}; ++$j)
  54         97  
175             {
176 38         87 $data[$j][$i] = $d->[$j];
177             }
178 16         25 ++$i;
179             }
180 2 50 33     21 die "Bad spline column index.\n" if($x < 0 or $x > $#data);
181              
182 2         6 $self->{splinex} = $x;
183              
184             # Not caring for undefined pieces of data, does Math::Spline care?
185 2         7 for(my $j = 0; $j <= $#data; ++$j)
186             {
187 5         299 $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 395 my $self = shift;
207 280         400 my $val = shift;
208 280         436 my $x = shift;
209 280 50       492 $x = 0 unless defined $x;
210 280 100 66     844 $self->prepare_splines($x) unless(defined $self->{splinex} and $self->{splinex} == $x);
211              
212 280         565 my @set;
213 280         373 for my $s (@{$self->{splines}})
  280         574  
214             {
215 750         16038 push(@set, $s->evaluate($val));
216             }
217              
218 280         9025 return \@set;
219             }
220              
221             #compute_index(\arrayofcols)
222             sub compute_index
223             {
224 7     7 1 12 my $self = shift;
225 7         12 my $ar = shift;
226 7         14 my $zahl = $self->{config}->{numregex};
227 7 50       18 $ar = [0] unless defined $ar;
228 7         12 foreach my $c (@{$ar}){ $self->{data_index}->[$c] = {};}
  7         13  
  5         20  
229 7         13 foreach my $d (@{$self->{data}})
  7         18  
230             {
231 66         90 foreach my $c (@{$ar})
  66         106  
232             {
233 46 50       343 my $key = $d->[$c] =~ /$zahl/ ? sprintf($self->{config}->{indexformat}, $d->[$c]) : $d->[$c];
234 46 50       180 $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         4 my $xl = shift;
244 2 50       5 $xl = [0] unless defined $xl;
245             #compute any missing indices
246 2         4 my @l = ();
247 2 50       4 foreach my $x (@{$xl}){ push(@l, $x) unless defined $self->{data_index}->[$x]; }
  2         6  
  2         7  
248 2         8 $self->compute_index(\@l);
249             #now sort all concerned indices
250 2         4 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         85  
  2         17  
254             {
255 20         26 push(@{$self->{sorted_data}->[$x]}, $self->{data_index}->[$x]->{$k});
  20         41  
256             }
257             }
258             }
259              
260              
261             sub write_data
262             {
263 9     9 1 17 my $self = shift;
264 9         14 my $handle = shift;
265 9         13 my $selection = shift;
266 9         19 for(my $i = 0; $i <= $#{$self->{data}}; ++$i)
  83         221  
267             {
268 74         114 print $handle ${$self->data_line($self->{data}->[$i],$selection)};
  74         202  
269             }
270 9         35 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         5 my $file = shift;
279 2         5 my $selection = shift;
280 2 50       8 $self->{out_file} = $file if defined $file;
281 2         4 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         6 binmode($handle);
286              
287 2 50       7 Text::ASCIIPipe::file_begin($handle) if($self->{config}{pipemode});
288             #header
289 2         7 $self->write_head($handle, $selection);
290             #data
291 2         9 $self->write_data($handle, $selection);
292 2 50       8 Text::ASCIIPipe::file_end($handle) if($self->{config}{pipemode});
293 2         22 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 9 my $self = shift; # No real need to shift here, but keeping in style.
301 5         16 return $self->write_head(@_);
302             }
303              
304             sub write_head
305             {
306 7     7 1 13 my $self = shift;
307 7         14 my $handle = shift;
308              
309 7         11 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         23  
312             {
313 6         8 foreach my $h (@{$self->{raw_header}})
  6         13  
314             {
315 12         22 print $handle ${$self->comment_line($h)};
  12         51  
316             }
317 6 50       34 if(defined $selection)
318             {
319 0         0 print $handle ${$self->title_line($selection)};
  0         0  
320             }
321             }
322             else
323             {
324 1         6 return $self->write_new_header($handle,$selection);
325             }
326 6         15 return 1;
327             }
328              
329             sub write_new_header
330             {
331 1     1 1 1 my $self = shift;
332 1         2 my $handle = shift;
333 1         3 my $selection = shift;
334 1 50       3 print $handle ${$self->comment_line(\$self->{title})} if defined $self->{title};
  1         9  
335 1         4 foreach my $c (@{$self->{comments}}){ print $handle ${$self->comment_line(\$c)}; }
  1         4  
  0         0  
  0         0  
336 1         2 print $handle ${$self->title_line($selection)};
  1         5  
337 1         3 return 1;
338             }
339              
340             sub set_of_noint
341             {
342 600     600 1 811 my $self = shift;
343 600         931 my $value = shift;
344 600         1010 my $zahl = $self->{config}->{numregex};
345 600 50       5256 $value = sprintf($self->{config}->{indexformat}, $value) if $value =~ /$zahl/;
346 600         1102 my $x = shift;
347 600 50       1112 $x = 0 unless defined $x;
348 600 100       1253 defined $self->{data_index}->[$x] or $self->compute_index([$x]);
349 600         1312 return $self->{data_index}->[$x]->{$value};
350             }
351              
352             sub set_of
353             {
354 600     600 1 884 my $self = shift;
355             #the easy way
356 600         1169 my $set = $self->set_of_noint(@_);
357             return $set
358 600 100 100     2314 if (defined $set or $self->{intmethod} == $none);
359              
360             #so, we got to do some Work;
361 460         734 my $val = shift;
362 460         648 my $x = shift;
363 460 50       879 $x = 0 unless defined $x;
364 460 100       913 if($self->{intmethod} == $spline)
365             {
366 280         608 return $self->spline_set($val, $x);
367             }
368             #find suitable data value pairs for interpolation
369 180         372 my $sets = $self->neighbours($val, $x);
370 180 50 33     339 return undef unless defined $sets and $#{$sets} == 1;
  180         457  
371             #compute
372 180         297 my @set = ();
373              
374 180         275 for(my $y = 0; $y <= $#{$sets->[0]}; ++$y)
  540         1168  
375             {
376 360 100       624 if($y != $x)
377             {
378 180         696 push(@set, linear_value($val, [$sets->[0][$x], $sets->[1][$x]], [$sets->[0][$y],$sets->[1][$y]]));
379             }
380 180         381 else{ push(@set, $val); }
381             }
382            
383 180         466 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 234 my $self = shift;
432 180         243 my $val = shift;
433 180         250 my $x = shift;
434            
435 180         245 my $n = undef;
436 180         237 my $e = undef;
437              
438 180 50       332 unless($self->{config}{orderedint})
439             {
440 180 100       346 defined $self->{sorted_data}->[$x] or $self->compute_sorted_index([$x]);
441 180 50       243 return undef unless $#{$self->{sorted_data}->[$x]} > 0; #senseless when not at least two points there
  180         483  
442              
443 180         261 foreach my $v (@{$self->{sorted_data}->[$x]})
  180         339  
444             {
445 1044         1336 $n = $e;
446 1044         1332 $e = $v;
447 1044 100 100     2319 if($v->[$x] > $val and defined $n){ last; }
  180         305  
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       340 return undef unless defined $n; #catches both empty array and one-element-array
472 180 50       345 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         429 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         7 my $max = undef;
487 1         2 foreach my $d (@{$self->{data}})
  1         7  
488             {
489 4         83 my $nm = &$ff([$d]);
490 4 100 66     18 $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 12 my $self = shift;
544 8         12 my $cols = shift;
545 8         10 my $down = shift;
546 8         15 my $sortcode = 'my $r; ';
547 8         20 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         15  
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         25 my $r = '$r';
556 9         24 my $a = '$a';
557 9         13 my $b = '$b';
558 9 100 66     47 if(defined $down and $down->[++$i])
559             {
560 1         2 $a = '$b';
561 1         2 $b = '$a';
562             }
563 9         33 $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         890 return eval 'sub {'.$sortcode.'}';
569             }
570              
571             sub sort_data
572             {
573 8     8 1 16 my $self = shift;
574 8         10 my $cols = shift;
575 8         12 my $down = shift;
576 8         12 my $sortfunc = shift;
577 8 50       18 unless(defined $sortfunc)
578             {
579 8         20 $sortfunc = $self->sort_func($cols,$down);
580             }
581 8         20 @{$self->{data}} = sort $sortfunc @{$self->{data}};
  8         25  
  8         204  
582 8         73 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         1 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       4 return 0 unless defined $ff;
599              
600 1         527 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         12 return 1;
614             }
615              
616             # delete indicated rows from data set
617             sub delete_rows
618             {
619 1     1 1 1 my $self = shift;
620 1         2 my $delist = shift;
621 1 50       3 return unless defined $delist;
622 1         1 my @delis = sort {$a <=> $b} @{$delist};
  0         0  
  1         3  
623 1   33     3 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 24 my $self = shift;
676 15 50       23 return @{$self->{data}} ? $#{$self->{data}[0]}+1 : 0;
  15         45  
  15         76  
677             }
678              
679             1;
680              
681             __END__