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.
@@ -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