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