File Coverage

blib/lib/Fsdb/Filter/dbcolhisto.pm
Criterion Covered Total %
statement 24 139 17.2
branch 0 74 0.0
condition 0 33 0.0
subroutine 8 19 42.1
pod 5 5 100.0
total 37 270 13.7


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2              
3             #
4             # dbcolhisto.pm
5             # Copyright (C) 1997-2008 by John Heidemann
6             # $Id: 018fca2849f105e4a7f53f6f3a2f5b463f780f70 $
7             #
8             # This program is distributed under terms of the GNU general
9             # public license, version 2. See the file COPYING
10             # in $dblibdir for details.
11             #
12              
13             package Fsdb::Filter::dbcolhisto;
14              
15             =head1 NAME
16              
17             dbcolhisto - compute a histogram over a column of Fsdb data
18              
19             =head1 SYNOPSIS
20              
21             dbcolhisto [-ag] [-W BucketWidth] [-S BucketStart] [-E BucketEnd] [-N NumberOfBuckets] column
22              
23             =head1 DESCRIPTION
24              
25             This program computes a histogram over a column of data.
26             Records containing non-numeric data are considered null
27             do not contribute to the stats (optionally they are treated as zeros).
28              
29             Defaults to 10 buckets over the exact range of data.
30             Up to three parameters (number of buckets, start, end, and width)
31             can be specified, the rest default accordingly.
32              
33             Buckets range from a value (given the the low column) to just below
34             the next low value and buckets are equal width.
35             If necessary, extra "max" buckets are created.
36             By default, the last bucket includes max (and is thus infinitesimally
37             larger than the other buckets). This irregularity can be removed
38             with the C<-I> option.
39              
40             This program requires O(number of buckets) memory
41             and O(size of data) temporary disk space.
42              
43              
44             =head1 OPTIONS
45              
46             =over 4
47              
48             =item B<-W> or B<--width> N
49              
50             Gives with width of each bucket, in data units.
51             Default is whatever gives 10 buckets over the whole range of data.
52              
53             =item B<-S> or B<--start> N
54              
55             Buckets start at value N, in data units.
56             Default is the minimum data value.
57              
58             =item B<-E> or B<--end> N
59              
60             Buckets end at value N, in data units.
61             Default is the maximum data value.
62              
63             =item B<-N> or B<--number> N
64              
65             Create N buckets.
66             The default is 10 buckets.
67              
68             =item B<-g> or B<--graphical>
69              
70             Generate a graphical histogram (with asterisks).
71             Default is numeric.
72              
73             =item B<-I> or B<--last-inclusive>
74              
75             Make the last bucket non-inclusive of the last value.
76              
77             =item B<-a>
78              
79             Compute stats over all records (treat non-numeric records
80             as zero rather than just ignoring them).
81             Default is non-numeric records are ignored.
82              
83             =back
84              
85             =for comment
86             begin_standard_fsdb_options
87              
88             This module also supports the standard fsdb options:
89              
90             =over 4
91              
92             =item B<-d>
93              
94             Enable debugging output.
95              
96             =item B<-i> or B<--input> InputSource
97              
98             Read from InputSource, typically a file name, or C<-> for standard input,
99             or (if in Perl) a IO::Handle, Fsdb::IO or Fsdb::BoundedQueue objects.
100              
101             =item B<-o> or B<--output> OutputDestination
102              
103             Write to OutputDestination, typically a file name, or C<-> for standard output,
104             or (if in Perl) a IO::Handle, Fsdb::IO or Fsdb::BoundedQueue objects.
105              
106             =item B<--autorun> or B<--noautorun>
107              
108             By default, programs process automatically,
109             but Fsdb::Filter objects in Perl do not run until you invoke
110             the run() method.
111             The C<--(no)autorun> option controls that behavior within Perl.
112              
113             =item B<--help>
114              
115             Show help.
116              
117             =item B<--man>
118              
119             Show full manual.
120              
121             =back
122              
123             =for comment
124             end_standard_fsdb_options
125              
126              
127             =head1 SAMPLE USAGE
128              
129             =head2 Input:
130              
131             #fsdb name id test1
132             a 1 80
133             b 2 70
134             c 3 65
135             d 4 90
136             e 5 70
137             f 6 90
138              
139             =head2 Command:
140              
141             cat DATA/grades.fsdb | dbcolhisto -S 0 -E 100 -N 10 test1
142              
143             =head2 Output:
144              
145             #fsdb low histogram
146             0 0
147             10 0
148             20 0
149             30 0
150             40 0
151             50 0
152             60 1
153             70 2
154             80 1
155             90 2
156             # | dbcolhisto -S 0 -E 100 -N 10 test1
157              
158              
159             =head1 SEE ALSO
160              
161             L,
162             L,
163             L
164              
165             =head1 BUGS
166              
167             This program could run in constant memory with no external storage
168             when the buckets are pre-specified. That optimization is not implemented.
169              
170              
171             =head1 CLASS FUNCTIONS
172              
173             =cut
174              
175             @ISA = qw(Fsdb::Filter);
176             ($VERSION) = 2.0;
177              
178 1     1   4865 use strict;
  1         2  
  1         29  
