rails-data-explorer 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +5 -1
- data/README.md +11 -0
- data/Rakefile +62 -0
- data/doc/how_to/release.md +23 -0
- data/doc/how_to/trouble_when_packaging_assets.md +8 -0
- data/lib/rails-data-explorer-no-rails.rb +42 -0
- data/lib/rails-data-explorer.rb +5 -9
- data/lib/rails-data-explorer/chart/box_plot.rb +5 -1
- data/lib/rails-data-explorer/chart/box_plot_group.rb +22 -5
- data/lib/rails-data-explorer/chart/contingency_table.rb +45 -10
- data/lib/rails-data-explorer/chart/histogram_categorical.rb +104 -3
- data/lib/rails-data-explorer/chart/histogram_quantitative.rb +99 -2
- data/lib/rails-data-explorer/chart/histogram_temporal.rb +10 -55
- data/lib/rails-data-explorer/chart/parallel_coordinates.rb +4 -0
- data/lib/rails-data-explorer/chart/parallel_set.rb +4 -0
- data/lib/rails-data-explorer/chart/pie_chart.rb +89 -8
- data/lib/rails-data-explorer/chart/scatterplot.rb +110 -8
- data/lib/rails-data-explorer/chart/stacked_bar_chart_categorical_percent.rb +133 -14
- data/lib/rails-data-explorer/data_series.rb +37 -2
- data/lib/rails-data-explorer/data_type/categorical.rb +72 -2
- data/lib/rails-data-explorer/data_type/quantitative.rb +41 -12
- data/lib/rails-data-explorer/data_type/quantitative/temporal.rb +3 -2
- data/lib/rails-data-explorer/exploration.rb +5 -1
- data/lib/rails-data-explorer/utils/data_binner.rb +31 -0
- data/lib/rails-data-explorer/utils/data_quantizer.rb +66 -0
- data/lib/rails_data_explorer.rb +133 -0
- data/rails-data-explorer.gemspec +4 -4
- data/spec/helper.rb +7 -0
- data/spec/helper_no_rails.rb +10 -0
- data/spec/rails-data-explorer/data_series_spec.rb +45 -0
- data/spec/rails-data-explorer/data_type/categorical_spec.rb +34 -0
- data/spec/rails-data-explorer/exploration_spec.rb +55 -0
- data/spec/rails-data-explorer/utils/data_binner_spec.rb +29 -0
- data/spec/rails-data-explorer/utils/data_quantizer_spec.rb +71 -0
- data/vendor/assets/javascripts/packaged/rails-data-explorer.min.js +1 -0
- data/vendor/assets/javascripts/rails-data-explorer.js +6 -5
- data/vendor/assets/javascripts/{d3.boxplot.js → sources/d3.boxplot.js} +10 -3
- data/vendor/assets/javascripts/{d3.parcoords.js → sources/d3.parcoords.js} +1 -1
- data/vendor/assets/javascripts/{d3.parsets.js → sources/d3.parsets.js} +3 -3
- data/vendor/assets/javascripts/{d3.v3.js → sources/d3.v3.js} +0 -0
- data/vendor/assets/javascripts/{nv.d3.js → sources/nv.d3.js} +0 -0
- data/vendor/assets/javascripts/sources/vega.js +7040 -0
- data/vendor/assets/stylesheets/packaged/rails-data-explorer.min.css +9 -0
- data/vendor/assets/stylesheets/rails-data-explorer.css +7 -7
- data/vendor/assets/stylesheets/{bootstrap-theme.css → sources/bootstrap-theme.css} +0 -0
- data/vendor/assets/stylesheets/{bootstrap.css → sources/bootstrap.css} +0 -0
- data/vendor/assets/stylesheets/{d3.boxplot.css → sources/d3.boxplot.css} +0 -0
- data/vendor/assets/stylesheets/{d3.parcoords.css → sources/d3.parcoords.css} +0 -0
- data/vendor/assets/stylesheets/{d3.parsets.css → sources/d3.parsets.css} +0 -0
- data/vendor/assets/stylesheets/{nv.d3.css → sources/nv.d3.css} +0 -0
- data/vendor/assets/stylesheets/{rde-default-style.css → sources/rde-default-style.css} +0 -0
- metadata +65 -28
@@ -17,6 +17,7 @@ class RailsDataExplorer
|
|
17
17
|
|
18
18
|
x_ds = x_candidates.first
|
19
19
|
y_ds = (y_candidates - [x_ds]).first
|
20
|
+
return false if x_ds.nil? || y_ds.nil?
|
20
21
|
|
21
22
|
# initialize data_matrix
|
22
23
|
data_matrix = { :_sum => { :_sum => 0 } }
|
@@ -38,25 +39,30 @@ class RailsDataExplorer
|
|
38
39
|
data_matrix[:_sum][:_sum] += 1
|
39
40
|
}
|
40
41
|
|
41
|
-
x_sorted_keys = x_ds.uniq_vals.sort
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
42
|
+
x_sorted_keys = x_ds.uniq_vals.sort(
|
43
|
+
&x_ds.label_sorter(
|
44
|
+
nil,
|
45
|
+
lambda { |a,b| data_matrix[b][:_sum] <=> data_matrix[a][:_sum] }
|
46
|
+
)
|
47
|
+
)
|
48
|
+
y_sorted_keys = y_ds.uniq_vals.sort(
|
49
|
+
&y_ds.label_sorter(
|
50
|
+
nil,
|
51
|
+
lambda { |a,b| data_matrix[:_sum][b] <=> data_matrix[:_sum][a] }
|
52
|
+
)
|
53
|
+
)
|
47
54
|
|
48
55
|
values = case @data_set.dimensions_count
|
49
56
|
when 2
|
50
57
|
y_sorted_keys.map { |y_val|
|
51
|
-
{
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
y: (data_matrix[x_val][y_val] / data_matrix[x_val][:_sum].to_f) }
|
58
|
+
x_sorted_keys.map { |x_val|
|
59
|
+
{
|
60
|
+
x: x_val,
|
61
|
+
y: (data_matrix[x_val][y_val] / data_matrix[x_val][:_sum].to_f) * 100,
|
62
|
+
c: y_val
|
57
63
|
}
|
58
64
|
}
|
59
|
-
}
|
65
|
+
}.flatten
|
60
66
|
else
|
61
67
|
raise(ArgumentError.new("Exactly two data series required for contingency table."))
|
62
68
|
end
|
@@ -72,8 +78,115 @@ class RailsDataExplorer
|
|
72
78
|
def render
|
73
79
|
return '' unless render?
|
74
80
|
ca = compute_chart_attrs
|
81
|
+
return '' unless ca
|
82
|
+
render_vega(ca)
|
83
|
+
end
|
84
|
+
|
85
|
+
def render_vega(ca)
|
75
86
|
%(
|
76
|
-
<div class="rde-chart rde-bar-chart">
|
87
|
+
<div class="rde-chart rde-stacked-bar-chart-categorical-percent">
|
88
|
+
<h3 class="rde-chart-title">Stacked Bar Chart</h3>
|
89
|
+
<div id="#{ dom_id }"></div>
|
90
|
+
<script type="text/javascript">
|
91
|
+
(function() {
|
92
|
+
var spec = {
|
93
|
+
"width": 800,
|
94
|
+
"height": 200,
|
95
|
+
"padding": {"top": 10, "left": 50, "bottom": 50, "right": 100},
|
96
|
+
"data": [
|
97
|
+
{
|
98
|
+
"name": "table",
|
99
|
+
"values": #{ ca[:values].to_json }
|
100
|
+
},
|
101
|
+
{
|
102
|
+
"name": "stats",
|
103
|
+
"source": "table",
|
104
|
+
"transform": [
|
105
|
+
{"type": "facet", "keys": ["data.x"]},
|
106
|
+
{"type": "stats", "value": "data.y"}
|
107
|
+
]
|
108
|
+
}
|
109
|
+
],
|
110
|
+
"scales": [
|
111
|
+
{
|
112
|
+
"name": "x",
|
113
|
+
"type": "ordinal",
|
114
|
+
"range": "width",
|
115
|
+
"domain": {"data": "table", "field": "data.x"}
|
116
|
+
},
|
117
|
+
{
|
118
|
+
"name": "y",
|
119
|
+
"type": "linear",
|
120
|
+
"range": "height",
|
121
|
+
"nice": true,
|
122
|
+
"domain": {"data": "stats", "field": "sum"}
|
123
|
+
},
|
124
|
+
{
|
125
|
+
"name": "color",
|
126
|
+
"type": "ordinal",
|
127
|
+
"range": "category10"
|
128
|
+
}
|
129
|
+
],
|
130
|
+
"axes": [
|
131
|
+
{
|
132
|
+
"type": "x",
|
133
|
+
"scale": "x",
|
134
|
+
"title": "#{ ca[:x_axis_label] }",
|
135
|
+
"format": #{ ca[:x_axis_tick_format] },
|
136
|
+
},
|
137
|
+
{
|
138
|
+
"type": "y",
|
139
|
+
"scale": "y",
|
140
|
+
"title": "#{ ca[:y_axis_label] }",
|
141
|
+
"format": #{ ca[:y_axis_tick_format] },
|
142
|
+
}
|
143
|
+
],
|
144
|
+
"marks": [
|
145
|
+
{
|
146
|
+
"type": "group",
|
147
|
+
"from": {
|
148
|
+
"data": "table",
|
149
|
+
"transform": [
|
150
|
+
{"type": "facet", "keys": ["data.c"]},
|
151
|
+
{"type": "stack", "point": "data.x", "height": "data.y"}
|
152
|
+
]
|
153
|
+
},
|
154
|
+
"marks": [
|
155
|
+
{
|
156
|
+
"type": "rect",
|
157
|
+
"properties": {
|
158
|
+
"enter": {
|
159
|
+
"x": {"scale": "x", "field": "data.x"},
|
160
|
+
"width": {"scale": "x", "band": true, "offset": -1},
|
161
|
+
"y": {"scale": "y", "field": "y"},
|
162
|
+
"y2": {"scale": "y", "field": "y2"},
|
163
|
+
"fill": {"scale": "color", "field": "data.c"}
|
164
|
+
},
|
165
|
+
}
|
166
|
+
}
|
167
|
+
]
|
168
|
+
}
|
169
|
+
],
|
170
|
+
"legends": [
|
171
|
+
{
|
172
|
+
"fill": "color",
|
173
|
+
}
|
174
|
+
],
|
175
|
+
};
|
176
|
+
|
177
|
+
vg.parse.spec(spec, function(chart) {
|
178
|
+
var view = chart({ el:"##{ dom_id }" }).update();
|
179
|
+
});
|
180
|
+
|
181
|
+
})();
|
182
|
+
</script>
|
183
|
+
</div>
|
184
|
+
)
|
185
|
+
end
|
186
|
+
|
187
|
+
def render_nvd3(ca)
|
188
|
+
%(
|
189
|
+
<div class="rde-chart rde-stacked-bar-chart-categorical-percent">
|
77
190
|
<h3 class="rde-chart-title">Stacked Bar Chart</h3>
|
78
191
|
<div id="#{ dom_id }", style="height: 200px;">
|
79
192
|
<svg></svg>
|
@@ -98,6 +211,12 @@ class RailsDataExplorer
|
|
98
211
|
|
99
212
|
chart.multibar.stacked(true);
|
100
213
|
chart.showControls(false);
|
214
|
+
chart.tooltipContent(
|
215
|
+
function(key, x, y, e, graph) {
|
216
|
+
return '<p>' + key + '</p>' + '<p>' + y + ' of ' + x + '</p>'
|
217
|
+
}
|
218
|
+
);
|
219
|
+
|
101
220
|
|
102
221
|
d3.select('##{ dom_id } svg')
|
103
222
|
.datum(data)
|
@@ -8,6 +8,19 @@ class RailsDataExplorer
|
|
8
8
|
delegate :available_chart_types, :to => :data_type, :prefix => false
|
9
9
|
delegate :available_chart_roles, :to => :data_type, :prefix => false
|
10
10
|
|
11
|
+
# Any data series with a dynamic range greater than this is considered
|
12
|
+
# having a large dynamic range
|
13
|
+
# We consider dynamic range the ratio between the largest and the smallest value.
|
14
|
+
def self.large_dynamic_range_cutoff
|
15
|
+
1000.0
|
16
|
+
end
|
17
|
+
|
18
|
+
# Any data series with more than this uniq vals is considered having many
|
19
|
+
# uniq values.
|
20
|
+
def self.many_uniq_vals_cutoff
|
21
|
+
20
|
22
|
+
end
|
23
|
+
|
11
24
|
# options: :chart_roles, :data_type (all optional)
|
12
25
|
def initialize(_name, _values, options={})
|
13
26
|
options = { chart_roles: [], data_type: nil }.merge(options)
|
@@ -53,6 +66,10 @@ class RailsDataExplorer
|
|
53
66
|
data_type.axis_tick_format(values)
|
54
67
|
end
|
55
68
|
|
69
|
+
def axis_scale
|
70
|
+
data_type.axis_scale(self)
|
71
|
+
end
|
72
|
+
|
56
73
|
def uniq_vals
|
57
74
|
@uniq_vals = values.uniq
|
58
75
|
end
|
@@ -69,6 +86,23 @@ class RailsDataExplorer
|
|
69
86
|
@max_val = values.compact.max
|
70
87
|
end
|
71
88
|
|
89
|
+
# Used to decide whether we can render certain chart types
|
90
|
+
def has_many_uniq_vals?
|
91
|
+
uniq_vals_count > self.class.many_uniq_vals_cutoff
|
92
|
+
end
|
93
|
+
|
94
|
+
def dynamic_range
|
95
|
+
max_val / [min_val, max_val].min.to_f
|
96
|
+
end
|
97
|
+
|
98
|
+
def has_large_dynamic_range?
|
99
|
+
dynamic_range > self.class.large_dynamic_range_cutoff
|
100
|
+
end
|
101
|
+
|
102
|
+
def label_sorter(label_val_key, value_sorter)
|
103
|
+
data_type.label_sorter(label_val_key, self, value_sorter)
|
104
|
+
end
|
105
|
+
|
72
106
|
private
|
73
107
|
|
74
108
|
# @param[Array<Symbol>] chart_role_overrides, :x, :y, :color
|
@@ -94,14 +128,15 @@ class RailsDataExplorer
|
|
94
128
|
|
95
129
|
def init_data_type(data_type_override)
|
96
130
|
if data_type_override.nil?
|
97
|
-
|
131
|
+
first_value = values.detect { |e| !e.nil? }
|
132
|
+
case first_value
|
98
133
|
when Integer, Bignum, Fixnum
|
99
134
|
DataType::Quantitative::Integer.new
|
100
135
|
when Float
|
101
136
|
DataType::Quantitative::Decimal.new
|
102
137
|
when String
|
103
138
|
DataType::Categorical.new
|
104
|
-
when DateTime, ActiveSupport::TimeWithZone
|
139
|
+
when Time, DateTime, ActiveSupport::TimeWithZone
|
105
140
|
DataType::Quantitative::Temporal.new
|
106
141
|
else
|
107
142
|
raise(ArgumentError.new("Can't infer data type for value: #{ values.first.class.inspect }"))
|
@@ -70,14 +70,22 @@ class RailsDataExplorer
|
|
70
70
|
m << { :label => "#{ k.to_s }_percent", :value => (v / total_count.to_f) * 100, :ruby_formatter => ruby_formatters[:percent] }
|
71
71
|
m
|
72
72
|
}.sort { |a,b| b[:value] <=> a[:value] }
|
73
|
-
r.insert(0, { :label => '
|
74
|
-
r.insert(0, { :label => '
|
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] })
|
75
75
|
r
|
76
76
|
end
|
77
77
|
|
78
78
|
# Returns an OpenStruct that describes a statistics table.
|
79
79
|
def descriptive_statistics_table(values)
|
80
80
|
desc_stats = descriptive_statistics(values)
|
81
|
+
if desc_stats.length < DataSeries.many_uniq_vals_cutoff
|
82
|
+
descriptive_statistics_table_horizontal(desc_stats)
|
83
|
+
else
|
84
|
+
descriptive_statistics_table_vertical(desc_stats)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def descriptive_statistics_table_horizontal(desc_stats)
|
81
89
|
labels = desc_stats.map { |e| e[:label].gsub(/_count|_percent/, '') }.uniq
|
82
90
|
table = OpenStruct.new(
|
83
91
|
:rows => []
|
@@ -108,10 +116,72 @@ class RailsDataExplorer
|
|
108
116
|
table
|
109
117
|
end
|
110
118
|
|
119
|
+
def descriptive_statistics_table_vertical(desc_stats)
|
120
|
+
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
|
+
}
|
130
|
+
)
|
131
|
+
labels.each { |label|
|
132
|
+
count_stat = desc_stats.detect { |e| "#{ label }_count" == e[:label] }
|
133
|
+
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
|
+
),
|
145
|
+
OpenStruct.new(
|
146
|
+
:value => percent_stat[:value],
|
147
|
+
:ruby_formatter => percent_stat[:ruby_formatter],
|
148
|
+
:tag => :td,
|
149
|
+
:css_class => 'rde-cell-value'
|
150
|
+
),
|
151
|
+
]
|
152
|
+
)
|
153
|
+
}
|
154
|
+
table
|
155
|
+
end
|
156
|
+
|
111
157
|
def axis_tick_format(values)
|
112
158
|
%(function(d) { return d })
|
113
159
|
end
|
114
160
|
|
161
|
+
# @param[Symbol, nil] label_val_key the hash key to use to get the label value during sort (sent to a,b)
|
162
|
+
# @param[DataSeries] data_series the ds that contains the uniq vals
|
163
|
+
# @param[Proc] value_sorter the sorting proc to use if not sorted numerically
|
164
|
+
# @return[Proc] a Proc that will be used by #sort
|
165
|
+
def label_sorter(label_val_key, data_series, value_sorter)
|
166
|
+
if data_series.uniq_vals.any? { |e| e.to_s =~ /^\d+/ }
|
167
|
+
# Sort numerical categories by key ASC
|
168
|
+
lambda { |a,b|
|
169
|
+
number_extractor = lambda { |val|
|
170
|
+
str = label_val_key ? val[label_val_key] : val
|
171
|
+
number = str.gsub(/^[^\d]*/, '').to_f
|
172
|
+
number += 1 if str =~ /^>/ # increase highest threshold by one for proper sorting
|
173
|
+
number
|
174
|
+
}
|
175
|
+
a_number = number_extractor.call(a)
|
176
|
+
b_number = number_extractor.call(b)
|
177
|
+
a_number <=> b_number
|
178
|
+
}
|
179
|
+
else
|
180
|
+
# Use provided value sorter
|
181
|
+
value_sorter
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
115
185
|
end
|
116
186
|
end
|
117
187
|
end
|
@@ -6,12 +6,12 @@ class RailsDataExplorer
|
|
6
6
|
|
7
7
|
def all_available_chart_types
|
8
8
|
[
|
9
|
-
{
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
},
|
9
|
+
# {
|
10
|
+
# chart_class: Chart::BoxPlot,
|
11
|
+
# chart_roles: [:y],
|
12
|
+
# dimensions_count_min: 1,
|
13
|
+
# dimensions_count_max: 1
|
14
|
+
# },
|
15
15
|
{
|
16
16
|
chart_class: Chart::HistogramQuantitative,
|
17
17
|
chart_roles: [:x],
|
@@ -44,34 +44,54 @@ class RailsDataExplorer
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def descriptive_statistics(values)
|
47
|
-
|
47
|
+
non_nil_values = values.find_all { |e| !(e.nil? || Float::NAN == e) }
|
48
|
+
stats = ::DescriptiveStatistics::Stats.new(non_nil_values)
|
48
49
|
ruby_formatters = {
|
49
|
-
:integer => Proc.new { |v|
|
50
|
-
|
51
|
-
|
50
|
+
:integer => Proc.new { |v|
|
51
|
+
v.nil? ? 'Null' : number_with_delimiter(v.round)
|
52
|
+
},
|
53
|
+
:decimal => Proc.new { |v|
|
54
|
+
case
|
55
|
+
when v.nil?
|
56
|
+
'Null'
|
57
|
+
when v.is_a?(Float) && v.nan?
|
58
|
+
'NaN'
|
59
|
+
else
|
60
|
+
number_with_precision(
|
61
|
+
v,
|
62
|
+
:precision => 3,
|
63
|
+
:significant => true,
|
64
|
+
:strip_insignificant_zeros => true,
|
65
|
+
:delimiter => ','
|
66
|
+
)
|
67
|
+
end
|
68
|
+
},
|
69
|
+
:pass_through => Proc.new { |v| (v.nil? || Float::NAN == v) ? 'NaN' : v },
|
52
70
|
}
|
53
71
|
[
|
54
72
|
{ :label => 'Min', :value => stats.min, :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
|
55
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 },
|
56
75
|
{ :label => '10%ile', :value => stats.value_from_percentile(10), :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
|
57
76
|
{ :label => '25%ile', :value => stats.value_from_percentile(25), :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
|
58
77
|
{ :label => 'Median', :value => stats.median, :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
|
59
78
|
{ :label => '75%ile', :value => stats.value_from_percentile(75), :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
|
60
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 },
|
61
81
|
{ :label => '99%ile', :value => stats.value_from_percentile(99), :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
|
62
82
|
{ :label => 'Max', :value => stats.max, :ruby_formatter => ruby_formatters[:decimal], :table_row => 1 },
|
63
|
-
{ :label => '', :value => '', :ruby_formatter => ruby_formatters[:pass_through], :table_row => 1 },
|
64
83
|
|
65
84
|
{ :label => 'Range', :value => stats.range, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
|
66
85
|
{ :label => 'Mean', :value => stats.mean, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
|
67
86
|
{ :label => 'Mode', :value => stats.mode, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
|
68
87
|
{ :label => 'Count', :value => values.length, :ruby_formatter => ruby_formatters[:integer], :table_row => 2 },
|
69
|
-
{ :label => 'Sum', :value =>
|
88
|
+
{ :label => 'Sum', :value => non_nil_values.inject(0) { |m,e| m += e }, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
|
70
89
|
{ :label => 'Variance', :value => stats.variance, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
|
71
90
|
{ :label => 'Std. dev.', :value => stats.standard_deviation, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
|
72
91
|
{ :label => 'Rel. std. dev.', :value => stats.relative_standard_deviation, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
|
73
92
|
{ :label => 'Skewness', :value => stats.skewness, :ruby_formatter => ruby_formatters[:decimal], :table_row => 2 },
|
74
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 },
|
75
95
|
]
|
76
96
|
end
|
77
97
|
|
@@ -104,6 +124,15 @@ class RailsDataExplorer
|
|
104
124
|
raise "Implement me in sub_class"
|
105
125
|
end
|
106
126
|
|
127
|
+
def axis_scale(data_series)
|
128
|
+
# Log scales can't handle 0 values
|
129
|
+
if data_series.min_val > 0.0 && data_series.has_large_dynamic_range?
|
130
|
+
'd3.scale.log'
|
131
|
+
else
|
132
|
+
'd3.scale.linear'
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
107
136
|
end
|
108
137
|
end
|
109
138
|
end
|
@@ -26,10 +26,11 @@ class RailsDataExplorer
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def descriptive_statistics(values)
|
29
|
+
non_nil_values = values.find_all { |e| !e.nil? }
|
29
30
|
ruby_formatter = Proc.new { |v| v.nil? ? '' : v.strftime('%a, %b %e, %Y, %l:%M:%S %p %Z') }
|
30
31
|
[
|
31
|
-
{ :label => 'Min', :value =>
|
32
|
-
{ :label => 'Max', :value =>
|
32
|
+
{ :label => 'Min', :value => non_nil_values.min, :ruby_formatter => ruby_formatter },
|
33
|
+
{ :label => 'Max', :value => non_nil_values.max, :ruby_formatter => ruby_formatter },
|
33
34
|
{ :label => 'Count', :value => values.length, :ruby_formatter => Proc.new { |e| number_with_delimiter(e) } },
|
34
35
|
]
|
35
36
|
end
|