flex-station-data 0.3.0 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +33 -0
  3. data/.gitignore +2 -0
  4. data/.rubocop.yml +106 -0
  5. data/.ruby-version +1 -0
  6. data/CHANGELOG.md +23 -1
  7. data/Gemfile +2 -0
  8. data/Gemfile.lock +51 -28
  9. data/README.md +16 -29
  10. data/Rakefile +5 -1
  11. data/bin/flex-station +33 -11
  12. data/bin/flex-station-linear-regression +19 -37
  13. data/flex-station-data.gemspec +7 -4
  14. data/lib/flex_station_data.rb +2 -0
  15. data/lib/flex_station_data/concerns/callable.rb +33 -0
  16. data/lib/flex_station_data/concerns/presenter.rb +4 -18
  17. data/lib/flex_station_data/concerns/service.rb +4 -18
  18. data/lib/flex_station_data/default_sample_map.rb +33 -0
  19. data/lib/flex_station_data/linear_regression.rb +2 -0
  20. data/lib/flex_station_data/plate.rb +14 -3
  21. data/lib/flex_station_data/presenters/plate_hash.rb +26 -0
  22. data/lib/flex_station_data/presenters/plates_hash.rb +26 -0
  23. data/lib/flex_station_data/presenters/sample_hash.rb +47 -0
  24. data/lib/flex_station_data/presenters/sample_regression_hash.rb +44 -0
  25. data/lib/flex_station_data/sample.rb +20 -5
  26. data/lib/flex_station_data/services/compute_mean.rb +2 -0
  27. data/lib/flex_station_data/services/load_plates.rb +12 -1
  28. data/lib/flex_station_data/services/parse_plate.rb +6 -4
  29. data/lib/flex_station_data/services/parse_plate_readings.rb +43 -36
  30. data/lib/flex_station_data/services/parse_sample_map.rb +47 -0
  31. data/lib/flex_station_data/services/sample_quality.rb +6 -5
  32. data/lib/flex_station_data/services/value_quality.rb +5 -1
  33. data/lib/flex_station_data/version.rb +3 -1
  34. data/lib/flex_station_data/wells.rb +16 -15
  35. metadata +31 -19
  36. data/bin/flex-station-sample-data +0 -54
  37. data/lib/flex_station_data/presenters/linear_regression/plate_hash.rb +0 -27
  38. data/lib/flex_station_data/presenters/linear_regression/plates_hash.rb +0 -28
  39. data/lib/flex_station_data/presenters/linear_regression/sample_hash.rb +0 -47
  40. data/lib/flex_station_data/presenters/linear_regression/sample_regression_hash.rb +0 -43
  41. data/lib/flex_station_data/presenters/linear_regression/verbose_sample_csv.rb +0 -28
  42. data/lib/flex_station_data/presenters/plate_csv.rb +0 -29
  43. data/lib/flex_station_data/presenters/plates_csv.rb +0 -28
  44. data/lib/flex_station_data/presenters/sample_csv.rb +0 -56
  45. data/lib/flex_station_data/readings.rb +0 -16
  46. data/lib/flex_station_data/services/parse_plate_samples.rb +0 -49
@@ -1,15 +1,13 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
4
+ require "flex_station_data/concerns/callable"
3
5
  require "flex_station_data/services/load_plates"
4
- require "flex_station_data/presenters/plates_csv"
5
- require "flex_station_data/presenters/linear_regression/plates_hash"
6
- require "flex_station_data/presenters/linear_regression/verbose_sample_csv"
7
-
8
- require "pry"
6
+ require "flex_station_data/presenters/plates_hash"
9
7
 
10
8
  module FlexStationData
11
9
  class LinearRegressionApp
12
- include Concerns::Service
10
+ include Concerns::Callable[:run]
13
11
 
14
12
  OPTION_RE = /\A--(\w+(?:-\w+)*)(?:=(.*))?\z/.freeze
