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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +33 -0
  3. data/.gitignore +2 -0
  4. data/.rubocop.yml +106 -0
  5. data/.ruby-version +1 -0
  6. data/CHANGELOG.md +23 -1
  7. data/Gemfile +2 -0
  8. data/Gemfile.lock +51 -28
  9. data/README.md +16 -29
  10. data/Rakefile +5 -1
  11. data/bin/flex-station +33 -11
  12. data/bin/flex-station-linear-regression +19 -37
  13. data/flex-station-data.gemspec +7 -4
  14. data/lib/flex_station_data.rb +2 -0
  15. data/lib/flex_station_data/concerns/callable.rb +33 -0
  16. data/lib/flex_station_data/concerns/presenter.rb +4 -18
  17. data/lib/flex_station_data/concerns/service.rb +4 -18
  18. data/lib/flex_station_data/default_sample_map.rb +33 -0
  19. data/lib/flex_station_data/linear_regression.rb +2 -0
  20. data/lib/flex_station_data/plate.rb +14 -3
  21. data/lib/flex_station_data/presenters/plate_hash.rb +26 -0
  22. data/lib/flex_station_data/presenters/plates_hash.rb +26 -0
  23. data/lib/flex_station_data/presenters/sample_hash.rb +47 -0
  24. data/lib/flex_station_data/presenters/sample_regression_hash.rb +44 -0
  25. data/lib/flex_station_data/sample.rb +20 -5
  26. data/lib/flex_station_data/services/compute_mean.rb +2 -0
  27. data/lib/flex_station_data/services/load_plates.rb +12 -1
  28. data/lib/flex_station_data/services/parse_plate.rb +6 -4
  29. data/lib/flex_station_data/services/parse_plate_readings.rb +43 -36
  30. data/lib/flex_station_data/services/parse_sample_map.rb +47 -0
  31. data/lib/flex_station_data/services/sample_quality.rb +6 -5
  32. data/lib/flex_station_data/services/value_quality.rb +5 -1
  33. data/lib/flex_station_data/version.rb +3 -1
  34. data/lib/flex_station_data/wells.rb +16 -15
  35. metadata +31 -19
  36. data/bin/flex-station-sample-data +0 -54
  37. data/lib/flex_station_data/presenters/linear_regression/plate_hash.rb +0 -27
  38. data/lib/flex_station_data/presenters/linear_regression/plates_hash.rb +0 -28
  39. data/lib/flex_station_data/presenters/linear_regression/sample_hash.rb +0 -47
  40. data/lib/flex_station_data/presenters/linear_regression/sample_regression_hash.rb +0 -43
  41. data/lib/flex_station_data/presenters/linear_regression/verbose_sample_csv.rb +0 -28
  42. data/lib/flex_station_data/presenters/plate_csv.rb +0 -29
  43. data/lib/flex_station_data/presenters/plates_csv.rb +0 -28
  44. data/lib/flex_station_data/presenters/sample_csv.rb +0 -56
  45. data/lib/flex_station_data/readings.rb +0 -16
  46. data/lib/flex_station_data/services/parse_plate_samples.rb +0 -49
