spout 0.10.0.beta9 → 0.10.0.beta10
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 +5 -0
- data/lib/spout/commands/graphs.rb +39 -34
- data/lib/spout/commands/images.rb +11 -6
- data/lib/spout/helpers/chart_types.rb +0 -383
- data/lib/spout/models/bucket.rb +22 -0
- data/lib/spout/models/domain.rb +3 -1
- data/lib/spout/models/form.rb +3 -2
- data/lib/spout/models/graphables/choices_vs_choices.rb +33 -0
- data/lib/spout/models/graphables/choices_vs_numeric.rb +56 -0
- data/lib/spout/models/graphables/default.rb +115 -0
- data/lib/spout/models/graphables/histogram.rb +53 -0
- data/lib/spout/models/graphables/numeric_vs_choices.rb +47 -0
- data/lib/spout/models/graphables/numeric_vs_numeric.rb +55 -0
- data/lib/spout/models/graphables.rb +46 -0
- data/lib/spout/models/option.rb +2 -2
- data/lib/spout/models/record.rb +19 -0
- data/lib/spout/models/tables/choices_vs_choices.rb +52 -0
- data/lib/spout/models/tables/choices_vs_numeric.rb +49 -0
- data/lib/spout/models/tables/default.rb +86 -0
- data/lib/spout/models/tables/numeric_vs_choices.rb +47 -0
- data/lib/spout/models/tables/numeric_vs_numeric.rb +55 -0
- data/lib/spout/models/tables.rb +41 -0
- data/lib/spout/models/variable.rb +10 -2
- data/lib/spout/templates/ruby-version +1 -1
- data/lib/spout/templates/travis.yml +1 -1
- data/lib/spout/version.rb +1 -1
- metadata +18 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb6c2e9b16149cba7a217e1e5dfec5764ac33e8a
|
4
|
+
data.tar.gz: e1a7fbd00c9c5678089d8d6266e791121fc2d468
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b93e28aa28e19ef8d3bec0fbc5107e8bae5b835eaca307bac36dec4c151ee3c7c7e27ba0ff8f050d81d1538256490b8cb786936b4d61acb1c71980da7860b662
|
7
|
+
data.tar.gz: 9e7d49b85dc352e0289de396ef75bfdebec45064a05ef992c2efe872519c61c901cc4811f60ea48c123dfa4b67c86aa634270e54c8f7a4dd1bec08f6281d143c
|
data/CHANGELOG.md
CHANGED
@@ -15,6 +15,11 @@
|
|
15
15
|
- **Testing Changes**
|
16
16
|
- Tests now include checks to assure that variable display_name fields don't exceed 255 length requirement
|
17
17
|
- `include Spout::Tests::VariableDisplayNameLength`
|
18
|
+
- **Gem Changes**
|
19
|
+
- Use of Ruby 2.1.4 is now recommended
|
20
|
+
|
21
|
+
### Refactoring
|
22
|
+
- Removing ChartTypes class in favor of a more modular Graph and Table class
|
18
23
|
|
19
24
|
## 0.9.1 (October 14, 2014)
|
20
25
|
|
@@ -7,6 +7,9 @@ require 'colorize'
|
|
7
7
|
|
8
8
|
require 'spout/helpers/subject_loader'
|
9
9
|
require 'spout/helpers/chart_types'
|
10
|
+
require 'spout/models/variable'
|
11
|
+
require 'spout/models/graphables'
|
12
|
+
require 'spout/models/tables'
|
10
13
|
require 'spout/helpers/config_reader'
|
11
14
|
require 'spout/helpers/send_file'
|
12
15
|
require 'spout/version'
|
@@ -27,7 +30,9 @@ module Spout
|
|
27
30
|
|
28
31
|
@config = Spout::Helpers::ConfigReader.new
|
29
32
|
|
30
|
-
|
33
|
+
@stratification_variable = Spout::Models::Variable.find_by_id @config.visit
|
34
|
+
|
35
|
+
if @stratification_variable == nil
|
31
36
|
if @config.visit == ''
|
32
37
|
puts "The visit variable in .spout.yml can't be blank."
|
33
38
|
else
|
@@ -36,7 +41,7 @@ module Spout
|
|
36
41
|
return self
|
37
42
|
end
|
38
43
|
|
39
|
-
missing_variables = @config.charts.select{|c| Spout::
|
44
|
+
missing_variables = @config.charts.select{|c| Spout::Models::Variable.find_by_id(c['chart']) == nil}
|
40
45
|
if missing_variables.count > 0
|
41
46
|
puts "Could not find the following chart variable#{'s' unless missing_variables.size == 1}: #{missing_variables.join(', ')}"
|
42
47
|
return self
|
@@ -54,7 +59,8 @@ module Spout
|
|
54
59
|
|
55
60
|
@chart_variables = @config.charts.unshift( { "chart" => @config.visit, "title" => 'Histogram' } )
|
56
61
|
|
57
|
-
@
|
62
|
+
@dictionary_root = Dir.pwd
|
63
|
+
@variable_files = Dir.glob(File.join(@dictionary_root, 'variables', '**', '*.json'))
|
58
64
|
|
59
65
|
t = Time.now
|
60
66
|
@graphs_folder = File.join("graphs", @standard_version)
|
@@ -87,22 +93,23 @@ module Spout
|
|
87
93
|
|
88
94
|
def compute_tables_and_charts
|
89
95
|
variable_files_count = @variable_files.count
|
96
|
+
|
90
97
|
@variable_files.each_with_index do |variable_file, file_index|
|
91
|
-
|
92
|
-
|
93
|
-
next unless
|
94
|
-
next unless
|
95
|
-
|
96
|
-
next unless Spout::Models::Subject.method_defined?(
|
98
|
+
variable = Spout::Models::Variable.new(variable_file, @dictionary_root)
|
99
|
+
|
100
|
+
next unless variable.errors.size == 0
|
101
|
+
next unless @valid_ids.include?(variable.id) or @valid_ids.size == 0
|
102
|
+
next unless ["numeric", "integer", "choices"].include?(variable.type)
|
103
|
+
next unless Spout::Models::Subject.method_defined?(variable.id)
|
97
104
|
|
98
105
|
if @deploy_mode
|
99
106
|
print "\r Graph Generation: " + "#{"% 3d" % ((file_index+1)*100/variable_files_count)}% Uploaded".colorize(:white)
|
100
107
|
else
|
101
|
-
puts "#{file_index+1} of #{variable_files_count}: #{
|
108
|
+
puts "#{file_index+1} of #{variable_files_count}: #{variable.folder}#{variable.id}"
|
102
109
|
end
|
103
110
|
|
104
|
-
@progress[
|
105
|
-
next if (not @deploy_mode and @progress[
|
111
|
+
@progress[variable.id] ||= {}
|
112
|
+
next if (not @deploy_mode and @progress[variable.id]['generated'] == true) or (@deploy_mode and @progress[variable.id]['uploaded'] == true)
|
106
113
|
|
107
114
|
stats = {
|
108
115
|
charts: {},
|
@@ -112,38 +119,43 @@ module Spout
|
|
112
119
|
@chart_variables.each do |chart_type_hash|
|
113
120
|
chart_type = chart_type_hash["chart"]
|
114
121
|
chart_title = chart_type_hash["title"].downcase.gsub(' ', '-')
|
122
|
+
chart_variable = Spout::Models::Variable.find_by_id(chart_type)
|
115
123
|
|
116
124
|
if chart_type == @config.visit
|
117
|
-
filtered_subjects = @subjects.select{ |s| s.send(chart_type) != nil }
|
125
|
+
filtered_subjects = @subjects.select{ |s| s.send(chart_type) != nil }
|
118
126
|
if filtered_subjects.count > 0
|
119
|
-
|
120
|
-
stats[:
|
127
|
+
graph = Spout::Models::Graphables.for(variable, chart_variable, nil, filtered_subjects)
|
128
|
+
stats[:charts][chart_title] = graph.to_hash
|
129
|
+
table = Spout::Models::Tables.for(variable, chart_variable, filtered_subjects, nil)
|
130
|
+
stats[:tables][chart_title] = table.to_hash
|
121
131
|
end
|
122
132
|
else
|
123
|
-
filtered_subjects = @subjects.select{ |s| s.send(chart_type) != nil }
|
124
|
-
if filtered_subjects.collect(&
|
125
|
-
|
126
|
-
stats[:
|
127
|
-
|
128
|
-
|
129
|
-
|
133
|
+
filtered_subjects = @subjects.select{ |s| s.send(chart_type) != nil }
|
134
|
+
if filtered_subjects.collect(&variable.id.to_sym).compact.count > 0
|
135
|
+
graph = Spout::Models::Graphables.for(variable, chart_variable, @stratification_variable, filtered_subjects)
|
136
|
+
stats[:charts][chart_title] = graph.to_hash
|
137
|
+
stats[:tables][chart_title] = @stratification_variable.domain.options.collect do |option|
|
138
|
+
visit_subjects = filtered_subjects.select{ |s| s._visit == option.value }
|
139
|
+
unknown_subjects = visit_subjects.select{ |s| s.send(variable.id) == nil }
|
140
|
+
table = Spout::Models::Tables.for(variable, chart_variable, visit_subjects, option.display_name)
|
141
|
+
(visit_subjects.count > 0 && visit_subjects.count != unknown_subjects.count) ? table.to_hash : nil
|
130
142
|
end.compact
|
131
143
|
end
|
132
144
|
end
|
133
145
|
end
|
134
146
|
|
135
|
-
chart_json_file = File.join(@graphs_folder, "#{
|
147
|
+
chart_json_file = File.join(@graphs_folder, "#{variable.id}.json")
|
136
148
|
File.open(chart_json_file, 'w') { |file| file.write( JSON.pretty_generate(stats) + "\n" ) }
|
137
149
|
|
138
|
-
@progress[
|
150
|
+
@progress[variable.id]['generated'] = true
|
139
151
|
|
140
|
-
if @deploy_mode and not @progress[
|
152
|
+
if @deploy_mode and not @progress[variable.id]['uploaded'] == true
|
141
153
|
response = send_to_server(chart_json_file)
|
142
154
|
if response.kind_of?(Hash) and response['upload'] == 'success'
|
143
|
-
@progress[
|
155
|
+
@progress[variable.id]['uploaded'] = true
|
144
156
|
else
|
145
157
|
puts "\nUPLOAD FAILED: ".colorize(:red) + File.basename(chart_json_file)
|
146
|
-
@progress[
|
158
|
+
@progress[variable.id]['uploaded'] = false
|
147
159
|
end
|
148
160
|
end
|
149
161
|
|
@@ -156,13 +168,6 @@ module Spout
|
|
156
168
|
response = Spout::Helpers::SendFile.post("#{@url}/datasets/#{@slug}/upload_graph.json", chart_json_file, @standard_version, @token)
|
157
169
|
end
|
158
170
|
|
159
|
-
# [["Visit 1", "1"], ["Visit 2", "2"], ["CVD Outcomes", "3"]]
|
160
|
-
def visits
|
161
|
-
@visits ||= begin
|
162
|
-
Spout::Helpers::ChartTypes::domain_array(@config.visit)
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
171
|
end
|
167
172
|
end
|
168
173
|
end
|
@@ -4,6 +4,8 @@ require 'rubygems'
|
|
4
4
|
require 'json'
|
5
5
|
require 'yaml'
|
6
6
|
|
7
|
+
require 'spout/models/variable'
|
8
|
+
require 'spout/models/graphables'
|
7
9
|
require 'spout/helpers/subject_loader'
|
8
10
|
require 'spout/helpers/chart_types'
|
9
11
|
require 'spout/helpers/config_reader'
|
@@ -68,7 +70,9 @@ module Spout
|
|
68
70
|
FileUtils.mkpath( options_folder )
|
69
71
|
tmp_options_file = File.join( options_folder, 'options.json' )
|
70
72
|
|
73
|
+
chart_variable = Spout::Models::Variable.find_by_id(@config.visit)
|
71
74
|
variable_files_count = @variable_files.count
|
75
|
+
|
72
76
|
@variable_files.each_with_index do |variable_file, file_index|
|
73
77
|
json = JSON.parse(File.read(variable_file)) rescue json = nil
|
74
78
|
next unless json
|
@@ -90,9 +94,10 @@ module Spout
|
|
90
94
|
|
91
95
|
filtered_subjects = @subjects.select{ |s| s.send(@config.visit) != nil }
|
92
96
|
|
93
|
-
|
97
|
+
variable = Spout::Models::Variable.find_by_id variable_name
|
98
|
+
graph = Spout::Models::Graphables.for(variable, chart_variable, nil, filtered_subjects)
|
94
99
|
|
95
|
-
if
|
100
|
+
if graph.valid?
|
96
101
|
File.open(tmp_options_file, "w") do |outfile|
|
97
102
|
outfile.puts <<-eos
|
98
103
|
{
|
@@ -106,21 +111,21 @@ module Spout
|
|
106
111
|
"text": ""
|
107
112
|
},
|
108
113
|
"xAxis": {
|
109
|
-
"categories": #{
|
114
|
+
"categories": #{graph.categories.to_json}
|
110
115
|
},
|
111
116
|
"yAxis": {
|
112
117
|
"title": {
|
113
|
-
"text": #{
|
118
|
+
"text": #{graph.units.to_json}
|
114
119
|
}
|
115
120
|
},
|
116
121
|
"plotOptions": {
|
117
122
|
"column": {
|
118
123
|
"pointPadding": 0.2,
|
119
124
|
"borderWidth": 0,
|
120
|
-
"stacking": #{
|
125
|
+
"stacking": #{graph.stacking.to_json}
|
121
126
|
}
|
122
127
|
},
|
123
|
-
"series": #{
|
128
|
+
"series": #{graph.series.to_json}
|
124
129
|
}
|
125
130
|
eos
|
126
131
|
end
|
@@ -33,389 +33,6 @@ module Spout
|
|
33
33
|
end
|
34
34
|
buckets
|
35
35
|
end
|
36
|
-
|
37
|
-
def self.get_json(file_name, file_type)
|
38
|
-
file = Dir.glob("#{file_type.to_s.downcase}s/**/#{file_name.to_s.downcase}.json", File::FNM_CASEFOLD).first
|
39
|
-
json = JSON.parse(File.read(file)) rescue json = nil
|
40
|
-
json
|
41
|
-
end
|
42
|
-
|
43
|
-
def self.get_variable(variable_name)
|
44
|
-
get_json(variable_name, 'variable')
|
45
|
-
end
|
46
|
-
|
47
|
-
def self.get_domain(json)
|
48
|
-
get_json(json['domain'], 'domain')
|
49
|
-
end
|
50
|
-
|
51
|
-
def self.domain_array(variable_name)
|
52
|
-
variable_file = Dir.glob("variables/**/#{variable_name.to_s.downcase}.json", File::FNM_CASEFOLD).first
|
53
|
-
json = JSON.parse(File.read(variable_file)) rescue json = nil
|
54
|
-
if json
|
55
|
-
domain_json = get_domain(json)
|
56
|
-
domain_json ? domain_json.collect{|option_hash| [option_hash['display_name'], option_hash['value']]} : []
|
57
|
-
else
|
58
|
-
[]
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def self.chart_arbitrary_choices_by_quartile(chart_type, subjects, json, method)
|
63
|
-
# CHART TYPE IS THE QUARTILE VARIABLE
|
64
|
-
return unless chart_variable_json = get_variable(chart_type)
|
65
|
-
return unless domain_json = get_domain(json)
|
66
|
-
|
67
|
-
title = "#{json['display_name']} by #{chart_variable_json['display_name']}"
|
68
|
-
subtitle = "By Visit"
|
69
|
-
# categories = ["Quartile One", "Quartile Two", "Quartile Three", "Quartile Four"]
|
70
|
-
units = 'percent'
|
71
|
-
series = []
|
72
|
-
|
73
|
-
filtered_subjects = subjects.select{ |s| s.send(method) != nil and s.send(chart_type) != nil }.sort_by(&chart_type.to_sym)
|
74
|
-
|
75
|
-
all_subject_values = filtered_subjects.collect(&method.to_sym).compact.sort
|
76
|
-
domain_json = remove_unused_missing_codes_from_domain(domain_json, all_subject_values.uniq)
|
77
|
-
|
78
|
-
categories = [:quartile_one, :quartile_two, :quartile_three, :quartile_four].collect do |quartile|
|
79
|
-
bucket = filtered_subjects.send(quartile).collect(&chart_type.to_sym)
|
80
|
-
"#{bucket.min} to #{bucket.max}"
|
81
|
-
end
|
82
|
-
|
83
|
-
domain_json.each do |option_hash|
|
84
|
-
data = [:quartile_one, :quartile_two, :quartile_three, :quartile_four].collect do |quartile|
|
85
|
-
filtered_subjects.send(quartile).select{ |s| s.send(method) == option_hash['value'] }.count
|
86
|
-
end
|
87
|
-
|
88
|
-
series << { name: option_hash['display_name'], data: data } unless filtered_subjects.size == 0
|
89
|
-
end
|
90
|
-
|
91
|
-
{ title: title, subtitle: subtitle, categories: categories, units: units, series: series, stacking: 'percent' }
|
92
|
-
end
|
93
|
-
|
94
|
-
def self.chart_arbitrary_by_quartile(chart_type, subjects, json, method, visits)
|
95
|
-
# CHART TYPE IS THE QUARTILE VARIABLE
|
96
|
-
return unless chart_variable_json = get_variable(chart_type)
|
97
|
-
return chart_arbitrary_choices_by_quartile(chart_type, subjects, json, method) if json['type'] == 'choices'
|
98
|
-
|
99
|
-
title = "#{json['display_name']} by #{chart_variable_json['display_name']}"
|
100
|
-
subtitle = "By Visit"
|
101
|
-
categories = ["Quartile One", "Quartile Two", "Quartile Three", "Quartile Four"]
|
102
|
-
units = json["units"]
|
103
|
-
series = []
|
104
|
-
|
105
|
-
series = []
|
106
|
-
|
107
|
-
visits.each do |visit_display_name, visit_value|
|
108
|
-
data = []
|
109
|
-
filtered_subjects = subjects.select{ |s| s._visit == visit_value and s.send(method) != nil and s.send(chart_type) != nil }.sort_by(&chart_type.to_sym)
|
110
|
-
|
111
|
-
[:quartile_one, :quartile_two, :quartile_three, :quartile_four].each do |quartile|
|
112
|
-
array = filtered_subjects.send(quartile).collect(&method.to_sym)
|
113
|
-
data << { y: (array.mean.round(1) rescue 0.0),
|
114
|
-
stddev: ("%0.1f" % array.standard_deviation rescue ''),
|
115
|
-
median: ("%0.1f" % array.median rescue ''),
|
116
|
-
min: ("%0.1f" % array.min rescue ''),
|
117
|
-
max: ("%0.1f" % array.max rescue ''),
|
118
|
-
n: array.n }
|
119
|
-
end
|
120
|
-
|
121
|
-
series << { name: visit_display_name, data: data } unless filtered_subjects.size == 0
|
122
|
-
end
|
123
|
-
|
124
|
-
{ title: title, subtitle: subtitle, categories: categories, units: units, series: series }
|
125
|
-
end
|
126
|
-
|
127
|
-
def self.table_arbitrary_by_quartile(chart_type, subjects, json, method, subtitle = nil)
|
128
|
-
return table_arbitrary_choices_by_quartile(chart_type, subjects, json, method, subtitle) if json['type'] == 'choices'
|
129
|
-
# CHART TYPE IS THE QUARTILE VARIABLE
|
130
|
-
return unless chart_variable_json = get_variable(chart_type)
|
131
|
-
|
132
|
-
|
133
|
-
headers = [
|
134
|
-
[""] + Spout::Helpers::ArrayStatistics::calculations.collect{|calculation_label, calculation_method| calculation_label} + ["Total"]
|
135
|
-
]
|
136
|
-
|
137
|
-
filtered_subjects = subjects.select{ |s| s.send(method) != nil and s.send(chart_type) != nil }.sort_by(&chart_type.to_sym)
|
138
|
-
|
139
|
-
rows = [:quartile_one, :quartile_two, :quartile_three, :quartile_four].collect do |quartile|
|
140
|
-
bucket = filtered_subjects.send(quartile)
|
141
|
-
row_subjects = bucket.collect(&method.to_sym)
|
142
|
-
data = Spout::Helpers::ArrayStatistics::calculations.collect do |calculation_label, calculation_method, calculation_type, calculation_format|
|
143
|
-
TableFormatting::format_number(row_subjects.send(calculation_method), calculation_type, calculation_format)
|
144
|
-
end
|
145
|
-
|
146
|
-
row_name = if row_subjects.size == 0
|
147
|
-
quartile.to_s.capitalize.gsub('_one', ' One').gsub('_two', ' Two').gsub('_three', ' Three').gsub('_four', ' Four')
|
148
|
-
else
|
149
|
-
"#{bucket.collect(&chart_type.to_sym).min} to #{bucket.collect(&chart_type.to_sym).max} #{chart_variable_json['units']}"
|
150
|
-
end
|
151
|
-
|
152
|
-
[row_name] + data + [{ text: TableFormatting::format_number(row_subjects.count, :count), style: 'font-weight:bold'}]
|
153
|
-
end
|
154
|
-
|
155
|
-
total_values = Spout::Helpers::ArrayStatistics::calculations.collect do |calculation_label, calculation_method, calculation_type, calculation_format|
|
156
|
-
total_count = filtered_subjects.collect(&method.to_sym).send(calculation_method)
|
157
|
-
{ text: TableFormatting::format_number(total_count, calculation_type, calculation_format), style: "font-weight:bold" }
|
158
|
-
end
|
159
|
-
|
160
|
-
footers = [
|
161
|
-
[{ text: "Total", style: "font-weight:bold" }] + total_values + [{ text: TableFormatting::format_number(filtered_subjects.count, :count), style: 'font-weight:bold'}]
|
162
|
-
]
|
163
|
-
|
164
|
-
{ title: "#{chart_variable_json['display_name']} vs #{json['display_name']}", subtitle: subtitle, headers: headers, footers: footers, rows: rows }
|
165
|
-
|
166
|
-
end
|
167
|
-
|
168
|
-
def self.table_arbitrary_choices_by_quartile(chart_type, subjects, json, method, subtitle)
|
169
|
-
# CHART TYPE IS THE QUARTILE VARIABLE
|
170
|
-
return unless chart_variable_json = get_variable(chart_type)
|
171
|
-
return unless domain_json = get_domain(json)
|
172
|
-
|
173
|
-
filtered_subjects = subjects.select{ |s| s.send(method) != nil and s.send(chart_type) != nil }.sort_by(&chart_type.to_sym)
|
174
|
-
all_subject_values = filtered_subjects.collect(&method.to_sym).compact.sort
|
175
|
-
domain_json = remove_unused_missing_codes_from_domain(domain_json, all_subject_values.uniq)
|
176
|
-
|
177
|
-
categories = [:quartile_one, :quartile_two, :quartile_three, :quartile_four].collect do |quartile|
|
178
|
-
bucket = filtered_subjects.send(quartile).collect(&chart_type.to_sym)
|
179
|
-
"#{bucket.min} to #{bucket.max} #{chart_variable_json['units']}"
|
180
|
-
end
|
181
|
-
|
182
|
-
headers = [
|
183
|
-
[""] + categories + ["Total"]
|
184
|
-
]
|
185
|
-
|
186
|
-
rows = []
|
187
|
-
|
188
|
-
rows = domain_json.collect do |option_hash|
|
189
|
-
row_subjects = filtered_subjects.select{ |s| s.send(method) == option_hash['value'] }
|
190
|
-
|
191
|
-
data = [:quartile_one, :quartile_two, :quartile_three, :quartile_four].collect do |quartile|
|
192
|
-
bucket = filtered_subjects.send(quartile).select{ |s| s.send(method) == option_hash['value'] }
|
193
|
-
TableFormatting::format_number(bucket.count, :count)
|
194
|
-
end
|
195
|
-
|
196
|
-
[option_hash['display_name']] + data + [{ text: TableFormatting::format_number(row_subjects.count, :count), style: 'font-weight:bold'}]
|
197
|
-
end
|
198
|
-
|
199
|
-
|
200
|
-
total_values = [:quartile_one, :quartile_two, :quartile_three, :quartile_four].collect do |quartile|
|
201
|
-
{ text: TableFormatting::format_number(filtered_subjects.send(quartile).count, :count), style: "font-weight:bold" }
|
202
|
-
end
|
203
|
-
|
204
|
-
footers = [
|
205
|
-
[{ text: "Total", style: "font-weight:bold" }] + total_values + [{ text: TableFormatting::format_number(filtered_subjects.count, :count), style: 'font-weight:bold'}]
|
206
|
-
]
|
207
|
-
|
208
|
-
{ title: "#{json['display_name']} vs #{chart_variable_json['display_name']}", subtitle: subtitle, headers: headers, footers: footers, rows: rows }
|
209
|
-
end
|
210
|
-
|
211
|
-
|
212
|
-
def self.chart_arbitrary_choices(chart_type, subjects, json, method)
|
213
|
-
return unless chart_variable_json = get_variable(chart_type)
|
214
|
-
return unless chart_variable_domain = domain_array(chart_type)
|
215
|
-
return unless domain_json = get_domain(json)
|
216
|
-
|
217
|
-
|
218
|
-
title = "#{json['display_name']} by #{chart_variable_json['display_name']}"
|
219
|
-
subtitle = "By Visit"
|
220
|
-
categories = chart_variable_domain.collect{|a| a[0]}
|
221
|
-
units = 'percent'
|
222
|
-
series = []
|
223
|
-
|
224
|
-
all_subject_values = subjects.collect(&method.to_sym).compact.sort
|
225
|
-
domain_json = remove_unused_missing_codes_from_domain(domain_json, all_subject_values.uniq)
|
226
|
-
|
227
|
-
domain_json.each do |option_hash|
|
228
|
-
domain_values = subjects.select{ |s| s.send(method) == option_hash['value'] }
|
229
|
-
|
230
|
-
data = chart_variable_domain.collect do |display_name, value|
|
231
|
-
domain_values.select{ |s| s.send(chart_type) == value }.count
|
232
|
-
end
|
233
|
-
series << { name: option_hash['display_name'], data: data }
|
234
|
-
end
|
235
|
-
|
236
|
-
{ title: title, subtitle: subtitle, categories: categories, units: units, series: series, stacking: 'percent' }
|
237
|
-
end
|
238
|
-
|
239
|
-
def self.chart_arbitrary(chart_type, subjects, json, method, visits)
|
240
|
-
return unless chart_variable_json = get_variable(chart_type)
|
241
|
-
return unless chart_variable_domain = domain_array(chart_type)
|
242
|
-
return chart_arbitrary_by_quartile(chart_type, subjects, json, method, visits) if ['numeric', 'integer'].include?(chart_variable_json['type'])
|
243
|
-
|
244
|
-
return chart_arbitrary_choices(chart_type, subjects, json, method) if json['type'] == 'choices'
|
245
|
-
|
246
|
-
title = "#{json['display_name']} by #{chart_variable_json['display_name']}"
|
247
|
-
subtitle = "By Visit"
|
248
|
-
categories = []
|
249
|
-
units = json["units"]
|
250
|
-
series = []
|
251
|
-
|
252
|
-
data = []
|
253
|
-
|
254
|
-
visits.each do |visit_display_name, visit_value|
|
255
|
-
visit_subjects = subjects.select{ |s| s._visit == visit_value and s.send(method) != nil }
|
256
|
-
if visit_subjects.count > 0
|
257
|
-
chart_variable_domain.each_with_index do |(display_name, value), index|
|
258
|
-
values = visit_subjects.select{|s| s.send(chart_type) == value }.collect(&method.to_sym)
|
259
|
-
data[index] ||= []
|
260
|
-
data[index] << (values.mean.round(2) rescue 0.0)
|
261
|
-
end
|
262
|
-
categories << visit_display_name
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
chart_variable_domain.each_with_index do |(display_name, value), index|
|
267
|
-
series << { name: display_name, data: data[index] }
|
268
|
-
end
|
269
|
-
|
270
|
-
{ title: title, subtitle: subtitle, categories: categories, units: units, series: series }
|
271
|
-
end
|
272
|
-
|
273
|
-
|
274
|
-
def self.table_arbitrary(chart_type, subjects, json, method, subtitle = nil)
|
275
|
-
return unless chart_variable_json = get_variable(chart_type)
|
276
|
-
return unless chart_variable_domain = domain_array(chart_type)
|
277
|
-
return table_arbitrary_by_quartile(chart_type, subjects, json, method, subtitle) if ['numeric', 'integer'].include?(chart_variable_json['type'])
|
278
|
-
return table_arbitrary_choices(chart_type, subjects, json, method, subtitle) if json['type'] == 'choices'
|
279
|
-
|
280
|
-
headers = [
|
281
|
-
[""] + Spout::Helpers::ArrayStatistics::calculations.collect{|calculation_label, calculation_method| calculation_label} + ["Total"]
|
282
|
-
]
|
283
|
-
|
284
|
-
filtered_subjects = subjects.select{ |s| s.send(chart_type) != nil }
|
285
|
-
|
286
|
-
rows = chart_variable_domain.collect do |display_name, value|
|
287
|
-
row_subjects = filtered_subjects.select{ |s| s.send(chart_type) == value }
|
288
|
-
|
289
|
-
row_cells = Spout::Helpers::ArrayStatistics::calculations.collect do |calculation_label, calculation_method, calculation_type, calculation_format|
|
290
|
-
count = row_subjects.collect(&method.to_sym).send(calculation_method)
|
291
|
-
(count == 0 && calculation_method == :count) ? { text: '-', class: 'text-muted' } : TableFormatting::format_number(count, calculation_type, calculation_format)
|
292
|
-
end
|
293
|
-
|
294
|
-
[display_name] + row_cells + [{ text: TableFormatting::format_number(row_subjects.count, :count), style: 'font-weight:bold'}]
|
295
|
-
end
|
296
|
-
|
297
|
-
total_values = Spout::Helpers::ArrayStatistics::calculations.collect do |calculation_label, calculation_method, calculation_type, calculation_format|
|
298
|
-
total_count = filtered_subjects.collect(&method.to_sym).send(calculation_method)
|
299
|
-
{ text: TableFormatting::format_number(total_count, calculation_type, calculation_format), style: "font-weight:bold" }
|
300
|
-
end
|
301
|
-
|
302
|
-
footers = [
|
303
|
-
[{ text: "Total", style: "font-weight:bold" }] + total_values + [{ text: TableFormatting::format_number(filtered_subjects.count, :count), style: 'font-weight:bold'}]
|
304
|
-
]
|
305
|
-
|
306
|
-
{ title: "#{chart_variable_json['display_name']} vs #{json['display_name']}", subtitle: subtitle, headers: headers, footers: footers, rows: rows }
|
307
|
-
|
308
|
-
end
|
309
|
-
|
310
|
-
def self.table_arbitrary_choices(chart_type, subjects, json, method, subtitle)
|
311
|
-
return unless chart_variable_json = get_variable(chart_type)
|
312
|
-
return unless chart_variable_domain = domain_array(chart_type)
|
313
|
-
return unless domain_json = get_domain(json)
|
314
|
-
|
315
|
-
headers = [
|
316
|
-
[""] + chart_variable_domain.collect{|display_name, value| display_name} + ["Total"]
|
317
|
-
]
|
318
|
-
|
319
|
-
filtered_subjects = subjects.select{ |s| s.send(chart_type) != nil }
|
320
|
-
|
321
|
-
all_subject_values = filtered_subjects.collect(&method.to_sym).compact.sort
|
322
|
-
domain_json = remove_unused_missing_codes_from_domain(domain_json, all_subject_values.uniq)
|
323
|
-
|
324
|
-
rows = domain_json.collect do |option_hash|
|
325
|
-
row_subjects = filtered_subjects.select{ |s| s.send(method) == option_hash['value'] }
|
326
|
-
row_cells = chart_variable_domain.collect do |display_name, value|
|
327
|
-
count = row_subjects.select{ |s| s.send(chart_type) == value }.count
|
328
|
-
count > 0 ? TableFormatting::format_number(count, :count) : { text: '-', class: 'text-muted' }
|
329
|
-
end
|
330
|
-
|
331
|
-
total = row_subjects.count
|
332
|
-
|
333
|
-
[option_hash['display_name']] + row_cells + [total == 0 ? { text: '-', class: 'text-muted' } : { text: TableFormatting::format_number(total, :count), style: 'font-weight:bold'}]
|
334
|
-
end
|
335
|
-
|
336
|
-
if filtered_subjects.select{|s| s.send(method) == nil }.count > 0
|
337
|
-
unknown_values = chart_variable_domain.collect do |display_name, value|
|
338
|
-
{ text: TableFormatting::format_number(filtered_subjects.select{ |s| s.send(chart_type) == value and s.send(method) == nil }.count, :count), class: 'text-muted' }
|
339
|
-
end
|
340
|
-
rows << [{ text: 'Unknown', class: 'text-muted'}] + unknown_values + [ { text: TableFormatting::format_number(filtered_subjects.select{|s| s.send(method) == nil}.count, :count), style: 'font-weight:bold', class: 'text-muted' } ]
|
341
|
-
end
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
total_values = chart_variable_domain.collect do |display_name, value|
|
346
|
-
total_count = filtered_subjects.select{|s| s.send(chart_type) == value }.count
|
347
|
-
{ text: (total_count == 0 ? "-" : TableFormatting::format_number(total_count, :count)), style: "font-weight:bold" }
|
348
|
-
end
|
349
|
-
|
350
|
-
footers = [
|
351
|
-
[{ text: "Total", style: "font-weight:bold" }] + total_values + [{ text: TableFormatting::format_number(filtered_subjects.count, :count), style: 'font-weight:bold'}]
|
352
|
-
]
|
353
|
-
|
354
|
-
{ title: "#{json['display_name']} vs #{chart_variable_json['display_name']}", subtitle: subtitle, headers: headers, footers: footers, rows: rows }
|
355
|
-
end
|
356
|
-
|
357
|
-
def self.chart_histogram(chart_type, subjects, json, method)
|
358
|
-
domain_json = get_domain(json)
|
359
|
-
return if json['type'] == 'choices' and not domain_json
|
360
|
-
return unless chart_variable_json = get_variable(chart_type)
|
361
|
-
return unless chart_variable_domain = domain_array(chart_type)
|
362
|
-
|
363
|
-
title = "#{json['display_name']}"
|
364
|
-
subtitle = "By Visit"
|
365
|
-
units = "Subjects"
|
366
|
-
x_axis_units = json["units"]
|
367
|
-
series = []
|
368
|
-
|
369
|
-
all_subject_values = subjects.collect(&method.to_sym).compact.sort
|
370
|
-
return nil if all_subject_values.count == 0
|
371
|
-
|
372
|
-
domain_json = remove_unused_missing_codes_from_domain(domain_json, all_subject_values.uniq) if domain_json
|
373
|
-
|
374
|
-
categories = pull_categories(json, method, all_subject_values, domain_json)
|
375
|
-
|
376
|
-
buckets = continuous_buckets(all_subject_values)
|
377
|
-
|
378
|
-
chart_variable_domain.each do |display_name, value|
|
379
|
-
visit_subjects = subjects.select{ |s| s.send(chart_type) == value and s.send(method) != nil }.collect(&method.to_sym).sort
|
380
|
-
next unless visit_subjects.size > 0
|
381
|
-
|
382
|
-
data = pull_data(json, visit_subjects, buckets, categories, domain_json)
|
383
|
-
|
384
|
-
series << { name: display_name, data: data }
|
385
|
-
end
|
386
|
-
|
387
|
-
{ title: title, subtitle: subtitle, categories: categories, units: units, series: series, x_axis_title: x_axis_units }
|
388
|
-
end
|
389
|
-
|
390
|
-
def self.pull_categories(json, method, all_subject_values, domain_json)
|
391
|
-
categories = []
|
392
|
-
if json['type'] == 'choices'
|
393
|
-
categories = domain_json.collect{|option_hash| option_hash['display_name']}
|
394
|
-
else
|
395
|
-
buckets = continuous_buckets(all_subject_values)
|
396
|
-
categories = buckets.collect{|b| "#{b[0]} to #{b[1]}"}
|
397
|
-
end
|
398
|
-
categories
|
399
|
-
end
|
400
|
-
|
401
|
-
def self.pull_data(json, visit_subjects, buckets, categories, domain_json)
|
402
|
-
data = []
|
403
|
-
if json['type'] == 'choices'
|
404
|
-
domain_json.each do |option_hash|
|
405
|
-
data << visit_subjects.select{ |v| v == option_hash['value'] }.count
|
406
|
-
end
|
407
|
-
else
|
408
|
-
visit_subjects.group_by{|v| get_bucket(buckets, v) }.each do |key, values|
|
409
|
-
data[categories.index(key)] = values.count if categories.index(key)
|
410
|
-
end
|
411
|
-
end
|
412
|
-
data
|
413
|
-
end
|
414
|
-
|
415
|
-
def self.remove_unused_missing_codes_from_domain(domain_json, unique_subject_values)
|
416
|
-
domain_json.select{|option_hash| option_hash['missing'] != true or (option_hash['missing'] == true and unique_subject_values.include?(option_hash['value']))}
|
417
|
-
end
|
418
|
-
|
419
36
|
end
|
420
37
|
end
|
421
38
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Spout
|
2
|
+
module Models
|
3
|
+
class Bucket
|
4
|
+
|
5
|
+
attr_accessor :start, :stop
|
6
|
+
|
7
|
+
def initialize(start, stop)
|
8
|
+
@start = start
|
9
|
+
@stop = stop
|
10
|
+
end
|
11
|
+
|
12
|
+
def in_bucket?(value)
|
13
|
+
value >= @start and value <= @stop
|
14
|
+
end
|
15
|
+
|
16
|
+
def display_name
|
17
|
+
"#{@start} to #{@stop}"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/spout/models/domain.rb
CHANGED