File Coverage

blib/lib/Data/Pivot.pm
Criterion Covered Total %
statement 112 132 84.8
branch 34 50 68.0
condition 12 13 92.3
subroutine 6 6 100.0
pod 1 3 33.3
total 165 204 80.8


line stmt bran cond sub pod time code
1             package Data::Pivot;
2            
3             #---------------------------------------------------------------------------------------------------------------------
4            
5             =head1 NAME
6            
7             Data::Pivot - Perl module to pivot a table
8            
9             =head1 SYNOPSIS
10            
11             use Data::Pivot;
12             @newtable = pivot( table => \@table,
13             headings => \@headings,
14             pivot_column => $pivot_col_no,
15             layout => 'vertical',
16             row_sum => 'Sum',
17             row_titles => 1,
18             format => '%5.2f',
19             )
20            
21             =head1 DESCRIPTION
22            
23             With Data::Pivot you can pivot a table like this:
24            
25             Some Fix Columns Pivot_Col Num_Values
26            
27             aaa bbb ccc 01 12.20
28             aaa bbb ccc 02 134.50
29             aaa bbb ccc 03 1.25
30             xxx yyy zzz 02 22.22
31             xxx yyy zzz 03 111.11
32            
33             Will be converted to:
34            
35             Some Fix Columns 01 02 03 Sum
36            
37             aaa bbb ccc 12.20 134.50 1.25 147.95
38             yyy xxx zzz 0.00 22.22 111.11 133.33
39            
40             The table can contain several columns of Num_Values, which will get into rows, if the layout is 'horizontal', like this:
41            
42             Some Fix Columns Pivot_Col Num_Val_1 Num_Val_2 Num_Val_3
43            
44             aaa bbb ccc 01 12.20 1.40 5.90
45             aaa bbb ccc 02 134.50 12.00 12.30
46             aaa bbb ccc 03 1.25 30.00 123.45
47             xxx yyy zzz 02 22.22 7.80 8.88
48             xxx yyy zzz 03 111.11 100.00 42.00
49            
50             Will be converted to:
51            
52             Some Fix Columns 01 02 03 Sum
53            
54             aaa bbb ccc Num_Val_1 12.20 134.50 1.25 147.95
55             Num_Val_2 1.40 12.00 30.00 43.40
56             Num_Val_3 5.90 12.30 123.45 141.65
57             xxx yyy zzz Num_Val_1 0.00 22.22 111.11 133.33
58             Num_Val_2 0.00 7.80 100.00 107.80
59             Num_Val_3 0.00 8.88 42.00 50.88
60            
61             Data::Pivot has only one function which does all the work.
62            
63             =head1 Functions
64            
65             =head2 pivot()
66            
67             =head2 Parameters:
68            
69             pivot receives several named parameters:
70            
71             =over
72            
73             =item table => \@table
74            
75             A reference to an array of arrays containing all the data but no headings.
76            
77             In the last example above:
78            
79             @table = ( [ 'aaa', 'bbb', 'ccc', '01', 12.2, 1.4, 5.9 ],
80             [ 'aaa', 'bbb', 'ccc', '02', 134.5, 12, 12.3 ],
81             [ 'aaa', 'bbb', 'ccc', '03', 1.25, 30, 123.45 ],
82             [ 'xxx', 'yyy', 'zzz', '02', 22.22, 7.8, 8.88 ],
83             [ 'xxx', 'yyy', 'zzz', '03', 111.11, 100, 42 ]
84             );
85            
86             =item headings => \@headings
87            
88             A reference to an array containing the column headings.
89            
90             In the last example above:
91            
92             @headings = ('Some', 'Fix', 'Columns', 'Pivot_Col', 'Num_Val_1', 'Num_Val_2', 'Num_Val_3');
93            
94             =item pivot_column => $no_of_col
95            
96             The column number over which the pivoting takes place
97            
98             In the last example above:
99            
100             $no_of_col = 3;
101            
102             =item layout => 'horizontal'
103            
104             'layout' determines whether the 'Num_Val' columns are arranged 'horizontal'ly or 'vertical'ly in the new table.
105            
106             =item row_sum => 'Sum'
107            
108             The title of the sum column, which sums up the new pivoted columns. If this is undef the column will be omitted.
109            
110             =item row_title1 => 1
111            
112             If this is true, a new column will be inserted after the fix columns if the layout is 'horizontal'. This column will have no heading and the contents will be the headings of the value columns.
113            
114             =item format => '%5.2f'
115            
116             Format may be a legal sprintf format string or a reference to a subroutine.
117             The format string will be applied to each pivoted column and the sum column.
118             The subroutine will be called with each pivoted column and the sum column as parameter.
119            
120             =back
121            
122             The full function call for the above example is:
123            
124             @newtable = pivot( table => \@table,
125             headings => \@headings,
126             pivot_column => $pivot_col_no,
127             row_sum => 'Sum',
128             row_titles => 1,
129             format => '%5.2f',
130             );
131            
132            
133             =cut
134            
135 1     1   102349 use 5.005;
  1         3  
  1         40  
