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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9643ed99fb5559e0e03c0ce39500e9408ec71b5147ea3a22089224595609a45c
4
- data.tar.gz: ad88d756b2726fb10cc81b82efeaef45cd0703a7308ef9af09853572f28437cc
3
+ metadata.gz: abdebffeaa580bb44163ea7d3ec38098e90a68300d112159e83ff0704bb11096
4
+ data.tar.gz: 04b0d95d5e3cff8c74cfee3a6bd3301913e2b0f2e92a2c9edcf68a0070b317bb
5
5
  SHA512:
6
- metadata.gz: 2874986268b538f9ebaffc882f245588f80c8f39b70ef6b3facd867d321f7019f6bc9512fbdcd0a1e698185bdc4843508819c4f026b8701812a4f96f0c4a9907
7
- data.tar.gz: 8f7d4948985b1ce67137f9db40a2aac1220cfce779f2ec38d1579880f63f4ab29ad911b4faad2d561c0dd68b6b2f592cb81a2065cb64f32c9f42110b7346b3ae
6
+ metadata.gz: be673a116f7e4753bae65720abcbedb3866573bfb8c935abbf025b0af81675edbf74b7025fb33e5e8c2442f9cd21781132a7fa34f4f7e644d6a292e579ed58a3
7
+ data.tar.gz: 92afd6795b00296585b86b449b55bca7bdafd4ff6a3c6dda8b6b9f4fbf727057a0ffbbfb1ecd572a4aeb7d8239b53ffdfc15bdc4564b868f6e55d18de9141123
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- flex-station-data (0.1.1)
4
+ flex-station-data (0.2.0)
5
5
  activesupport
6
6
  linefit
7
7
 
@@ -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/linear_regression"
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
- attr_reader :args
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 call
29
- plates.each do |file, file_plates|
30
- puts "File: #{file.to_path}"
31
- file_plates.each_with_index.map do |plate, plate_index|
32
- puts "Plate: #{plate_index + 1}"
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(&regression_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
- attr_reader :args
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 call
26
- plates.each do |file, file_plates|
27
- puts "File: #{file.to_path}"
28
- file_plates.each_with_index.map do |plate, plate_index|
29
- puts "Plate: #{plate_index + 1}"
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
@@ -5,6 +5,10 @@ module FlexStationData
5
5
  module Service
6
6
  extend ActiveSupport::Concern
7
7
 
8
+ def to_proc
9
+ Proc.new(&method(:call))
10
+ end
11
+
8
12
  class_methods do
9
13
  def call(*args, &block)
10
14
  new(*args).call(&block)
@@ -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(&regression_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
@@ -22,7 +22,9 @@ module FlexStationData
22
22
  end
23
23
 
24
24
  def call
25
- data_blocks.map(&FlexStationData::ParsePlate)
25
+ data_blocks.each_with_index.map do |data_block, index|
26
+ FlexStationData::ParsePlate.call(index + 1, data_block)
27
+ end
26
28
  end
27
29
  end
28
30
  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
@@ -1,3 +1,3 @@
1
1
  module FlexStationData
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  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.1.1
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
- rubyforge_project:
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