179 1     1   4 use Pod::Usage;
  1         2  
  1         70  
180 1     1   5 use Carp;
  1         2  
  1         43  
181              
182 1     1   5 use Fsdb::Filter;
  1         2  
  1         16  
183 1     1   4 use Fsdb::IO::Reader;
  1         2  
  1         19  
184 1     1   3 use Fsdb::IO::Writer;
  1         2  
  1         16  
185 1     1   3 use Fsdb::IO::Replayable;
  1         2  
  1         23  
186 1     1   4 use Fsdb::Support qw($is_numeric_regexp);
  1         2  
  1         1064  
187              
188              
189             =head2 new
190              
191             $filter = new Fsdb::Filter::dbcolhisto(@arguments);
192              
193             Create a new dbcolhisto object, taking command-line arguments.
194              
195             =cut
196              
197             sub new ($@) {
198 0     0 1   my $class = shift @_;
199 0           my $self = $class->SUPER::new(@_);
200 0           bless $self, $class;
201 0           $self->set_defaults;
202 0           $self->parse_options(@_);
203 0           $self->SUPER::post_new();
204 0           return $self;
205             }
206              
207              
208             =head2 set_defaults
209              
210             $filter->set_defaults();
211              
212             Internal: set up defaults.
213              
214             =cut
215              
216             sub set_defaults ($) {
217 0     0 1   my($self) = @_;
218 0           $self->SUPER::set_defaults();
219 0           $self->{_bucket_width} = undef;
220 0           $self->{_bucket_start} = undef;
221 0           $self->{_bucket_end} = undef;
222 0           $self->{_bucket_count} = undef;
223 0           $self->{_graphical_output} = undef;
224 0           $self->{_last_inclusive} = 1;
225 0           $self->{_include_non_numeric} = undef;
226 0           $self->{_fscode} = undef;
227             }
228              
229             =head2 parse_options
230              
231             $filter->parse_options(@ARGV);
232              
233             Internal: parse command-line arguments.
234              
235             =cut
236              
237             sub parse_options ($@) {
238 0     0 1   my $self = shift @_;
239              
240 0           my(@argv) = @_;
241             $self->get_options(
242             \@argv,
243 0     0     'help|?' => sub { pod2usage(1); },
244 0     0     'man' => sub { pod2usage(-verbose => 2); },
245             'autorun!' => \$self->{_autorun},
246             'close!' => \$self->{_close},
247             'a|include-non-numeric!' => \$self->{_include_non_numeric},
248             'd|debug+' => \$self->{_debug},
249             'E|end=f' => \$self->{_bucket_end},
250             'F|fs|cs|fieldseparator|columnseparator=s' => \$self->{_fscode},
251             'g|graphical!' => \$self->{_graphical_output},
252 0     0     'i|input=s' => sub { $self->parse_io_option('input', @_); },
253             'I|last-inclusive!' => \$self->{_last_inclusive},
254             'log!' => \$self->{_logprog},
255             'N|number=i' => \$self->{_bucket_count},
256 0     0     'o|output=s' => sub { $self->parse_io_option('output', @_); },
257             'S|start=f' => \$self->{_bucket_start},
258             'W|width=f' => \$self->{_bucket_width},
259 0 0         ) or pod2usage(2);
260 0           $self->parse_target_column(\@argv);
261             }
262              
263             =head2 setup
264              
265             $filter->setup();
266              
267             Internal: setup, parse headers.
268              
269             =cut
270              
271             sub setup ($) {
272 0     0 1   my($self) = @_;
273              
274 0 0         pod2usage(2) if (!defined($self->{_target_column}));
275              
276 0           $self->finish_io_option('input', -comment_handler => $self->create_delay_comments_sub);
277 0           $self->{_target_coli} = $self->{_in}->col_to_i($self->{_target_column});
278              
279 0           my @output_options = (-cols => [qw(low histogram)]);
280             unshift (@output_options, -fscode => $self->{_fscode})
281 0 0         if (defined($self->{_fscode}));
282 0           $self->finish_io_option('output', @output_options);
283             }
284              
285             =head2 run
286              
287             $filter->run();
288              
289             Internal: run over each rows.
290              
291             =cut
292             sub run ($) {
293 0     0 1   my($self) = @_;
294 0           my $read_fastpath_sub = $self->{_in}->fastpath_sub();
295 0           my $write_fastpath_sub = $self->{_out}->fastpath_sub();
296              
297             #
298             # scan the data to find min/max/n
299             # (We could just invoke dbcolstats, but that's overkill.)
300             #
301 0           my($min, $max);
302 0           my($n) = 0;
303              
304 0           my $replayable = new Fsdb::IO::Replayable(-writer_args => [ -cols => [qw(x)] ]);
305 0           my $replayable_writer = $replayable->writer;
306 0           my $replayable_writer_fastpath_sub = $replayable_writer->fastpath_sub();
307 0           my $fref;
308 0           my($xf) = $self->{_target_coli};
309 0           my @of;
310             my $x;
311 0           while ($fref = &$read_fastpath_sub()) {
312 0           $x = $fref->[$xf];
313 0 0         if (!$self->{_include_non_numeric}) {
314 0 0         next if ($x !~ /$is_numeric_regexp/);
315             };
316 0 0 0       $min = $x if (!defined($min) || $x < $min);
317 0 0 0       $max = $x if (!defined($max) || $x > $max);
318 0           $n++;
319 0           $of[0] = $x;
320 0           &$replayable_writer_fastpath_sub(\@of);
321             };
322 0           $replayable->close;
323              
324             #
325             # sanity check
326             #
327 0 0         if ($n == 0) {
    0          
328 0           croak $self->{_prog} . ": histogram impossible with no input\n";
329             } elsif ($n == 1) {
330 0           croak $self->{_prog} . ": histogram impossible with singleton input\n";
331             };
332              
333             #
334             # Figure out bucket parameters.
335             # Yuck. Constraint solving in Perl.
336             #
337 0           my $bucket_start = $self->{_bucket_start};
338 0           my $bucket_end = $self->{_bucket_end};
339 0           my $bucket_width = $self->{_bucket_width};
340 0           my $bucket_count = $self->{_bucket_count};
341 0 0         my($n_defined) =
    0          
    0          
    0          
342             (defined($bucket_start) ? 1 : 0) +
343             (defined($bucket_end) ? 1 : 0) +
344             (defined($bucket_width) ? 1 : 0) +
345             (defined($bucket_count) ? 1 : 0);
346 0 0         if ($n_defined >= 4) {
    0          
    0          
    0          
    0          
347 0           croak $self->{_prog} . ": parameters over-specified.\n";
348             } elsif ($n_defined == 3) {
349             # fall through, clean up handles it.
350             } elsif ($n_defined == 2) {
351 0 0 0       if (defined($bucket_start) && defined($bucket_end)) {
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
352 0           $bucket_count = 10;
353             } elsif (defined($bucket_start) && defined($bucket_width)) {
354 0           $bucket_count = 10;
355             } elsif (defined($bucket_start) && defined($bucket_count)) {
356 0           $bucket_end = $max;
357             } elsif (defined($bucket_end) && defined($bucket_width)) {
358 0           $bucket_count = 10;
359             } elsif (defined($bucket_end) && defined($bucket_count)) {
360 0           $bucket_start = $min;
361             } elsif (defined($bucket_width) && defined($bucket_count)) {
362 0           my($mid) = ($max - $min) / 2 + $min;
363 0           $bucket_start = $mid - $bucket_width * $bucket_count / 2;
364             } else {
365 0           die "internal error\n";
366             };
367             # Figure the rest out below.
368             } elsif ($n_defined == 1) {
369 0 0 0       if (defined($bucket_start)) {
    0          
    0          
370 0           $bucket_end = $max;
371 0           $bucket_count = 10;
372             } elsif (defined($bucket_end)) {
373 0           $bucket_start = $min;
374 0           $bucket_count = 10;
375             } elsif (defined($bucket_width) || defined($bucket_count)) {
376 0           $bucket_start = $min;
377 0           $bucket_end = $max;
378             } else {
379 0           die "internal error\n";
380             };
381             } elsif ($n_defined < 1) {
382 0           $bucket_start = $min;
383 0           $bucket_end = $max;
384 0           $bucket_count = 10;
385             };
386             # clean up
387 0 0         $bucket_start = $bucket_end - $bucket_width * $bucket_count
388             if (!defined($bucket_start));
389 0 0         $bucket_end = $bucket_start + $bucket_width * $bucket_count
390             if (!defined($bucket_end));
391 0 0         $bucket_width = ($bucket_end - $bucket_start) / $bucket_count
392             if (!defined($bucket_width));
393 0 0         $bucket_count = ($bucket_end - $bucket_start) / $bucket_width
394             if (!defined($bucket_count));
395 0           $bucket_width += 0.0;
396              
397             #
398             # Compute the histogram.
399             #
400 0           my(@buckets) = (0) x $bucket_count;
401 0           my($low_bucket, $high_bucket) = (0, 0);
402              
403 0           my $replayable_reader = $replayable->reader;
404 0           my $replayable_reader_fastpath_sub = $replayable_reader->fastpath_sub();
405 0           while ($fref = &$replayable_reader_fastpath_sub()) {
406 0           my $x = $fref->[0] + 0;
407 0           my($b) = ($x - $bucket_start) / ($bucket_width);
408 0 0         if ($b < 0) {
    0          
409 0           $low_bucket++;
410             } elsif ($b >= $bucket_count) {
411 0 0 0       if (($x == $high_bucket || $b == $bucket_count) && $self->{_last_inclusive}) {
      0        
412 0           $buckets[$bucket_count]++;
413             } else {
414 0           $high_bucket++;
415             };
416             } else {
417 0           $buckets[int($b)]++;
418             };
419             }
420 0           $replayable_reader->close;
421              
422             my $format_sub = $self->{_graphical_output} ?
423 0     0     sub { return "*" x $_[0]; } :
424 0 0   0     sub { return $_[0]; };
  0            
425              
426 0 0         if ($low_bucket) {
427 0           @of = ("<" . $bucket_start, &$format_sub($low_bucket));
428 0           &$write_fastpath_sub(\@of);
429             };
430 0           foreach (0..$#buckets) {
431 0           @of = ($_ * $bucket_width + $bucket_start, &$format_sub($buckets[$_]));
432 0           &$write_fastpath_sub(\@of);
433             };
434 0 0         if ($high_bucket) {
435 0           my $last = $#buckets * $bucket_width + $bucket_start;
436 0           @of = (">=" . $last, &$format_sub($high_bucket));
437 0           &$write_fastpath_sub(\@of);
438             };
439             }
440              
441              
442             =head1 AUTHOR and COPYRIGHT
443              
444             Copyright (C) 1991-2008 by John Heidemann
445              
446             This program is distributed under terms of the GNU general
447             public license, version 2. See the file COPYING
448             with the distribution for details.
449              
450             =cut
451              
452             1;