File Coverage

blib/lib/Data/Pivoter.pm
Criterion Covered Total %
statement 94 111 84.6
branch 30 54 55.5
condition 10 15 66.6
subroutine 14 14 100.0
pod 3 3 100.0
total 151 197 76.6


, row=> , , row=>, data=>, is the column containing data going
line stmt bran cond sub pod time code
1             package Data::Pivoter;
2              
3 5     5   2785 use strict;
  5         7  
  5         176  
4 5     5   25 use vars qw($VERSION);
  5         9  
  5         365  
5             $VERSION='0.08';
6              
7             =head1 NAME
8              
9             Data::Pivoter - Perl extension for pivot / cross tabulation of data
10              
11              
12             =head1 SYNOPSIS
13              
14             $pivoter = Table::Pivoter->new(col=>
15             data=> ,
16             group=> ,
17             function=> ,
18             numeric => ,
19             donotvalidate=> ,
20             test=>);
21              
22             $pivotedtableref = $pivoter->pivot(\@rawtable)
23             if $pivoter->ok;
24              
25            
26             =head1 DESCRIPTION
27              
28             A pivot object is created using new. Various parameters may be specified to
29             alter how the table is pivoted. The actual pivot of a table is perfomed using the method pivot.
30              
31             =cut
32              
33              
34 5     5   23 use vars '$AUTOLOAD';
  5         12  
  5         159  
35            
36 5     5   5551 use Data::Dumper;
  5         68949  
  5         378  
37 5     5   44 use Carp;
  5         30  
  5         6937  
