NAME

Text::CSV::Pivot - Transform CSV file into Pivot Table format.

VERSION

Version 0.09

DESCRIPTION

Recently I was asked to prepare pivot table using csv file at work. Having done that using quick and dirty perl script, I decided to clean up and make it generic so that others can also benefit.

Below is sample data, I used for prototype as source csv file.

+----------------+-----------+--------+--------+
| Student        | Subject   | Result | Year   |
+----------------+-----------+--------+--------+
| Smith, John    | Music     | 7.0    | Year 1 |
| Smith, John    | Maths     | 4.0    | Year 1 |
| Smith, John    | History   | 9.0    | Year 1 |
| Smith, John    | Language  | 7.0    | Year 1 |
| Smith, John    | Geography | 9.0    | Year 1 |
| Gabriel, Peter | Music     | 2.0    | Year 1 |
| Gabriel, Peter | Maths     | 10.0   | Year 1 |
| Gabriel, Peter | History   | 7.0    | Year 1 |
| Gabriel, Peter | Language  | 4.0    | Year 1 |
| Gabriel, Peter | Geography | 10.0   | Year 1 |
+----------------+-----------+--------+--------+

I aim to get something like this below.

+----------------+--------+-----------+---------+----------+-------+-------+
| Student        | Year   | Geography | History | Language | Maths | Music |
+----------------+--------+-----------+---------+----------+-------+-------+
| Gabriel, Peter | Year 1 | 10.0      | 7.0     | 4.0      | 10.0  | 2.0   |
| Smith, John    | Year 1 | 9.0       | 9.0     | 7.0      | 4.0   | 7.0   |
+----------------+--------+-----------+---------+----------+-------+-------+

With the help of Text::CSV::Pivot, I came up with the following solution.

use strict; use warnings;
use Text::CSV::Pivot;

Text::CSV::Pivot->new({ input_file    => 'sample.csv',
                        col_key_idx   => 0,
                        col_name_idx  => 1,
                        col_value_idx => 2 })->transform;

After executing the above code, I got the expected result in sample.pivot.csv.

SYNOPSIS

Let's assume we have the following source csv file (sample.csv):

+----------------+-----------+-----------------+
| Student        | Subject   | Result | Year   |
+----------------+-----------+--------+--------+
| Smith, John    | Music     | 7.0    | Year 1 |
| Smith, John    | Maths     | 4.0    | Year 1 |
| Smith, John    | History   | 9.0    | Year 1 |
| Smith, John    | Geography | 9.0    | Year 1 |
| Gabriel, Peter | Music     | 2.0    | Year 1 |
| Gabriel, Peter | Maths     | 10.0   | Year 1 |
| Gabriel, Peter | History   | 7.0    | Year 1 |
| Gabriel, Peter | Language  | 4.0    | Year 1 |
+----------------+-----------+--------+--------+

If you notice, the student "Smith, John" do not have any score for the subject "Language" and the student "Gabriel, Peter" missing score for "Geography".

use strict; use warnings;
use Text::CSV::Pivot;

Text::CSV::Pivot->new({ input_file    => 'sample.csv',
                        col_key_idx   => 0,
                        col_name_idx  => 1,
                        col_value_idx => 2 })->transform;

The above code would then create the result in sample.pivot.csv as below:

+----------------+--------+-----------+---------+----------+-------+-------+
| Student        | Year   | Geography | History | Language | Maths | Music |
+----------------+--------+-----------+---------+----------+-------+-------+
| Gabriel, Peter | Year 1 | 10.0      | 7.0     |          | 10.0  | 2.0   |
| Smith, John    | Year 1 |           | 9.0     | 7.0      | 4.0   | 7.0   |
+----------------+--------+-----------+---------+----------+-------+-------+

In case, we would want to skip "Year" column then the following code:

use strict; use warnings;
use Text::CSV::Pivot;

Text::CSV::Pivot->new({ input_file    => 'sample.csv',
                        col_key_idx   => 0,
                        col_name_idx  => 1,
                        col_value_idx => 2,
                        col_skip_idx  => [3] })->transform;

You should get the result in sample.pivot.csv as below:

+----------------+-----------+---------+----------+-------+-------+
| Student        | Geography | History | Language | Maths | Music |
+----------------+-----------+---------+----------+-------+-------+
| Gabriel, Peter | 10.0      | 7.0     |          | 10.0  | 2.0   |
| Smith, John    |           | 9.0     | 7.0      | 4.0   | 7.0   |
+----------------+-----------+---------+----------+-------+-------+

CLI

With the Text::CSV::Pivot v0.06 or above, there is a CLI tool supplied with the distribution called csv-pivot.