15
13
 
@@ -30,15 +28,15 @@ module FlexStationData
30
28
  end
31
29
 
32
30
  def threshold
33
- Float(option(:threshold)) rescue nil
34
- end
35
-
36
- def verbose?
37
- option(:verbose)
31
+ Float(option(:threshold))
32
+ rescue ArgumentError, TypeError
33
+ nil
38
34
  end
39
35
 
40
36
  def min_r_squared
41
- Float(option("min-r-squared")) rescue 0.75
37
+ Float(option("min-r-squared"))
38
+ rescue ArgumentError, TypeError
39
+ 0.75
42
40
  end
43
41
 
44
42
  def files
@@ -51,21 +49,9 @@ module FlexStationData
51
49
  end
52
50
  end
53
51
 
54
- def verbose_csv
55
- plates.flat_map do |file, file_plates|
56
- Presenters::PlatesCsv.present(
57
- file,
58
- file_plates,
59
- sample_presenter: Presenters::LinearRegression::VerboseSampleCsv,
60
- threshold: threshold,
61
- min_r_squared: min_r_squared
62
- )
63
- end
64
- end
65
-
66
- def summary_hashes
52
+ def plate_hashes
67
53
  plates.flat_map do |file, file_plates|
68
- Presenters::LinearRegression::PlatesHash.present(
54
+ Presenters::PlatesHash.present(
69
55
  file,
70
56
  file_plates,
71
57
  threshold: threshold,
@@ -76,13 +62,13 @@ module FlexStationData
76
62
 
77
63
  def clean_values(values)
78
64
  values.each_with_object([]) do |value, memo|
79
- memo << ((value != memo.compact.last) ? value : nil)
65
+ memo << (value != memo.compact.last ? value : nil)
80
66
  end
81
67
  end
82
68
 
83
- def summary_hash
84
- @summary_hash ||= begin
85
- result = summary_hashes.each_with_object({}) do |hash, memo|
69
+ def hash
70
+ @hash ||= begin
71
+ result = plate_hashes.each_with_object({}) do |hash, memo|
86
72
  hash.each do |header, value|
87
73
  memo[header] ||= []
88
74
  memo[header] << value
@@ -94,15 +80,11 @@ module FlexStationData
94
80
  end
95
81
  end
96
82
 
97
- def summary_csv
98
- [ summary_hash.keys, *summary_hash.values.transpose ]
99
- end
100
-
101
83
  def csv
102
- verbose? ? verbose_csv : summary_csv
84
+ [ hash.keys, *hash.values.transpose ]
103
85
  end
104
86
 
105
- def call
87
+ def run
106
88
  CSV do |out|
107
89
  csv.each do |row|
108
90
  out << row
@@ -112,4 +94,4 @@ module FlexStationData
112
94
  end
113
95
  end
114
96
 
115
- FlexStationData::LinearRegressionApp.call(*ARGV)
97
+ FlexStationData::LinearRegressionApp.run(*ARGV)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  lib = File.expand_path("lib", __dir__)
2
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
5
  require "flex_station_data/version"
@@ -21,19 +23,20 @@ Gem::Specification.new do |spec|
21
23
 
22
24
  # Specify which files should be added to the gem when it is released.
23
25
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
26
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
25
27
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
28
  end
27
29
  spec.bindir = "bin"
28
30
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
29
31
  spec.require_paths = ["lib"]
30
32
 
31
- spec.required_ruby_version = ">= 2.4.4"
33
+ spec.required_ruby_version = ">= 2.7.1"
32
34
 
33
35
  spec.add_development_dependency "bundler", "~> 2.0"
34
- spec.add_development_dependency "rake", "~> 10.0"
35
- spec.add_development_dependency "rspec", "~> 3.8"
36
36
  spec.add_development_dependency "pry"
37
+ spec.add_development_dependency "rake", "~> 13.0"
38
+ spec.add_development_dependency "rspec", "~> 3.8"
39
+ spec.add_development_dependency "rubocop-rspec"
37
40
 
38
41
  spec.add_runtime_dependency "activesupport"
39
42
  spec.add_runtime_dependency "linefit"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "flex_station_data/version"
2
4
 
3
5
  module FlexStationData
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexStationData
4
+ module Concerns
5
+ module Callable
6
+ class << self
7
+ def included(base)
8
+ base.include with(:call)
9
+ end
10
+
11
+ def with(verb)
12
+ callable_modules[verb.to_sym] ||= Module.new do
13
+ define_singleton_method(:included) do |base|
14
+ base.define_singleton_method(verb) do |*args|
15
+ new(*args).public_send(verb)
16
+ end
17
+
18
+ base.define_singleton_method(:to_proc) do
19
+ method(verb).to_proc
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ alias [] with
26
+
27
+ def callable_modules
28
+ @callable_modules ||= {}
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,23 +1,9 @@
1
- require "active_support/concern"
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "callable"
2
4
 
3
5
  module FlexStationData
4
6
  module Concerns
5
- module Presenter
6
- extend ActiveSupport::Concern
7
-
8
- def to_proc
9
- Proc.new(&method(:present))
10
- end
11
-
12
- class_methods do
13
- def present(*args, &block)
14
- new(*args).present(&block)
15
- end
16
-
17
- def to_proc
18
- Proc.new(&method(:present))
19
- end
20
- end
21
- end
7
+ Presenter = Callable[:present]
22
8
  end
23
9
  end
@@ -1,23 +1,9 @@
1
- require "active_support/concern"
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "callable"
2
4
 
3
5
  module FlexStationData
4
6
  module Concerns
5
- module Service
6
- extend ActiveSupport::Concern
7
-
8
- def to_proc
9
- Proc.new(&method(:call))
10
- end
11
-
12
- class_methods do
13
- def call(*args, &block)
14
- new(*args).call(&block)
15
- end
16
-
17
- def to_proc
18
- Proc.new(&method(:call))
19
- end
20
- end
21
- end
7
+ Service = Callable
22
8
  end
23
9
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexStationData
4
+ class DefaultSampleMap
5
+ attr_reader :rows, :columns, :wells_per_sample
6
+
7
+ def initialize(rows, columns, wells_per_sample)
8
+ @rows = rows
9
+ @columns = columns
10
+ @wells_per_sample = wells_per_sample
11
+ end
12
+
13
+ def [](sample_label)
14
+ sample_label = Integer(sample_label)
15
+ map[sample_label] ||= map_sample(sample_label)
16
+ end
17
+
18
+ private
19
+
20
+ def map_sample(sample_label)
21
+ column, row = (sample_label - 1).divmod(rows)
22
+ row_label = ("A".ord + row).chr
23
+ base_column = (column * wells_per_sample) + 1
24
+ (0...wells_per_sample).map do |column_offset|
25
+ [ row_label, base_column + column_offset ].join("")
26
+ end
27
+ end
28
+
29
+ def map
30
+ @map ||= {}
31
+ end
32
+ end
33
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext"
2
4
  require "linefit"
3
5
 
@@ -1,12 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "flex_station_data/sample"
4
+
1
5
  module FlexStationData
2
6
  class Plate
3
- attr_reader :label, :times, :temperatures, :samples
7
+ attr_reader :label, :times, :temperatures, :wells, :sample_map
4
8
 
5
- def initialize(label, times, temperatures, samples)
9
+ def initialize(label, times, temperatures, wells, sample_map)
6
10
  @label = label
7
11
  @times = times
8
12
  @temperatures = temperatures
9
- @samples = samples
13
+ @wells = wells
14
+ @sample_map = sample_map
15
+ end
16
+
17
+ def samples
18
+ @samples ||= sample_map.map do |label, well_labels|
19
+ Sample.new(label, well_labels, self)
20
+ end
10
21
  end
11
22
  end
12
23
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "flex_station_data/presenters/sample_hash"
4
+
5
+ module FlexStationData
6
+ module Presenters
7
+ class PlateHash
8
+ include Concerns::Presenter
9
+
10
+ attr_reader :plate, :options
11
+
12
+ delegate :times, :samples, to: :plate
13
+
14
+ def initialize(plate, **options)
15
+ @plate = plate
16
+ @options = options
17
+ end
18
+
19
+ def present
20
+ samples.map do |sample|
21
+ { "plate" => plate.label }.merge(SampleHash.present(times, sample, **options))
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "flex_station_data/presenters/plate_hash"
4
+
5
+ module FlexStationData
6
+ module Presenters
7
+ class PlatesHash
8
+ include Concerns::Presenter
9
+
10
+ attr_reader :file, :plates, :options
11
+
12
+ def initialize(file, plates, **options)
13
+ @file = file
14
+ @plates = plates
15
+ @options = options
16
+ end
17
+
18
+ def present
19
+ base = { "file" => file.basename.to_s }
20
+ plates.flat_map do |plate|
21
+ PlateHash.present(plate, **options).map(&base.method(:merge))
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "flex_station_data/concerns/presenter"
4
+ require "flex_station_data/services/sample_quality"
5
+ require "flex_station_data/presenters/sample_regression_hash"
6
+
7
+ module FlexStationData
8
+ module Presenters
9
+ class SampleHash
10
+ include Concerns::Presenter
11
+
12
+ attr_reader :times, :sample, :quality_control, :options
13
+
14
+ def initialize(times, sample, **options)
15
+ @times = times
16
+ @sample = sample
17
+ @options = options
18
+ end
19
+
20
+ def errors
21
+ @errors ||= SampleQuality.call(sample, **options).reject(&:good?)
22
+ end
23
+
24
+ def errors?
25
+ errors.present?
26
+ end
27
+
28
+ def errors_hash
29
+ { "error" => errors.first&.to_s }
30
+ end
31
+
32
+ def wells_hash
33
+ { "wells" => sample.wells.join(", ") }
34
+ end
35
+
36
+ def regression_hash
37
+ return SampleRegressionHash.headers.zip([]).to_h if errors?
38
+
39
+ SampleRegressionHash.present(times, sample.mean, **options).transform_values(&:first)
40
+ end
41
+
42
+ def present
43
+ { "sample" => sample.label }.merge(wells_hash).merge(errors_hash).merge(regression_hash)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "flex_station_data/concerns/presenter"
4
+ require "flex_station_data/linear_regression"
5
+
6
+ module FlexStationData
7
+ module Presenters
8
+ class SampleRegressionHash
9
+ include Concerns::Presenter
10
+
11
+ PRODUCTS = {
12
+ slope: "slope",
13
+ intercept: "intercept",
14
+ r_squared: "R²",
15
+ quality: "quality"
16
+ }.freeze
17
+
18
+ attr_reader :times, :sample_values, :min_r_squared, :options
19
+
20
+ def initialize(times, *sample_values, min_r_squared: nil, **options)
21
+ @times = times
22
+ @sample_values = sample_values
23
+ @min_r_squared = min_r_squared
24
+ @options = options
25
+ end
26
+
27
+ def sample_regressions
28
+ @sample_regressions ||= sample_values.map do |values|
29
+ FlexStationData::LinearRegression.new(times, values, min_r_squared: min_r_squared)
30
+ end
31
+ end
32
+
33
+ def present
34
+ PRODUCTS.each_with_object({}) do |(method, label), memo|
35
+ memo[label] = sample_regressions.map(&method)
36
+ end
37
+ end
38
+
39
+ def self.headers
40
+ PRODUCTS.values
41
+ end
42
+ end
43
+ end
44
+ end