Class: Ruport::Data::Table
- Inherits:
-
Object
- Object
- Ruport::Data::Table
- Extended by:
- Forwardable, FromCSV
- Includes:
- Enumerable, Controller::Hooks
- Defined in:
- lib/ruport/data/table.rb
Overview
Overview
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.
Direct Known Subclasses
Defined Under Namespace
Modules: FromCSV Classes: Pivot
Instance Attribute Summary collapse
-
#column_names ⇒ Object
This Table’s column names.
-
#data ⇒ Object
readonly
This Table’s data.
Class Method Summary collapse
-
.inherited(base) ⇒ Object
:nodoc:.
Instance Method Summary collapse
-
#+(other) ⇒ Object
Used to merge two Tables by rows.
-
#<<(row) ⇒ Object
Used to add extra data to the Table.
-
#add_column(name, options = {}) ⇒ Object
Adds an extra column to the Table.
-
#add_columns(names, options = {}) ⇒ Object
Add multiple extra columns to the Table.
-
#add_row(row_data, options = {}) ⇒ Object
Add a row to a certain location within the existing table.
-
#column(name) ⇒ Object
Returns an array of values for the given column name.
-
#eql?(other) ⇒ Boolean
(also: #==)
Compares this Table to another Table and returns
true
if both thedata
andcolumn_names
are equal. - #feed_element(row) ⇒ Object
-
#initialize(options = {}) {|feeder| ... } ⇒ Table
constructor
Creates a new table based on the supplied options.
-
#initialize_copy(from) ⇒ Object
Create a copy of the Table.
-
#method_missing(id, *args, &block) ⇒ Object
Provides a shortcut for the
as()
method by converting a call toas(:format_name)
into a call toto_format_name
. -
#pivot(pivot_column, options = {}) ⇒ Object
Creates a new table with values from the specified pivot column transformed into columns.
-
#record_class ⇒ Object
Returns the record class constant being used by the table.
-
#reduce(columns = column_names, range = nil, &block) ⇒ Object
(also: #sub_table!)
Generates a sub table in place, modifying the receiver.
-
#remove_column(col) ⇒ Object
Removes the given column from the table.
-
#remove_columns(*cols) ⇒ Object
Removes multiple columns from the table.
-
#rename_column(old_name, new_name) ⇒ Object
Renames a column.
-
#rename_columns(old_cols = nil, new_cols = nil) ⇒ Object
Renames multiple columns.
-
#reorder(*indices) ⇒ Object
Allows you to change the order of, or reduce the number of columns in a Table.
-
#replace_column(old_col, new_col = nil, &block) ⇒ Object
Allows you to specify a new column to replace an existing column in your table via a block.
-
#row_search(search, options = {}) ⇒ Object
Search row for a string and return the position.
-
#rows_with(columns, &block) ⇒ Object
Get an array of records from the Table limited by the criteria specified.
-
#sigma(column = nil) ⇒ Object
(also: #sum)
Calculates sums.
-
#sort_rows_by(col_names = nil, options = {}, &block) ⇒ Object
Returns a sorted table.
-
#sort_rows_by!(col_names = nil, options = {}, &block) ⇒ Object
Same as Table#sort_rows_by, but self modifying.
-
#sub_table(cor = column_names, range = nil, &block) ⇒ Object
Generates a sub table.
-
#swap_column(a, b) ⇒ Object
Exchanges one column with another.
-
#to_group(name = nil) ⇒ Object
Convert the Table into a Group using the supplied group name.
-
#to_s ⇒ Object
Uses Ruport’s built-in text formatter to render this Table into a String.
Methods included from FromCSV
Methods included from Controller::Hooks
Constructor Details
#initialize(options = {}) {|feeder| ... } ⇒ Table
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]
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 |
# File 'lib/ruport/data/table.rb', line 333 def initialize(={}) @column_names = [:column_names] ? [:column_names].dup : [] @record_class = [:record_class] && [:record_class].name || "Ruport::Data::Record" @data = [] feeder = Feeder.new(self) Array([:filters]).each { |f| feeder.filter(&f) } Array([:transforms]).each { |t| feeder.transform(&t) } if [:data] [: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 |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(id, *args, &block) ⇒ Object
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])
.
983 984 985 986 987 |
# File 'lib/ruport/data/table.rb', line 983 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 |
Instance Attribute Details
#column_names ⇒ Object
This Table’s column names
364 365 366 |
# File 'lib/ruport/data/table.rb', line 364 def column_names @column_names end |
#data ⇒ Object (readonly)
This Table’s data
367 368 369 |
# File 'lib/ruport/data/table.rb', line 367 def data @data end |
Class Method Details
.inherited(base) ⇒ Object
:nodoc:
308 309 310 |
# File 'lib/ruport/data/table.rb', line 308 def self.inherited(base) #:nodoc: base.renders_as_table end |
Instance Method Details
#+(other) ⇒ Object
465 466 467 468 469 470 |
# File 'lib/ruport/data/table.rb', line 465 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 |
#<<(row) ⇒ Object
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]
431 432 433 434 |
# File 'lib/ruport/data/table.rb', line 431 def <<(row) @data << recordize(row) return self end |
#add_column(name, options = {}) ⇒ Object
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.new("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 }
535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 |
# File 'lib/ruport/data/table.rb', line 535 def add_column(name,={}) if pos = [:position] column_names.insert(pos,name) elsif pos = [:after] column_names.insert(column_names.index(pos)+1,name) elsif pos = [:before] column_names.insert(column_names.index(pos),name) else column_names << name end if block_given? each { |r| r[name] = yield(r) || [:default] } else each { |r| r[name] = [:default] } end; self end |
#add_columns(names, options = {}) ⇒ Object
562 563 564 565 566 567 568 569 |
# File 'lib/ruport/data/table.rb', line 562 def add_columns(names,={}) raise "Greg isn't smart enough to figure this out.\n" \ "Send ideas in at github" if block_given? need_reverse = !!([:after] || [:position]) names = names.reverse if need_reverse names.each { |n| add_column(n,) } self end |
#add_row(row_data, options = {}) ⇒ Object
Add a row to a certain location within the existing table.
data.add_row(, :position => 0)
441 442 443 444 |
# File 'lib/ruport/data/table.rb', line 441 def add_row(row_data, ={}) @data.insert([:position] || @data.length, recordize(row_data)) return self end |
#column(name) ⇒ Object
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]
766 767 768 769 770 771 772 773 774 775 776 777 |
# File 'lib/ruport/data/table.rb', line 766 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 |
#eql?(other) ⇒ Boolean Also known as: ==
413 414 415 |
# File 'lib/ruport/data/table.rb', line 413 def eql?(other) data.eql?(other.data) && column_names.eql?(other.column_names) end |
#feed_element(row) ⇒ Object
989 990 991 |
# File 'lib/ruport/data/table.rb', line 989 def feed_element(row) recordize(row) end |
#initialize_copy(from) ⇒ Object
942 943 944 945 946 947 |
# File 'lib/ruport/data/table.rb', line 942 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 |
#pivot(pivot_column, options = {}) ⇒ Object
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.
:operation
-
The operation to perform on
:values
column. Supported operations are:first
,:sum
,:count
,:mean
,:min
, and:max
. If not specified, the default is:first
.
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 |
+---------------+
210 211 212 213 214 215 216 217 218 |
# File 'lib/ruport/data/table.rb', line 210 def pivot(pivot_column, = {}) group_column = [:group_by] || raise(ArgumentError, ":group_by option required") value_column = [:values] || raise(ArgumentError, ":values option required") Pivot.new( self, group_column, pivot_column, value_column, ).to_table end |
#record_class ⇒ Object
Returns the record class constant being used by the table.
447 448 449 |
# File 'lib/ruport/data/table.rb', line 447 def record_class @record_class.split("::").inject(Class) { |c,el| c.send(:const_get,el) } end |
#reduce(columns = column_names, range = nil, &block) ⇒ Object Also known as: sub_table!
Generates a sub table in place, modifying the receiver. See documentation for sub_table
.
750 751 752 753 754 755 |
# File 'lib/ruport/data/table.rb', line 750 def reduce(columns=column_names,range=nil,&block) t = sub_table(columns,range,&block) @data = t.data @column_names = t.column_names self end |
#remove_column(col) ⇒ Object
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
578 579 580 581 582 |
# File 'lib/ruport/data/table.rb', line 578 def remove_column(col) col = column_names[col] if col.kind_of? Integer column_names.delete(col) each { |r| r.send(:delete,col) } end |
#remove_columns(*cols) ⇒ Object
Removes multiple columns from the table. May use name or position Will autosplat arrays.
Example: table.remove_columns(‘a’,‘b’,‘c’) table.remove_columns()
591 592 593 594 |
# File 'lib/ruport/data/table.rb', line 591 def remove_columns(*cols) cols = cols[0] if cols[0].kind_of? Array cols.each { |col| remove_column(col) } end |
#rename_column(old_name, new_name) ⇒ Object
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. }
old_values == new_values #=> true
table.column_names.include?("a") #=> false
606 607 608 609 610 |
# File 'lib/ruport/data/table.rb', line 606 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 |
#rename_columns(old_cols = nil, new_cols = nil) ⇒ Object
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"]
625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 |
# File 'lib/ruport/data/table.rb', line 625 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 |
#reorder(*indices) ⇒ Object
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"]
489 490 491 492 493 494 495 496 497 498 499 500 |
# File 'lib/ruport/data/table.rb', line 489 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 |
#replace_column(old_col, new_col = nil, &block) ⇒ Object
Allows you to specify a new column to replace an existing column
in your table via a block.
Example:
>> a = Table.new(%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 |
+------------+
696 697 698 699 700 701 702 703 |
# File 'lib/ruport/data/table.rb', line 696 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 |
#row_search(search, options = {}) ⇒ Object
Search row for a string and return the position
Example:
table = Table.new :data => [["Mow Lawn","50"], ["Sew","40"], ["Clean dishes","5"]],
:column_names => %w[task cost]
table.row_search("Sew", :column => 0) #=> [[1,2,3], [1,4,6]]
Search for a number in column 0 greater than 999.
result = table.row_search(999, :column => 0, :greater_than => true)
909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 |
# File 'lib/ruport/data/table.rb', line 909 def row_search(search, ={}) position = 0 if column = [:column] self.each do |row| if gt=[:greater_than] return position if row[column] > search end if lt=[:less_than] return position if row[column] < search end unless gt or lt if row[column] =~ /#{search}/ # Search for part of or whole search text. return position end end position += 1 end end end |
#rows_with(columns, &block) ⇒ Object
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]]
887 888 889 890 891 892 893 894 895 |
# File 'lib/ruport/data/table.rb', line 887 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 |
#sigma(column = nil) ⇒ Object Also known as: sum
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
794 795 796 797 798 799 800 801 802 803 804 805 806 |
# File 'lib/ruport/data/table.rb', line 794 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 |
#sort_rows_by(col_names = nil, options = {}, &block) ⇒ Object
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)
839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 |
# File 'lib/ruport/data/table.rb', line 839 def sort_rows_by(col_names=nil, ={}, &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 [:order] == :descending table = self.class.new( :data => data_array, :column_names => @column_names, :record_class => record_class ) return table end |
#sort_rows_by!(col_names = nil, options = {}, &block) ⇒ Object
Same as Table#sort_rows_by, but self modifying. See sort_rows_by
for documentation.
871 872 873 874 |
# File 'lib/ruport/data/table.rb', line 871 def sort_rows_by!(col_names=nil,={},&block) table = sort_rows_by(col_names,,&block) @data = table.data end |
#sub_table(cor = column_names, range = nil, &block) ⇒ Object
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
735 736 737 738 739 740 741 742 743 744 745 |
# File 'lib/ruport/data/table.rb', line 735 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 |
#swap_column(a, b) ⇒ Object
Exchanges one column with another.
Example:
>> a = Table.new(%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 |
+-----------+
668 669 670 671 672 673 674 675 676 677 678 |
# File 'lib/ruport/data/table.rb', line 668 def swap_column(a,b) if [a,b].all? { |r| r.kind_of? Integer } 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 |