Class: Crosstab::Crosstab

Inherits:
Generic
  • Object
show all
Defined in:
lib/crosstab/crosstab.rb

Instance Method Summary collapse

Methods inherited from Generic

#printed?, #qualification, #qualifies?, #title

Constructor Details

#initialize(&block) ⇒ Crosstab

Pass in a block and we’ll execute it within the context of this class.

Example:

my_crosstab = Crosstab.new do
  banner do
    column "Total"
    column "18-34", :b => 1
    column "35-54", :b => 2
  end

  table do
    row "Male", :a => 1
    row "Female", :a => 2
  end
end


19
20
21
# File 'lib/crosstab/crosstab.rb', line 19

def initialize(&block)
  instance_eval(&block) if block
end

Instance Method Details

Creates a new Crosstab::Banner

Example:

# First let's look at the default banner with its total column:

banner
#=> Crosstab::Banner   

#   banner.columns.first.title
#=> "Total"

# Now let's create a new banner.
banner do
  column "Male", :a => 1
  column "Female", :a => 2
end

banner.columns.first.title
#=> "Male"
banner.columns.last.title
#=> "Female"


72
73
74
75
76
77
78
# File 'lib/crosstab/crosstab.rb', line 72

def banner(&block)
  if block
    @banner = Crosstab::Banner.new(&block)
  else
    @banner ||= Crosstab::Banner.new
  end
end

#calculateObject

Runs the calculations.

Warning: This is a CPU-heavy method. If you’re working with a large record size, processing time can explode out of control. You can expect this routine to process 1,000,000+ transactions a second. What’s a transaction? It’s just about 1 record x 1 row x 1 column.

Some examples: 1 second => N=1,000 * 100 rows * 10 columns

1 second =>       N=100,000 *  10 rows *  1 column
16.6 minutes => N=1,000,000 * 100 rows * 10 columns


122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/crosstab/crosstab.rb', line 122

def calculate
  # pre-calculate which interviews belong in the banner run
  working_records = data_source.select do |i|      
    self.qualifies? i and banner.qualifies? i
  end
    
  # pre-calculate which interviews belong in each column
  banner.columns.each do |column|
    column.records(working_records.select { |i| column.qualifies? i })
  end
    
  tables.each do |table|
    banner.columns.each_with_index do |column, column_index|
      # pre-calculate which interviews belong in this table
      table_records = column.records.select do |i|              
        table.qualifies? i
      end
      
      # do the actual stub calculations          
      table.rows.each do |row|
        # if this row is part of a group, and the group hasn't already been calculated for this cell...
        if row.group and row.group.cells[column_index].nil?
          row.group.cells[column_index] = Crosstab::Cell.new
          row.group.cells[column_index].base table_records.length
          row.group.cells[column_index].frequency table_records.select { |i| row.group.qualifies? i }.length
        end
        
        # The actual normal row calculations
        row.cells[column_index] ||= Crosstab::Cell.new
        row.cells[column_index].base table_records.length
        row.cells[column_index].frequency table_records.select { |i| row.qualifies? i }.length
      end
    end
  end
end

#data_source(value = nil) ⇒ Object

DSL accessor for the data_source attribute which normally contains an empty array or an array of hashes. To install your own data, just pass in an array of hashes as the argument.

Example:

my_crosstab = Crosstab::Crosstab.new

my_crosstab.data_source
# => []

my_crosstab.data_source [{:a => 1, :b => 2},
                         {:a => 2, :b => 2}]

my_crosstab.data_source
# => [{:a => 1, :b => 2},{:a => 2, :b => 2}]


41
42
43
44
45
46
47
# File 'lib/crosstab/crosstab.rb', line 41

def data_source(value=nil)
  if value
    @data_source = value
  else
    @data_source ||= []
  end
end

#table(&block) ⇒ Object

Creates a new Crosstab::Table and appends it to tables

Example:

my_crosstab = Crosstab.new
my_crosstab.tables
# => []

my_crosstab.table do
  title "Q.B Age"
  row "18-34", :b => 1
  row "35-54", :b => 2
end  

my_crosstab.tables
# => [Crosstab::Table]

my_crosstab.tables.first.rows.first.title
#=> "18-34"

my_crosstab.tables.first.rows.last.title
#=> "35-54"


