rails-data-explorer 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +18 -0
  4. data/doc/how_to/release.md +2 -5
  5. data/lib/rails-data-explorer-no-rails.rb +1 -0
  6. data/lib/rails-data-explorer.rb +5 -0
  7. data/lib/rails-data-explorer/chart.rb +11 -11
  8. data/lib/rails-data-explorer/chart/box_plot_group.rb +10 -3
  9. data/lib/rails-data-explorer/chart/contingency_table.rb +58 -45
  10. data/lib/rails-data-explorer/chart/descriptive_statistics_table.rb +2 -2
  11. data/lib/rails-data-explorer/chart/histogram_categorical.rb +10 -4
  12. data/lib/rails-data-explorer/chart/histogram_quantitative.rb +12 -5
  13. data/lib/rails-data-explorer/chart/histogram_temporal.rb +6 -0
  14. data/lib/rails-data-explorer/chart/parallel_coordinates.rb +4 -4
  15. data/lib/rails-data-explorer/chart/parallel_set.rb +3 -3
  16. data/lib/rails-data-explorer/chart/stacked_bar_chart_categorical_percent.rb +3 -3
  17. data/lib/rails-data-explorer/constants.rb +5 -0
  18. data/lib/rails-data-explorer/data_series.rb +20 -14
  19. data/lib/rails-data-explorer/data_set.rb +4 -0
  20. data/lib/rails-data-explorer/data_type/categorical.rb +84 -72
  21. data/lib/rails-data-explorer/data_type/quantitative.rb +46 -48
  22. data/lib/rails-data-explorer/data_type/quantitative/temporal.rb +23 -17
  23. data/lib/rails-data-explorer/exploration.rb +20 -4
  24. data/lib/rails-data-explorer/statistics/rng_category.rb +1 -1
  25. data/lib/rails-data-explorer/utils/data_binner.rb +20 -10
  26. data/lib/rails-data-explorer/utils/data_quantizer.rb +6 -6
  27. data/lib/rails-data-explorer/utils/rde_table.rb +62 -0
  28. data/lib/rails_data_explorer.rb +35 -20
  29. data/rails-data-explorer.gemspec +1 -1
  30. data/spec/rails-data-explorer/exploration_spec.rb +4 -4
  31. data/spec/rails-data-explorer/utils/data_binner_spec.rb +3 -3
  32. metadata +30 -50
@@ -14,6 +14,9 @@ class RailsDataExplorer
14
14
  m[key] += 1
15
15
  m
16
16
  }
17
+ histogram_values_ds = DataSeries.new('_', h.values)
18
+ y_scale_type = histogram_values_ds.axis_scale(:vega)
19
+ bar_y2_val = 'log' == y_scale_type ? histogram_values_ds.min_val / 10.0 : 0
17
20
  width = 800
18
21
  {
19
22
  values: h.map { |k,v| { x: k, y: v } },
@@ -25,6 +28,9 @@ class RailsDataExplorer
25
28
  bar_width: 2,
26
29
  y_axis_label: 'Frequency',
27
30
  y_axis_tick_format: "d3.format('r')",
31
+ y_scale_type: y_scale_type,
32
+ y_scale_domain: [bar_y2_val, histogram_values_ds.max_val],
33
+ bar_y2_val: bar_y2_val,
28
34
  }
29
35
  end
30
36
 
@@ -81,10 +81,10 @@ class RailsDataExplorer
81
81
  m
82
82
  }
83
83
  {
84
- :dimensions => dimension_names,
85
- :values => dimension_values,
86
- :types => dimension_types,
87
- :alpha => 1 / ([Math.log([number_of_values, 2].max), 10].min) # from 1.0 to 0.1
84
+ dimensions: dimension_names,
85
+ values: dimension_values,
86
+ types: dimension_types,
87
+ alpha: 1 / ([Math.log([number_of_values, 2].max), 10].min) # from 1.0 to 0.1
88
88
  }
89
89
  end
90
90
 
@@ -11,7 +11,7 @@ class RailsDataExplorer
11
11
 
12
12
  def compute_chart_attrs
13
13
  dimension_data_series = @data_set.data_series.find_all { |ds|
14
- (ds.chart_roles[Chart::ParallelCoordinates] & [:dimension, :any]).any?
14
+ (ds.chart_roles[Chart::ParallelSet] & [:dimension, :any]).any?
15
15
  }
