flex-station-data 0.3.0 → 1.0.2

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