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.
@@ -4,11 +4,12 @@
4
4
  # "code_book": "Baseline-Visit-Intake-Questionnaire.pdf"
5
5
  # }
6
6
 
7
- require 'json'
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
@@ -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