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
data/lib/spout/models/form.rb
CHANGED
@@ -4,11 +4,12 @@
|
|
4
4
|
# "code_book": "Baseline-Visit-Intake-Questionnaire.pdf"
|
5
5
|
# }
|
6
6
|
|
7
|
-
require '
|
7
|
+
require 'spout/models/record'
|
8
8
|
|
9
9
|
module Spout
|
10
10
|
module Models
|
11
|
-
class Form
|
11
|
+
class Form < Spout::Models::Record
|
12
|
+
|
12
13
|
attr_accessor :id, :display_name, :code_book
|
13
14
|
attr_accessor :errors
|
14
15
|
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spout/models/graphables/default'
|
2
|
+
|
3
|
+
module Spout
|
4
|
+
module Models
|
5
|
+
module Graphables
|
6
|
+
class ChoicesVsChoices < Spout::Models::Graphables::Default
|
7
|
+
|
8
|
+
def categories
|
9
|
+
filtered_domain_options(@chart_variable).collect(&:display_name)
|
10
|
+
end
|
11
|
+
|
12
|
+
def units
|
13
|
+
'percent'
|
14
|
+
end
|
15
|
+
|
16
|
+
def series
|
17
|
+
filtered_domain_options(@variable).collect do |option|
|
18
|
+
filtered_subjects = @subjects.select{ |s| s.send(@variable.id) == option.value }
|
19
|
+
data = filtered_domain_options(@chart_variable).collect do |chart_option|
|
20
|
+
filtered_subjects.select{ |s| s.send(@chart_variable.id) == chart_option.value }.count
|
21
|
+
end
|
22
|
+
{ name: option.display_name, data: data }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def stacking
|
27
|
+
'percent'
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spout/models/graphables/default'
|
2
|
+
require 'spout/helpers/array_statistics'
|
3
|
+
|
4
|
+
module Spout
|
5
|
+
module Models
|
6
|
+
module Graphables
|
7
|
+
class ChoicesVsNumeric < Spout::Models::Graphables::Default
|
8
|
+
|
9
|
+
def categories
|
10
|
+
filtered_subjects = filter_and_sort_subjects
|
11
|
+
|
12
|
+
return [] if filtered_subjects.size == 0
|
13
|
+
|
14
|
+
[:quartile_one, :quartile_two, :quartile_three, :quartile_four].collect do |quartile|
|
15
|
+
quartile = filtered_subjects.send(quartile).collect(&@chart_variable.id.to_sym)
|
16
|
+
"#{quartile.min} to #{quartile.max}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def units
|
21
|
+
'percent'
|
22
|
+
end
|
23
|
+
|
24
|
+
def series
|
25
|
+
filtered_subjects = filter_and_sort_subjects
|
26
|
+
|
27
|
+
return [] if filtered_subjects.size == 0
|
28
|
+
|
29
|
+
filtered_domain_options(@variable).collect do |option|
|
30
|
+
data = [:quartile_one, :quartile_two, :quartile_three, :quartile_four].collect do |quartile|
|
31
|
+
filtered_subjects.send(quartile).select{ |s| s.send(@variable.id) == option.value }.count
|
32
|
+
end
|
33
|
+
{ name: option.display_name, data: data }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def stacking
|
38
|
+
'percent'
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def filter_and_sort_subjects
|
44
|
+
@filter_and_sort_subjects ||= begin
|
45
|
+
@subjects.select do |s|
|
46
|
+
s.send(@variable.id) != nil and s.send(@chart_variable.id) != nil
|
47
|
+
end.sort_by(&@chart_variable.id.to_sym)
|
48
|
+
rescue
|
49
|
+
[]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'spout/models/variable'
|
2
|
+
require 'spout/models/bucket'
|
3
|
+
|
4
|
+
module Spout
|
5
|
+
module Models
|
6
|
+
module Graphables
|
7
|
+
class Default
|
8
|
+
|
9
|
+
attr_reader :variable, :chart_variable, :stratification_variable, :subjects
|
10
|
+
|
11
|
+
def initialize(variable, chart_variable, stratification_variable, subjects)
|
12
|
+
@variable = variable
|
13
|
+
@chart_variable = chart_variable
|
14
|
+
@stratification_variable = stratification_variable
|
15
|
+
@subjects = subjects
|
16
|
+
|
17
|
+
@values = subjects.collect(&@variable.id.to_sym) rescue @values = []
|
18
|
+
@values_unique = @values.uniq
|
19
|
+
|
20
|
+
@buckets = continuous_buckets
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_hash
|
24
|
+
if valid?
|
25
|
+
{ title: title, subtitle: subtitle, categories: categories, units: units, series: series, stacking: stacking, x_axis_title: x_axis_title }
|
26
|
+
else
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def valid?
|
32
|
+
if @variable == nil or @chart_variable == nil or @values == []
|
33
|
+
false
|
34
|
+
elsif @variable.type == 'choices' and @variable.domain.options == []
|
35
|
+
false
|
36
|
+
elsif @chart_variable.type == 'choices' and @chart_variable.domain.options == []
|
37
|
+
false
|
38
|
+
else
|
39
|
+
true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def title
|
44
|
+
"#{@variable.display_name} by #{@chart_variable.display_name}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def subtitle
|
48
|
+
"By Visit"
|
49
|
+
end
|
50
|
+
|
51
|
+
def categories
|
52
|
+
[]
|
53
|
+
end
|
54
|
+
|
55
|
+
def units
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def series
|
60
|
+
[]
|
61
|
+
end
|
62
|
+
|
63
|
+
def stacking
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
|
67
|
+
def x_axis_title
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def continuous_buckets
|
74
|
+
values_numeric = @values.select{|v| v.kind_of? Numeric}
|
75
|
+
return [] if values_numeric.count == 0
|
76
|
+
minimum_bucket = values_numeric.min
|
77
|
+
maximum_bucket = values_numeric.max
|
78
|
+
max_buckets = 12
|
79
|
+
bucket_size = ((maximum_bucket - minimum_bucket) / max_buckets.to_f)
|
80
|
+
precision = (bucket_size == 0 ? 0 : [-Math.log10(bucket_size).floor, 0].max)
|
81
|
+
|
82
|
+
buckets = []
|
83
|
+
(0..(max_buckets-1)).to_a.each do |index|
|
84
|
+
start = (minimum_bucket + index * bucket_size)
|
85
|
+
stop = (start + bucket_size)
|
86
|
+
buckets << Spout::Models::Bucket.new(start.round(precision),stop.round(precision))
|
87
|
+
end
|
88
|
+
buckets
|
89
|
+
end
|
90
|
+
|
91
|
+
def get_bucket(value)
|
92
|
+
return nil if @buckets.size == 0 or not value.kind_of?(Numeric)
|
93
|
+
@buckets.each do |b|
|
94
|
+
return b.display_name if b.in_bucket?(value)
|
95
|
+
end
|
96
|
+
if value <= @buckets.first.start
|
97
|
+
@buckets.first.display_name
|
98
|
+
else
|
99
|
+
@buckets.last.display_name
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns variable options that are either:
|
104
|
+
# a) are not missing codes
|
105
|
+
# b) or are marked as missing codes but represented in the dataset
|
106
|
+
def filtered_domain_options(variable)
|
107
|
+
variable.domain.options.select do |o|
|
108
|
+
o.missing != true or (o.missing == true and @values_unique.include?(o.value))
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spout/models/graphables/default'
|
2
|
+
|
3
|
+
module Spout
|
4
|
+
module Models
|
5
|
+
module Graphables
|
6
|
+
class Histogram < Spout::Models::Graphables::Default
|
7
|
+
|
8
|
+
def title
|
9
|
+
@variable.display_name
|
10
|
+
end
|
11
|
+
|
12
|
+
def categories
|
13
|
+
if @variable.type == 'choices'
|
14
|
+
filtered_domain_options(@variable).collect(&:display_name)
|
15
|
+
else
|
16
|
+
@buckets.collect(&:display_name)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def units
|
21
|
+
'Subjects'
|
22
|
+
end
|
23
|
+
|
24
|
+
def series
|
25
|
+
@chart_variable.domain.options.collect do |option|
|
26
|
+
visit_subjects = @subjects.select{ |s| s.send(@chart_variable.id) == option.value and s.send(@variable.id) != nil } rescue visit_subjects = []
|
27
|
+
visit_subject_values = visit_subjects.collect(&@variable.id.to_sym).sort rescue visit_subject_values = []
|
28
|
+
next unless visit_subject_values.size > 0
|
29
|
+
|
30
|
+
data = []
|
31
|
+
|
32
|
+
if @variable.type == 'choices'
|
33
|
+
data = filtered_domain_options(@variable).collect do |option|
|
34
|
+
visit_subject_values.select{ |v| v == option.value }.count
|
35
|
+
end
|
36
|
+
else
|
37
|
+
visit_subject_values.group_by{|v| get_bucket(v) }.each do |key, values|
|
38
|
+
data[categories.index(key)] = values.count if categories.index(key)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
{ name: option.display_name, data: data }
|
43
|
+
end.compact
|
44
|
+
end
|
45
|
+
|
46
|
+
def x_axis_title
|
47
|
+
@variable.units
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spout/models/graphables/default'
|
2
|
+
|
3
|
+
module Spout
|
4
|
+
module Models
|
5
|
+
module Graphables
|
6
|
+
class NumericVsChoices < Spout::Models::Graphables::Default
|
7
|
+
|
8
|
+
def categories
|
9
|
+
categories_result = []
|
10
|
+
@stratification_variable.domain.options.each do |option|
|
11
|
+
visit_subjects = @subjects.select{ |s| s._visit == option.value and s.send(@variable.id) != nil } rescue visit_subjects = []
|
12
|
+
if visit_subjects.count > 0
|
13
|
+
categories_result << option.display_name
|
14
|
+
end
|
15
|
+
end
|
16
|
+
categories_result
|
17
|
+
end
|
18
|
+
|
19
|
+
def units
|
20
|
+
@variable.units
|
21
|
+
end
|
22
|
+
|
23
|
+
def series
|
24
|
+
data = []
|
25
|
+
|
26
|
+
@stratification_variable.domain.options.each do |option|
|
27
|
+
visit_subjects = @subjects.select{ |s| s._visit == option.value and s.send(@variable.id) != nil } rescue visit_subjects = []
|
28
|
+
if visit_subjects.count > 0
|
29
|
+
|
30
|
+
filtered_domain_options(@chart_variable).each_with_index do |option, index|
|
31
|
+
values = visit_subjects.select{|s| s.send(@chart_variable.id) == option.value }.collect(&@variable.id.to_sym)
|
32
|
+
data[index] ||= []
|
33
|
+
data[index] << (values.mean.round(2) rescue 0.0)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
filtered_domain_options(@chart_variable).each_with_index.collect do |option, index|
|
40
|
+
{ name: option.display_name, data: data[index] }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spout/models/graphables/default'
|
2
|
+
require 'spout/helpers/array_statistics'
|
3
|
+
|
4
|
+
module Spout
|
5
|
+
module Models
|
6
|
+
module Graphables
|
7
|
+
class NumericVsNumeric < Spout::Models::Graphables::Default
|
8
|
+
|
9
|
+
def categories
|
10
|
+
["Quartile One", "Quartile Two", "Quartile Three", "Quartile Four"]
|
11
|
+
end
|
12
|
+
|
13
|
+
def units
|
14
|
+
@variable.units
|
15
|
+
end
|
16
|
+
|
17
|
+
def series
|
18
|
+
@stratification_variable.domain.options.collect do |option|
|
19
|
+
filtered_subjects = filter_and_sort_subjects_by_stratification_option(option)
|
20
|
+
next if filtered_subjects.size == 0
|
21
|
+
|
22
|
+
data = [:quartile_one, :quartile_two, :quartile_three, :quartile_four].collect do |quartile|
|
23
|
+
array = filtered_subjects.send(quartile).collect(&@variable.id.to_sym)
|
24
|
+
array_statistics(array)
|
25
|
+
end
|
26
|
+
|
27
|
+
{ name: option.display_name, data: data }
|
28
|
+
end.compact
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def filter_and_sort_subjects_by_stratification_option(option)
|
34
|
+
begin
|
35
|
+
@subjects.select do |s|
|
36
|
+
s._visit == option.value and s.send(@variable.id) != nil and s.send(@chart_variable.id) != nil
|
37
|
+
end.sort_by(&@chart_variable.id.to_sym)
|
38
|
+
rescue
|
39
|
+
[]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def array_statistics(array)
|
44
|
+
{ y: (array.mean.round(1) rescue 0.0),
|
45
|
+
stddev: ("%0.1f" % array.standard_deviation rescue ''),
|
46
|
+
median: ("%0.1f" % array.median rescue ''),
|
47
|
+
min: ("%0.1f" % array.min rescue ''),
|
48
|
+
max: ("%0.1f" % array.max rescue ''),
|
49
|
+
n: array.n }
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spout/models/graphables/default'
|
2
|
+
require 'spout/models/graphables/histogram'
|
3
|
+
require 'spout/models/graphables/numeric_vs_choices'
|
4
|
+
require 'spout/models/graphables/choices_vs_choices'
|
5
|
+
require 'spout/models/graphables/numeric_vs_numeric'
|
6
|
+
require 'spout/models/graphables/choices_vs_numeric'
|
7
|
+
|
8
|
+
|
9
|
+
module Spout
|
10
|
+
module Models
|
11
|
+
module Graphables
|
12
|
+
|
13
|
+
DEFAULT_CLASS = Spout::Models::Graphables::Default
|
14
|
+
GRAPHABLE_CLASSES = {
|
15
|
+
'histogram' => Spout::Models::Graphables::Histogram,
|
16
|
+
'numeric_vs_choices' => Spout::Models::Graphables::NumericVsChoices,
|
17
|
+
'choices_vs_choices' => Spout::Models::Graphables::ChoicesVsChoices,
|
18
|
+
'numeric_vs_numeric' => Spout::Models::Graphables::NumericVsNumeric,
|
19
|
+
'choices_vs_numeric' => Spout::Models::Graphables::ChoicesVsNumeric
|
20
|
+
}
|
21
|
+
|
22
|
+
def self.for(variable, chart_variable, stratification_variable, subjects)
|
23
|
+
graph_type = get_graph_type(variable, chart_variable, stratification_variable)
|
24
|
+
(GRAPHABLE_CLASSES[graph_type] || DEFAULT_CLASS).new(variable, chart_variable, stratification_variable, subjects)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.get_graph_type(variable, chart_variable, stratification_variable)
|
28
|
+
if stratification_variable == nil
|
29
|
+
'histogram'
|
30
|
+
else
|
31
|
+
"#{variable_to_graph_type(variable)}_vs_#{variable_to_graph_type(chart_variable)}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.variable_to_graph_type(variable)
|
36
|
+
variable_type = (variable ? variable.type : nil)
|
37
|
+
case variable_type when 'numeric', 'integer'
|
38
|
+
'numeric'
|
39
|
+
else
|
40
|
+
variable_type
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/spout/models/option.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
module Spout
|
2
2
|
module Models
|
3
3
|
class Option
|
4
|
-
attr_accessor :display_name, :value, :description
|
4
|
+
attr_accessor :display_name, :value, :description, :missing
|
5
5
|
|
6
6
|
def initialize(option_hash)
|
7
|
-
%w( display_name value description ).each do |method|
|
7
|
+
%w( display_name value description missing ).each do |method|
|
8
8
|
instance_variable_set("@#{method}", (option_hash.kind_of?(Hash) ? option_hash : {})[method])
|
9
9
|
end
|
10
10
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Spout
|
5
|
+
module Models
|
6
|
+
class Record
|
7
|
+
|
8
|
+
class << self
|
9
|
+
# Only returns records with zero json errors, nil otherwise
|
10
|
+
def find_by_id(id)
|
11
|
+
dictionary_root = FileUtils.pwd # '.'
|
12
|
+
file_name = Dir.glob(File.join(dictionary_root, "#{self.name.split("::").last.to_s.downcase}s", "**", "#{id.to_s.downcase}.json"), File::FNM_CASEFOLD).first
|
13
|
+
variable = new(file_name, dictionary_root)
|
14
|
+
(variable.errors.size > 0 ? nil : variable)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spout/models/tables/default'
|
2
|
+
|
3
|
+
module Spout
|
4
|
+
module Models
|
5
|
+
module Tables
|
6
|
+
class ChoicesVsChoices < Spout::Models::Tables::Default
|
7
|
+
|
8
|
+
def title
|
9
|
+
"#{@variable.display_name} vs #{@chart_variable.display_name}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def headers
|
13
|
+
[ [""] + filtered_domain_options(@chart_variable).collect(&:display_name) + ["Total"] ]
|
14
|
+
end
|
15
|
+
|
16
|
+
def footers
|
17
|
+
total_values = filtered_domain_options(@chart_variable).collect do |option|
|
18
|
+
total_count = @filtered_subjects.select{|s| s.send(@chart_variable.id) == option.value }.count
|
19
|
+
{ text: (total_count == 0 ? "-" : Spout::Helpers::TableFormatting::format_number(total_count, :count)), style: "font-weight:bold" }
|
20
|
+
end
|
21
|
+
[
|
22
|
+
[{ text: "Total", style: "font-weight:bold" }] + total_values + [{ text: Spout::Helpers::TableFormatting::format_number(@filtered_subjects.count, :count), style: 'font-weight:bold'}]
|
23
|
+
]
|
24
|
+
end
|
25
|
+
|
26
|
+
def rows
|
27
|
+
rows_result = []
|
28
|
+
rows_result = filtered_domain_options(@variable).collect do |option|
|
29
|
+
row_subjects = @filtered_subjects.select{ |s| s.send(@variable.id) == option.value }
|
30
|
+
row_cells = filtered_domain_options(@chart_variable).collect do |chart_option|
|
31
|
+
count = row_subjects.select{ |s| s.send(@chart_variable.id) == chart_option.value }.count
|
32
|
+
count > 0 ? Spout::Helpers::TableFormatting::format_number(count, :count) : { text: '-', class: 'text-muted' }
|
33
|
+
end
|
34
|
+
|
35
|
+
total = row_subjects.count
|
36
|
+
|
37
|
+
[option.display_name] + row_cells + [total == 0 ? { text: '-', class: 'text-muted' } : { text: Spout::Helpers::TableFormatting::format_number(total, :count), style: 'font-weight:bold'}]
|
38
|
+
end
|
39
|
+
|
40
|
+
if @filtered_subjects.select{|s| s.send(@variable.id) == nil }.count > 0
|
41
|
+
unknown_values = filtered_domain_options(@chart_variable).collect do |chart_option|
|
42
|
+
{ text: Spout::Helpers::TableFormatting::format_number(@filtered_subjects.select{ |s| s.send(@chart_variable.id) == chart_option.value and s.send(@variable.id) == nil }.count, :count), class: 'text-muted' }
|
43
|
+
end
|
44
|
+
rows_result << [{ text: 'Unknown', class: 'text-muted'}] + unknown_values + [ { text: Spout::Helpers::TableFormatting::format_number(@filtered_subjects.select{|s| s.send(@variable.id) == nil}.count, :count), style: 'font-weight:bold', class: 'text-muted' } ]
|
45
|
+
end
|
46
|
+
rows_result
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spout/models/tables/default'
|
2
|
+
require 'spout/helpers/array_statistics'
|
3
|
+
|
4
|
+
module Spout
|
5
|
+
module Models
|
6
|
+
module Tables
|
7
|
+
class ChoicesVsNumeric < Spout::Models::Tables::Default
|
8
|
+
|
9
|
+
def title
|
10
|
+
"#{@variable.display_name} vs #{@chart_variable.display_name}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def headers
|
14
|
+
categories = [:quartile_one, :quartile_two, :quartile_three, :quartile_four].collect do |quartile|
|
15
|
+
bucket = @filtered_both_variables_subjects.send(quartile).collect(&@chart_variable.id.to_sym)
|
16
|
+
"#{bucket.min} to #{bucket.max} #{@chart_variable.units}"
|
17
|
+
end
|
18
|
+
|
19
|
+
[ [""] + categories + ["Total"] ]
|
20
|
+
end
|
21
|
+
|
22
|
+
def footers
|
23
|
+
total_values = [:quartile_one, :quartile_two, :quartile_three, :quartile_four].collect do |quartile|
|
24
|
+
{ text: Spout::Helpers::TableFormatting::format_number(@filtered_both_variables_subjects.send(quartile).count, :count), style: "font-weight:bold" }
|
25
|
+
end
|
26
|
+
|
27
|
+
[
|
28
|
+
[{ text: "Total", style: "font-weight:bold" }] + total_values + [{ text: Spout::Helpers::TableFormatting::format_number(@filtered_both_variables_subjects.count, :count), style: 'font-weight:bold'}]
|
29
|
+
]
|
30
|
+
end
|
31
|
+
|
32
|
+
def rows
|
33
|
+
filtered_both_variables_domain_options(@variable).collect do |option|
|
34
|
+
row_subjects = @filtered_both_variables_subjects.select{ |s| s.send(@variable.id) == option.value }
|
35
|
+
|
36
|
+
data = [:quartile_one, :quartile_two, :quartile_three, :quartile_four].collect do |quartile|
|
37
|
+
bucket = @filtered_both_variables_subjects.send(quartile).select{ |s| s.send(@variable.id) == option.value }
|
38
|
+
Spout::Helpers::TableFormatting::format_number(bucket.count, :count)
|
39
|
+
end
|
40
|
+
|
41
|
+
[option.display_name] + data + [{ text: Spout::Helpers::TableFormatting::format_number(row_subjects.count, :count), style: 'font-weight:bold'}]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'spout/models/variable'
|
2
|
+
require 'spout/helpers/table_formatting'
|
3
|
+
|
4
|
+
module Spout
|
5
|
+
module Models
|
6
|
+
module Tables
|
7
|
+
class Default
|
8
|
+
|
9
|
+
attr_reader :variable, :chart_variable, :subjects, :subtitle
|
10
|
+
|
11
|
+
def initialize(variable, chart_variable, subjects, subtitle)
|
12
|
+
@variable = variable
|
13
|
+
@chart_variable = chart_variable
|
14
|
+
@subjects = subjects
|
15
|
+
@subtitle = subtitle
|
16
|
+
|
17
|
+
@filtered_subjects = @subjects.select{ |s| s.send(@chart_variable.id) != nil } rescue @filtered_subjects = []
|
18
|
+
@filtered_both_variables_subjects = subjects.select{ |s| s.send(@variable.id) != nil and s.send(@chart_variable.id) != nil }.sort_by(&@chart_variable.id.to_sym) rescue @filtered_both_variables_subjects = []
|
19
|
+
|
20
|
+
@values = @filtered_subjects.collect(&@variable.id.to_sym).uniq rescue @values = []
|
21
|
+
|
22
|
+
@values_unique = @values.uniq
|
23
|
+
|
24
|
+
@values_both_variables = @filtered_both_variables_subjects.collect(&@variable.id.to_sym).uniq rescue @values_both_variables = []
|
25
|
+
@values_both_variables_unique = @values_both_variables.uniq
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_hash
|
29
|
+
if valid?
|
30
|
+
{ title: title, subtitle: @subtitle, headers: headers, footers: footers, rows: rows }
|
31
|
+
else
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# TODO: Same as graphables/default.rb REFACTOR
|
37
|
+
def valid?
|
38
|
+
if @variable == nil or @chart_variable == nil or @values == []
|
39
|
+
false
|
40
|
+
elsif @variable.type == 'choices' and @variable.domain.options == []
|
41
|
+
false
|
42
|
+
elsif @chart_variable.type == 'choices' and @chart_variable.domain.options == []
|
43
|
+
false
|
44
|
+
else
|
45
|
+
true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def title
|
50
|
+
""
|
51
|
+
end
|
52
|
+
|
53
|
+
def headers
|
54
|
+
[]
|
55
|
+
end
|
56
|
+
|
57
|
+
def footers
|
58
|
+
[]
|
59
|
+
end
|
60
|
+
|
61
|
+
def rows
|
62
|
+
[]
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# Returns variable options that are either:
|
69
|
+
# a) are not missing codes
|
70
|
+
# b) or are marked as missing codes but represented in the dataset
|
71
|
+
def filtered_domain_options(variable)
|
72
|
+
variable.domain.options.select do |o|
|
73
|
+
o.missing != true or (o.missing == true and @values_unique.include?(o.value))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def filtered_both_variables_domain_options(variable)
|
78
|
+
variable.domain.options.select do |o|
|
79
|
+
o.missing != true or (o.missing == true and @values_both_variables_unique.include?(o.value))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|