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