spout 0.10.2 → 0.11.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|