@@ -1,54 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "flex_station_data/services/load_plates"
4
- require "flex_station_data/presenters/plates_csv"
5
-
6
- module FlexStationData
7
- class SampleDataApp
8
- include Concerns::Service
9
-
10
- OPTION_RE = /\A--(\w+(?:-\w+)*)(?:=(.*))?\z/.freeze
11
-
12
- attr_reader :args, :options
13
-
14
- def initialize(*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 files
29
- @files ||= args.map { |arg| Pathname(arg) }
30
- end
31
-
32
- def plates
33
- @plates ||= files.map do |file|
34
- [ file, LoadPlates.call(file) ]
35
- end
36
- end
37
-
38
- def csv
39
- plates.flat_map do |file, file_plates|
40
- Presenters::PlatesCsv.present(file, file_plates, threshold: threshold)
41
- end
42
- end
43
-
44
- def call
45
- CSV do |out|
46
- csv.each do |row|
47
- out << row
48
- end
49
- end
50
- end
51
- end
52
- end
53
-
54
- FlexStationData::SampleDataApp.call(*ARGV)
@@ -1,27 +0,0 @@
1
- require "flex_station_data/presenters/linear_regression/sample_hash"
2
-
3
- module FlexStationData
4
- module Presenters
5
- module LinearRegression
6
- class PlateHash
7
- include Concerns::Presenter
8
-
9
- attr_reader :plate, :sample_presenter, :options
10
-
11
- delegate :times, :samples, to: :plate
12
-
13
- def initialize(plate, sample_presenter: SampleHash, **options)
14
- @plate = plate
15
- @sample_presenter = sample_presenter
16
- @options = options
17
- end
18
-
19
- def present
20
- samples.map do |sample|
21
- { "plate" => plate.label }.merge(sample_presenter.present(times, sample, **options))
22
- end
23
- end
24
- end
25
- end
26
- end
27
- end
@@ -1,28 +0,0 @@
1
- require "flex_station_data/presenters/linear_regression/plate_hash"
2
-
3
- module FlexStationData
4
- module Presenters
5
- module LinearRegression
6
- class PlatesHash
7
- include Concerns::Presenter
8
-
9
- attr_reader :file, :plates, :plate_presenter, :options
10
-
11
- def initialize(file, plates, plate_presenter: PlateHash, **options)
12
- @file = file
13
- @plates = plates
14
- @plate_presenter = plate_presenter
15
- @options = options
16
- end
17
-
18
- def present
19
- plates.flat_map do |plate|
20
- plate_presenter.present(plate, **options).map do |plate_hash|
21
- { "file" => file.basename.to_s }.merge(plate_hash)
22
- end
23
- end
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,47 +0,0 @@
1
- require "flex_station_data/presenters/sample_csv"
2
- require "flex_station_data/presenters/linear_regression/sample_regression_hash"
3
-
4
- module FlexStationData
5
- module Presenters
6
- module LinearRegression
7
- class SampleHash
8
- include Concerns::Presenter
9
-
10
- attr_reader :times, :sample, :quality_control, :options
11
-
12
- def initialize(times, sample, quality_control: SampleQuality, **options)
13
- @times = times
14
- @sample = sample
15
- @quality_control = quality_control
16
- @options = options
17
- end
18
-
19
- def errors
20
- @errors ||= quality_control.call(sample, **options).reject(&:good?)
21
- end
22
-
23
- def errors?
24
- errors.present?
25
- end
26
-
27
- def errors_hash
28
- { "error" => errors.first&.to_s }
29
- end
30
-
31
- def wells_hash
32
- { "wells" => sample.readings.map(&:label).join(", ") }
33
- end
34
-
35
- def regression_hash
36
- return SampleRegressionHash.headers.zip([]).to_h if errors?
37
-
38
- SampleRegressionHash.present(times, sample.mean.values, **options).transform_values(&:first)
39
- end
40
-
41
- def present
42
- { "sample" => sample.label }.merge(wells_hash).merge(errors_hash).merge(regression_hash)
43
- end
44
- end
45
- end
46
- end
47
- end
@@ -1,43 +0,0 @@
1
- require "flex_station_data/linear_regression"
2
-
3
- module FlexStationData
4
- module Presenters
5
- module LinearRegression
6
- class SampleRegressionHash
7
- include Concerns::Presenter
8
-
9
- PRODUCTS = {
10
- slope: "slope",
11
- intercept: "intercept",
12
- r_squared: "R²",
13
- quality: "quality"
14
- }.freeze
15
-
16
- attr_reader :times, :sample_values, :min_r_squared, :options
17
-
18
- def initialize(times, *sample_values, min_r_squared: nil, **options)
19
- @times = times
20
- @sample_values = sample_values
21
- @min_r_squared = min_r_squared
22
- @options = options
23
- end
24
-
25
- def sample_regressions
26
- @sample_regressions ||= sample_values.map do |values|
27
- FlexStationData::LinearRegression.new(times, values, min_r_squared: min_r_squared)
28
- end
29
- end
30
-
31
- def present
32
- PRODUCTS.each_with_object({}) do |(method, label), memo|
33
- memo[label] = sample_regressions.map(&method)
34
- end
35
- end
36
-
37
- def self.headers
38
- PRODUCTS.values
39
- end
40
- end
41
- end
42
- end
43
- end
@@ -1,28 +0,0 @@
1
- require "flex_station_data/presenters/sample_csv"
2
- require "flex_station_data/presenters/linear_regression/sample_regression_hash"
3
-
4
- module FlexStationData
5
- module Presenters
6
- module LinearRegression
7
- class VerboseSampleCsv < Presenters::SampleCsv
8
- include Concerns::Presenter
9
-
10
- def sample_values
11
- [ *sample.readings, sample.mean ].map(&:values)
12
- end
13
-
14
- def regressions_hash
15
- SampleRegressionHash.present(times, *sample_values, **options)
16
- end
17
-
18
- def regressions_csv
19
- regressions_hash.to_a.map(&:flatten)
20
- end
21
-
22
- def values_csv
23
- super + regressions_csv
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,29 +0,0 @@
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, :sample_presenter, :options
9
-
10
- delegate :times, :samples, to: :plate
11
-
12
- def initialize(plate, sample_presenter: SampleCsv, **options)
13
- @plate = plate
14
- @sample_presenter = sample_presenter
15
- @options = options
16
- end
17
-
18
- def samples_csv
19
- samples.flat_map do |sample|
20
- sample_presenter.present(times, sample, **options)
21
- end
22
- end
23
-
24
- def present
25
- [ ["Plate #{plate.label}"], *samples_csv ]
26
- end
27
- end
28
- end
29
- end
@@ -1,28 +0,0 @@
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, :plate_presenter, :options
9
-
10
- def initialize(file, plates, plate_presenter: PlateCsv, **options)
11
- @file = file
12
- @plates = plates
13
- @plate_presenter = plate_presenter
14
- @options = options
15
- end
16
-
17
- def plates_csv
18
- plates.flat_map do |plate|
19
- plate_presenter.present(plate, **options)
20
- end
21
- end
22
-
23
- def present
24
- [ ["File: #{file.to_path}"], *plates_csv ]
25
- end
26
- end
27
- end
28
- end
@@ -1,56 +0,0 @@
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, :options
11
-
12
- def initialize(times, sample, quality_control: SampleQuality, **options)
13
- @times = times
14
- @sample = sample
15
- @quality_control = quality_control
16
- @options = options
17
- end
18
-
19
- def readings
20
- @readings ||= [ Readings.new("time", times), *sample.readings, sample.mean ]
21
- end
22
-
23
- def headers
24
- readings.map(&:label)
25
- end
26
-
27
- def rows
28
- readings.map(&:values).transpose
29
- end
30
-
31
- def label
32
- "Sample #{sample.label}"
33
- end
34
-
35
- def errors
36
- @errors ||= quality_control.call(sample, **options).reject(&:good?)
37
- end
38
-
39
- def errors?
40
- errors.present?
41
- end
42
-
43
- def errors_csv
44
- errors.map(&:to_s).map(&method(:Array))
45
- end
46
-
47
- def values_csv
48
- [ headers, *rows ]
49
- end
50
-
51
- def present
52
- [ [label] ] + (errors? ? errors_csv : values_csv) + [ [] ]
53
- end
54
- end
55
- end
56
- end
@@ -1,16 +0,0 @@
1
- require "flex_station_data/services/compute_mean"
2
-
3
- module FlexStationData
4
- class Readings
5
- attr_reader :label, :values
6
-
7
- def initialize(label, values)
8
- @label = label
9
- @values = values
10
- end
11
-
12
- def self.mean(*readings, label: "mean")
13
- new(label, readings.map(&:values).transpose.map(&ComputeMean))
14
- end
15
- end
16
- end
@@ -1,49 +0,0 @@
1
- require "flex_station_data/readings"
2
- require "flex_station_data/concerns/service"
3
-
4
- require "flex_station_data/sample"
5
-
6
- module FlexStationData
7
- class ParsePlateSamples
8
- include Concerns::Service
9
-
10
- def initialize(sample_map_block, wells)
11
- @sample_map_block = sample_map_block
12
- @wells = wells
13
- end
14
-
15
- def map_rows
16
- @map_rows ||= begin
17
- rows = sample_map_block.drop_while { |row| row[0] != "Sample" }.drop(1)
18
- rows = rows.map { |row| row.map(&:presence) }
19
- rows.take_while { |row| row.any?(&:present?) }.to_a
20
- end
21
- end
22
-
23
- def map_columns
24
- @map_columns ||= map_rows.transpose
25
- end
26
-
27
- def labels
28
- @labels ||= map_columns[0].compact
29
- end
30
-
31
- def wells_per_sample
32
- map_rows.size / labels.size
33
- end
34
-
35
- def sample_map
36
- @sample_map ||= map_columns[1].each_slice(wells_per_sample).to_a
37
- end
38
-
39
- def call
40
- labels.zip(sample_map).map do |label, well_labels|
41
- Sample.new(label, well_labels.map(&wells.method(:readings)))
42
- end
43
- end
44
-
45
- private
46
-
47
- attr_reader :sample_map_block, :wells
48
- end
49
- end