flex-station-data 0.1.1 → 0.2.0
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/Gemfile.lock +1 -1
- data/bin/flex-station-linear-regression +41 -25
- data/bin/flex-station-sample-data +39 -18
- data/lib/flex_station_data/concerns/presenter.rb +23 -0
- data/lib/flex_station_data/concerns/service.rb +4 -0
- data/lib/flex_station_data/plate.rb +3 -2
- data/lib/flex_station_data/presenters/plate_csv.rb +23 -0
- data/lib/flex_station_data/presenters/plates_csv.rb +21 -0
- data/lib/flex_station_data/presenters/sample_csv.rb +55 -0
- data/lib/flex_station_data/presenters/sample_linear_regression_csv.rb +29 -0
- data/lib/flex_station_data/services/load_plates.rb +3 -1
- data/lib/flex_station_data/services/parse_plate.rb +4 -3
- data/lib/flex_station_data/services/sample_quality.rb +18 -0
- data/lib/flex_station_data/services/value_quality.rb +73 -0
- data/lib/flex_station_data/version.rb +1 -1
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: abdebffeaa580bb44163ea7d3ec38098e90a68300d112159e83ff0704bb11096
|
4
|
+
data.tar.gz: 04b0d95d5e3cff8c74cfee3a6bd3301913e2b0f2e92a2c9edcf68a0070b317bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be673a116f7e4753bae65720abcbedb3866573bfb8c935abbf025b0af81675edbf74b7025fb33e5e8c2442f9cd21781132a7fa34f4f7e644d6a292e579ed58a3
|
7
|
+
data.tar.gz: 92afd6795b00296585b86b449b55bca7bdafd4ff6a3c6dda8b6b9f4fbf727057a0ffbbfb1ecd572a4aeb7d8239b53ffdfc15bdc4564b868f6e55d18de9141123
|
data/Gemfile.lock
CHANGED
@@ -3,16 +3,41 @@
|
|
3
3
|
#!/usr/bin/env ruby
|
4
4
|
|
5
5
|
require "flex_station_data/services/load_plates"
|
6
|
-
require "flex_station_data/
|
6
|
+
require "flex_station_data/presenters/plates_csv"
|
7
|
+
require "flex_station_data/presenters/plate_csv"
|
8
|
+
require "flex_station_data/presenters/sample_linear_regression_csv"
|
7
9
|
|
8
10
|
module FlexStationData
|
9
11
|
class LinearRegressionApp
|
10
12
|
include Concerns::Service
|
11
13
|
|
12
|
-
|
14
|
+
OPTION_RE = /\A--(\w+(?:-\w+)*)(?:=(.*))?\z/.freeze
|
15
|
+
|
16
|
+
attr_reader :args, :options
|
13
17
|
|
14
18
|
def initialize(*args)
|
15
|
-
@args = args
|
19
|
+
@options, @args = args.partition { |arg| arg =~ OPTION_RE }
|
20
|
+
@options.map! do |option|
|
21
|
+
option.scan(OPTION_RE).first
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def threshold
|
26
|
+
name, value = options.reverse.detect { |name, value| name == "threshold" }
|
27
|
+
return nil if name.blank?
|
28
|
+
|
29
|
+
Float(value)
|
30
|
+
end
|
31
|
+
|
32
|
+
def value_quality_control
|
33
|
+
@value_quality_control ||= begin
|
34
|
+
value_threshold = threshold
|
35
|
+
->(value) { ValueQuality.call(value, threshold: value_threshold) }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def sample_quality_control
|
40
|
+
->(sample) { SampleQuality.call(sample, value_quality_control: value_quality_control) }
|
16
41
|
end
|
17
42
|
|
18
43
|
def files
|
@@ -25,32 +50,23 @@ module FlexStationData
|
|
25
50
|
end
|
26
51
|
end
|
27
52
|
|
28
|
-
def
|
29
|
-
plates.
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
plate.samples.each do |sample|
|
34
|
-
readings_labels = sample.readings.map(&:label)
|
35
|
-
puts "Sample #{sample.label}"
|
36
|
-
CSV do |out|
|
37
|
-
readings = [*sample.readings, sample.mean]
|
38
|
-
out << ["time", *readings.map(&:label)]
|
39
|
-
plate.times.zip(*readings.map(&:values)).each do |row|
|
40
|
-
out << row
|
41
|
-
end
|
42
|
-
|
43
|
-
regression_factory = LinearRegression.method(:new).curry(2)[plate.times]
|
44
|
-
regressions = readings.map(&:values).map(®ression_factory)
|
45
|
-
out << ["slope", *regressions.map(&:slope)]
|
46
|
-
out << ["R²", *regressions.map(&:r_squared)]
|
47
|
-
|
48
|
-
out << []
|
49
|
-
end
|
53
|
+
def csv
|
54
|
+
plates.flat_map do |file, file_plates|
|
55
|
+
Presenters::PlatesCsv.present(file, file_plates) do |plate|
|
56
|
+
Presenters::PlateCsv.present(plate) do |times, sample|
|
57
|
+
Presenters::SampleLinearRegressionCsv.present(times, sample, quality_control: sample_quality_control)
|
50
58
|
end
|
51
59
|
end
|
52
60
|
end
|
53
61
|
end
|
62
|
+
|
63
|
+
def call
|
64
|
+
CSV do |out|
|
65
|
+
csv.each do |row|
|
66
|
+
out << row
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
54
70
|
end
|
55
71
|
end
|
56
72
|
|
@@ -1,15 +1,39 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require "flex_station_data/services/load_plates"
|
4
|
+
require "flex_station_data/presenters/plates_csv"
|
4
5
|
|
5
6
|
module FlexStationData
|
6
7
|
class SampleDataApp
|
7
8
|
include Concerns::Service
|
8
9
|
|
9
|
-
|
10
|
+
OPTION_RE = /\A--(\w+(?:-\w+)*)(?:=(.*))?\z/.freeze
|
11
|
+
|
12
|
+
attr_reader :args, :options
|
10
13
|
|
11
14
|
def initialize(*args)
|
12
|
-
@args = args
|
15
|
+
@options, @args = args.partition { |arg| arg =~ OPTION_RE }
|
16
|
+
@options.map! do |option|
|
17
|
+
option.scan(OPTION_RE).first
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def threshold
|
22
|
+
name, value = options.reverse.detect { |name, value| name == "threshold" }
|
23
|
+
return nil if name.blank?
|
24
|
+
|
25
|
+
Float(value)
|
26
|
+
end
|
27
|
+
|
28
|
+
def value_quality_control
|
29
|
+
@value_quality_control ||= begin
|
30
|
+
value_threshold = threshold
|
31
|
+
->(value) { ValueQuality.call(value, threshold: value_threshold) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def sample_quality_control
|
36
|
+
->(sample) { SampleQuality.call(sample, value_quality_control: value_quality_control) }
|
13
37
|
end
|
14
38
|
|
15
39
|
def files
|
@@ -22,26 +46,23 @@ module FlexStationData
|
|
22
46
|
end
|
23
47
|
end
|
24
48
|
|
25
|
-
def
|
26
|
-
plates.
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
plate.samples.each do |sample|
|
31
|
-
readings_labels = sample.readings.map(&:label)
|
32
|
-
puts "Sample #{sample.label}"
|
33
|
-
CSV do |out|
|
34
|
-
mean = sample.mean
|
35
|
-
out << ["time", *sample.readings.map(&:label), mean.label]
|
36
|
-
plate.times.zip(*sample.readings.map(&:values), mean.values).each do |row|
|
37
|
-
out << row
|
38
|
-
end
|
39
|
-
out << []
|
40
|
-
end
|
49
|
+
def csv
|
50
|
+
plates.flat_map do |file, file_plates|
|
51
|
+
Presenters::PlatesCsv.present(file, file_plates) do |plate|
|
52
|
+
Presenters::PlateCsv.present(plate) do |times, sample|
|
53
|
+
Presenters::SampleCsv.present(times, sample, quality_control: sample_quality_control)
|
41
54
|
end
|
42
55
|
end
|
43
56
|
end
|
44
57
|
end
|
58
|
+
|
59
|
+
def call
|
60
|
+
CSV do |out|
|
61
|
+
csv.each do |row|
|
62
|
+
out << row
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
45
66
|
end
|
46
67
|
end
|
47
68
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
|
3
|
+
module FlexStationData
|
4
|
+
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
|
22
|
+
end
|
23
|
+
end
|
@@ -1,8 +1,9 @@
|
|
1
1
|
module FlexStationData
|
2
2
|
class Plate
|
3
|
-
attr_reader :times, :temperatures, :samples
|
3
|
+
attr_reader :label, :times, :temperatures, :samples
|
4
4
|
|
5
|
-
def initialize(times, temperatures, samples)
|
5
|
+
def initialize(label, times, temperatures, samples)
|
6
|
+
@label = label
|
6
7
|
@times = times
|
7
8
|
@temperatures = temperatures
|
8
9
|
@samples = samples
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "flex_station_data/presenters/sample_csv"
|
2
|
+
|
3
|
+
module FlexStationData
|
4
|
+
module Presenters
|
5
|
+
class PlateCsv
|
6
|
+
include Concerns::Presenter
|
7
|
+
|
8
|
+
attr_reader :plate
|
9
|
+
|
10
|
+
delegate :times, :samples, to: :plate
|
11
|
+
|
12
|
+
def initialize(plate)
|
13
|
+
@plate = plate
|
14
|
+
end
|
15
|
+
|
16
|
+
def present(&sample_presenter)
|
17
|
+
sample_presenter ||= SampleCsv
|
18
|
+
sample_presenter = sample_presenter.curry(2)[times]
|
19
|
+
[ ["Plate #{plate.label}"], *samples.flat_map(&sample_presenter) ]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "flex_station_data/presenters/plate_csv"
|
2
|
+
|
3
|
+
module FlexStationData
|
4
|
+
module Presenters
|
5
|
+
class PlatesCsv
|
6
|
+
include Concerns::Presenter
|
7
|
+
|
8
|
+
attr_reader :file, :plates
|
9
|
+
|
10
|
+
def initialize(file, plates)
|
11
|
+
@file = file
|
12
|
+
@plates = plates
|
13
|
+
end
|
14
|
+
|
15
|
+
def present(&plate_presenter)
|
16
|
+
plate_presenter ||= PlateCsv
|
17
|
+
[ ["File: #{file.to_path}"], *plates.flat_map(&plate_presenter) ]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require "flex_station_data/concerns/presenter"
|
2
|
+
require "flex_station_data/readings"
|
3
|
+
require "flex_station_data/services/sample_quality"
|
4
|
+
|
5
|
+
module FlexStationData
|
6
|
+
module Presenters
|
7
|
+
class SampleCsv
|
8
|
+
include Concerns::Presenter
|
9
|
+
|
10
|
+
attr_reader :times, :sample, :quality_control
|
11
|
+
|
12
|
+
def initialize(times, sample, quality_control: SampleQuality)
|
13
|
+
@times = times
|
14
|
+
@sample = sample
|
15
|
+
@quality_control = quality_control
|
16
|
+
end
|
17
|
+
|
18
|
+
def readings
|
19
|
+
@readings ||= [ Readings.new("time", times), *sample.readings, sample.mean ]
|
20
|
+
end
|
21
|
+
|
22
|
+
def headers
|
23
|
+
readings.map(&:label)
|
24
|
+
end
|
25
|
+
|
26
|
+
def rows
|
27
|
+
readings.map(&:values).transpose
|
28
|
+
end
|
29
|
+
|
30
|
+
def label
|
31
|
+
"Sample #{sample.label}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def errors
|
35
|
+
@errors ||= quality_control.call(sample).reject(&:good?)
|
36
|
+
end
|
37
|
+
|
38
|
+
def errors?
|
39
|
+
errors.present?
|
40
|
+
end
|
41
|
+
|
42
|
+
def errors_csv
|
43
|
+
errors.map(&:to_s).map(&method(:Array))
|
44
|
+
end
|
45
|
+
|
46
|
+
def values_csv
|
47
|
+
[ headers, *rows ]
|
48
|
+
end
|
49
|
+
|
50
|
+
def present
|
51
|
+
[ [label] ] + (errors? ? errors_csv : values_csv) + [ [] ]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "flex_station_data/presenters/sample_csv"
|
2
|
+
require "flex_station_data/linear_regression"
|
3
|
+
|
4
|
+
module FlexStationData
|
5
|
+
module Presenters
|
6
|
+
class SampleLinearRegressionCsv < SampleCsv
|
7
|
+
include Concerns::Presenter
|
8
|
+
|
9
|
+
def regression_factory
|
10
|
+
LinearRegression.method(:new).curry(2)[times]
|
11
|
+
end
|
12
|
+
|
13
|
+
def regressions
|
14
|
+
[ *sample.readings, sample.mean ].map(&:values).map(®ression_factory)
|
15
|
+
end
|
16
|
+
|
17
|
+
def regressions_csv
|
18
|
+
[
|
19
|
+
[ "slope", *regressions.map(&:slope) ],
|
20
|
+
[ "R²", *regressions.map(&:r_squared) ]
|
21
|
+
]
|
22
|
+
end
|
23
|
+
|
24
|
+
def values_csv
|
25
|
+
super + regressions_csv
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -8,9 +8,10 @@ module FlexStationData
|
|
8
8
|
class ParsePlate
|
9
9
|
include Concerns::Service
|
10
10
|
|
11
|
-
attr_reader :plate_data
|
11
|
+
attr_reader :label, :plate_data
|
12
12
|
|
13
|
-
def initialize(plate_data)
|
13
|
+
def initialize(label, plate_data)
|
14
|
+
@label = label
|
14
15
|
@plate_data = plate_data
|
15
16
|
end
|
16
17
|
|
@@ -21,7 +22,7 @@ module FlexStationData
|
|
21
22
|
def call
|
22
23
|
times, temperatures, wells = ParsePlateReadings.call(data_blocks[0])
|
23
24
|
samples = ParsePlateSamples.call(data_blocks[1], wells)
|
24
|
-
Plate.new(times, temperatures, samples)
|
25
|
+
Plate.new(label, times, temperatures, samples)
|
25
26
|
end
|
26
27
|
end
|
27
28
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "flex_station_data/services/value_quality"
|
2
|
+
|
3
|
+
module FlexStationData
|
4
|
+
class SampleQuality
|
5
|
+
include Concerns::Service
|
6
|
+
|
7
|
+
attr_reader :sample, :value_quality_control
|
8
|
+
|
9
|
+
def initialize(sample, value_quality_control: ValueQuality)
|
10
|
+
@sample = sample
|
11
|
+
@value_quality_control = value_quality_control
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
sample.readings.flat_map(&:values).map(&value_quality_control).uniq(&:to_s)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require "singleton"
|
2
|
+
require "active_support/core_ext"
|
3
|
+
|
4
|
+
module FlexStationData
|
5
|
+
class ValueQuality
|
6
|
+
include Concerns::Service
|
7
|
+
|
8
|
+
class Good
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
def good?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
"good"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Bad
|
21
|
+
attr_reader :description
|
22
|
+
|
23
|
+
def initialize(description)
|
24
|
+
@description ||= description
|
25
|
+
end
|
26
|
+
|
27
|
+
def good?
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
description
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :value, :threshold
|
37
|
+
|
38
|
+
def initialize(value, threshold: nil)
|
39
|
+
@value = value
|
40
|
+
@threshold = threshold
|
41
|
+
end
|
42
|
+
|
43
|
+
def no_data?
|
44
|
+
value.blank?
|
45
|
+
end
|
46
|
+
|
47
|
+
def saturated?
|
48
|
+
value == "#SAT"
|
49
|
+
end
|
50
|
+
|
51
|
+
def invalid?
|
52
|
+
!(no_data? || saturated? || value.is_a?(Numeric))
|
53
|
+
end
|
54
|
+
|
55
|
+
def below_threshold?
|
56
|
+
threshold.present? && value.is_a?(Numeric) && value < threshold
|
57
|
+
end
|
58
|
+
|
59
|
+
def call
|
60
|
+
if no_data?
|
61
|
+
Bad.new("No data")
|
62
|
+
elsif saturated?
|
63
|
+
Bad.new("Saturated")
|
64
|
+
elsif invalid?
|
65
|
+
Bad.new("Invalid data")
|
66
|
+
elsif below_threshold?
|
67
|
+
Bad.new("Below threshold")
|
68
|
+
else
|
69
|
+
Good.instance
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flex-station-data
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Carney
|
@@ -117,9 +117,14 @@ files:
|
|
117
117
|
- bin/flex-station-sample-data
|
118
118
|
- flex-station-data.gemspec
|
119
119
|
- lib/flex_station_data.rb
|
120
|
+
- lib/flex_station_data/concerns/presenter.rb
|
120
121
|
- lib/flex_station_data/concerns/service.rb
|
121
122
|
- lib/flex_station_data/linear_regression.rb
|
122
123
|
- lib/flex_station_data/plate.rb
|
124
|
+
- lib/flex_station_data/presenters/plate_csv.rb
|
125
|
+
- lib/flex_station_data/presenters/plates_csv.rb
|
126
|
+
- lib/flex_station_data/presenters/sample_csv.rb
|
127
|
+
- lib/flex_station_data/presenters/sample_linear_regression_csv.rb
|
123
128
|
- lib/flex_station_data/readings.rb
|
124
129
|
- lib/flex_station_data/sample.rb
|
125
130
|
- lib/flex_station_data/services/compute_mean.rb
|
@@ -127,6 +132,8 @@ files:
|
|
127
132
|
- lib/flex_station_data/services/parse_plate.rb
|
128
133
|
- lib/flex_station_data/services/parse_plate_readings.rb
|
129
134
|
- lib/flex_station_data/services/parse_plate_samples.rb
|
135
|
+
- lib/flex_station_data/services/sample_quality.rb
|
136
|
+
- lib/flex_station_data/services/value_quality.rb
|
130
137
|
- lib/flex_station_data/version.rb
|
131
138
|
- lib/flex_station_data/wells.rb
|
132
139
|
homepage: https://github.com/johncarney/flex-station-data
|
@@ -150,8 +157,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
150
157
|
- !ruby/object:Gem::Version
|
151
158
|
version: '0'
|
152
159
|
requirements: []
|
153
|
-
|
154
|
-
rubygems_version: 2.7.6
|
160
|
+
rubygems_version: 3.0.3
|
155
161
|
signing_key:
|
156
162
|
specification_version: 4
|
157
163
|
summary: Data analysis tool for FlexStation microplate reader
|