Usage: csv-pivot [OPTIONS]...

    OPTIONS:
       -i, --i, -input-file,    --input-file=s     input file (required)
       -o, --o, -output-file,   --output-file=s    output file (optional)
       -k, --k, -col-key-idx,   --col-key-idx=i    key column index (required)
       -n, --n, -col-name-idx,  --col-name-idx=i   name column index (required)
       -v, --v, -col-value-idx, --col-value-idx=i  value column index (required)
       -s, --s, -col-skip-idx,  --col-skip-idx=s   comma separated skip column index (optional)
       -h, --help                                  print this message

If you want to do something like below:

use strict; use warnings;
use Text::CSV::Pivot;

Text::CSV::Pivot->new({ input_file    => 'sample.csv',
                        col_key_idx   => 0,
                        col_name_idx  => 1,
                        col_value_idx => 2,
                        col_skip_idx  => [3] })->transform;

Then this can be achieved using CLI csv-pivot like below:

$ cvs-pivot -i sample.csv -k 0 -n 1 -v 2 -s 3

CONSTRUCTOR

The following table explains the parameters for the constructor. However you can also pass any valid parameters for Text::CSV.

+---------------+----------+---------------------------------------------------+
| Name          | Required | Description                                       |
+---------------+----------+---------------------------------------------------+
| input_file    | Yes      | Path to the source csv file.                      |
| output_file   | No       | Path to the output csv file.                      |
| col_key_idx   | Yes      | Column index that uniquely identify each row.     |
| col_name_idx  | Yes      | Column index that would provide new column name.  |
| col_value_idx | Yes      | Column index that would provide new column value. |
| col_skip_idx  | No       | Column index to ignore in the output csv.         |
+---------------+----------+---------------------------------------------------+

Please note output_file is optional. If missing, it would create one for you. For example if you provide input_file as "sample.csv" then it would create output file as "sample.pivot.csv".

Column index starts with 0, left to right. So in the example below, the col_key_idx would be 0. Similarly col_name_idx and col_value_idx would be 1 and 2 resp. In case, we would want to skip the column "Year" in the output file, then col_skip_idx would be [3]. All index related parameters except col_skip_idx would expect number 0 or more. The col_skip_idx would expected an ArrayRef of column index.

+----------------+-----------+--------+--------+
| Student        | Subject   | Result | Year   |
+----------------+-----------+--------+--------+
| Smith, John    | Music     | 7.0    | Year 1 |
| Smith, John    | Maths     | 4.0    | Year 1 |
| Smith, John    | History   | 9.0    | Year 1 |
| Smith, John    | Language  | 7.0    | Year 1 |
| Smith, John    | Geography | 9.0    | Year 1 |
| Gabriel, Peter | Music     | 2.0    | Year 1 |
| Gabriel, Peter | Maths     | 10.0   | Year 1 |
| Gabriel, Peter | History   | 7.0    | Year 1 |
| Gabriel, Peter | Language  | 4.0    | Year 1 |
| Gabriel, Peter | Geography | 10.0   | Year 1 |
+----------------+-----------+--------+--------+

Let's assume, we want column "Student" to be our key column, the "Subject" column to provide us the new column name and "Result" column for the values. Also "Year" column to be skipped.Then the call would look like something below:

use strict; use warnings;
use Text::CSV::Pivot;

Text::CSV::Pivot->new({ input_file    => 'sample.csv',
                        output_file   => 'output.csv',
                        col_key_idx   => 0,
                        col_name_idx  => 1,
                        col_value_idx => 2,
                        col_skip_idx  => [3] })->transform;

METHODS

transform()

Tranform the source csv into the corresponding pivot csv based on the data passed to the constructor.

AUTHOR

Mohammad S Anwar, <mohammad.anwar at yahoo.com>

REPOSITORY

https://github.com/manwar/Text-CSV-Pivot

SEE ALSO

Data::Pivot

BUGS

Please report any bugs / feature requests to bug-text-csv-pivot at rt.cpan.org or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Text-CSV-Pivot. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

You can find documentation for this module with the perldoc command.

perldoc Text::CSV::Pivot

You can also look for information at:

LICENSE AND COPYRIGHT

Copyright (C) 2018 Mohammad S Anwar.

This program is free software; you can redistribute it and / or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at:

http://www.perlfoundation.org/artistic_license_2_0

Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License.By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license.

If your Modified Version has been derived from a Modified Version made by someone other than you,you are nevertheless required to ensure that your Modified Version complies with the requirements of this license.

This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder.

This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement,then this Artistic License to you shall terminate on the date that such litigation is filed.

Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.