38              
39             my $debug = $ENV{PIVOTER_DEBUG} || 0;
40              
41             =head1 Methods
42              
43             =head2 new
44              
45              
46             Table::Pivoter->new(col=>
47             group=>, function=>, numeric=>,
48             donotvalidate=>,
49             test=>boolean);
50              
51             Creates a new pivoter object where
52             to be the column headings, is the column going to row headings and
53             is the data column. is a column used for higher level grouping,
54             i.e. splitting the data into different tables.
55              
56             {numerical} is used to flag that the data are numerical so that the correct
57             sorting function is being used.
58             {function} is a function to
59             compile the data set for each row/col combination (Still not implemented) If
60             no function is given, the last value for each data point is returned.
61              
62             The
63             inputdata to new are validated to check that row,col, and data are defined and
64             that row and col differs. If this behaviour for some reason not is wanted,
65             donotvalidate can be set to a true value. The property test may be set to avoid
66             output from the validation (esp for the internal testing). To check for a well-
67             defined pivoter object, call the method ok.
68              
69              
70             Planned features (except for implementing the compilation function) includes to
71             add customizable sorting functions for rows and columns.
72              
73             =cut
74              
75              
76              
77             sub _validate{
78             # Checks if a pivoter object is well-defined
79 10     10   20 my $self=shift;
80             # col, row and data must be defined
81 10   100     96 my $validated =
82             defined $self->{_colhead} &&
83             defined $self->{_rowhead} &&
84             defined $self->{_data};
85             # If all are defined, must check that row and column are different rows
86 10 100       32 $validated = not($self->{_colhead} == $self->{_rowhead})
87             if $validated;
88 10         32 local $^W=0;
89 10 50 66     47 carp ("Definition error:
90             Col = $self->{_colhead}
91             Row = $self->{_rowhead}
92             Data= $self->{_data}\n") unless $validated || $self->{_testing};
93 10         54 return $validated;
94              
95             }
96              
97              
98              
99             sub _keysort{
100 14     14   18 my $self = shift;
101 14         18 my $href = shift;
102 14         25 my $i = shift;
103 14 100       35 my $sortfunc=$i eq 'C'?$self->{_sortfunccol}:$self->{_sortfuncrow};
104 14         18 $i=0;
105 14         57 foreach my $key (sort {&$sortfunc} keys %$href){
  34         45  
106 39 50       94 $href->{$key}=++$i if defined $key;
107 39 50       106 print "Key: $key [$i]\n" if $debug > 2;
108             }
109             }
110              
111              
112             sub new{
113 13     13 1 1044 my $class = shift;
114 13         54 my %para=@_;
115 13 50       37 print "[C,R,D,G]:$para{col},$para{row},$para{data},$para{group}\n" if $debug;
116 13 50 33     63 print "Don't validate\n" if $debug and $para{donotvalidate};
117 13 50 33     40 print "Function: $para{function}\n" if $debug and $para{function};
118 13         203 my $self = {
119             _colhead => $para{col},
120             _rowhead => $para{row},
121             _data => $para{data},
122             _function=> $para{function},
123             _group => $para{group},
124             _donotvalidate =>$para{donotvalidate},
125             _numeric => $para{numeric},
126             _testing => $para{test}
127             };
128 13 50       41 print Dumper(\$self) if $debug>9;
129 13 50       31 print "New[R,C] : $self->{_rowhead},$self->{_colhead}\n" if $debug >3;
130 13 50       43 carp("Sorry, functions are still not working in Data::Pivoter...\n")
131             if $self->{_function};
132             {
133 13         19 local $^W=0; # Turns of warnings to avoid lots of
  13         42  
134             # "Use of uninitialized value in pattern match"
135 13 100       41 if ($self->{_numeric}=~/C/i){
136 1     18   4 $self->{_sortfunccol}= sub {$a <=> $b} }
  18         34  
137             else{
138 12     66   58 $self->{_sortfunccol}= sub {$a cmp $b} };
  66         111  
139 13 100       38 if ($self->{_numeric}=~/R/i){
140 1     6   5 $self->{_sortfuncrow}= sub {$a <=> $b} }
  6         13  
141             else{
142 12     28   84 $self->{_sortfuncrow}= sub {$a cmp $b} };
  28         56  
143             }
144 13         31 bless $self,$class;
145 13   100     88 $self->{_OK}=$self->{_donotvalidate} || $self->_validate ;
146 13         47 return $self;
147             }
148              
149             =head2 pivot
150              
151             @pivotedtable = $pivoter->pivot (@rawtable);
152              
153             The pivoter method actually performs the pivot with the parameters given in new
154             and returns the pivoted table.
155              
156             =cut
157              
158              
159             sub pivot{
160 7     7 1 62 my $self = shift;
161 7         11 my($table,$rows,$r,$c,$g,%rkeys,%ckeys,%gkeys,%hashtable,@pivot, @table);
162 7         9 @table = @{ shift() }; # Throws in a ref, needs the table
  7         20  
163 7 50       26 print "Pivot[R,C]: $self->{_rowhead},$self->{_colhead}\n" if $debug > 3;
164 7         20 for ($rows = 0;$rows < @table;$rows++){
165 54 50       108 print "[\$rows: $rows]Pivot[R,C]: $self->{_rowhead},$self->{_colhead}\n"
166             if $debug > 3;
167 54 50       97 print "row :>$table[$rows][$self->{_rowhead}]<\n" if $debug > 3;
168 54 50       109 print "col :>$table[$rows][$self->{_colhead}]<\n" if $debug > 3;
169 54         84 my $row = $table[$rows][$self->{_rowhead}];
170 54         67 my $col = $table[$rows][$self->{_colhead}];
171 54         55 my $group;
172             # Collects the unique row, col and group values
173 54 100       114 $rkeys{$row}=++$r unless $rkeys{$row};
174 54 100       108 $ckeys{$col}=++$c unless $ckeys{$col};
175 54 50       96 if ($self->{_group}){
176 0         0 $group = $table[$rows][$self->{_group}];
177 0 0       0 $gkeys{$group}=++$g unless $gkeys{$group};
178             }
179 54         48 my $ref; # Referres to the element in the pivot hash
180 54 50       133 if (defined $group){
181 0         0 $ref=\$hashtable{$row}{$col}{$group}
182             }else{
183 54         103 $ref=\$hashtable{$row}{$col}
184             }
185 54 50       94 unless ($self->{_function}){
186             # No function is defined, just picks up the value
187 54         166 $$ref=$table[$rows][$self->{_data}];
188             }else{
189 0         0 push @$ref, \$table[$rows][$self->{_data}];
190             # Treats the $ref as an array reference and
191             # collects the data into that array to use the given function on them
192             # after all the data have been collected.
193             }
194             }
195             # Preparing the correct sorting of the data
196 7         23 $self->_keysort(\%rkeys,'R');
197 7         21 $self->_keysort(\%ckeys,'C');
198            
199             # [0][0] is always undef
200 7         14 $c=1; # Puts in the row headers in the pivottable:
201 7         17 foreach my $colkey (sort {&{$self->{_sortfunccol}}} keys %ckeys){
  17         17  
  17         26  
202 20         41 $pivot[0][$c++] = $colkey;
203             }
204             # The row and col headers are in the first column and row
205 7         22 foreach my $rowkey (sort {&{$self->{_sortfuncrow}}} keys %rkeys){
  17         19  
  17         31  
206             # Puts in the col headers:
207 19         38 $pivot[$rkeys{$rowkey}][0] = $rowkey;
208 19         42 foreach my $colkey (sort {&{$self->{_sortfunccol}}} keys %ckeys){
  50         50  
  50         74  
209             # foreach my $colkey (sort {&{$self->{_sortfunccol}}} keys %ckeys){
210             # Puts in the values in the finished table:
211 56         142 $pivot[$rkeys{$rowkey}][$ckeys{$colkey}] = $hashtable{$rowkey}{$colkey};
212             }
213             }
214 7 50       21 print '@pivot : ',Dumper(\@pivot) if $debug > 5;
215 7 50       16 if ($self->{_function}){
216 0         0 for ($r=1,@pivot,$r++){
217 0         0 my $warn = $^W;
218 0         0 $^W=undef;
219 0         0 my $row=$pivot[$r];
220 0         0 for ($c=1,@{$row},$c++){
  0         0  
221 0 0       0 print "[$r,$c] @{$pivot[$r][$c]}" if $debug > 2;
  0         0  
222             # eval{$pivot[$r][$c]= eval{$self->{_function}(@{$pivot[$r][$c]})}};
223 0         0 eval{${$pivot[$r][$c]}=$self->{_function}};
  0         0  
  0         0  
224             }
225 0         0 $^W=$warn;
226             }
227 0 0       0 print "\n" if $debug >2;
228             }
229              
230 7         61 return \@pivot;
231             }
232              
233              
234             =head2 ok
235              
236             The method may be called to see if the pivoter object is well-defined. If donotvalidate is set, then this method will always return true.
237              
238             =cut
239              
240             sub ok{
241 6     6 1 65 my $self=shift;
242 6         20 return $self->{_OK}
243             }
244              
245             =head3 New algorithms
246              
247             A possible enhancement is to use two different types of functions for
248             compilation, one which needs all the data avaliable to perform the calculation,
249             another that can can be applied to the data before all the datapoints are
250             known, (e.g. to return the max value from the data set) to avoid going through
251             the data set twice when possible
252              
253             =cut
254              
255             =head1 System variables
256              
257             The variable PIVOTER_DEBUG may be set to get debugging output. A higher numerical
258             value gives more output.
259              
260             =cut
261              
262              
263             =head1 Licencing
264              
265             This module is distributed under the artistic licence, i.e. the same licence at Perl itself.
266              
267             =cut
268              
269             =head1 AUTHOR
270              
271             Morten A.K. Sickel, Morten.Sickel@newmedia.no
272              
273             =head1 SEE ALSO
274              
275             perl(1).
276              
277             =cut
278              
279              
280             1;
281