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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +33 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +106 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +23 -1
- data/Gemfile +2 -0
- data/Gemfile.lock +51 -28
- data/README.md +16 -29
- data/Rakefile +5 -1
- data/bin/flex-station +33 -11
- data/bin/flex-station-linear-regression +19 -37
- data/flex-station-data.gemspec +7 -4
- data/lib/flex_station_data.rb +2 -0
- data/lib/flex_station_data/concerns/callable.rb +33 -0
- data/lib/flex_station_data/concerns/presenter.rb +4 -18
- data/lib/flex_station_data/concerns/service.rb +4 -18
- data/lib/flex_station_data/default_sample_map.rb +33 -0
- data/lib/flex_station_data/linear_regression.rb +2 -0
- data/lib/flex_station_data/plate.rb +14 -3
- data/lib/flex_station_data/presenters/plate_hash.rb +26 -0
- data/lib/flex_station_data/presenters/plates_hash.rb +26 -0
- data/lib/flex_station_data/presenters/sample_hash.rb +47 -0
- data/lib/flex_station_data/presenters/sample_regression_hash.rb +44 -0
- data/lib/flex_station_data/sample.rb +20 -5
- data/lib/flex_station_data/services/compute_mean.rb +2 -0
- data/lib/flex_station_data/services/load_plates.rb +12 -1
- data/lib/flex_station_data/services/parse_plate.rb +6 -4
- data/lib/flex_station_data/services/parse_plate_readings.rb +43 -36
- data/lib/flex_station_data/services/parse_sample_map.rb +47 -0
- data/lib/flex_station_data/services/sample_quality.rb +6 -5
- data/lib/flex_station_data/services/value_quality.rb +5 -1
- data/lib/flex_station_data/version.rb +3 -1
- data/lib/flex_station_data/wells.rb +16 -15
- metadata +31 -19
- data/bin/flex-station-sample-data +0 -54
- data/lib/flex_station_data/presenters/linear_regression/plate_hash.rb +0 -27
- data/lib/flex_station_data/presenters/linear_regression/plates_hash.rb +0 -28
- data/lib/flex_station_data/presenters/linear_regression/sample_hash.rb +0 -47
- data/lib/flex_station_data/presenters/linear_regression/sample_regression_hash.rb +0 -43
- data/lib/flex_station_data/presenters/linear_regression/verbose_sample_csv.rb +0 -28
- data/lib/flex_station_data/presenters/plate_csv.rb +0 -29
- data/lib/flex_station_data/presenters/plates_csv.rb +0 -28
- data/lib/flex_station_data/presenters/sample_csv.rb +0 -56
- data/lib/flex_station_data/readings.rb +0 -16
- 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/
|
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::
|
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))
|
34
|
-
|
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"))
|
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
|
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::
|
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 << (
|
65
|
+
memo << (value != memo.compact.last ? value : nil)
|
80
66
|
end
|
81
67
|
end
|
82
68
|
|
83
|
-
def
|
84
|
-
@
|
85
|
-
result =
|
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
|
-
|
84
|
+
[ hash.keys, *hash.values.transpose ]
|
103
85
|
end
|
104
86
|
|
105
|
-
def
|
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.
|
97
|
+
FlexStationData::LinearRegressionApp.run(*ARGV)
|
data/flex-station-data.gemspec
CHANGED
@@ -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(
|
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.
|
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"
|
data/lib/flex_station_data.rb
CHANGED
@@ -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
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "callable"
|
2
4
|
|
3
5
|
module FlexStationData
|
4
6
|
module Concerns
|
5
|
-
|
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
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "callable"
|
2
4
|
|
3
5
|
module FlexStationData
|
4
6
|
module Concerns
|
5
|
-
|
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,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, :
|
7
|
+
attr_reader :label, :times, :temperatures, :wells, :sample_map
|
4
8
|
|
5
|
-
def initialize(label, times, temperatures,
|
9
|
+
def initialize(label, times, temperatures, wells, sample_map)
|
6
10
|
@label = label
|
7
11
|
@times = times
|
8
12
|
@temperatures = temperatures
|
9
|
-
@
|
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
|