16
16
  return false if dimension_data_series.empty?
17
17
 
@@ -28,8 +28,8 @@ class RailsDataExplorer
28
28
  }
29
29
  end
30
30
  {
31
- :dimensions => dimension_names,
32
- :values => dimension_values
31
+ dimensions: dimension_names,
32
+ values: dimension_values
33
33
  }
34
34
  end
35
35
 
@@ -9,10 +9,10 @@ class RailsDataExplorer
9
9
 
10
10
  def compute_chart_attrs
11
11
  x_candidates = @data_set.data_series.find_all { |ds|
12
- (ds.chart_roles[Chart::ContingencyTable] & [:x, :any]).any?
12
+ (ds.chart_roles[Chart::StackedBarChartCategoricalPercent] & [:x, :any]).any?
13
13
  }.sort { |a,b| b.uniq_vals.length <=> a.uniq_vals.length }
14
14
  y_candidates = @data_set.data_series.find_all { |ds|
15
- (ds.chart_roles[Chart::ContingencyTable] & [:y, :any]).any?
15
+ (ds.chart_roles[Chart::StackedBarChartCategoricalPercent] & [:y, :any]).any?
16
16
  }
17
17
 
18
18
  x_ds = x_candidates.first
@@ -20,7 +20,7 @@ class RailsDataExplorer
20
20
  return false if x_ds.nil? || y_ds.nil?
21
21
 
22
22
  # initialize data_matrix
