spout 0.10.2 → 0.11.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +36 -0
- data/README.md +3 -30
- data/lib/spout/commands/coverage.rb +2 -1
- data/lib/spout/commands/deploy.rb +82 -77
- data/lib/spout/commands/exporter.rb +2 -3
- data/lib/spout/commands/graphs.rb +68 -67
- data/lib/spout/commands/help.rb +155 -0
- data/lib/spout/helpers/array_statistics.rb +36 -30
- data/lib/spout/helpers/chart_types.rb +2 -2
- data/lib/spout/helpers/config_reader.rb +5 -5
- data/lib/spout/helpers/json_request.rb +1 -2
- data/lib/spout/helpers/json_request_generic.rb +87 -0
- data/lib/spout/helpers/quietly.rb +2 -4
- data/lib/spout/helpers/semantic.rb +7 -11
- data/lib/spout/helpers/send_file.rb +23 -25
- data/lib/spout/helpers/subject_loader.rb +41 -32
- data/lib/spout/helpers/table_formatting.rb +7 -6
- data/lib/spout/models/bucket.rb +5 -4
- data/lib/spout/models/coverage_result.rb +1 -1
- data/lib/spout/models/dictionary.rb +3 -1
- data/lib/spout/models/domain.rb +7 -6
- data/lib/spout/models/empty.rb +17 -0
- data/lib/spout/models/form.rb +8 -5
- data/lib/spout/models/graphables/default.rb +41 -18
- data/lib/spout/models/graphables/histogram.rb +6 -7
- data/lib/spout/models/graphables.rb +3 -5
- data/lib/spout/models/option.rb +6 -2
- data/lib/spout/models/outlier_result.rb +3 -3
- data/lib/spout/models/record.rb +21 -3
- data/lib/spout/models/subject.rb +4 -7
- data/lib/spout/models/tables/choices_vs_choices.rb +29 -17
- data/lib/spout/models/tables/choices_vs_numeric.rb +19 -12
- data/lib/spout/models/tables/default.rb +19 -32
- data/lib/spout/models/tables/numeric_vs_choices.rb +9 -13
- data/lib/spout/models/tables/numeric_vs_numeric.rb +9 -11
- data/lib/spout/models/tables.rb +4 -6
- data/lib/spout/models/variable.rb +51 -13
- data/lib/spout/tasks/engine.rake +1 -1
- data/lib/spout/templates/ruby-version +1 -1
- data/lib/spout/templates/travis.yml +1 -1
- data/lib/spout/tests/domain_format.rb +2 -2
- data/lib/spout/tests/domain_name_format.rb +15 -0
- data/lib/spout/tests/form_name_format.rb +14 -0
- data/lib/spout/tests/variable_name_format.rb +14 -0
- data/lib/spout/tests.rb +18 -13
- data/lib/spout/version.rb +3 -3
- data/lib/spout/views/index.html.erb +2 -2
- data/lib/spout/views/outliers.html.erb +1 -1
- data/lib/spout.rb +13 -58
- data/spout.gemspec +14 -15
- metadata +25 -25
- data/lib/spout/commands/images.rb +0 -199
- data/lib/spout/support/javascripts/data.js +0 -17
- data/lib/spout/support/javascripts/highcharts-convert.js +0 -583
- data/lib/spout/support/javascripts/highcharts-more.js +0 -50
- data/lib/spout/support/javascripts/highstock.js +0 -353
- data/lib/spout/support/javascripts/jquery.1.9.1.min.js +0 -5
@@ -12,28 +12,26 @@ require 'spout/models/graphables'
|
|
12
12
|
require 'spout/models/tables'
|
13
13
|
require 'spout/helpers/config_reader'
|
14
14
|
require 'spout/helpers/send_file'
|
15
|
+
require 'spout/helpers/json_request_generic'
|
15
16
|
require 'spout/version'
|
16
17
|
|
17
18
|
module Spout
|
18
19
|
module Commands
|
19
20
|
class Graphs
|
20
|
-
def initialize(
|
21
|
+
def initialize(argv, standard_version, deploy_mode = false, url = '', slug = '', token = '', webserver_name = '', subjects = nil)
|
21
22
|
@deploy_mode = deploy_mode
|
22
23
|
@url = url
|
23
24
|
@standard_version = standard_version
|
24
25
|
@slug = slug
|
25
26
|
@token = token
|
26
27
|
@webserver_name = webserver_name
|
27
|
-
|
28
|
-
argv = variables
|
29
|
-
|
30
|
-
@clean = (argv.delete('--no-resume') != nil or argv.delete('--clean'))
|
28
|
+
@clean = !(argv.delete('--no-resume').nil? && argv.delete('--clean').nil?)
|
31
29
|
|
32
30
|
@config = Spout::Helpers::ConfigReader.new
|
33
31
|
|
34
32
|
@stratification_variable = Spout::Models::Variable.find_by_id @config.visit
|
35
33
|
|
36
|
-
if @stratification_variable
|
34
|
+
if @stratification_variable.nil?
|
37
35
|
if @config.visit == ''
|
38
36
|
puts "The visit variable in .spout.yml can't be blank."
|
39
37
|
else
|
@@ -42,56 +40,51 @@ module Spout
|
|
42
40
|
return self
|
43
41
|
end
|
44
42
|
|
45
|
-
missing_variables = @config.charts.select{|c| Spout::Models::Variable.find_by_id(c['chart'])
|
43
|
+
missing_variables = @config.charts.select { |c| Spout::Models::Variable.find_by_id(c['chart']).nil? }
|
46
44
|
if missing_variables.count > 0
|
47
45
|
puts "Could not find the following chart variable#{'s' unless missing_variables.size == 1}: #{missing_variables.join(', ')}"
|
48
46
|
return self
|
49
47
|
end
|
50
48
|
|
51
|
-
|
52
|
-
|
49
|
+
rows_arg = argv.find { |arg| /^--rows=(\d*)/ =~ arg }
|
50
|
+
argv.delete(rows_arg)
|
51
|
+
@number_of_rows = rows_arg.gsub(/--rows=/, '').to_i if rows_arg
|
53
52
|
|
54
|
-
|
55
|
-
@number_of_rows = match_data[1].to_i
|
56
|
-
argv_string.gsub!(match_data[0], '')
|
57
|
-
end
|
53
|
+
@valid_ids = argv.collect { |s| s.to_s.downcase }.compact.reject { |s| s == '' }
|
58
54
|
|
59
|
-
@
|
60
|
-
|
61
|
-
@chart_variables = @config.charts.unshift( { "chart" => @config.visit, "title" => 'Histogram' } )
|
55
|
+
@chart_variables = @config.charts.unshift('chart' => @config.visit, 'title' => 'Histogram')
|
62
56
|
|
63
57
|
@dictionary_root = Dir.pwd
|
64
58
|
@variable_files = Dir.glob(File.join(@dictionary_root, 'variables', '**', '*.json'))
|
65
59
|
|
66
60
|
t = Time.now
|
67
|
-
@graphs_folder = File.join(
|
61
|
+
@graphs_folder = File.join('graphs', @standard_version)
|
68
62
|
FileUtils.mkpath @graphs_folder
|
69
63
|
|
70
|
-
|
71
64
|
@subjects = if subjects
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
65
|
+
subjects
|
66
|
+
else
|
67
|
+
@subject_loader = Spout::Helpers::SubjectLoader.new(@variable_files, @valid_ids, @standard_version, @number_of_rows, @config.visit)
|
68
|
+
@subject_loader.load_subjects_from_csvs!
|
69
|
+
@subjects = @subject_loader.subjects
|
70
|
+
end
|
78
71
|
|
79
72
|
load_current_progress
|
80
73
|
|
81
74
|
compute_tables_and_charts
|
82
75
|
|
83
|
-
puts "Took #{Time.now - t} seconds." if @subjects.size > 0
|
76
|
+
puts "Took #{Time.now - t} seconds." if @subjects.size > 0 && !@deploy_mode
|
84
77
|
end
|
85
78
|
|
86
79
|
def load_current_progress
|
87
|
-
@progress_file = File.join(@graphs_folder,
|
80
|
+
@progress_file = File.join(@graphs_folder, '.progress.json')
|
88
81
|
@progress = JSON.parse(File.read(@progress_file)) rescue @progress = {}
|
89
|
-
@progress = {} if !@progress.
|
82
|
+
@progress = {} if !@progress.is_a?(Hash) || @clean || @progress['SPOUT_VERSION'] != Spout::VERSION::STRING
|
90
83
|
@progress['SPOUT_VERSION'] = Spout::VERSION::STRING
|
91
84
|
end
|
92
85
|
|
93
86
|
def save_current_progress
|
94
|
-
File.open(@progress_file,
|
87
|
+
File.open(@progress_file, 'w') do |f|
|
95
88
|
f.write(JSON.pretty_generate(@progress) + "\n")
|
96
89
|
end
|
97
90
|
end
|
@@ -104,30 +97,25 @@ module Spout
|
|
104
97
|
end
|
105
98
|
end
|
106
99
|
|
107
|
-
def send_to_server(chart_json_file)
|
108
|
-
response = Spout::Helpers::SendFile.post("#{@url}/datasets/#{@slug}/upload_graph.json", chart_json_file, @standard_version, @token)
|
109
|
-
end
|
110
|
-
|
111
|
-
|
112
100
|
def iterate_through_variables
|
113
101
|
variable_files_count = @variable_files.count
|
114
102
|
@variable_files.each_with_index do |variable_file, file_index|
|
115
103
|
variable = Spout::Models::Variable.new(variable_file, @dictionary_root)
|
116
104
|
|
117
105
|
next unless variable.errors.size == 0
|
118
|
-
next unless @valid_ids.include?(variable.id)
|
119
|
-
next unless
|
106
|
+
next unless @valid_ids.include?(variable.id) || @valid_ids.size == 0
|
107
|
+
next unless %w(numeric integer choices).include?(variable.type)
|
120
108
|
next unless Spout::Models::Subject.method_defined?(variable.id)
|
121
109
|
|
122
110
|
if @deploy_mode
|
123
111
|
print "\r Graph Generation: " + "#{"% 3d" % ((file_index+1)*100/variable_files_count)}% Uploaded".colorize(:white)
|
124
112
|
else
|
125
|
-
puts "#{file_index+1} of #{variable_files_count}: #{variable.folder}#{variable.id}"
|
113
|
+
puts "#{file_index + 1} of #{variable_files_count}: #{variable.folder}#{variable.id}"
|
126
114
|
end
|
127
115
|
|
128
116
|
@progress[variable.id] ||= {}
|
129
117
|
@progress[variable.id]['uploaded'] ||= []
|
130
|
-
next if (
|
118
|
+
next if (!@deploy_mode && @progress[variable.id]['generated'] == true) || (@deploy_mode && @progress[variable.id]['uploaded'].include?(@webserver_name))
|
131
119
|
|
132
120
|
stats = {
|
133
121
|
charts: {},
|
@@ -135,51 +123,64 @@ module Spout
|
|
135
123
|
}
|
136
124
|
|
137
125
|
@chart_variables.each do |chart_type_hash|
|
138
|
-
chart_type = chart_type_hash[
|
139
|
-
chart_title = chart_type_hash[
|
126
|
+
chart_type = chart_type_hash['chart']
|
127
|
+
chart_title = chart_type_hash['title'].downcase.gsub(' ', '-')
|
140
128
|
chart_variable = Spout::Models::Variable.find_by_id(chart_type)
|
141
129
|
|
130
|
+
filtered_subjects = @subjects.reject { |s| s.send(chart_type).nil? || s.send(variable.id).nil? }
|
131
|
+
|
132
|
+
next if filtered_subjects.collect(&variable.id.to_sym).compact_empty.count == 0
|
142
133
|
if chart_type == @config.visit
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
table = Spout::Models::Tables.for(variable, chart_variable, filtered_subjects, nil)
|
148
|
-
stats[:tables][chart_title] = table.to_hash
|
149
|
-
end
|
134
|
+
graph = Spout::Models::Graphables.for(variable, chart_variable, nil, filtered_subjects)
|
135
|
+
stats[:charts][chart_title] = graph.to_hash
|
136
|
+
table = Spout::Models::Tables.for(variable, chart_variable, filtered_subjects, nil, totals: false)
|
137
|
+
stats[:tables][chart_title] = table.to_hash
|
150
138
|
else
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
unknown_subjects = visit_subjects.select{ |s| s.send(variable.id) == nil }
|
158
|
-
table = Spout::Models::Tables.for(variable, chart_variable, visit_subjects, option.display_name)
|
159
|
-
(visit_subjects.count > 0 && visit_subjects.count != unknown_subjects.count) ? table.to_hash : nil
|
160
|
-
end.compact
|
161
|
-
end
|
139
|
+
graph = Spout::Models::Graphables.for(variable, chart_variable, @stratification_variable, filtered_subjects)
|
140
|
+
stats[:charts][chart_title] = graph.to_hash
|
141
|
+
stats[:tables][chart_title] = @stratification_variable.domain.options.collect do |option|
|
142
|
+
visit_subjects = filtered_subjects.select { |s| s._visit == option.value }
|
143
|
+
Spout::Models::Tables.for(variable, chart_variable, visit_subjects, option.display_name).to_hash
|
144
|
+
end.compact
|
162
145
|
end
|
163
146
|
end
|
164
147
|
|
165
148
|
chart_json_file = File.join(@graphs_folder, "#{variable.id}.json")
|
166
|
-
File.open(chart_json_file, 'w') { |file| file.write(
|
167
|
-
|
149
|
+
File.open(chart_json_file, 'w') { |file| file.write(JSON.pretty_generate(stats) + "\n") }
|
168
150
|
@progress[variable.id]['generated'] = true
|
169
151
|
|
170
|
-
if @deploy_mode
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
152
|
+
if @deploy_mode && !@progress[variable.id]['uploaded'].include?(@webserver_name)
|
153
|
+
values = @subjects.collect(&variable.id.to_sym).compact_empty
|
154
|
+
variable.n = values.n
|
155
|
+
variable.unknown = values.unknown
|
156
|
+
variable.total = values.count
|
157
|
+
if %w(numeric integer).include?(variable.type)
|
158
|
+
variable.mean = values.mean
|
159
|
+
variable.stddev = values.standard_deviation
|
160
|
+
variable.median = values.median
|
161
|
+
variable.min = values.min
|
162
|
+
variable.max = values.max
|
176
163
|
end
|
164
|
+
send_variable_params_to_server(variable, stats)
|
177
165
|
end
|
178
166
|
end
|
179
|
-
|
180
167
|
end
|
181
168
|
|
182
|
-
|
169
|
+
def send_variable_params_to_server(variable, stats)
|
170
|
+
params = { auth_token: @token, version: @standard_version,
|
171
|
+
dataset: @slug, variable: variable.deploy_params,
|
172
|
+
domain: (variable.domain ? variable.domain.deploy_params : nil),
|
173
|
+
forms: variable.forms.collect(&:deploy_params) }
|
174
|
+
params[:variable][:spout_stats] = stats.to_json
|
175
|
+
(response, status) = Spout::Helpers::JsonRequestGeneric.post("#{@url}/api/v1/variables/create_or_update.json", params)
|
176
|
+
if response.is_a?(Hash) && status.is_a?(Net::HTTPSuccess)
|
177
|
+
# puts "response: #{response}".colorize(:blue)
|
178
|
+
@progress[variable.id]['uploaded'] << @webserver_name
|
179
|
+
else
|
180
|
+
puts "\nUPLOAD FAILED: ".colorize(:red) + variable.id
|
181
|
+
puts "- Error: #{response.inspect}"
|
182
|
+
end
|
183
|
+
end
|
183
184
|
end
|
184
185
|
end
|
185
186
|
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
|
3
|
+
module Spout
|
4
|
+
module Commands
|
5
|
+
class Help
|
6
|
+
def initialize(argv)
|
7
|
+
send((Spout::COMMANDS[argv[1].to_s.scan(/\w/).first] || :help))
|
8
|
+
end
|
9
|
+
|
10
|
+
def help
|
11
|
+
puts <<-EOT
|
12
|
+
Usage: spout COMMAND [ARGS]
|
13
|
+
|
14
|
+
The most common spout commands are:
|
15
|
+
[n]ew Create a new Spout dictionary.
|
16
|
+
`spout new <project_name>` creates a new
|
17
|
+
data dictionary in `./<project_name>`
|
18
|
+
[t]est Run tests and show failing tests
|
19
|
+
[i]mport Import a CSV file into the JSON dictionary
|
20
|
+
[e]xport [1.0.0] Export the JSON dictionary to CSV format
|
21
|
+
[c]overage Coverage report, requires dataset CSVs
|
22
|
+
in `<project_name>/csvs/<version>`
|
23
|
+
[o]utliers Outlier report, requires dataset CSVs
|
24
|
+
in `<project_name>/csvs/<version>`
|
25
|
+
[g]raphs Generates JSON graphs for each variable
|
26
|
+
in a dataset and places them
|
27
|
+
in `<project_name>/graphs/<version>/`
|
28
|
+
[d]eploy NAME Push dataset and data dictionary to a
|
29
|
+
webserver specified in `.spout.yml`
|
30
|
+
[v]ersion Returns the version of Spout
|
31
|
+
|
32
|
+
Commands can be referenced by the first letter:
|
33
|
+
Ex: `spout t`, for test
|
34
|
+
|
35
|
+
You can also get more in depth help by typing:
|
36
|
+
Ex: `spout help deploy`, to list all deploy flags
|
37
|
+
|
38
|
+
EOT
|
39
|
+
end
|
40
|
+
|
41
|
+
def new_project
|
42
|
+
puts <<-EOT
|
43
|
+
Usage: spout new <project_name>
|
44
|
+
|
45
|
+
More information here:
|
46
|
+
|
47
|
+
https://github.com/sleepepi/spout#generate-a-new-repository-from-an-existing-csv-file
|
48
|
+
|
49
|
+
EOT
|
50
|
+
end
|
51
|
+
|
52
|
+
def version
|
53
|
+
puts <<-EOT
|
54
|
+
Usage: spout version
|
55
|
+
|
56
|
+
EOT
|
57
|
+
end
|
58
|
+
|
59
|
+
def test
|
60
|
+
puts <<-EOT
|
61
|
+
Usage: spout test
|
62
|
+
|
63
|
+
EOT
|
64
|
+
end
|
65
|
+
|
66
|
+
def importer
|
67
|
+
puts <<-EOT
|
68
|
+
Usage: spout import <csv_file>
|
69
|
+
|
70
|
+
Optional Flags:
|
71
|
+
--domains Specify to import CSV of domains
|
72
|
+
|
73
|
+
More information:
|
74
|
+
https://github.com/sleepepi/spout#generate-a-new-repository-from-an-existing-csv-file
|
75
|
+
https://github.com/sleepepi/spout#importing-domains-from-an-existing-csv-file
|
76
|
+
EOT
|
77
|
+
end
|
78
|
+
|
79
|
+
def exporter
|
80
|
+
puts <<-EOT
|
81
|
+
Usage: spout export
|
82
|
+
|
83
|
+
Exports data dictionary to CSV format.
|
84
|
+
|
85
|
+
More information here:
|
86
|
+
|
87
|
+
https://github.com/sleepepi/spout#create-a-csv-data-dictionary-from-your-json-repository
|
88
|
+
|
89
|
+
EOT
|
90
|
+
end
|
91
|
+
|
92
|
+
def coverage_report
|
93
|
+
puts <<-EOT
|
94
|
+
Usage: spout coverage
|
95
|
+
|
96
|
+
Generates `coverage/index.html` that can be viewed in browser.
|
97
|
+
|
98
|
+
EOT
|
99
|
+
end
|
100
|
+
|
101
|
+
def generate_charts_and_tables
|
102
|
+
puts <<-EOT
|
103
|
+
Usage: spout graphs
|
104
|
+
|
105
|
+
Optional Flags:
|
106
|
+
--clean Regenerate all graphs (default is to resume
|
107
|
+
where command last left off)
|
108
|
+
--rows=N Limit the number of rows read from CSVs to a
|
109
|
+
maximum of N rows
|
110
|
+
<variable> Only generate graphs for the specified variable(s)
|
111
|
+
Ex: spout graphs age gender
|
112
|
+
|
113
|
+
EOT
|
114
|
+
end
|
115
|
+
|
116
|
+
def outliers_report
|
117
|
+
puts <<-EOT
|
118
|
+
Usage: spout outliers
|
119
|
+
|
120
|
+
Generates `coverage/outliers.html` that can be viewed in browser.
|
121
|
+
|
122
|
+
More information here:
|
123
|
+
|
124
|
+
https://github.com/sleepepi/spout#identify-outliers-in-your-dataset
|
125
|
+
|
126
|
+
EOT
|
127
|
+
end
|
128
|
+
|
129
|
+
def deploy
|
130
|
+
puts <<-EOT
|
131
|
+
Usage: spout deploy NAME
|
132
|
+
|
133
|
+
NAME is the name of the webserver listed in `.spout.yml` file
|
134
|
+
Optional Flags:
|
135
|
+
--clean Regenerate all variables (default is to resume
|
136
|
+
where command last left off)
|
137
|
+
--rows=N Limit the number of rows read from CSVs to a
|
138
|
+
maximum of N rows
|
139
|
+
<variable> Only deploy specified variable(s)
|
140
|
+
Ex: spout deploy production age gender
|
141
|
+
--skip-checks Skips Spout checks
|
142
|
+
--skip-graphs Skip generation of variable graphs
|
143
|
+
--skip-csvs Skip upload of Data Dictionary and Dataset CSVs
|
144
|
+
--token=TOKEN Provide token via command-line for automated
|
145
|
+
processes
|
146
|
+
|
147
|
+
More information here:
|
148
|
+
|
149
|
+
https://github.com/sleepepi/spout#deploy-your-data-dictionary-to-a-staging-or-production-webserver
|
150
|
+
|
151
|
+
EOT
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -1,41 +1,46 @@
|
|
1
|
+
# Extensions to the Array class to calculate quartiles, outliers, and statistics
|
1
2
|
class Array
|
3
|
+
def compact_empty
|
4
|
+
compact.reject { |a| a.is_a?(Spout::Models::Empty) }
|
5
|
+
end
|
6
|
+
|
2
7
|
def n
|
3
|
-
|
8
|
+
compact_empty.count
|
4
9
|
end
|
5
10
|
|
6
11
|
def mean
|
7
|
-
array =
|
12
|
+
array = compact_empty
|
8
13
|
return nil if array.size == 0
|
9
14
|
array.inject(:+).to_f / array.size
|
10
15
|
end
|
11
16
|
|
12
17
|
def sample_variance
|
13
|
-
array =
|
18
|
+
array = compact_empty
|
14
19
|
m = array.mean
|
15
|
-
sum = array.inject(0){|
|
20
|
+
sum = array.inject(0) { |a, e| a + (e - m)**2 }
|
16
21
|
sum / (array.length - 1).to_f
|
17
22
|
end
|
18
23
|
|
19
24
|
def standard_deviation
|
20
|
-
array =
|
25
|
+
array = compact_empty
|
21
26
|
return nil if array.size < 2
|
22
|
-
|
27
|
+
Math.sqrt(array.sample_variance)
|
23
28
|
end
|
24
29
|
|
25
30
|
def median
|
26
|
-
array =
|
31
|
+
array = compact_empty.sort
|
27
32
|
return nil if array.size == 0
|
28
33
|
len = array.size
|
29
|
-
len
|
34
|
+
len.odd? ? array[len / 2] : (array[len / 2 - 1] + array[len / 2]).to_f / 2
|
30
35
|
end
|
31
36
|
|
32
37
|
def unknown
|
33
|
-
|
38
|
+
count { |a| a.is_a?(Spout::Models::Empty) }
|
34
39
|
end
|
35
40
|
|
36
41
|
def quartile_sizes
|
37
|
-
quartile_size =
|
38
|
-
quartile_fraction =
|
42
|
+
quartile_size = count / 4
|
43
|
+
quartile_fraction = count % 4
|
39
44
|
|
40
45
|
quartile_sizes = [quartile_size] * 4
|
41
46
|
(0..quartile_fraction - 1).to_a.each do |index|
|
@@ -46,75 +51,76 @@ class Array
|
|
46
51
|
end
|
47
52
|
|
48
53
|
def quartile_one
|
49
|
-
self[0..(
|
54
|
+
self[0..(quartile_sizes[0] - 1)]
|
50
55
|
end
|
51
56
|
|
52
57
|
def quartile_two
|
53
|
-
sizes =
|
58
|
+
sizes = quartile_sizes
|
54
59
|
start = sizes[0]
|
55
60
|
stop = start + sizes[1] - 1
|
56
61
|
self[start..stop]
|
57
62
|
end
|
58
63
|
|
59
64
|
def quartile_three
|
60
|
-
sizes =
|
65
|
+
sizes = quartile_sizes
|
61
66
|
start = sizes[0] + sizes[1]
|
62
67
|
stop = start + sizes[2] - 1
|
63
68
|
self[start..stop]
|
64
69
|
end
|
65
70
|
|
66
71
|
def quartile_four
|
67
|
-
sizes =
|
72
|
+
sizes = quartile_sizes
|
68
73
|
start = sizes[0] + sizes[1] + sizes[2]
|
69
74
|
stop = start + sizes[3] - 1
|
70
75
|
self[start..stop]
|
71
76
|
end
|
72
77
|
|
73
78
|
def compact_min
|
74
|
-
|
79
|
+
compact_empty.min
|
75
80
|
end
|
76
81
|
|
77
82
|
def compact_max
|
78
|
-
|
83
|
+
compact_empty.max
|
79
84
|
end
|
80
85
|
|
81
86
|
def outliers
|
82
|
-
array =
|
87
|
+
array = compact_empty.sort.select { |v| v.is_a?(Numeric) }
|
83
88
|
q1 = (array.quartile_one + array.quartile_two).median
|
84
89
|
q3 = (array.quartile_three + array.quartile_four).median
|
85
|
-
return [] if q1
|
90
|
+
return [] if q1.nil? || q3.nil?
|
86
91
|
iq_range = q3 - q1
|
87
92
|
inner_fence_lower = q1 - iq_range * 1.5
|
88
93
|
inner_fence_upper = q3 + iq_range * 1.5
|
89
|
-
|
90
|
-
outer_fence_upper = q3 + iq_range * 3
|
91
|
-
array.select{ |v| v > inner_fence_upper or v < inner_fence_lower }
|
94
|
+
array.select { |v| v > inner_fence_upper || v < inner_fence_lower }
|
92
95
|
end
|
93
96
|
|
94
97
|
def major_outliers
|
95
|
-
array =
|
98
|
+
array = compact_empty.sort.select { |v| v.is_a?(Numeric) }
|
96
99
|
q1 = (array.quartile_one + array.quartile_two).median
|
97
100
|
q3 = (array.quartile_three + array.quartile_four).median
|
98
|
-
return [] if q1
|
101
|
+
return [] if q1.nil? || q3.nil?
|
99
102
|
iq_range = q3 - q1
|
100
|
-
inner_fence_lower = q1 - iq_range * 1.5
|
101
|
-
inner_fence_upper = q3 + iq_range * 1.5
|
102
103
|
outer_fence_lower = q1 - iq_range * 3
|
103
104
|
outer_fence_upper = q3 + iq_range * 3
|
104
|
-
array.select{ |v| v > outer_fence_upper
|
105
|
+
array.select { |v| v > outer_fence_upper || v < outer_fence_lower }
|
105
106
|
end
|
106
107
|
|
107
108
|
def minor_outliers
|
108
|
-
|
109
|
+
outliers - major_outliers
|
109
110
|
end
|
110
|
-
|
111
111
|
end
|
112
112
|
|
113
113
|
module Spout
|
114
114
|
module Helpers
|
115
115
|
class ArrayStatistics
|
116
116
|
def self.calculations
|
117
|
-
[[
|
117
|
+
[['N', :n, :count],
|
118
|
+
['Mean', :mean, :decimal],
|
119
|
+
['StdDev', :standard_deviation, :decimal, '± %s'],
|
120
|
+
['Median', :median, :decimal],
|
121
|
+
['Min', :compact_min, :decimal],
|
122
|
+
['Max', :compact_max, :decimal],
|
123
|
+
['Unknown', :unknown, :count]]
|
118
124
|
end
|
119
125
|
end
|
120
126
|
end
|
@@ -5,7 +5,7 @@ module Spout
|
|
5
5
|
module Helpers
|
6
6
|
class ChartTypes
|
7
7
|
def self.get_bucket(buckets, value)
|
8
|
-
return nil if buckets.size == 0 or not value.
|
8
|
+
return nil if buckets.size == 0 or not value.is_a?(Numeric)
|
9
9
|
buckets.each do |b|
|
10
10
|
return "#{b[0]} to #{b[1]}" if value >= b[0] and value <= b[1]
|
11
11
|
end
|
@@ -17,7 +17,7 @@ module Spout
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def self.continuous_buckets(values)
|
20
|
-
values.select!{|v| v.
|
20
|
+
values.select!{|v| v.is_a? Numeric}
|
21
21
|
return [] if values.count == 0
|
22
22
|
minimum_bucket = values.min
|
23
23
|
maximum_bucket = values.max
|
@@ -17,18 +17,18 @@ module Spout
|
|
17
17
|
def parse_yaml_file
|
18
18
|
spout_config = YAML.load_file('.spout.yml')
|
19
19
|
|
20
|
-
if spout_config.
|
20
|
+
if spout_config.is_a?(Hash)
|
21
21
|
@slug = spout_config['slug'].to_s.strip
|
22
22
|
@visit = spout_config['visit'].to_s.strip
|
23
23
|
|
24
|
-
@charts = if spout_config['charts'].
|
25
|
-
spout_config['charts'].select{|c| c.
|
24
|
+
@charts = if spout_config['charts'].is_a?(Array)
|
25
|
+
spout_config['charts'].select{|c| c.is_a?(Hash)}
|
26
26
|
else
|
27
27
|
[]
|
28
28
|
end
|
29
29
|
|
30
|
-
@webservers = if spout_config['webservers'].
|
31
|
-
spout_config['webservers'].select{|c| c.
|
30
|
+
@webservers = if spout_config['webservers'].is_a?(Array)
|
31
|
+
spout_config['webservers'].select{|c| c.is_a?(Hash)}
|
32
32
|
else
|
33
33
|
[]
|
34
34
|
end
|