136 1     1   6 use strict;
  1         2  
  1         40  
137 1     1   5 use vars qw($VERSION @ISA %EXPORT_TAGS @EXPORT_OK @EXPORT);
  1         7  
  1         1969  
138            
139             require Exporter;
140            
141             @ISA = qw(Exporter);
142            
143             %EXPORT_TAGS = ( 'all' => [ qw(
144             pivot
145             ) ] );
146            
147             @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
148            
149             @EXPORT = qw(
150             pivot
151             );
152             $VERSION = '0.05';
153            
154             #---------------------------------------------------------------------------------------------------------------------
155            
156             sub pivot {
157 4     4 1 7457 my %parms = @_;
158            
159 4 100 100     28 if (exists $parms{layout} and $parms{layout} eq 'vertical') {
160 1         7 return pivot_vertical(@_);
161             } else {
162 3         11 return pivot_horizontal(@_);
163             }
164             }
165            
166             #---------------------------------------------------------------------------------------------------------------------
167            
168             sub pivot_horizontal {
169 3     3 0 16 my %parms = @_;
170            
171 3         6 my $table = $parms{table};
172 3         7 my $headings = $parms{headings};
173 3         4 my $pivot_column = $parms{pivot_column};
174 3         5 my $row_sum = $parms{row_sum};
175 3         5 my $row_titles = $parms{row_titles};
176 3         6 my $format = $parms{format};
177            
178            
179 3         10 my @sum_columns = $pivot_column + 1 .. $#$headings;
180 3         6 my $flatrow;
181 3         5 my $oldflatrow = '';
182 3         4 my %pivot_cols;
183             my $lastrow;
184 0         0 my @newtable;
185            
186             #---- initialise %pivot_cols
187             #
188 3         7 foreach my $row (@$table) {
189 15   100     83 $pivot_cols{$row->[$pivot_column]} ||= [ (0) x @sum_columns ];
190             }
191            
192             #---- read table line by line
193             #
194 3         9 foreach my $row (@$table) {
195 15         32 $flatrow = join '', @{$row}[0..$pivot_column - 1];
  15         34  
196            
197 15 100 100     54 if ($flatrow ne $oldflatrow && $lastrow) {
198 2         5 foreach my $pivot_row (0..$#sum_columns) {
199 4         7 my @newrow;
200 4 100       10 if (!$pivot_row) {
201 2         7 splice @$lastrow, $pivot_column;
202 2         7 @newrow = @$lastrow;
203             } else {
204 2         6 @newrow = ('') x @$lastrow;
205             }
206 4 100       13 push @newrow, $headings->[$sum_columns[$pivot_row]] if $row_titles;
207            
208             #---- sums for each row
209             #
210 4         6 my $rowsum = 0;
211 4         12 foreach (keys %pivot_cols) {
212 12         24 $rowsum += $pivot_cols{$_}->[$pivot_row];
213             }
214            
215             #---- create new row
216             #
217 4 50       11 if ($format) {
218 4 50       10 if (ref $format eq 'CODE') {
219 0 0       0 push @newrow, (map({ $format->($pivot_cols{$_}->[$pivot_row]) } sort keys %pivot_cols), $row_sum ? $format->($rowsum) : ());
  0         0  
220             } else {
221 4 100       17 push @newrow, (map({ sprintf($format, $pivot_cols{$_}->[$pivot_row]) } sort keys %pivot_cols), $row_sum ? sprintf($format, $rowsum) : ());
  12         110  
222             }
223             } else {
224 0 0       0 push @newrow, (map({ $pivot_cols{$_}->[$pivot_row] } sort keys %pivot_cols), $row_sum ? $rowsum : ());
  0         0  
225             }
226 4         16 push @newtable, \@newrow;
227             }
228            
229             #---- initialise %pivot_cols
230             #
231 2         39 $pivot_cols{$_} = [ (0) x @sum_columns ] for (keys %pivot_cols);
232             }
233            
234 15         30 foreach (0..$#sum_columns) {
235 27         63 $pivot_cols{$row->[$pivot_column]}->[$_] = $row->[$sum_columns[$_]];
236             }
237            
238 15         25 $lastrow = $row;
239 15         27 $oldflatrow = $flatrow;
240             }
241 3 50       10 if ($lastrow) {
242 3         7 foreach my $pivot_row (0..$#sum_columns) {
243 5         7 my @newrow;
244 5 100       10 if (!$pivot_row) {
245 3         9 splice @$lastrow, $pivot_column;
246 3         14 @newrow = @$lastrow;
247             } else {
248 2         7 @newrow = ('') x @$lastrow;
249             }
250 5 100       12 push @newrow, $headings->[$sum_columns[$pivot_row]] if $row_titles;
251            
252 5         11 my $rowsum = 0;
253 5         13 foreach (keys %pivot_cols) {
254 15         28 $rowsum += $pivot_cols{$_}->[$pivot_row];
255             }
256 5 100       13 if ($format) {
257 4 50       10 if (ref $format eq 'CODE') {
258 0 0       0 push @newrow, (map({ $format->($pivot_cols{$_}->[$pivot_row]) } sort keys %pivot_cols), $row_sum ? $format->($rowsum) : ());
  0         0  
259             } else {
260 4 100       12 push @newrow, (map({ sprintf($format, $pivot_cols{$_}->[$pivot_row]) } sort keys %pivot_cols), $row_sum ? sprintf($format, $rowsum) : ());
  12         84  
261             }
262             } else {
263 1 50       4 push @newrow, (map({ $pivot_cols{$_}->[$pivot_row] } sort keys %pivot_cols), $row_sum ? $rowsum : ());
  3         11  
264             }
265 5         37 push @newtable, \@newrow;
266             }
267             }
268            
269 3 100       33 splice @$headings, $pivot_column, @sum_columns + 1, ($row_titles ? '' : (), (sort keys %pivot_cols), $row_sum ? $row_sum : ());
    100          
270 3         33 return @newtable;
271             }
272            
273             #---------------------------------------------------------------------------------------------------------------------
274            
275             sub pivot_vertical {
276 1     1 0 7 my %parms = @_;
277            
278 1         3 my $table = $parms{table};
279 1         2 my $headings = $parms{headings};
280 1         2 my $pivot_column = $parms{pivot_column};
281 1         2 my $row_sum = 0;
282 1         3 my $row_titles = $parms{row_titles};
283 1         3 my $format = $parms{format};
284            
285            
286 1         3 my @sum_columns = $pivot_column + 1 .. $#$headings;
287 1         2 my $flatrow;
288 1         3 my $oldflatrow = '';
289 1         2 my %pivot_cols;
290             my $lastrow;
291 0         0 my @newtable;
292            
293             #---- initialise %pivot_cols
294             #
295 1         3 foreach my $row (@$table) {
296 6         12 foreach (@sum_columns) {
297 18   50     1097 $pivot_cols{$row->[$pivot_column] . sprintf('<<<%03d>>>', $_) . $headings->[$_]} ||= 0;
298             }
299             }
300            
301             #---- read table line by line
302             #
303 1         3 foreach my $row (@$table) {
304 6         12 $flatrow = join '', @{$row}[0..$pivot_column - 1];
  6         15  
305            
306 6 100 100     21 if ($flatrow ne $oldflatrow && $lastrow) {
307 1         2 my @newrow;
308            
309             #---- create new row
310             #
311 1 50       5 if ($format) {
312 1 50       6 if (ref $format eq 'CODE') {
313 1         3 push @newrow, (@{$lastrow}[0..$pivot_column - 1], map({ $format->($pivot_cols{$_}) } sort keys %pivot_cols));
  1         20  
  9         146  
314             } else {
315 0         0 push @newrow, (@{$lastrow}[0..$pivot_column - 1], map({ sprintf($format, $pivot_cols{$_}) } sort keys %pivot_cols));
  0         0  
  0         0  
316             }
317             } else {
318 0         0 push @newrow, (@{$lastrow}[0..$pivot_column - 1], map({ $pivot_cols{$_} } sort keys %pivot_cols));
  0         0  
  0         0  
319             }
320 1         21 push @newtable, \@newrow;
321            
322             #---- initialise %pivot_cols
323             #
324 1         9 $pivot_cols{$_} = 0 for (keys %pivot_cols);
325             }
326            
327 6         10 foreach (@sum_columns) {
328 18         53 $pivot_cols{$row->[$pivot_column] . sprintf('<<<%03d>>>', $_) . $headings->[$_]} = $row->[$_];
329             }
330            
331 6         9 $lastrow = $row;
332 6         9 $oldflatrow = $flatrow;
333             }
334 1 50       5 if ($lastrow) {
335 1         2 my @newrow;
336            
337 1 50       4 if ($format) {
338 1 50       6 if (ref $format eq 'CODE') {
339 1         4 push @newrow, (@{$lastrow}[0..$pivot_column - 1], map({ $format->($pivot_cols{$_}) } sort keys %pivot_cols));
  1         12  
  9         104  
340             } else {
341 0         0 push @newrow, (@{$lastrow}[0..$pivot_column - 1], map({ sprintf($format, $pivot_cols{$_}) } sort keys %pivot_cols));
  0         0  
  0         0  
342             }
343             } else {
344 0         0 push @newrow, (@{$lastrow}[0..$pivot_column - 1], map({ $pivot_cols{$_} } sort keys %pivot_cols));
  0         0  
  0         0  
345             }
346 1         590 push @newtable, \@newrow;
347             }
348            
349 1         8 splice @$headings, $pivot_column, @sum_columns + 1, (map {s/(.*?)<<<\d+>>>(.*)/$2 $1/; $_} sort keys %pivot_cols);
  9         53  
  9         24  
350 1         22 return @newtable;
351             }
352            
353            
354             1;
355             __END__