108
109
110
# File 'lib/crosstab/crosstab.rb', line 108

def table(&block)
  tables << Crosstab::Table.new(&block)
end

#tablesObject

Returns the array of tables for this Crosstab.



81
82
83
# File 'lib/crosstab/crosstab.rb', line 81

def tables
  @tables ||= []  
end

#to_sObject



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/crosstab/crosstab.rb', line 158

def to_s
  calculate
  
  widths = { :hyphenation_min => 3,
             :row_header => 29, # Width of the title of each row
             :row_indent => 2, # Indent 2 spaces if the row is part of a group
             :column => 7,
             :divider => 2 }
  
  letter_lookup = {}
  (1..26).each { |x| letter_lookup[x] = (x+64).chr }
  
  format_strings =        { :group_headers  => " " * widths[:row_header] + " " * widths[:divider] + (banner.columns.collect { |x| x.group }.to_freq_chart.collect { |x| (x[1].nil? ? " " : "|") * (widths[:column] * x[0] + widths[:divider] * (x[0] - 1))  + " " * widths[:divider]}.join),
                            :group_border   => " " * widths[:row_header] + " " * widths[:divider] + (banner.columns.collect { |x| x.group }.to_freq_chart.collect { |x| (x[1].nil? ? " " : "-") * (widths[:column] * x[0] + widths[:divider] * (x[0] - 1))  + " " * widths[:divider]}.join),
                            :column_headers => " " * widths[:row_header] + " " * widths[:divider] + ("|" * widths[:column] + " " * widths[:divider]) * banner.columns.length,
                            :column_border  => " " * widths[:row_header] + " " * widths[:divider] + ("-" * widths[:column] + " " * widths[:divider]) * banner.columns.length,
                            :baseline       => "[" * widths[:row_header] + " " * widths[:divider] + ("]" * widths[:column] + " " * widths[:divider]) * banner.columns.length,
                            :rows           => "[" * widths[:row_header] + " " * widths[:divider] + ("]" * widths[:column] + " " * widths[:divider]) * banner.columns.length,
                            :indented_rows  => " " * widths[:row_indent] + "[" * (widths[:row_header] - widths[:row_indent]) + " " * widths[:divider] + ("]" * widths[:column] + " " * widths[:divider]) * banner.columns.length,
                            :underline_row  => "_" * widths[:row_header],
                            :line_break     => "",
                            :page_break     => "-" * 72 }

  r = Text::Reform.new
  r.min_break = widths[:hyphenation_min]
  
  report_stack = []
  tables.each_with_index do |tbl, i|
    # Table Header
    report_stack << "Table #{i + 1}"
    report_stack << tbl.title.dup if tbl.title

    # Group headers
    
    if banner.columns.any? { |x| x.group }
      report_stack << format_strings[:group_headers]

      banner.columns.each do |col|
        if col.group
          unless report_stack.last == col.group.title
            report_stack << col.group.title.dup
          end
        end
      end
      
      report_stack << format_strings[:group_border]        
    end 
    
    # Column Headers
    report_stack << format_strings[:column_headers]
    banner.columns.each_with_index do |col, col_index|
      report_stack << [ col.title.dup, "(#{letter_lookup[col_index + 1]})"]
    end

    report_stack << format_strings[:column_border]
    
    # Baseline
    report_stack << format_strings[:baseline]
    report_stack << "(BASE)"
    report_stack += tbl.rows[0].cells.collect { |x| x.base }
    report_stack << format_strings[:line_break]
    
    # Each row
    tbl.rows.each do |row|
      if row.group and not row.group.printed?
        row.group.printed? true # Set to true so it won't be printed again
        
        report_stack << format_strings[:rows]
        report_stack << [row.group.title.dup, "-" * widths[:row_header]]
        report_stack += row.group.cells.collect { |cell| cell.result }
        report_stack << format_strings[:line_break]
      end
      
      report_stack << format_strings[row.group ? :indented_rows : :rows] # if it's part of a group then indent it.
      report_stack << row.title.dup
      report_stack += row.cells.collect { |cell| cell.result }
      report_stack << format_strings[:line_break]
    end      

    report_stack << format_strings[:page_break]      
  end
  
  r.format(*report_stack)
end