Changeset 1257

Show
Ignore:
Timestamp:
02/03/08 18:59:49 (10 months ago)
Author:
sandal
Message:

Ryan's pivot code. #380

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • ruport/trunk/lib/ruport/data/table.rb

    r1246 r1257  
    2424  # 
    2525  class Table 
     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 
    26150 
    27151    # === Overview