This class is one of the core classes for building and working with data in Ruport. The idea is to get your data into a standard form, regardless of its source (a database, manual arrays, ActiveRecord, CSVs, etc.).
Table is intended to be used as the data store for structured, tabular data.
Once your data is in a Table object, it can be manipulated to suit your needs, then used to build a report.
Creates a new table based on the supplied options.
Valid options:
:data |
An Array of Arrays representing the records in this Table. |
:column_names |
An Array containing the column names for this Table. |
:filters |
A proc or array of procs that set up conditions to filter the data being added to the table. |
:transforms |
A proc or array of procs that perform transformations on the data being added to the table. |
:record_class |
Specify the class of the table's records. |
Example:
table = Table.new :data => [[1,2,3], [3,4,5]], :column_names => %w[a b c]
# File lib/ruport/data/table.rb, line 265 def initialize(options={}) @column_names = options[:column_names] ? options[:column_names].dup : [] @record_class = options[:record_class] && options[:record_class].name || "Ruport::Data::Record" @data = [] feeder = Feeder.new(self) Array(options[:filters]).each { |f| feeder.filter(&f) } Array(options[:transforms]).each { |t| feeder.transform(&t) } if options[:data] options[:data].each do |e| if e.kind_of?(Record) e = if @column_names.empty? or e.attributes.all? { |a| a.kind_of?(Numeric) } e.to_a else e.to_hash.values_at(*@column_names) end end r = recordize(e) feeder << r end end yield(feeder) if block_given? end
Used to merge two Tables by rows. Raises an ArgumentError if the Tables don't have identical columns.
Example:
inky = Table.new :data => [[1,2], [3,4]], :column_names => %w[a b] blinky = Table.new :data => [[5,6]], :column_names => %w[a b] sue = inky + blinky sue.data #=> [[1,2],[3,4],[5,6]]
# File lib/ruport/data/table.rb, line 387 def +(other) raise ArgumentError unless other.column_names == @column_names self.class.new( :column_names => @column_names, :data => @data + other.data, :record_class => record_class ) end
Used to add extra data to the Table. row can be an Array, Hash or Record. It also can be anything that implements a meaningful to_hash or to_ary.
Example:
data = Table.new :data => [[1,2], [3,4]], :column_names => %w[a b] data << [8,9] data << { :a => 4, :b => 5} data << Record.new [5,6], :attributes => %w[a b]
# File lib/ruport/data/table.rb, line 363 def <<(row) @data << recordize(row) return self end
Adds an extra column to the Table.
Available Options:
:default |
The default value to use for the column in existing rows. Set to nil if not specified. |
:position |
Inserts the column at the indicated position number. |
:before |
Inserts the new column before the column indicated (by name). |
:after |
Inserts the new column after the column indicated (by name). |
If a block is provided, it will be used to build up the column.
Example:
data = Table("a","b") { |t| t << [1,2] << [3,4] } # basic usage, column full of 1's data.add_column 'new_column', :default => 1 # new empty column before new_column data.add_column 'new_col2', :before => 'new_column' # new column placed just after column a data.add_column 'new_col3', :position => 1 # new column built via a block, added at the end of the table data.add_column("new_col4") { |r| r.a + r.b }
# File lib/ruport/data/table.rb, line 457 def add_column(name,options={}) if pos = options[:position] column_names.insert(pos,name) elsif pos = options[:after] column_names.insert(column_names.index(pos)+1,name) elsif pos = options[:before] column_names.insert(column_names.index(pos),name) else column_names << name end if block_given? each { |r| r[name] = yield(r) || options[:default] } else each { |r| r[name] = options[:default] } end; self end
Add multiple extra columns to the Table. See add_column for a list of available options.
Example:
data = Table("a","b") { |t| t << [1,2] << [3,4] } data.add_columns ['new_column_1','new_column_2'], :default => 1
# File lib/ruport/data/table.rb, line 484 def add_columns(names,options={}) raise "Greg isn't smart enough to figure this out.\n"+ "Send ideas in at http://list.rubyreports.org" if block_given? need_reverse = !!(options[:after] || options[:position]) names = names.reverse if need_reverse names.each { |n| add_column(n,options) } self end
Returns an array of values for the given column name.
Example:
table = [[1,2],[3,4],[5,6]].to_table(%w[col1 col2]) table.column("col1") #=> [1,3,5]
# File lib/ruport/data/table.rb, line 688 def column(name) case(name) when Integer unless column_names.empty? raise ArgumentError if name > column_names.length end else raise ArgumentError unless column_names.include?(name) end map { |r| r[name] } end
Sets the column names for this table. new_column_names should be an array listing the names of the columns.
Example:
table = Table.new :data => [[1,2,3], [3,4,5]], :column_names => %w[a b c] table.column_names = %w[e f g]
# File lib/ruport/data/table.rb, line 315 def column_names=(new_column_names) columns = new_column_names.zip(@column_names) @column_names.replace(new_column_names.dup) unless @data.empty? each { |r| columns.each_with_index { |x,i| if x[1].nil? r.rename_attribute(i,x[0]) elsif x[1] != x[0] r.rename_attribute(x[1],x[0],false) end } r.send(:reindex, @column_names) } end end
Compares this Table to another Table and returns true if both the data and column_names are equal.
Example:
one = Table.new :data => [[1,2], [3,4]], :column_names => %w[a b] two = Table.new :data => [[1,2], [3,4]], :column_names => %w[a b] one.eql?(two) #=> true
# File lib/ruport/data/table.rb, line 345 def eql?(other) data.eql?(other.data) && column_names.eql?(other.column_names) end
# File lib/ruport/data/table.rb, line 874 def feed_element(row) recordize(row) end
Create a copy of the Table. Records will be copied as well.
Example:
one = Table.new :data => [[1,2], [3,4]], :column_names => %w[a b] two = one.dup
# File lib/ruport/data/table.rb, line 827 def initialize_copy(from) @record_class = from.record_class.name @column_names = from.column_names.dup @data = [] from.data.each { |r| self << r.dup } end
Provides a shortcut for the as() method by converting a call to as(:format_name) into a call to to_format_name
Also converts a call to rows_with_columnname to a call to rows_with(:columnname => args[0]).
# File lib/ruport/data/table.rb, line 868 def method_missing(id,*args,&block) return as($1.to_sym,*args,&block) if id.to_s =~ /^to_(.*)/ return rows_with($1.to_sym => args[0]) if id.to_s =~ /^rows_with_(.*)/ super end
Creates a new table with values from the specified pivot column transformed into columns.
Required options:
:group_by |
The name of a column whose unique values should become rows in the new table. |
:values |
The name of a column that should supply the values for the pivoted columns. |
Optional:
:pivot_order |
An ordering specification for the pivoted columns, in terms of the source rows. If this is a Proc there is an optional second argument that receives the name of the pivot column, which due to implementation oddity currently is removed from the row provided in the first argument. This wart will likely be fixed in a future version. |
Example:
Given a table my_table:
+-------------------------+ | Group | Segment | Value | +-------------------------+ | A | 1 | 0 | | A | 2 | 1 | | B | 1 | 2 | | B | 2 | 3 | +-------------------------+
Pivoting the table on the Segment column:
my_table.pivot('Segment', :group_by => 'Group', :values => 'Value', :pivot_order => proc {|row, name| name})
Yields a new table like this:
+---------------+ | Group | 1 | 2 | +---------------+ | A | 0 | 1 | | B | 2 | 3 | +---------------+
# File lib/ruport/data/table.rb, line 141 def pivot(pivot_column, options = {}) group_column = options[:group_by] || raise(ArgumentError, ":group_by option required") value_column = options[:values] || raise(ArgumentError, ":values option required") Pivot.new( self, group_column, pivot_column, value_column, options ).to_table end
Returns the record class constant being used by the table.
# File lib/ruport/data/table.rb, line 369 def record_class @record_class.split("::").inject(Class) { |c,el| c.send(:const_get,el) } end
Generates a sub table in place, modifying the receiver. See documentation for sub_table.
# File lib/ruport/data/table.rb, line 672 def reduce(columns=column_names,range=nil,&block) t = sub_table(columns,range,&block) @data = t.data @column_names = t.column_names self end
Removes the given column from the table. May use name or position.
Example:
table.remove_column(0) #=> removes the first column table.remove_column("apple") #=> removes column named apple
# File lib/ruport/data/table.rb, line 500 def remove_column(col) col = column_names[col] if col.kind_of? Fixnum column_names.delete(col) each { |r| r.send(:delete,col) } end
Removes multiple columns from the table. May use name or position Will autosplat arrays.
Example: table.remove_columns('a','b','c') table.remove_columns()
# File lib/ruport/data/table.rb, line 513 def remove_columns(*cols) cols = cols[0] if cols[0].kind_of? Array cols.each { |col| remove_column(col) } end
Renames a column. Will update Record attributes as well.
Example:
old_values = table.map { |r| r.a } table.rename_column("a","zanzibar") new_values = table.map { |r| r.zanzibar } old_values == new_values #=> true table.column_names.include?("a") #=> false
# File lib/ruport/data/table.rb, line 528 def rename_column(old_name,new_name) index = column_names.index(old_name) or return self.column_names[index] = new_name each { |r| r.rename_attribute(old_name,new_name,false)} end
Renames multiple columns. Takes either a hash of "old" => "new" names or two arrays of names %w[old names],%w[new names].
Example:
table.column_names #=> ["a", "b"] table.rename_columns ["a", "b"], ["c", "d"] table.column_names #=> ["c", "d"] table.column_names #=> ["a", "b"] table.rename_columns {"a" => "c", "b" => "d"} table.column_names #=> ["c", "d"]
# File lib/ruport/data/table.rb, line 547 def rename_columns(old_cols=nil,new_cols=nil) if block_given? if old_cols old_cols.each { |c| rename_column(c,yield(c)) } else column_names.each { |c| rename_column(c,yield(c)) } end return end raise ArgumentError unless old_cols if new_cols raise ArgumentError, "odd number of arguments" unless old_cols.size == new_cols.size h = Hash[*old_cols.zip(new_cols).flatten] else h = old_cols end h.each {|old,new| rename_column(old,new) } end
Allows you to change the order of, or reduce the number of columns in a Table.
Example:
a = Table.new :data => [[1,2,3],[4,5,6]], :column_names => %w[a b c] a.reorder("b","c","a") a.column_names #=> ["b","c","a"] a = Table.new :data => [[1,2,3],[4,5,6]], :column_names => %w[a b c] a.reorder(1,2,0) a.column_names #=> ["b","c","a"] a = Table.new :data => [[1,2,3],[4,5,6]], :column_names => %w[a b c] a.reorder(0,2) a.column_names #=> ["a","c"]
# File lib/ruport/data/table.rb, line 411 def reorder(*indices) raise(ArgumentError,"Can't reorder without column names set!") if @column_names.empty? indices = indices[0] if indices[0].kind_of? Array if indices.all? { |i| i.kind_of? Integer } indices.map! { |i| @column_names[i] } end reduce(indices) end
Allows you to specify a new column to replace an existing column in your table via a block.
Example:
>> a = Table(%w[a b c]) { |t| t << [1,2,3] << [4,5,6] } >> a.replace_column("c","c2") { |r| r.c * 2 + r.a }
>> puts a
+------------+ | a | b | c2 | +------------+ | 1 | 2 | 7 | | 4 | 5 | 16 | +------------+
# File lib/ruport/data/table.rb, line 618 def replace_column(old_col,new_col=nil,&block) if new_col add_column(new_col,:after => old_col,&block) remove_column(old_col) else each { |r| r[old_col] = yield(r) } end end
Get an array of records from the Table limited by the criteria specified.
Example:
table = Table.new :data => [[1,2,3], [1,4,6], [4,5,6]], :column_names => %w[a b c] table.rows_with(:a => 1) #=> [[1,2,3], [1,4,6]] table.rows_with(:a => 1, :b => 4) #=> [[1,4,6]] table.rows_with_a(1) #=> [[1,2,3], [1,4,6]] table.rows_with(%w[a b]) {|a,b| [a,b] == [1,4] } #=> [[1,4,6]]
# File lib/ruport/data/table.rb, line 809 def rows_with(columns,&block) select { |r| if block block[*(columns.map { |c| r.get(c) })] else columns.all? { |k,v| r.get(k) == v } end } end
Calculates sums. If a column name or index is given, it will try to convert each element of that column to an integer or float and add them together.
If a block is given, it yields each Record so that you can do your own calculation.
Example:
table = [[1,2],[3,4],[5,6]].to_table(%w[col1 col2]) table.sigma("col1") #=> 9 table.sigma(0) #=> 9 table.sigma { |r| r.col1 + r.col2 } #=> 21 table.sigma { |r| r.col2 + 1 } #=> 15
# File lib/ruport/data/table.rb, line 716 def sigma(column=nil) inject(0) { |s,r| if column s + if r.get(column).kind_of? Numeric r.get(column) else r.get(column) =~ /\./ ? r.get(column).to_f : r.get(column).to_i end else s + yield(r) end } end
Returns a sorted table. If col_names is specified, the block is ignored and the table is sorted by the named columns.
The second argument specifies sorting options. Currently only :order is supported. Default order is ascending, to sort decending use :order => :descending
Example:
table = [[4, 3], [2, 5], [7, 1]].to_table(%w[col1 col2 ]) # returns a new table sorted by col1 table.sort_rows_by {|r| r["col1"]} # returns a new table sorted by col1, in descending order table.sort_rows_by(nil, :order => :descending) {|r| r["col1"]} # returns a new table sorted by col2 table.sort_rows_by(["col2"]) # returns a new table sorted by col2, descending order table.sort_rows_by("col2", :order => :descending) # returns a new table sorted by col1, then col2 table.sort_rows_by(["col1", "col2"]) # returns a new table sorted by col1, then col2, in descending order table.sort_rows_by(["col1", "col2"], :order => descending)
# File lib/ruport/data/table.rb, line 761 def sort_rows_by(col_names=nil, options={}, &block) # stabilizer is needed because of # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/170565 stabilizer = 0 nil_rows, sortable = partition do |r| Array(col_names).any? { |c| r[c].nil? } end data_array = if col_names sortable.sort_by do |r| stabilizer += 1 [Array(col_names).map {|col| r[col]}, stabilizer] end else sortable.sort_by(&block) end data_array += nil_rows data_array.reverse! if options[:order] == :descending table = self.class.new( :data => data_array, :column_names => @column_names, :record_class => record_class ) return table end
Same as Table#sort_rows_by, but self modifying. See sort_rows_by for documentation.
# File lib/ruport/data/table.rb, line 793 def sort_rows_by!(col_names=nil,options={},&block) table = sort_rows_by(col_names,options,&block) @data = table.data end
Generates a sub table
Examples:
table = [[1,2,3,4],[5,6,7,8],[9,10,11,12]].to_table(%w[a b c d])
Using column_names and a range:
sub_table = table.sub_table(%w[a b],1..-1) sub_table == [[5,6],[9,10]].to_table(%w[a b]) #=> true
Using just column_names:
sub_table = table.sub_table(%w[a d]) sub_table == [[1,4],[5,8],[9,12]].to_table(%w[a d]) #=> true
Using column_names and a block:
sub_table = table.sub_table(%w[d b]) { |r| r.a < 6 } sub_table == [[4,2],[8,6]].to_table(%w[d b]) #=> true
Using a range for row reduction:
sub_table = table.sub_table(1..-1) sub_table == [[5,6,7,8],[9,10,11,12]].to_table(%w[a b c d]) #=> true
Using just a block:
sub_table = table.sub_table { |r| r.c > 10 } sub_table == [[9,10,11,12]].to_table(%w[a b c d]) #=> true
# File lib/ruport/data/table.rb, line 657 def sub_table(cor=column_names,range=nil,&block) if range self.class.new(:column_names => cor,:data => data[range]) elsif cor.kind_of?(Range) self.class.new(:column_names => column_names,:data => data[cor]) elsif block self.class.new( :column_names => cor, :data => data.select(&block)) else self.class.new( :column_names => cor, :data => data) end end
Exchanges one column with another.
Example:
>> a = Table(%w[a b c]) { |t| t << [1,2,3] << [4,5,6] } >> puts a +-----------+ | a | b | c | +-----------+ | 1 | 2 | 3 | | 4 | 5 | 6 | +-----------+ >> a.swap_column("a","c") >> puts a +-----------+ | c | b | a | +-----------+ | 3 | 2 | 1 | | 6 | 5 | 4 | +-----------+
# File lib/ruport/data/table.rb, line 590 def swap_column(a,b) if [a,b].all? { |r| r.kind_of? Fixnum } col_a,col_b = column_names[a],column_names[b] column_names[a] = col_b column_names[b] = col_a else a_ind, b_ind = [column_names.index(a), column_names.index(b)] column_names[b_ind] = a column_names[a_ind] = b end end
Convert the Table into a Group using the supplied group name.
data = Table.new :data => [[1,2], [3,4]], :column_names => %w[a b] group = data.to_group("my_group")
# File lib/ruport/data/table.rb, line 852 def to_group(name=nil) Group.new( :data => data, :column_names => column_names, :name => name, :record_class => record_class ) end
Uses Ruport's built-in text formatter to render this Table into a String.
Example:
data = Table.new :data => [[1,2], [3,4]], :column_names => %w[a b] puts data.to_s
# File lib/ruport/data/table.rb, line 842 def to_s as(:text) end
Generated with the Darkfish Rdoc Generator 2.