flex-station-data 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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