csv_pivot 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YTc3MjE4MmI5Y2YxZmZmZjU1MTk4MWY4ZmUyMmUwMTgyNTU0MTgwZQ==
5
+ data.tar.gz: !binary |-
6
+ MTEzZTYwMWJlZThmY2FiNTdiYTU5ZmRjNWEwOTQ5MzdjODNhYTM3OA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MTc1YTAyM2ExMDQ3Yzc2NzZhNDEwZmNiMGNiZmJhZTVhODdiNzczYzgxZDNi
10
+ YjMzNGNjZDAzMjNjZDFmZjM2MjYxZGQ2OGYwZjhhMWRmOTJjMjcxMTE3OTQx
11
+ NmFmZjUxYTY3NWYzYmQ1YzNlYjk2YTIxYjMwN2ZkZDJhYzVmMzE=
12
+ data.tar.gz: !binary |-
13
+ ZmQ1ZTE4ODE3YmFiMzk1MmZiOTVlMmRmNDIzYjY5NzA5ZGVhMzA3Mzk3ZTM0
14
+ YWJkZjhlYjBkNjc1NDc0YmNkZGUxM2IwNTUwY2VjM2YyMTFkZDgwODNmYjNl
15
+ MjA1YzkwM2EzYzdmZDBhMzUwZTMwOGViMWY0NmVjNGVjMzcwZWI=
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --warnings
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in csv_pivot.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 TODO: Write your name
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,202 @@
1
+ # CsvPivot
2
+
3
+
4
+ The CsvPivot gem takes a table in the form of an array of arrays, or a file path to a csv file. It then creates a pivot table on the data from a specified column(s) and row(s) to pivot on.
5
+ It returns an array of arrays, or can be given an output path to create a pivoted csv file.
6
+ It can be given a proc for user defined aggregation methods.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'csv_pivot'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install csv_pivot
21
+
22
+ ## Usage
23
+
24
+ require 'csv_pivot'
25
+
26
+ The following are the args that must be specified:
27
+ ```
28
+ input = {
29
+ :input_path => "spec/fixtures/testcsv.csv", # or :input_data
30
+ :pivot_rows => "date", # group by
31
+ :pivot_columns => "name", # new headers
32
+ :pivot_data => "baz"
33
+ }
34
+ ```
35
+
36
+ call with:
37
+ ```
38
+ array_of_arrays = CsvPivot::PivotTable.new(input).pivot
39
+ ```
40
+
41
+ ###### Example 1
42
+ assuming you have a csv that looks something like this:
43
+
44
+ | foo | bar | baz | date | name |
45
+ | --------|---------|-------|---------|-------|
46
+ | 1 | 2 | 3 | 4/1/11 | mark |
47
+ | 4 | 5 | 6 | 5/15/14 | mark |
48
+ | 7 | 8 | 9 | 4/7/12 | bear |
49
+ | 10 | 11 | 12 | 5/11/11 | bear |
50
+ | 1 | 2 | 3 | 4/1/11 | mark |
51
+ | 4 | 5 | 6 | 5/11/11 | mark |
52
+ | 7 | 8 | 9 | 4/7/12 | bear |
53
+ | 10 | 11 | 12 | 5/15/14 | bear |
54
+
55
+
56
+ calling
57
+ ```
58
+ array_of_arrays = CsvPivot::PivotTable.new(input).pivot
59
+ ```
60
+ returns
61
+ ```
62
+ [["date", "mark", "bear"], ["4/1/11", 6], ["5/15/14", 6, 12], ["4/7/12", nil, 18], ["5/11/11", 6, 12]]
63
+ ```
64
+ which if printed to a csv row by row would be equivalent to:
65
+
66
+ | date | mark | bear |
67
+ |---------|---------|-------|
68
+ | 4/1/11 | 6 | nil |
69
+ | 5/15/14 | 6 | 12 |
70
+ | 4/7/12 | nil | 18 |
71
+ | 5/11/11 | 6 | 12 |
72
+
73
+ *note that nils are returned in the absence of data and not zeroes.*
74
+
75
+ ###### Example 2
76
+
77
+ A csv of the form (no headers):
78
+
79
+ | | | | | |
80
+ | --------|---------|-------|---------|-------|
81
+ | 1 | 2 | 3 | 4/1/11 | mark |
82
+ | 4 | 5 | 6 | 5/15/14 | mark |
83
+ | 7 | 8 | 9 | 4/7/12 | bear |
84
+ | 10 | 11 | 12 | 5/11/11 | bear |
85
+ | 1 | 2 | 3 | 4/1/11 | mark |
86
+ | 4 | 5 | 6 | 5/11/11 | mark |
87
+ | 7 | 8 | 9 | 4/7/12 | bear |
88
+ | 10 | 11 | 12 | 5/15/14 | bear |
89
+
90
+ ```
91
+ p = Proc.new do |array| # a proc that will return the count (see aggregation methods below)
92
+ array.length
93
+ end
94
+
95
+ input = {
96
+ :input_path => "spec/fixtures/testcsv.csv", # location of csv file
97
+ :pivot_rows => 3, # group by (index of the date column)
98
+ :pivot_columns => 4, # new headers (index of the name column)
99
+ :pivot_data => 2, # data to aggregate (index of baz column)
100
+ :aggregate_method => p,
101
+ :column_total => true,
102
+ :row_total => true
103
+ }
104
+
105
+ array_of_arrays = CsvPivot::PivotTable.new(input).pivot
106
+ puts array_of_arrays.inspect
107
+ => [["date", "mark", "bear", "Total"], ["4/1/11", 2, nil, 2.0], ["5/15/14", 1, 1, 2.0], ["4/7/12", nil, 2, 2.0], ["5/11/11", 1, 1, 2.0], ["Total", 4.0, 4.0, 8.0]]
108
+
109
+ ```
110
+
111
+ which is equivalent to the following table:
112
+
113
+ | nil | mark | bear | Total |
114
+ |---------|---------|-------|-------|
115
+ | 4/1/11 | 2 | nil | 2.0 |
116
+ | 5/15/14 | 1 | 1 | 2.0 |
117
+ | 4/7/12 | nil | 2 | 2.0 |
118
+ | 5/11/11 | 1 | 1 | 2.0 |
119
+ | Total | 4.0 | 4.0 | 8.0 |
120
+
121
+ The last index of the Total row displays the sum of the Total column.
122
+
123
+ #### Optional Arguments
124
+ * :sort => boolean
125
+ # sorts columns and rows
126
+ * :headers => boolean
127
+ # input data has a header row (default true)
128
+ * :aggregate_method => Proc
129
+ # a proc that takes an array of values and returns desired output (e.g. average, max, min, sum, count, etc...)
130
+ * :column_total => boolean
131
+ # return total column (default false)
132
+ * :row_total => boolean
133
+ # return total row (default false)
134
+
135
+ ### Pivot to CSV
136
+ Calling
137
+ ```
138
+ CsvPivot::PivotTable.new(input).pivot_to_csv(output_path)
139
+ ```
140
+ will not return an array of arrays. It will print your data to a csv file specified by output_path.
141
+ ###### example
142
+ ```
143
+ output_path = "spec/fixtures/testcsv_pivoted.csv"
144
+
145
+ input = {:input_path => "spec/fixtures/testcsv.csv",
146
+ :pivot_rows => "date",
147
+ :pivot_columns => "name",
148
+ :pivot_data => "baz" }
149
+
150
+ CsvPivot::PivotTable.new(input).pivot_to_csv(output_path)
151
+ File.exists? output_path
152
+ => true
153
+ ```
154
+
155
+ ### Aggregation Methods
156
+ The default aggregation method, when no aggregate_method is specified, is sum.
157
+ The proc looks like:
158
+ ```
159
+ p = Proc.new do |array|
160
+ array.map(&:to_i).reduce(0, :+)
161
+ end
162
+ ```
163
+ #### Alternate aggregation method examples
164
+
165
+ Below are some examples of alternate aggregation methods. The csv_pivot gem makes no assumptions about the data passed to it. Data from a csv is a string. This will need to be cast to a numeric (int or float) before arithmetic can be performed on it. Casting to int (or not) is the responsibility of the proc. The default proc is sum, but note that it casts the members of the array to an int. Below are some examples of writing your own procs. Anything goes as long as it works on an array of values and returns a single value.
166
+
167
+ ###### Average (floats)
168
+ ```
169
+ p = Proc.new do |array|
170
+ array.map(&:to_f).reduce(0, :+)/array.length
171
+ end
172
+ ```
173
+ ###### Max (ints)
174
+ ```
175
+ p = Proc.new do |array|
176
+ array.map(&:to_i).max
177
+ end
178
+ ```
179
+ ###### Concat an array of strings (comma separate)
180
+ ```
181
+ p = Proc.new do |array|
182
+ array.join(",")
183
+ end
184
+ ```
185
+ ###### Count (type insensitive)
186
+ ```
187
+ p = Proc.new do |array|
188
+ array.length
189
+ end
190
+ ```
191
+
192
+
193
+ note that for user defined procs, the data to be aggregated is a string, and must be cast to numeric for mathematical aggregation.
194
+
195
+
196
+ ## Contributing
197
+
198
+ 1. Fork it ( https://github.com/[my-github-username]/csv_pivot/fork )
199
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
200
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
201
+ 4. Push to the branch (`git push origin my-new-feature`)
202
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'csv_pivot/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "csv_pivot"
8
+ spec.version = CsvPivot::VERSION
9
+ spec.authors = ["Knut Knutson"]
10
+ spec.summary = %q{Pivots an row major ordered array or csv file and returns an array or csv file.}
11
+ spec.homepage = ""
12
+ spec.license = "MIT"
13
+
14
+ spec.files = `git ls-files -z`.split("\x0")
15
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.add_development_dependency "bundler", "~> 1.6"
20
+ spec.add_development_dependency "rake"
21
+ spec.add_development_dependency "rspec"
22
+ end
@@ -0,0 +1,8 @@
1
+ #require "csv_pivot/version"
2
+ require "csv_pivot/pivot_table"
3
+ require "csv_pivot/csv_handler"
4
+ require "csv_pivot/array_handler"
5
+
6
+ module CsvPivot
7
+
8
+ end # end module
@@ -0,0 +1,27 @@
1
+ class ArrayHandler
2
+ def create_data_store
3
+ data_store = Hash.new
4
+ if @headers
5
+ rows = @input_array[0].index(@pivot_rows)
6
+ cols = @input_array[0].index(@pivot_columns)
7
+ data = @input_array[0].index(@pivot_data)
8
+ else
9
+ rows = @pivot_rows
10
+ cols = @pivot_columns
11
+ data = @pivot_data
12
+ end
13
+
14
+ @input_array.each_with_index do |row, i|
15
+ if (@headers && i == 0) then next end
16
+ if data_store.include? "#{row[rows]}:#{row[cols]}" then
17
+ data_store["#{row[rows]}:#{row[cols]}"][:data].push(row[data])
18
+ else
19
+ data_store.store("#{row[rows]}:#{row[cols]}",
20
+ {:row => row[rows],
21
+ :column => row[cols],
22
+ :data => [row[data]]} )
23
+ end
24
+ end
25
+ data_store
26
+ end
27
+ end
@@ -0,0 +1,18 @@
1
+ require 'csv'
2
+
3
+ class CsvHandler
4
+ def create_data_store_from_csv
5
+ data_store = Hash.new
6
+ CSV.foreach(@input_path, :headers => @headers) do |row|
7
+ if data_store.include? "#{row[@pivot_rows]}:#{row[@pivot_columns]}" then
8
+ data_store["#{row[@pivot_rows]}:#{row[@pivot_columns]}"][:data].push(row[@pivot_data])
9
+ else
10
+ data_store.store("#{row[@pivot_rows]}:#{row[@pivot_columns]}",
11
+ {:row => row[@pivot_rows],
12
+ :column => row[@pivot_columns],
13
+ :data => [row[@pivot_data]]} )
14
+ end
15
+ end
16
+ data_store
17
+ end
18
+ end
@@ -0,0 +1,183 @@
1
+ require 'csv'
2
+
3
+ module CsvPivot
4
+ class PivotTable
5
+
6
+ DEFAULT_OPTIONS = {
7
+ :headers => true,
8
+ :sort => false,
9
+ :sort_on => 0,
10
+ :column_total => false,
11
+ :row_total => false
12
+ }
13
+
14
+ def initialize(opts = {})
15
+ p = Proc.new do |array| # the default aggregation method: sum
16
+ array.map(&:to_i).reduce(0, :+)
17
+ end
18
+ @opts = DEFAULT_OPTIONS.merge(opts)
19
+ @input_path = @opts[:input_path]
20
+ @input_array = @opts[:input_data]
21
+ @pivot_rows = @opts[:pivot_rows]
22
+ @pivot_columns = @opts[:pivot_columns]
23
+ @pivot_data = @opts[:pivot_data]
24
+ @sort = @opts[:sort]
25
+ @headers = @opts[:headers]
26
+ @column_total = @opts[:column_total]
27
+ @row_total = @opts[:row_total]
28
+ @method = @opts[:aggregate_method] || p
29
+ end
30
+
31
+ def pivot
32
+ if @input_path
33
+ data_store = create_data_store_from_csv
34
+ else
35
+ data_store = create_data_store
36
+ end
37
+
38
+ aggregate_data(data_store)
39
+
40
+ column_map, row_map = map_columns_and_rows(data_store)
41
+ sort(column_map, row_map) if @sort
42
+
43
+ create_table(data_store, column_map, row_map)
44
+ end
45
+
46
+ def pivot_to_csv(output_file)
47
+ pivot_table = pivot
48
+ output_csv(pivot_table, output_file)
49
+ end
50
+
51
+ def create_data_store
52
+ data_store = Hash.new
53
+ if @headers
54
+ rows = @input_array[0].index(@pivot_rows)
55
+ cols = @input_array[0].index(@pivot_columns)
56
+ data = @input_array[0].index(@pivot_data)
57
+ else
58
+ rows = @pivot_rows
59
+ cols = @pivot_columns
60
+ data = @pivot_data
61
+ end
62
+
63
+ @input_array.each_with_index do |row, i|
64
+ if (@headers && i == 0) then next end
65
+ if data_store.include? "#{row[rows]}:#{row[cols]}" then
66
+ data_store["#{row[rows]}:#{row[cols]}"][:data].push(row[data])
67
+ else
68
+ data_store.store("#{row[rows]}:#{row[cols]}",
69
+ {:row => row[rows],
70
+ :column => row[cols],
71
+ :data => [row[data]]} )
72
+ end
73
+ end
74
+ data_store
75
+ end
76
+
77
+ def create_data_store_from_csv
78
+ data_store = Hash.new
79
+ CSV.foreach(@input_path, :headers => @headers) do |row|
80
+ if data_store.include? "#{row[@pivot_rows]}:#{row[@pivot_columns]}" then
81
+ data_store["#{row[@pivot_rows]}:#{row[@pivot_columns]}"][:data].push(row[@pivot_data])
82
+ else
83
+ data_store.store("#{row[@pivot_rows]}:#{row[@pivot_columns]}",
84
+ {:row => row[@pivot_rows],
85
+ :column => row[@pivot_columns],
86
+ :data => [row[@pivot_data]]} )
87
+ end
88
+ end
89
+ data_store
90
+ end
91
+
92
+ def aggregate_data(data_store)
93
+ data_store.each_value do |value|
94
+ value[:data] = @method.call(value[:data])
95
+ end
96
+ end
97
+
98
+ def map_columns_and_rows(data_store)
99
+ column_map = Hash.new
100
+ row_map = Hash.new
101
+ col_i = row_i = 1
102
+ data_store.each_value do |value|
103
+ if !column_map.include? value[:column]
104
+ column_map.store(value[:column], col_i)
105
+ col_i += 1
106
+ end
107
+ if !row_map.include? value[:row]
108
+ row_map.store(value[:row], row_i)
109
+ row_i += 1
110
+ end
111
+ end
112
+ [column_map, row_map]
113
+ end
114
+
115
+ def sort(column_map, row_map)
116
+ sorted_columns = column_map.keys.sort
117
+ sorted_rows = row_map.keys.sort
118
+ sorted_columns.each_with_index do |column, index|
119
+ column_map[column] = index + 1
120
+ end
121
+ sorted_rows.each_with_index do |row, index|
122
+ row_map[row] = index + 1
123
+ end
124
+ end
125
+
126
+ def create_table(data_store, column_map, row_map)
127
+ pivoted_table = [[]]
128
+ column_map.each do |key, value|
129
+ pivoted_table[0][value] = key
130
+ end
131
+ row_map.each do |key, value|
132
+ pivoted_table[value] = [key]
133
+ end
134
+ data_store.each_value do |value|
135
+ row = row_map[value[:row]]
136
+ column = column_map[value[:column]]
137
+ pivoted_table[row][column] = value[:data]
138
+ end
139
+ pivoted_table[0][0] = @pivot_rows if @headers
140
+ add_column_total(pivoted_table) if @column_total
141
+ add_row_total(pivoted_table) if @row_total
142
+ pivoted_table
143
+ end
144
+
145
+ def output_csv(pivoted_table, output_file)
146
+ CSV.open(output_file, "w") do |csv|
147
+ pivoted_table.each do |row|
148
+ csv << row
149
+ end
150
+ end
151
+ end
152
+
153
+ def add_column_total(table)
154
+ i = table[0].length
155
+ table[0][i] = "Total"
156
+ table.each_with_index do |row, index|
157
+ next if index == 0
158
+ row[i] = row[1..i].map(&:to_f).reduce(0, :+)
159
+ end
160
+ end
161
+
162
+ def add_row_total(table)
163
+ i = table.length
164
+ table[i] = ["Total"]
165
+ table.each_with_index do |row, j|
166
+ next if j == 0 || j == i
167
+ row.each_with_index do |value, k|
168
+ next if k == 0
169
+ if table[i][k]
170
+ table[i][k] += value.to_f
171
+ else
172
+ table[i][k] = value.to_f
173
+ end
174
+ end
175
+ end
176
+ puts table.inspect
177
+ end
178
+
179
+ end # end class
180
+ end
181
+
182
+
183
+
File without changes
@@ -0,0 +1,3 @@
1
+ module CsvPivot
2
+ VERSION = "0.0.1"
3
+ end
Binary file
@@ -0,0 +1 @@
1
+ foo,bar,baz,date,name
@@ -0,0 +1 @@
1
+ 1,2,3,4/1/11,mark
@@ -0,0 +1,228 @@
1
+ require 'csv'
2
+ require 'spec_helper'
3
+ require 'csv_pivot/pivot_table'
4
+
5
+ describe CsvPivot::PivotTable do
6
+ #it exists
7
+ describe '#pivot' do
8
+ context 'Sum With Headers from csv' do
9
+
10
+ let(:input) { {:input_path => "spec/fixtures/testcsv.csv",
11
+ :pivot_rows => "date", # group by
12
+ :pivot_columns => "name", # new headers
13
+ :pivot_data => "baz"} }
14
+ let(:output) { CsvPivot::PivotTable.new(input).pivot }
15
+ let(:expected) { [ ["date","mark","bear"],
16
+ ["4/1/11",6],
17
+ ["5/15/14",6,12],
18
+ ["4/7/12",nil,18],
19
+ ["5/11/11",6,12] ] }
20
+
21
+ it 'produces pivoted data' do
22
+ expect(output).to eq expected
23
+ end
24
+ end
25
+
26
+ context 'sum Without Headers from csv' do
27
+
28
+ let(:input) { {:input_path => "spec/fixtures/testcsv_noheaders.csv",
29
+ :pivot_rows => 3, # group by
30
+ :pivot_columns => 4, # new headers
31
+ :pivot_data => 2,
32
+ :headers => false} }
33
+ let(:output) { CsvPivot::PivotTable.new(input).pivot }
34
+ let(:expected) { [ [nil,"mark","bear"],
35
+ ["4/1/11",6],
36
+ ["5/15/14",6,12],
37
+ ["4/7/12",nil,18],
38
+ ["5/11/11",6,12] ] }
39
+
40
+ it 'produces pivoted data' do
41
+ expect(output).to eq expected
42
+ end
43
+ end
44
+
45
+ context 'Sum With Headers and Sort from csv' do
46
+
47
+ let(:input) { {:input_path => "spec/fixtures/testcsv.csv",
48
+ :pivot_rows => "date", # group by
49
+ :pivot_columns => "name", # new headers
50
+ :pivot_data => "baz",
51
+ :sort => true} }
52
+ let(:output) { CsvPivot::PivotTable.new(input).pivot }
53
+ let(:expected) { [ ["date","bear","mark"],
54
+ ["4/1/11",nil,6],
55
+ ["4/7/12",18],
56
+ ["5/11/11",12,6],
57
+ ["5/15/14",12,6] ] }
58
+
59
+ it 'produces pivoted data' do
60
+ expect(output).to eq expected
61
+ end
62
+ end
63
+
64
+ context 'Sum With Headers from array' do
65
+ data = [ ["foo", "bar", "baz", "date", "name"],
66
+ [1, 2, 3, "4/1/11", "mark"],
67
+ [4, 5, 6, "5/15/14", "mark"],
68
+ [7, 8, 9, "4/7/12", "bear"],
69
+ [10, 11, 12, "5/11/11", "bear"],
70
+ [1, 2, 3, "4/1/11", "mark"],
71
+ [4, 5, 6, "5/11/11", "mark"],
72
+ [7, 8, 9, "4/7/12", "bear"],
73
+ [10, 11, 12, "5/15/14", "bear"] ]
74
+
75
+ let(:input) { {:input_data => data,
76
+ :pivot_rows => "date", # group by
77
+ :pivot_columns => "name", # new headers
78
+ :pivot_data => "baz"} }
79
+ let(:output) { CsvPivot::PivotTable.new(input).pivot }
80
+ let(:expected) { [ ["date","mark","bear"],
81
+ ["4/1/11",6],
82
+ ["5/15/14",6,12],
83
+ ["4/7/12",nil,18],
84
+ ["5/11/11",6,12] ] }
85
+
86
+ it 'produces pivoted data' do
87
+ expect(output).to eq expected
88
+ end
89
+ end
90
+
91
+ context 'Sum With No Headers from array and sort' do
92
+ data = [ [1, 2, 3, "4/1/11", "mark"],
93
+ [4, 5, 6, "5/15/14", "mark"],
94
+ [7, 8, 9, "4/7/12", "bear"],
95
+ [10, 11, 12, "5/11/11", "bear"],
96
+ [1, 2, 3, "4/1/11", "mark"],
97
+ [4, 5, 6, "5/11/11", "mark"],
98
+ [7, 8, 9, "4/7/12", "bear"],
99
+ [10, 11, 12, "5/15/14", "bear"] ]
100
+
101
+ let(:input) { {:input_data => data,
102
+ :pivot_rows => 3, # group by
103
+ :pivot_columns => 4, # new headers
104
+ :pivot_data => 2,
105
+ :headers => false,
106
+ :sort => true} }
107
+ let(:output) { CsvPivot::PivotTable.new(input).pivot }
108
+ let(:expected) { [ [nil,"bear","mark"],
109
+ ["4/1/11",nil,6],
110
+ ["4/7/12",18],
111
+ ["5/11/11",12,6],
112
+ ["5/15/14",12,6] ] }
113
+
114
+ it 'produces pivoted data' do
115
+ expect(output).to eq expected
116
+ end
117
+ end
118
+
119
+ context 'Sum With Headers from csv to csv' do
120
+ output_path = "spec/fixtures/testcsv_pivoted.csv"
121
+
122
+ let(:input) { {:input_path => "spec/fixtures/testcsv.csv",
123
+ :pivot_rows => "date", # group by
124
+ :pivot_columns => "name", # new headers
125
+ :pivot_data => "baz"} }
126
+ let(:output) { `rm -f #{output_path}`
127
+ CsvPivot::PivotTable.new(input).pivot_to_csv(output_path)
128
+ File.exists? output_path }
129
+ let(:expected) { true }
130
+
131
+ it 'produces a csv file' do
132
+ expect(output).to eq expected
133
+ end
134
+
135
+ let(:output) { retrieved_data = Array.new
136
+ CSV.foreach(output_path, :headers => true) do |row|
137
+ retrieved_data << row
138
+ end
139
+ retrieved_data }
140
+ let(:expected) { [ ["date","mark","bear"],
141
+ ["4/1/11",6],
142
+ ["5/15/14",6,12],
143
+ ["4/7/12",nil,18],
144
+ ["5/11/11",6,12] ] }
145
+
146
+ it 'produced csv file is pivoted' do
147
+ expect(output).to eq expected
148
+ end
149
+ end
150
+
151
+ #TEST DIFFERENT AGGREGATION METHODS
152
+ context 'Average With Headers from csv' do
153
+ p = Proc.new do |array|
154
+ array.map(&:to_i).reduce(0, :+)/array.length
155
+ end
156
+ let(:input) { {:input_path => "spec/fixtures/testcsv.csv",
157
+ :pivot_rows => "date", # group by
158
+ :pivot_columns => "name", # new headers
159
+ :pivot_data => "baz",
160
+ :aggregate_method => p} }
161
+ let(:output) { CsvPivot::PivotTable.new(input).pivot }
162
+ let(:expected) { [ ["date","mark","bear"],
163
+ ["4/1/11",3],
164
+ ["5/15/14",6,12],
165
+ ["4/7/12",nil,9],
166
+ ["5/11/11",6,12] ] }
167
+
168
+ it 'produces pivoted data' do
169
+ expect(output).to eq expected
170
+ end
171
+ end
172
+
173
+ context 'Count With Headers from csv' do
174
+ p = Proc.new do |array|
175
+ array.length
176
+ end
177
+ let(:input) { {:input_path => "spec/fixtures/testcsv.csv",
178
+ :pivot_rows => "date", # group by
179
+ :pivot_columns => "name", # new headers
180
+ :pivot_data => "baz",
181
+ :aggregate_method => p} }
182
+ let(:output) { CsvPivot::PivotTable.new(input).pivot }
183
+ let(:expected) { [ ["date","mark","bear"],
184
+ ["4/1/11",2],
185
+ ["5/15/14",1,1],
186
+ ["4/7/12",nil,2],
187
+ ["5/11/11",1,1] ] }
188
+
189
+ it 'produces pivoted data' do
190
+ expect(output).to eq expected
191
+ end
192
+ end
193
+
194
+ context 'Sum With Headers and Totals Column and Row from csv' do
195
+ let(:input) { {:input_path => "spec/fixtures/testcsv.csv",
196
+ :pivot_rows => "date", # group by
197
+ :pivot_columns => "name", # new headers
198
+ :pivot_data => "baz",
199
+ :column_total => true,
200
+ :row_total => true} }
201
+ let(:output) { CsvPivot::PivotTable.new(input).pivot }
202
+ let(:expected) { [ ["date","mark","bear","Total"],
203
+ ["4/1/11",6,nil,6],
204
+ ["5/15/14",6,12,18],
205
+ ["4/7/12",nil,18,18],
206
+ ["5/11/11",6,12,18],
207
+ ["Total",18,42,60] ] }
208
+
209
+ it 'produces pivoted data' do
210
+ expect(output).to eq expected
211
+ end
212
+ end
213
+
214
+ end # end describe '#pivot'
215
+
216
+ end # end describe CsvPivot::PivotTable
217
+
218
+ =begin
219
+ [ [foo,bar,baz,date,name],
220
+ [1,2,3,"4/1/11",mark],
221
+ [4,5,6,"5/15/14",mark],
222
+ [7,8,9,"4/7/12",bear],
223
+ [10,11,12,"5/11/11",bear],
224
+ [1,2,3,"4/1/11",mark],
225
+ [4,5,6,"5/11/11",mark],
226
+ [7,8,9,"4/7/12",bear],
227
+ [10,11,12,"5/15/14",bear] ]
228
+ =end
@@ -0,0 +1,7 @@
1
+
2
+ require 'spec_helper'
3
+ require 'csv_pivot'
4
+
5
+ describe CsvPivot do
6
+ #it exists
7
+ end
@@ -0,0 +1,78 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
4
+ # file to always be loaded, without a need to explicitly require it in any files.
5
+ #
6
+ # Given that it is always loaded, you are encouraged to keep this file as
7
+ # light-weight as possible. Requiring heavyweight dependencies from this file
8
+ # will add to the boot time of your test suite on EVERY test run, even for an
9
+ # individual file that may not need all of that loaded. Instead, make a
10
+ # separate helper file that requires this one and then use it only in the specs
11
+ # that actually need it.
12
+ #
13
+ # The `.rspec` file also contains a few flags that are not defaults but that
14
+ # users commonly want.
15
+ #
16
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
17
+ RSpec.configure do |config|
18
+ # The settings below are suggested to provide a good initial experience
19
+ # with RSpec, but feel free to customize to your heart's content.
20
+ =begin
21
+ # These two settings work together to allow you to limit a spec run
22
+ # to individual examples or groups you care about by tagging them with
23
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
24
+ # get run.
25
+ config.filter_run :focus
26
+ config.run_all_when_everything_filtered = true
27
+
28
+ # Many RSpec users commonly either run the entire suite or an individual
29
+ # file, and it's useful to allow more verbose output when running an
30
+ # individual spec file.
31
+ if config.files_to_run.one?
32
+ # Use the documentation formatter for detailed output,
33
+ # unless a formatter has already been configured
34
+ # (e.g. via a command-line flag).
35
+ config.default_formatter = 'doc'
36
+ end
37
+
38
+ # Print the 10 slowest examples and example groups at the
39
+ # end of the spec run, to help surface which specs are running
40
+ # particularly slow.
41
+ config.profile_examples = 10
42
+
43
+ # Run specs in random order to surface order dependencies. If you find an
44
+ # order dependency and want to debug it, you can fix the order by providing
45
+ # the seed, which is printed after each run.
46
+ # --seed 1234
47
+ config.order = :random
48
+
49
+ # Seed global randomization in this process using the `--seed` CLI option.
50
+ # Setting this allows you to use `--seed` to deterministically reproduce
51
+ # test failures related to randomization by passing the same `--seed` value
52
+ # as the one that triggered the failure.
53
+ Kernel.srand config.seed
54
+
55
+ # rspec-expectations config goes here. You can use an alternate
56
+ # assertion/expectation library such as wrong or the stdlib/minitest
57
+ # assertions if you prefer.
58
+ config.expect_with :rspec do |expectations|
59
+ # Enable only the newer, non-monkey-patching expect syntax.
60
+ # For more details, see:
61
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
62
+ expectations.syntax = :expect
63
+ end
64
+
65
+ # rspec-mocks config goes here. You can use an alternate test double
66
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
67
+ config.mock_with :rspec do |mocks|
68
+ # Enable only the newer, non-monkey-patching expect syntax.
69
+ # For more details, see:
70
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
71
+ mocks.syntax = :expect
72
+
73
+ # Prevents you from mocking or stubbing a method that does not exist on
74
+ # a real object. This is generally recommended.
75
+ mocks.verify_partial_doubles = true
76
+ end
77
+ =end
78
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: csv_pivot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Knut Knutson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description:
56
+ email:
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - .gitignore
62
+ - .rspec
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - csv_pivot.gemspec
68
+ - lib/csv_pivot.rb
69
+ - lib/csv_pivot/array_handler.rb
70
+ - lib/csv_pivot/csv_handler.rb
71
+ - lib/csv_pivot/pivot_table.rb
72
+ - lib/csv_pivot/ruby_file.rb
73
+ - lib/csv_pivot/version.rb
74
+ - spec/.DS_Store
75
+ - spec/fixtures/testcsv.csv
76
+ - spec/fixtures/testcsv_noheaders.csv
77
+ - spec/lib/csv_pivot/pivot_table_spec.rb
78
+ - spec/lib/csv_pivot_spec.rb
79
+ - spec/spec_helper.rb
80
+ homepage: ''
81
+ licenses:
82
+ - MIT
83
+ metadata: {}
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 2.2.2
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: Pivots an row major ordered array or csv file and returns an array or csv
104
+ file.
105
+ test_files:
106
+ - spec/.DS_Store
107
+ - spec/fixtures/testcsv.csv
108
+ - spec/fixtures/testcsv_noheaders.csv
109
+ - spec/lib/csv_pivot/pivot_table_spec.rb
110
+ - spec/lib/csv_pivot_spec.rb
111
+ - spec/spec_helper.rb