File Coverage

blib/lib/Text/NumericData/App/txdcontract.pm
Criterion Covered Total %
statement 95 115 82.6
branch 28 42 66.6
condition 10 15 66.6
subroutine 8 8 100.0
pod 0 6 0.0
total 141 186 75.8


line stmt bran cond sub pod time code
1             package Text::NumericData::App::txdcontract;
2              
3 1     1   73883 use Text::NumericData::App;
  1         3  
  1         37  
4              
5 1     1   7 use strict;
  1         2  
  1         1442  
6              
7             # This is just a placeholder because of a past build system bug.
8             # The one and only version for Text::NumericData is kept in
9             # the Text::NumericData module itself.
10             our $VERSION = '1';
11             $VERSION = eval $VERSION;
12              
13             #the infostring says it all
14             my $infostring = 'contract a data file by computing the mean over n input rows to produce one output row
15              
16             txdcontract n < 100rows.dat > 50rows.dat
17              
18             A partial bin at the end is dropped. When you choose value-based binning, there
19             will always be some data dropped at the end. That way, you only get mean values
20             that actually represent a full interval/bin. Furthermore, the input data is
21             assumed to be sorted according to the column chosen for value-based binning.';
22              
23             our @ISA = ('Text::NumericData::App');
24              
25             my $stats_stddev = 1;
26             my $stats_minmax = 2;
27              
28             sub new
29             {
30 1     1 0 102 my $class = shift;
31 1         15 my @pars = (
32             'bincol', '', 'c'
33             , 'bin by values in given column instead of contracting by row count'
34             , 'binsize', 1, 'b', 'size of one bin'
35             , 'stats', 0, 's', 'add columns with statistic values for the bins'
36             . " ($stats_stddev: standard deviation,"
37             . " $stats_minmax: min and max values, "
38             . ($stats_stddev|$stats_minmax).": stddev, min, max)"
39             );
40              
41 1         18 return $class->SUPER::new
42             ({
43             parconf =>
44             {
45             info=>$infostring # default version
46             # default author
47             # default copyright
48             }
49             ,pardef => \@pars
50             ,pipemode => 1
51             ,pipe_init => \&preinit
52             ,pipe_begin => \&init
53             ,pipe_data => \&process_data
54             ,pipe_header => \&process_header
55             ,pipe_first_data => \&process_first_data
56             });
57             }
58              
59             sub preinit
60             {
61 4     4 0 9 my $self = shift;
62 4         6 my $n = shift(@{$self->{argv}});
  4         10  
63 4         18 my $bincol = int($self->{param}{bincol});
64 4 50       12 if($bincol > 0)
65             {
66 0         0 $self->{bincol} = $bincol-1;
67 0 0       0 unless($self->{param}{binsize} > 0)
68             {
69 0         0 print STDERR "Need positive binsize!\n";
70 0         0 return -1;
71             }
72 0         0 $self->{binsize} = 0+$self->{param}{binsize};
73             }
74             else
75             {
76 4 50       9 unless(defined $n)
77             {
78 0         0 print STDERR "Need n as argument!\n";
79 0         0 return -1;
80             }
81 4 50       13 unless($n>0)
82             {
83 0         0 print STDERR "Need n>0!\n";
84 0         0 return -1;
85             }
86 4         9 $self->{n} = int($n);
87             }
88 4         10 return 0;
89             }
90              
91             sub init
92             {
93 4     4 0 413 my $self = shift;
94              
95 4         23 $self->new_txd();
96 4         38 $self->{binval} = undef;
97 4         12 $self->{mean} = [];
98 4         11 $self->{binbuffer} = [];
99 4 50       16 $self->{meancount} = defined $self->{n} ? $self->{n} : 0;
100 4         5 $self->{ln} = 0;
101 4         13 $self->{sline} = '';
102             }
103              
104             # Delay header printout for processing column headers.
105             sub process_header
106             {
107 8     8 0 15 my $self = shift;
108 8         13 my $sline = $_[0];
109 8         14 $_[0] = $self->{sline};
110 8         17 $self->{sline} = $sline;
111             }
112              
113             # Append stats titles. This is rather convoluted, I need to make this nicer.
114             sub process_first_data
115             {
116 4     4 0 8 my $self = shift;
117 4         8 my $txd = $self->{txd};
118 4         13 my $data = $txd->line_data($_[0]);
119 4 50       6 if(@{$self->{txd}{titles}})
  4         14  
120             {
121 4         6 my $cols = @{$data};
  4         9  
122 4         5 my $devi = $cols;
123 4 100       21 my $mini = $self->{param}{stats} & $stats_stddev ? $devi+$cols : $cols;
124 4         13 for(my $i=0; $i<$cols; ++$i)
125             {
126 12 50       29 my $tit = defined $txd->{titles}[$i] ? $txd->{titles}[$i] : ($i+1);
127             $txd->{titles}[$devi+$i] = 'dev:'.$tit
128 12 100       33 if($self->{param}{stats} & $stats_stddev);
129 12 100       32 if($self->{param}{stats} & $stats_minmax)
130             {
131 6         15 $txd->{titles}[$mini+$i] = 'min:'.$tit;
132 6         19 $txd->{titles}[$mini+$cols+$i] = 'max:'.$tit;
133             }
134             }
135 4         23 return $self->{txd}->title_line();
136             }
137 0         0 else{ return \$self->{sline}; }
138             }
139              
140             sub process_data
141             {
142 16     16 0 35 my $self = shift;
143 16         47 my $data = $self->{txd}->line_data($_[0]);
144 16         31 my $bin_finished;
145             my $binval;
146 16         26 $_[0] = '';
147             return
148 16 50       35 unless defined $data;
149              
150             # Line-based binning knows already where the boundary is, can
151             # compute before output.
152 16 50       35 if(defined $self->{n})
153             {
154 16         30 for(my $i = 0; $i <= $#{$data}; ++$i)
  64         129  
155             {
156 48         102 $self->{mean}[$i] += $data->[$i];
157             }
158 12         25 push(@{$self->{binbuffer}}, [@{$data}])
  12         31  
159 16 100       62 if($self->{param}{stats});
160 16         32 ++$self->{ln};
161             }
162             else # Value-based binning needs to figure out if we crossed the border.
163             {
164             $binval = sprintf('%.0f', $data->[$self->{bincol}]/$self->{binsize})
165 0         0 * $self->{binsize};
166 0   0     0 $bin_finished = (@{$self->{mean}} and $binval != $self->{binval});
167             }
168              
169 16 100 66     91 if($bin_finished or (defined $self->{n} and $self->{ln} == $self->{n}))
      66        
170             {
171 8         20 my @outdata;
172 8         11 for(@{$self->{mean}})
  8         19  
173             {
174 24         60 push(@outdata, $_ /= $self->{ln});
175             }
176 8 100       24 if($self->{param}{stats} & $stats_stddev)
177             {
178 4         8 my @sum;
179 4         14 for(my $d=0; $d<$self->{ln}; ++$d)
180             {
181 8         14 for(my $i=0; $i<@{$self->{mean}}; ++$i)
  32         70  
182             {
183 24         56 $sum[$i] += ($self->{binbuffer}[$d][$i] - $self->{mean}[$i])**2;
184             }
185             }
186 4         17 for(my $i=0; $i<@{$self->{mean}}; ++$i)
  16         35  
187             {
188 12         26 push(@outdata, sqrt($sum[$i]/$self->{ln}));
189             }
190             }
191 8 100       20 if($self->{param}{stats} & $stats_minmax)
192             {
193 4         9 my @min;
194             my @max;
195 4         11 for(my $d=0; $d<$self->{ln}; ++$d)
196             {
197 8         13 for(my $i=0; $i<@{$self->{mean}}; ++$i)
  32         72  
198             {
199             $max[$i] = $self->{binbuffer}[$d][$i]
200             if( not defined $max[$i] or
201 24 100 100     75 $self->{binbuffer}[$d][$i] > $max[$i] );
202             $min[$i] = $self->{binbuffer}[$d][$i]
203             if( not defined $min[$i] or
204 24 100 100     77 $self->{binbuffer}[$d][$i] < $min[$i] );
205             }
206             }
207             # Loop ensures that we really have the given amount of entries,
208             # even if buffer was empty.
209 4         29 for(my $i=0; $i<@{$self->{mean}}; ++$i)
  16         34  
210             {
211 12         22 push(@outdata, $min[$i]);
212             }
213 4         9 for(my $i=0; $i<@{$self->{mean}}; ++$i)
  16         37  
214             {
215 12         21 push(@outdata, $max[$i]);
216             }
217             }
218              
219             $outdata[$self->{bincol}] = $self->{binval}
220 8 50       17 if $bin_finished;
221 8         12 $_[0] = ${$self->{txd}->data_line(\@outdata)};
  8         28  
222 8         13 @{$self->{mean}} = ();
  8         18  
223 8         13 @{$self->{binbuffer}} = ();
  8         17  
224 8         20 $self->{ln} = 0;
225             }
226              
227             # Need to add bin values after output. Slightly twisted logic compared to
228             # line-based binning.
229 16 50       60 unless(defined $self->{n})
230             {
231 0           $self->{binval} = $binval;
232 0           for(my $i = 0; $i <= $#{$data}; ++$i)
  0            
233             {
234 0           $self->{mean}[$i] += $data->[$i];
235             }
236 0           push(@{$self->{binbuffer}}, [@{$data}])
  0            
237 0 0         if($self->{param}{stats});
238 0           ++$self->{ln};
239             }
240             }
241              
242             1;