| | 26 | |
|---|
| | 27 | class Pivot #:nodoc: |
|---|
| | 28 | |
|---|
| | 29 | def initialize(table, group_col, pivot_col, summary_col, options = {}) |
|---|
| | 30 | @table = table |
|---|
| | 31 | @group_column = group_col |
|---|
| | 32 | @pivot_column = pivot_col |
|---|
| | 33 | @summary_column = summary_col |
|---|
| | 34 | @pivot_order = options[:pivot_order] |
|---|
| | 35 | end |
|---|
| | 36 | |
|---|
| | 37 | def convert_row_order_to_group_order(row_order_spec) |
|---|
| | 38 | case row_order_spec |
|---|
| | 39 | when Array |
|---|
| | 40 | proc {|group| |
|---|
| | 41 | row_order_spec.map {|e| group[0][e].to_s } |
|---|
| | 42 | } |
|---|
| | 43 | when Proc |
|---|
| | 44 | proc {|group| |
|---|
| | 45 | if row_order_spec.arity == 2 |
|---|
| | 46 | row_order_spec.call(group[0], group.name) |
|---|
| | 47 | else |
|---|
| | 48 | row_order_spec.call(group[0]) |
|---|
| | 49 | end |
|---|
| | 50 | } |
|---|
| | 51 | when NilClass |
|---|
| | 52 | nil |
|---|
| | 53 | else |
|---|
| | 54 | proc {|group| group[0][row_order_spec].to_s } |
|---|
| | 55 | end |
|---|
| | 56 | end |
|---|
| | 57 | |
|---|
| | 58 | def columns_from_pivot |
|---|
| | 59 | ordering = convert_row_order_to_group_order(@pivot_order) |
|---|
| | 60 | pivot_column_grouping = Grouping(@table, :by => @pivot_column) |
|---|
| | 61 | pivot_column_grouping.each {|n,g| g.add_column(n) { n }} |
|---|
| | 62 | pivot_column_grouping.sort_grouping_by!(ordering) if ordering |
|---|
| | 63 | result = [] |
|---|
| | 64 | pivot_column_grouping.each {|name,_| result << name } |
|---|
| | 65 | result |
|---|
| | 66 | end |
|---|
| | 67 | |
|---|
| | 68 | def group_column_entries |
|---|
| | 69 | @table.map {|row| row[@group_column]}.uniq |
|---|
| | 70 | end |
|---|
| | 71 | |
|---|
| | 72 | def to_table |
|---|
| | 73 | result = Table() |
|---|
| | 74 | result.add_column(@group_column) |
|---|
| | 75 | pivoted_columns = columns_from_pivot |
|---|
| | 76 | pivoted_columns.each { |name| result.add_column(name) } |
|---|
| | 77 | outer_grouping = Grouping(@table, :by => @group_column) |
|---|
| | 78 | group_column_entries.each {|outer_group_name| |
|---|
| | 79 | outer_group = outer_grouping[outer_group_name] |
|---|
| | 80 | pivot_values = pivoted_columns.inject({}) do |hsh, e| |
|---|
| | 81 | matching_rows = outer_group.rows_with(@pivot_column => e) |
|---|
| | 82 | hsh[e] = matching_rows.first && matching_rows.first[@summary_column] |
|---|
| | 83 | hsh |
|---|
| | 84 | end |
|---|
| | 85 | result << [outer_group_name] + pivoted_columns.map {|e| |
|---|
| | 86 | pivot_values[e] |
|---|
| | 87 | } |
|---|
| | 88 | } |
|---|
| | 89 | result |
|---|
| | 90 | end |
|---|
| | 91 | |
|---|
| | 92 | end |
|---|
| | 93 | |
|---|
| | 94 | # Creates a new table with values from the specified pivot column |
|---|
| | 95 | # transformed into columns. |
|---|
| | 96 | # |
|---|
| | 97 | # Required options: |
|---|
| | 98 | # <b><tt>:group_by</tt></b>:: The name of a column whose unique |
|---|
| | 99 | # values should become rows in the new |
|---|
| | 100 | # table. |
|---|
| | 101 | # |
|---|
| | 102 | # <b><tt>:values</tt></b>:: The name of a column that should supply |
|---|
| | 103 | # the values for the pivoted columns. |
|---|
| | 104 | # |
|---|
| | 105 | # Optional: |
|---|
| | 106 | # <b><tt>:pivot_order</tt></b>:: An ordering specification for the |
|---|
| | 107 | # pivoted columns, in terms of the source |
|---|
| | 108 | # rows. If this is a Proc there is an |
|---|
| | 109 | # optional second argument that receives |
|---|
| | 110 | # the name of the pivot column, which due |
|---|
| | 111 | # to implementation oddity currently is |
|---|
| | 112 | # removed from the row provided in the |
|---|
| | 113 | # first argument. This wart will likely |
|---|
| | 114 | # be fixed in a future version. |
|---|
| | 115 | # |
|---|
| | 116 | # Example: |
|---|
| | 117 | # |
|---|
| | 118 | # Given a table <em>my_table</em>: |
|---|
| | 119 | # +-------------------------+ |
|---|
| | 120 | # | Group | Segment | Value | |
|---|
| | 121 | # +-------------------------+ |
|---|
| | 122 | # | A | 1 | 0 | |
|---|
| | 123 | # | A | 2 | 1 | |
|---|
| | 124 | # | B | 1 | 2 | |
|---|
| | 125 | # | B | 2 | 3 | |
|---|
| | 126 | # +-------------------------+ |
|---|
| | 127 | # |
|---|
| | 128 | # Pivoting the table on the Segment column: |
|---|
| | 129 | # |
|---|
| | 130 | # my_table.pivot('Segment', :group_by => 'Group', :values => 'Value', |
|---|
| | 131 | # :pivot_order => proc {|row, name| name}) |
|---|
| | 132 | # |
|---|
| | 133 | # Yields a new table like this: |
|---|
| | 134 | # +---------------+ |
|---|
| | 135 | # | Group | 1 | 2 | |
|---|
| | 136 | # +---------------+ |
|---|
| | 137 | # | A | 0 | 1 | |
|---|
| | 138 | # | B | 2 | 3 | |
|---|
| | 139 | # +---------------+ |
|---|
| | 140 | # |
|---|
| | 141 | def pivot(pivot_column, options = {}) |
|---|
| | 142 | group_column = options[:group_by] || |
|---|
| | 143 | raise(ArgumentError, ":group_by option required") |
|---|
| | 144 | value_column = options[:values] || |
|---|
| | 145 | raise(ArgumentError, ":values option required") |
|---|
| | 146 | Pivot.new( |
|---|
| | 147 | self, group_column, pivot_column, value_column, options |
|---|
| | 148 | ).to_table |
|---|
| | 149 | end |
|---|