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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +10 -0
- data/README.md +18 -0
- data/doc/how_to/release.md +2 -5
- data/lib/rails-data-explorer-no-rails.rb +1 -0
- data/lib/rails-data-explorer.rb +5 -0
- data/lib/rails-data-explorer/chart.rb +11 -11
- data/lib/rails-data-explorer/chart/box_plot_group.rb +10 -3
- data/lib/rails-data-explorer/chart/contingency_table.rb +58 -45
- data/lib/rails-data-explorer/chart/descriptive_statistics_table.rb +2 -2
- data/lib/rails-data-explorer/chart/histogram_categorical.rb +10 -4
- data/lib/rails-data-explorer/chart/histogram_quantitative.rb +12 -5
- data/lib/rails-data-explorer/chart/histogram_temporal.rb +6 -0
- data/lib/rails-data-explorer/chart/parallel_coordinates.rb +4 -4
- data/lib/rails-data-explorer/chart/parallel_set.rb +3 -3
- data/lib/rails-data-explorer/chart/stacked_bar_chart_categorical_percent.rb +3 -3
- data/lib/rails-data-explorer/constants.rb +5 -0
- data/lib/rails-data-explorer/data_series.rb +20 -14
- data/lib/rails-data-explorer/data_set.rb +4 -0
- data/lib/rails-data-explorer/data_type/categorical.rb +84 -72
- data/lib/rails-data-explorer/data_type/quantitative.rb +46 -48
- data/lib/rails-data-explorer/data_type/quantitative/temporal.rb +23 -17
- data/lib/rails-data-explorer/exploration.rb +20 -4
- data/lib/rails-data-explorer/statistics/rng_category.rb +1 -1
- data/lib/rails-data-explorer/utils/data_binner.rb +20 -10
- data/lib/rails-data-explorer/utils/data_quantizer.rb +6 -6
- data/lib/rails-data-explorer/utils/rde_table.rb +62 -0
- data/lib/rails_data_explorer.rb +35 -20
- data/rails-data-explorer.gemspec +1 -1
- data/spec/rails-data-explorer/exploration_spec.rb +4 -4
- data/spec/rails-data-explorer/utils/data_binner_spec.rb +3 -3
- 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
|
-
:
|
85
|
-
:
|
86
|
-
:
|
87
|
-
:
|
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::
|
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
|
-
:
|
32
|
-
:
|
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::
|
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::
|
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 = { :
|
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
|
@@ -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, :
|
9
|
-
delegate :available_chart_roles, :
|
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.
|
15
|
-
|
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.
|
21
|
-
|
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
|
-
|
70
|
-
|
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
|
79
|
+
@uniq_vals ||= values.uniq
|
75
80
|
end
|
76
81
|
|
77
82
|
def uniq_vals_count
|
78
|
-
@uniq_vals_count
|
83
|
+
@uniq_vals_count ||= uniq_vals.length
|
79
84
|
end
|
80
85
|
|
81
86
|
def min_val
|
82
|
-
@min_val
|
87
|
+
@min_val ||= values.compact.min
|
83
88
|
end
|
84
89
|
|
85
90
|
def max_val
|
86
|
-
@max_val
|
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.
|
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.
|
105
|
+
dynamic_range > self.class.large_dynamic_range_threshold
|
100
106
|
end
|
101
107
|
|
102
108
|
def label_sorter(label_val_key, value_sorter)
|
@@ -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
|
-
:
|
9
|
-
:
|
10
|
-
:
|
11
|
-
:
|
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
|
-
:
|
66
|
-
:
|
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 << { :
|
70
|
-
m << { :
|
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
|
73
|
-
|
74
|
-
|
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
|
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.
|
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 =
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
:
|
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 <<
|
101
|
-
:
|
102
|
-
|
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
|
-
|
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 <<
|
109
|
-
:
|
110
|
-
|
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
|
-
|
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 =
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
:
|
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 <<
|
135
|
-
:
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
:
|
142
|
-
:
|
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
|
-
|
146
|
-
:
|
147
|
-
|
148
|
-
:
|
149
|
-
:
|
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 =~
|
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
|
-
|
179
|
+
number_and_full_string_extractor = lambda { |val|
|
170
180
|
str = label_val_key ? val[label_val_key] : val
|
171
|
-
number = str.gsub(/^[^\d]*/, '')
|
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
|
-
|
176
|
-
|
177
|
-
|
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
|
-
:
|
50
|
+
integer: Proc.new { |v|
|
51
51
|
v.nil? ? 'Null' : number_with_delimiter(v.round)
|
52
52
|
},
|
53
|
-
:
|
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
|
-
:
|
63
|
-
:
|
64
|
-
:
|
65
|
-
:
|
62
|
+
precision: 3,
|
63
|
+
significant: true,
|
64
|
+
strip_insignificant_zeros: true,
|
65
|
+
delimiter: ','
|
66
66
|
)
|
67
67
|
end
|
68
68
|
},
|
69
|
-
:
|
69
|
+
pass_through: Proc.new { |v| (v.nil? || Float::NAN == v) ? 'NaN' : v },
|
70
70
|
}
|
71
71
|
[
|
72
|
-
{ :
|
73
|
-
{ :
|
74
|
-
{ :
|
75
|
-
{ :
|
76
|
-
{ :
|
77
|
-
{ :
|
78
|
-
{ :
|
79
|
-
{ :
|
80
|
-
{ :
|
81
|
-
{ :
|
82
|
-
{ :
|
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
|
-
{ :
|
85
|
-
{ :
|
86
|
-
{ :
|
87
|
-
{ :
|
88
|
-
{ :
|
89
|
-
{ :
|
90
|
-
{ :
|
91
|
-
{ :
|
92
|
-
{ :
|
93
|
-
{ :
|
94
|
-
{ :
|
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
|
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 =
|
102
|
-
:rows => []
|
103
|
-
)
|
101
|
+
table = Utils::RdeTable.new([])
|
104
102
|
[1,2].each do |table_row|
|
105
|
-
table.rows <<
|
106
|
-
:
|
107
|
-
:
|
108
|
-
|
109
|
-
|
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 <<
|
113
|
-
:
|
114
|
-
:
|
115
|
-
|
116
|
-
|
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
|
|