23
- data_matrix = { :_sum => { :_sum => 0 } }
23
+ data_matrix = { _sum: { _sum: 0 } }
24
24
  x_ds.uniq_vals.each { |x_val|
25
25
  data_matrix[x_val] = {}
26
26
  data_matrix[x_val][:_sum] = 0
@@ -0,0 +1,5 @@
1
+ class RailsDataExplorer
2
+
3
+ GREATER_ZERO = 1.0 / 1_000_000 # The smallest value to use if we have to avoid zero (div by zero)
4
+
5
+ end
@@ -5,20 +5,20 @@ class RailsDataExplorer
5
5
  # http://en.wikipedia.org/wiki/Significant_figures
6
6
 
7
7
  attr_reader :data_type, :name, :values, :chart_roles
8
- delegate :available_chart_types, :to => :data_type, :prefix => false
9
- delegate :available_chart_roles, :to => :data_type, :prefix => false
8
+ delegate :available_chart_types, to: :data_type, prefix: false
9
+ delegate :available_chart_roles, to: :data_type, prefix: false
10
10
 
11
11
  # Any data series with a dynamic range greater than this is considered
12
12
  # having a large dynamic range
13
13
  # We consider dynamic range the ratio between the largest and the smallest value.
14
- def self.large_dynamic_range_cutoff
15
- 1000.0
14
+ def self.large_dynamic_range_threshold
15
+ 10000.0
16
16
  end
17
17
 
18
18
  # Any data series with more than this uniq vals is considered having many
19
19
  # uniq values.
20
- def self.many_uniq_vals_cutoff
21
- 20
20
+ def self.many_uniq_vals_threshold
21
+ 30
22
22
  end
23
23
 
24
24
  # options: :chart_roles, :data_type (all optional)
@@ -40,6 +40,10 @@ class RailsDataExplorer
40
40
  @data_type.descriptive_statistics_table(values)
41
41
  end
42
42
 
43
+ def number_of_values
44
+ values.length
45
+ end
46
+
43
47
  def values_summary
44
48
  if values.length < 3 || values.inspect.length < 80
45
49
  values.inspect
@@ -66,37 +70,39 @@ class RailsDataExplorer
66
70
  data_type.axis_tick_format(values)
67
71
  end
68
72
 
69
- def axis_scale
70
- data_type.axis_scale(self)
73
+ # @param[Symbol] d3_or_vega :d3 or :vega
74
+ def axis_scale(d3_or_vega)
75
+ data_type.axis_scale(self, d3_or_vega)
71
76
  end
72
77
 
73
78
  def uniq_vals
74
- @uniq_vals = values.uniq
79
+ @uniq_vals ||= values.uniq
75
80
  end
76
81
 
77
82
  def uniq_vals_count
78
- @uniq_vals_count = uniq_vals.length
83
+ @uniq_vals_count ||= uniq_vals.length
79
84
  end
80
85
 
81
86
  def min_val
82
- @min_val = values.compact.min
87
+ @min_val ||= values.compact.min
83
88
  end
84
89
 
85
90
  def max_val
86
- @max_val = values.compact.max
91
+ @max_val ||= values.compact.max
87
92
  end
88
93
 
89
94
  # Used to decide whether we can render certain chart types
90
95
  def has_many_uniq_vals?
91
- uniq_vals_count > self.class.many_uniq_vals_cutoff
96
+ uniq_vals_count > self.class.many_uniq_vals_threshold
92
97
  end
93
98
 
94
99
  def dynamic_range
100
+ # TODO: avoid division by zero
95
101
  max_val / [min_val, max_val].min.to_f
96
102
  end
97
103
 
98
104
  def has_large_dynamic_range?
99
- dynamic_range > self.class.large_dynamic_range_cutoff
105
+ dynamic_range > self.class.large_dynamic_range_threshold
100
106
  end
101
107
 
102
108
  def label_sorter(label_val_key, value_sorter)
@@ -58,6 +58,10 @@ class RailsDataExplorer
58
58
  # TODO: all elements in a series are of same type
59
59
  end
60
60
 
61
+ def number_of_values
62
+ @data_series.first.number_of_values
63
+ end
64
+
61
65
  def dimensions_count
62
66
  @data_series.length
63
67
  end
@@ -2,20 +2,22 @@ class RailsDataExplorer
2
2
  class DataType
3
3
  class Categorical < DataType
4
4
 
5
+ # TODO: when there are too many categories, only separate the N most
6
+ # significant ones and group all other values under "Other"
5
7
  def all_available_chart_types
6
8
  [
7
9
  {
8
- :chart_class => Chart::HistogramCategorical,
9
- :chart_roles => [:x],
10
- :dimensions_count_min => 1,
11
- :dimensions_count_max => 1,
12
- },
13
- {
14
- :chart_class => Chart::PieChart,
15
- :chart_roles => [:any],
16
- :dimensions_count_min => 1,
17
- :dimensions_count_max => 1,
10
+ chart_class: Chart::HistogramCategorical,
11
+ chart_roles: [:x],
12
+ dimensions_count_min: 1,
13
+ dimensions_count_max: 1,
18
14
  },
15
+ # {
16
+ # chart_class: Chart::PieChart,
17
+ # chart_roles: [:any],
18
+ # dimensions_count_min: 1,
19
+ # dimensions_count_max: 1,
20
+ # },
19
21
  {
20
22
  chart_class: Chart::BoxPlotGroup,
21
23
  chart_roles: [:y],
@@ -38,6 +40,12 @@ class RailsDataExplorer
38
40
  dimensions_count_min: 2,
39
41
  dimensions_count_max: 2,
40
42
  },
43
+ {
44
+ chart_class: Chart::StackedHistogramTemporal,
45
+ chart_roles: [:y],
46
+ dimensions_count_min: 2,
47
+ dimensions_count_max: 2,
48
+ },
41
49
  {
42
50
  chart_class: Chart::ContingencyTable,
43
51
  chart_roles: [:any],
@@ -60,25 +68,31 @@ class RailsDataExplorer
60
68
 
61
69
  def descriptive_statistics(values)
62
70
  frequencies = values.inject(Hash.new(0)) { |m,e| m[e] += 1; m }
71
+ labels_ds = DataSeries.new('_', values.uniq)
63
72
  total_count = values.length
64
73
  ruby_formatters = {
65
- :integer => Proc.new { |v| number_with_delimiter(v.round) },
66
- :percent => Proc.new { |v| number_to_percentage(v, :precision => 3, :significant => true, :strip_insignificant_zeros => true, :delimiter => ',') },
74
+ integer: Proc.new { |v| number_with_delimiter(v.round) },
75
+ percent: Proc.new { |v| number_to_percentage(v, precision: 3, significant: true, strip_insignificant_zeros: true, delimiter: ',') },
67
76
  }
68
77
  r = frequencies.inject([]) { |m, (k,v)|
69
- m << { :label => "#{ k.to_s }_count", :value => v, :ruby_formatter => ruby_formatters[:integer] }
70
- m << { :label => "#{ k.to_s }_percent", :value => (v / total_count.to_f) * 100, :ruby_formatter => ruby_formatters[:percent] }
78
+ m << { label: "#{ k.to_s }_count", value: v, ruby_formatter: ruby_formatters[:integer] }
79
+ m << { label: "#{ k.to_s }_percent", value: (v / total_count.to_f) * 100, ruby_formatter: ruby_formatters[:percent] }
71
80
  m
72
- }.sort { |a,b| b[:value] <=> a[:value] }
73
- r.insert(0, { :label => '[Total]_count', :value => total_count, :ruby_formatter => ruby_formatters[:integer] })
74
- r.insert(0, { :label => '[Total]_percent', :value => 100, :ruby_formatter => ruby_formatters[:percent] })
81
+ }.sort(
82
+ &labels_ds.label_sorter(
83
+ :label,
84
+ lambda { |a,b| b[:value] <=> a[:value] }
85
+ )
86
+ )
87
+ r.insert(0, { label: '[Total]_count', value: total_count, ruby_formatter: ruby_formatters[:integer] })
88
+ r.insert(0, { label: '[Total]_percent', value: 100, ruby_formatter: ruby_formatters[:percent] })
75
89
  r
76
90
  end
77
91
 
78
- # Returns an OpenStruct that describes a statistics table.
92
+ # Returns an object that describes a statistics table.
79
93
  def descriptive_statistics_table(values)
80
94
  desc_stats = descriptive_statistics(values)
81
- if desc_stats.length < DataSeries.many_uniq_vals_cutoff
95
+ if desc_stats.length < DataSeries.many_uniq_vals_threshold
82
96
  descriptive_statistics_table_horizontal(desc_stats)
83
97
  else
84
98
  descriptive_statistics_table_vertical(desc_stats)
@@ -87,68 +101,64 @@ class RailsDataExplorer
87
101
 
88
102
  def descriptive_statistics_table_horizontal(desc_stats)
89
103
  labels = desc_stats.map { |e| e[:label].gsub(/_count|_percent/, '') }.uniq
90
- table = OpenStruct.new(
91
- :rows => []
92
- )
93
- table.rows << OpenStruct.new(
94
- :css_class => 'rde-column_header',
95
- :tag => :tr,
96
- :cells => labels.map { |label|
97
- OpenStruct.new(:value => label, :ruby_formatter => Proc.new { |e| e }, :tag => :th, :css_class => 'rde-cell-label')
98
- }
104
+ table = Utils::RdeTable.new([])
105
+ table.rows << Utils::RdeTableRow.new(
106
+ :tr,
107
+ labels.map { |label|
108
+ Utils::RdeTableCell.new(:th, label, ruby_formatter: Proc.new { |e| e }, css_class: 'rde-cell-label')
109
+ },
110
+ css_class: 'rde-column_header'
99
111
  )
100
- table.rows << OpenStruct.new(
101
- :css_class => 'rde-data_row',
102
- :tag => :tr,
103
- :cells => labels.map { |label|
112
+ table.rows << Utils::RdeTableRow.new(
113
+ :tr,
114
+ labels.map { |label|
104
115
  stat = desc_stats.detect { |e| "#{ label }_count" == e[:label] }
105
- OpenStruct.new(:value => stat[:value], :ruby_formatter => stat[:ruby_formatter], :tag => :td, :css_class => 'rde-cell-value')
106
- }
116
+ Utils::RdeTableCell.new(:td, stat[:value], ruby_formatter: stat[:ruby_formatter], css_class: 'rde-cell-value')
117
+ },
118
+ css_class: 'rde-data_row'
107
119
  )
108
- table.rows << OpenStruct.new(
109
- :css_class => 'rde-data_row',
110
- :tag => :tr,
111
- :cells => labels.map { |label|
120
+ table.rows << Utils::RdeTableRow.new(
121
+ :tr,
122
+ labels.map { |label|
112
123
  stat = desc_stats.detect { |e| "#{ label }_percent" == e[:label] }
113
- OpenStruct.new(:value => stat[:value], :ruby_formatter => stat[:ruby_formatter], :tag => :td, :css_class => 'rde-cell-value')
114
- }
124
+ Utils::RdeTableCell.new(:td, stat[:value], ruby_formatter: stat[:ruby_formatter], css_class: 'rde-cell-value')
125
+ },
126
+ css_class: 'rde-data_row'
115
127
  )
116
128
  table
117
129
  end
118
130
 
119
131
  def descriptive_statistics_table_vertical(desc_stats)
120
132
  labels = desc_stats.map { |e| e[:label].gsub(/_count|_percent/, '') }.uniq
121
- table = OpenStruct.new(
122
- :rows => []
123
- )
124
- table.rows << OpenStruct.new(
125
- :css_class => 'rde-column_header',
126
- :tag => :tr,
127
- :cells => %w[Value Count Percent].map { |label|
128
- OpenStruct.new(:value => label, :tag => :th, :css_class => 'rde-cell-label')
129
- }
133
+ table = Utils::RdeTable.new([])
134
+ table.rows << Utils::RdeTableRow.new(
135
+ :tr,
136
+ %w[Value Count Percent].map { |label|
137
+ Utils::RdeTableCell.new(:th, label, css_class: 'rde-cell-label')
138
+ },
139
+ css_class: 'rde-column_header',
130
140
  )
131
141
  labels.each { |label|
132
142
  count_stat = desc_stats.detect { |e| "#{ label }_count" == e[:label] }
133
143
  percent_stat = desc_stats.detect { |e| "#{ label }_percent" == e[:label] }
134
- table.rows << OpenStruct.new(
135
- :css_class => 'rde-data_row',
136
- :tag => :tr,
137
- :cells => [
138
- OpenStruct.new(:value => label, :tag => :td, :css_class => 'rde-cell-value'),
139
- OpenStruct.new(
140
- :value => count_stat[:value],
141
- :ruby_formatter => count_stat[:ruby_formatter],
142
- :tag => :td,
143
- :css_class => 'rde-cell-value'
144
+ table.rows << Utils::RdeTableRow.new(
145
+ :tr,
146
+ [
147
+ Utils::RdeTableCell.new(:td, label, css_class: 'rde-cell-value'),
148
+ Utils::RdeTableCell.new(
149
+ :td,
150
+ count_stat[:value],
151
+ ruby_formatter: count_stat[:ruby_formatter],
152
+ css_class: 'rde-cell-value'
144
153
  ),
145
- OpenStruct.new(
146
- :value => percent_stat[:value],
147
- :ruby_formatter => percent_stat[:ruby_formatter],
148
- :tag => :td,
149
- :css_class => 'rde-cell-value'
154
+ Utils::RdeTableCell.new(
155
+ :td,
156
+ percent_stat[:value],
157
+ ruby_formatter: percent_stat[:ruby_formatter],
158
+ css_class: 'rde-cell-value'
150
159
  ),
151
- ]
160
+ ],
161
+ css_class: 'rde-data_row',
152
162
  )
153
163
  }
154
164
  table
@@ -163,18 +173,20 @@ class RailsDataExplorer
163
173
  # @param[Proc] value_sorter the sorting proc to use if not sorted numerically
164
174
  # @return[Proc] a Proc that will be used by #sort
165
175
  def label_sorter(label_val_key, data_series, value_sorter)
166
- if data_series.uniq_vals.any? { |e| e.to_s =~ /^\d+/ }
176
+ if data_series.uniq_vals.any? { |e| e.to_s =~ /^[\+\-]?\d+/ }
167
177
  # Sort numerical categories by key ASC
168
178
  lambda { |a,b|
169
- number_extractor = lambda { |val|
179
+ number_and_full_string_extractor = lambda { |val|
170
180
  str = label_val_key ? val[label_val_key] : val
171
- number = str.gsub(/^[^\d]*/, '').to_f
181
+ number = str.gsub(/^[^\d\+\-]*/, '') # remove non-digit leading chars
182
+ .gsub(',', '') # remove delimiter commas, they throw off to_f parsing
183
+ .to_f
172
184
  number += 1 if str =~ /^>/ # increase highest threshold by one for proper sorting
173
- number
185
+ [number, str]
174
186
  }
175
- a_number = number_extractor.call(a)
176
- b_number = number_extractor.call(b)
177
- a_number <=> b_number
187
+ a_number_and_full_string = number_and_full_string_extractor.call(a)
188
+ b_number_and_full_string = number_and_full_string_extractor.call(b)
189
+ a_number_and_full_string <=> b_number_and_full_string
178
190
  }
179
191
  else
180
192
  # Use provided value sorter
@@ -47,10 +47,10 @@ class RailsDataExplorer
47
47
  non_nil_values = values.find_all { |e| !(e.nil? || Float::NAN == e) }
48
48
  stats = ::DescriptiveStatistics::Stats.new(non_nil_values)
49
49
  ruby_formatters = {
50
- :integer => Proc.new { |v|
50
+ integer: Proc.new { |v|
51
51
  v.nil? ? 'Null' : number_with_delimiter(v.round)
52
52
  },
53
- :decimal => Proc.new { |v|
53
+ decimal: Proc.new { |v|
54
54
  case
55
55
  when v.nil?
56
56
  'Null'
@@ -59,62 +59,60 @@ class RailsDataExplorer
59
59
  else
60
60
  number_with_precision(
61
61
  v,
62
- :precision => 3,
63
- :significant => true,
64
- :strip_insignificant_zeros => true,
65
- :delimiter => ','
62
+ precision: 3,
63
+ significant: true,
64
+ strip_insignificant_zeros: true,
65
+ delimiter: ','
66
66
  )
67
67
  end
68
68
  },
69
- :pass_through => Proc.new { |v| (v.nil? || Float::NAN == v) ? 'NaN' : v },
69
+ pass_through: Proc.new { |v| (v.nil? || Float::NAN == v) ? 'NaN' : v },
70
70
  }
71
71
  [
72
- { :label => 'Min', :value => stats.min, :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
73
- { :label => '1%ile', :value => stats.value_from_percentile(1), :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
74
- { :label => '5%ile', :value => stats.value_from_percentile(5), :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
75
- { :label => '10%ile', :value => stats.value_from_percentile(10), :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
76
- { :label => '25%ile', :value => stats.value_from_percentile(25), :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
77
- { :label => 'Median', :value => stats.median, :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
78
- { :label => '75%ile', :value => stats.value_from_percentile(75), :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
79
- { :label => '90%ile', :value => stats.value_from_percentile(90), :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
80
- { :label => '95%ile', :value => stats.value_from_percentile(95), :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
81
- { :label => '99%ile', :value => stats.value_from_percentile(99), :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
82
- { :label => 'Max', :value => stats.max, :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
72
+ { label: 'Min', value: stats.min, ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
73
+ { label: '1%ile', value: stats.value_from_percentile(1), ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
74
+ { label: '5%ile', value: stats.value_from_percentile(5), ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
75
+ { label: '10%ile', value: stats.value_from_percentile(10), ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
76
+ { label: '25%ile', value: stats.value_from_percentile(25), ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
77
+ { label: 'Median', value: stats.median, ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
78
+ { label: '75%ile', value: stats.value_from_percentile(75), ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
79
+ { label: '90%ile', value: stats.value_from_percentile(90), ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
80
+ { label: '95%ile', value: stats.value_from_percentile(95), ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
81
+ { label: '99%ile', value: stats.value_from_percentile(99), ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
82
+ { label: 'Max', value: stats.max, ruby_formatter: ruby_formatters[:decimal], table_row: 1 },
83
83
 
84
- { :label => 'Range', :value => stats.range, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
85
- { :label => 'Mean', :value => stats.mean, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
86
- { :label => 'Mode', :value => stats.mode, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
87
- { :label => 'Count', :value => values.length, :ruby_formatter => ruby_formatters[:integer], :table_row => 2 },
88
- { :label => 'Sum', :value => non_nil_values.inject(0) { |m,e| m += e }, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
89
- { :label => 'Variance', :value => stats.variance, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
90
- { :label => 'Std. dev.', :value => stats.standard_deviation, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
91
- { :label => 'Rel. std. dev.', :value => stats.relative_standard_deviation, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
92
- { :label => 'Skewness', :value => stats.skewness, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
93
- { :label => 'Kurtosis', :value => stats.kurtosis, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
94
- { :label => '', :value => '', :ruby_formatter => ruby_formatters[:pass_through], :table_row => 2 },
84
+ { label: 'Range', value: stats.range, ruby_formatter: ruby_formatters[:decimal], table_row: 2 },
85
+ { label: 'Mean', value: stats.mean, ruby_formatter: ruby_formatters[:decimal], table_row: 2 },
86
+ { label: 'Mode', value: stats.mode, ruby_formatter: ruby_formatters[:decimal], table_row: 2 },
87
+ { label: 'Count', value: values.length, ruby_formatter: ruby_formatters[:integer], table_row: 2 },
88
+ { label: 'Sum', value: non_nil_values.inject(0) { |m,e| m += e }, ruby_formatter: ruby_formatters[:decimal], table_row: 2 },
89
+ { label: 'Variance', value: stats.variance, ruby_formatter: ruby_formatters[:decimal], table_row: 2 },
90
+ { label: 'Std. dev.', value: stats.standard_deviation, ruby_formatter: ruby_formatters[:decimal], table_row: 2 },
91
+ { label: 'Rel. std. dev.', value: stats.relative_standard_deviation, ruby_formatter: ruby_formatters[:decimal], table_row: 2 },
92
+ { label: 'Skewness', value: stats.skewness, ruby_formatter: ruby_formatters[:decimal], table_row: 2 },
93
+ { label: 'Kurtosis', value: stats.kurtosis, ruby_formatter: ruby_formatters[:decimal], table_row: 2 },
94
+ { label: '', value: '', ruby_formatter: ruby_formatters[:pass_through], table_row: 2 },
95
95
  ]
96
96
  end
97
97
 
98
- # Returns an OpenStruct that describes a statistics table.
98
+ # Returns an object that describes a statistics table.
99
99
  def descriptive_statistics_table(values)
100
100
  desc_stats = descriptive_statistics(values)
101
- table = OpenStruct.new(
102
- :rows => []
103
- )
101
+ table = Utils::RdeTable.new([])
104
102
  [1,2].each do |table_row|
105
- table.rows << OpenStruct.new(
106
- :css_class => 'rde-column_header',
107
- :tag => :tr,
108
- :cells => desc_stats.find_all { |e| table_row == e[:table_row] }.map { |stat|
109
- OpenStruct.new(:value => stat[:label], :ruby_formatter => Proc.new { |e| e }, :tag => :th, :css_class => 'rde-cell-label')
110
- }
103
+ table.rows << Utils::RdeTableRow.new(
104
+ :tr,
105
+ desc_stats.find_all { |e| table_row == e[:table_row] }.map { |stat|
106
+ Utils::RdeTableCell.new(:th, stat[:label], ruby_formatter: Proc.new { |e| e }, css_class: 'rde-cell-label')
107
+ },
108
+ css_class: 'rde-column_header'
111
109
  )
112
- table.rows << OpenStruct.new(
113
- :css_class => 'rde-data_row',
114
- :tag => :tr,
115
- :cells => desc_stats.find_all { |e| table_row == e[:table_row] }.map { |stat|
116
- OpenStruct.new(:value => stat[:value], :ruby_formatter => stat[:ruby_formatter], :tag => :td, :css_class => 'rde-cell-value')
117
- }
110
+ table.rows << Utils::RdeTableRow.new(
111
+ :tr,
112
+ desc_stats.find_all { |e| table_row == e[:table_row] }.map { |stat|
113
+ Utils::RdeTableCell.new(:td, stat[:value], ruby_formatter: stat[:ruby_formatter], css_class: 'rde-cell-value')
114
+ },
115
+ css_class: 'rde-data_row'
118
116
  )
119
117
  end
120
118
  table
@@ -124,12 +122,12 @@ class RailsDataExplorer
124
122
  raise "Implement me in sub_class"
125
123
  end
126
124
 
127
- def axis_scale(data_series)
125
+ def axis_scale(data_series, d3_or_vega)
128
126
  # Log scales can't handle 0 values
129
127
  if data_series.min_val > 0.0 && data_series.has_large_dynamic_range?
130
- 'd3.scale.log'
128
+ { d3: 'd3.scale.log', vega: 'log' }[d3_or_vega]
131
129
  else
132
- 'd3.scale.linear'
130
+ { d3: 'd3.scale.linear', vega: 'linear' }[d3_or_vega]
133
131
  end
134
132
  end
135
133