flex-station-data 0.2.0 → 0.3.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: abdebffeaa580bb44163ea7d3ec38098e90a68300d112159e83ff0704bb11096
4
- data.tar.gz: 04b0d95d5e3cff8c74cfee3a6bd3301913e2b0f2e92a2c9edcf68a0070b317bb
3
+ metadata.gz: 90dd1e2d4c0a1b2987871308823f9b2f501ec0e0d302cc70e3a042ee82195009
4
+ data.tar.gz: ad02c5b5fa0f757d307445bd22bb7b1367e7b6d88564da313f90b73ddaa4a80f
5
5
  SHA512:
6
- metadata.gz: be673a116f7e4753bae65720abcbedb3866573bfb8c935abbf025b0af81675edbf74b7025fb33e5e8c2442f9cd21781132a7fa34f4f7e644d6a292e579ed58a3
7
- data.tar.gz: 92afd6795b00296585b86b449b55bca7bdafd4ff6a3c6dda8b6b9f4fbf727057a0ffbbfb1ecd572a4aeb7d8239b53ffdfc15bdc4564b868f6e55d18de9141123
6
+ metadata.gz: a338dc370ee092711577bc532c8790560af9a01eade3efe114e75ad2f4120f10f13078044a4c71c09fbb35583a65e5b75c579a912d2b06ad24b840da8af2e641
7
+ data.tar.gz: 231eb833491fc460f2a20eee1b5498525d48d6efae9c29c2c4e3ae43fbdb580067b0f1fe4f114cee078f2e51df5e04d8610a27a73ac3488529674aa087980b2b
data/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # 0.3.0
2
+
3
+ * The linear regression analysis report now produces a summary report of the
4
+ regression for each sample. The sample data can by added by sSpecifying the
5
+ `--verbose` option.
6
+
7
+ # 0.2.0
8
+
9
+ * Adds quality control checks. Samples with values that are saturated,
10
+ missing, or below a user-specified threshold are suppressed in the output.
11
+
12
+ # 0.1.1
13
+
14
+ * Bump the minimum required Ruby version to 2.4.4. On of our gem dependencies
15
+ relies on this version.
16
+
17
+ # 0.1.0
18
+
19
+ * Initial version
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- flex-station-data (0.2.0)
4
+ flex-station-data (0.3.0)
5
5
  activesupport
6
6
  linefit
7
7
 
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # FlexStationData
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/flex_station_data`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ Tools for reading and analyzing data from the FlexStation microplate reader.
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ Currently this is somewhere between alpha and beta.
6
6
 
7
7
  ## Installation
8
8
 
@@ -20,15 +20,50 @@ Or install it yourself as:
20
20
 
21
21
  $ gem install flex-station-data
22
22
 
23
+ ## Updating
24
+
25
+ To update your installation, use:
26
+
27
+ $ gem update flex-station-data
28
+
23
29
  ## Usage
24
30
 
25
- TODO: Write usage instructions here
31
+ ### Viewing the sample data
32
+
33
+ To view the sample results from a set of plate readings, use the following
34
+ command:
35
+
36
+ $ flex-station sample-data <source file> [--threshold=<threshold>]
37
+
38
+ Where `source file` is the file that you got from the reader. You can specify
39
+ multiple source files. The `threshold` setting is optional. If provided,
40
+ samples with values that are below the `threshold` value will be skipped.
41
+ `threshold` must be a number.
42
+
43
+ The output is in CSV format. You'll probably want to save it to a file. You
44
+ can do that by piping the output to a file:
45
+
46
+ $ flex-station sample-data source-data.csv --threshold=300 > sample-data.csv
47
+
48
+ ### Performing linear regression analysis on the sample data
49
+
50
+ To perform a linear regression analysis on the sample data, use the following
51
+ command:
52
+
53
+ $ flex-station-data liinear-regression <source file> [--threshold=<threshold>] [--verbose] [--min-r-squared=<mininmum R²>]
54
+
55
+ Note that the `source file` and `threshold` options are the same as for the
56
+ `sample-data` command above. If a `--min-r-squared` value is given, samples
57
+ with a R² value that falls below the threshold will be flagged as "poor fits."
58
+ If no `--min-r-squared` is specified, a default of 0.75 will be used.
26
59
 
27
- ## Development
60
+ By default the linear regression tool will produce a summary report giving the
61
+ slope, intercept, and R² values for each sample. However if `--verbose` is
62
+ specified, it will also include the sample data and regressions for each well.
28
63
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
64
+ As with the sample data tool, you will probably want to pipe the output to a file:
30
65
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
66
+ $ flex-station linear-regression source-data.csv --threshold=300 > linear-regression.csv
32
67
 
33
68
  ## Contributing
34
69
 
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- #!/usr/bin/env ruby
4
-
5
3
  require "flex_station_data/services/load_plates"
6
4
  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"
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"
9
9
 
10
10
  module FlexStationData
11
11
  class LinearRegressionApp
@@ -22,22 +22,23 @@ module FlexStationData
22
22
  end
23
23
  end
24
24
 
25
- def threshold
26
- name, value = options.reverse.detect { |name, value| name == "threshold" }
27
- return nil if name.blank?
25
+ def option(option_name)
26
+ name, value = options.reverse.detect { |name, _| name == option_name.to_s }
27
+ return if name.blank?
28
28
 
29
- Float(value)
29
+ value.nil? || value
30
30
  end
31
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
32
+ def threshold
33
+ Float(option(:threshold)) rescue nil
37
34
  end
38
35
 
39
- def sample_quality_control
40
- ->(sample) { SampleQuality.call(sample, value_quality_control: value_quality_control) }
36
+ def verbose?
37
+ option(:verbose)
38
+ end
39
+
40
+ def min_r_squared
41
+ Float(option("min-r-squared")) rescue 0.75
41
42
  end
42
43
 
43
44
  def files
@@ -50,16 +51,57 @@ module FlexStationData
50
51
  end
51
52
  end
52
53
 
53
- def csv
54
+ def verbose_csv
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
54
67
  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)
68
+ Presenters::LinearRegression::PlatesHash.present(
69
+ file,
70
+ file_plates,
71
+ threshold: threshold,
72
+ min_r_squared: min_r_squared
73
+ )
74
+ end
75
+ end
76
+
77
+ def clean_values(values)
78
+ values.each_with_object([]) do |value, memo|
79
+ memo << ((value != memo.compact.last) ? value : nil)
80
+ end
81
+ end
82
+
83
+ def summary_hash
84
+ @summary_hash ||= begin
85
+ result = summary_hashes.each_with_object({}) do |hash, memo|
86
+ hash.each do |header, value|
87
+ memo[header] ||= []
88
+ memo[header] << value
58
89
  end
59
90
  end
91
+ result["file"] = clean_values(result["file"])
92
+ result["plate"] = clean_values(result["plate"])
93
+ result
60
94
  end
61
95
  end
62
96
 
97
+ def summary_csv
98
+ [ summary_hash.keys, *summary_hash.values.transpose ]
99
+ end
100
+
101
+ def csv
102
+ verbose? ? verbose_csv : summary_csv
103
+ end
104
+
63
105
  def call
64
106
  CSV do |out|
65
107
  csv.each do |row|
@@ -25,17 +25,6 @@ module FlexStationData
25
25
  Float(value)
26
26
  end
27
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) }
37
- end
38
-
39
28
  def files
40
29
  @files ||= args.map { |arg| Pathname(arg) }
41
30
  end
@@ -48,11 +37,7 @@ module FlexStationData
48
37
 
49
38
  def csv
50
39
  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)
54
- end
55
- end
40
+ Presenters::PlatesCsv.present(file, file_plates, threshold: threshold)
56
41
  end
57
42
  end
58
43
 
@@ -3,11 +3,12 @@ require "linefit"
3
3
 
4
4
  module FlexStationData
5
5
  class LinearRegression
6
- attr_reader :x, :y
6
+ attr_reader :x, :y, :min_r_squared
7
7
 
8
- def initialize(x, y)
8
+ def initialize(x, y, min_r_squared: 0.0, **)
9
9
  @x = x
10
10
  @y = y
11
+ @min_r_squared = min_r_squared.to_f
11
12
  end
12
13
 
13
14
  def slope
@@ -24,6 +25,10 @@ module FlexStationData
24
25
  nil
25
26
  end
26
27
 
28
+ def quality
29
+ "poor fit" if r_squared.present? && r_squared < min_r_squared
30
+ end
31
+
27
32
  private
28
33
 
29
34
  def coefficients
@@ -0,0 +1,27 @@
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
@@ -0,0 +1,28 @@
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
@@ -0,0 +1,47 @@
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
@@ -0,0 +1,43 @@
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
@@ -0,0 +1,28 @@
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
@@ -5,18 +5,24 @@ module FlexStationData
5
5
  class PlateCsv
6
6
  include Concerns::Presenter
7
7
 
8
- attr_reader :plate
8
+ attr_reader :plate, :sample_presenter, :options
9
9
 
10
10
  delegate :times, :samples, to: :plate
11
11
 
12
- def initialize(plate)
12
+ def initialize(plate, sample_presenter: SampleCsv, **options)
13
13
  @plate = plate
14
+ @sample_presenter = sample_presenter
15
+ @options = options
14
16
  end
15
17
 
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) ]
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 ]
20
26
  end
21
27
  end
22
28
  end
@@ -5,16 +5,23 @@ module FlexStationData
5
5
  class PlatesCsv
6
6
  include Concerns::Presenter
7
7
 
8
- attr_reader :file, :plates
8
+ attr_reader :file, :plates, :plate_presenter, :options
9
9
 
10
- def initialize(file, plates)
10
+ def initialize(file, plates, plate_presenter: PlateCsv, **options)
11
11
  @file = file
12
12
  @plates = plates
13
+ @plate_presenter = plate_presenter
14
+ @options = options
13
15
  end
14
16
 
15
- def present(&plate_presenter)
16
- plate_presenter ||= PlateCsv
17
- [ ["File: #{file.to_path}"], *plates.flat_map(&plate_presenter) ]
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 ]
18
25
  end
19
26
  end
20
27
  end
@@ -7,12 +7,13 @@ module FlexStationData
7
7
  class SampleCsv
8
8
  include Concerns::Presenter
9
9
 
10
- attr_reader :times, :sample, :quality_control
10
+ attr_reader :times, :sample, :quality_control, :options
11
11
 
12
- def initialize(times, sample, quality_control: SampleQuality)
12
+ def initialize(times, sample, quality_control: SampleQuality, **options)
13
13
  @times = times
14
14
  @sample = sample
15
15
  @quality_control = quality_control
16
+ @options = options
16
17
  end
17
18
 
18
19
  def readings
@@ -32,7 +33,7 @@ module FlexStationData
32
33
  end
33
34
 
34
35
  def errors
35
- @errors ||= quality_control.call(sample).reject(&:good?)
36
+ @errors ||= quality_control.call(sample, **options).reject(&:good?)
36
37
  end
37
38
 
38
39
  def errors?
@@ -4,15 +4,20 @@ module FlexStationData
4
4
  class SampleQuality
5
5
  include Concerns::Service
6
6
 
7
- attr_reader :sample, :value_quality_control
7
+ attr_reader :sample, :value_quality_control, :options
8
8
 
9
- def initialize(sample, value_quality_control: ValueQuality)
9
+ def initialize(sample, value_quality_control: ValueQuality, **options)
10
10
  @sample = sample
11
11
  @value_quality_control = value_quality_control
12
+ @options = options
13
+ end
14
+
15
+ def value_quality(value)
16
+ value_quality_control.call(value, **options)
12
17
  end
13
18
 
14
19
  def call
15
- sample.readings.flat_map(&:values).map(&value_quality_control).uniq(&:to_s)
20
+ sample.readings.flat_map(&:values).map(&method(:value_quality)).uniq(&:to_s)
16
21
  end
17
22
  end
18
23
  end
@@ -35,7 +35,7 @@ module FlexStationData
35
35
 
36
36
  attr_reader :value, :threshold
37
37
 
38
- def initialize(value, threshold: nil)
38
+ def initialize(value, threshold: nil, **_options)
39
39
  @value = value
40
40
  @threshold = threshold
41
41
  end
@@ -1,3 +1,3 @@
1
1
  module FlexStationData
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flex-station-data
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Carney
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-23 00:00:00.000000000 Z
11
+ date: 2019-08-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -106,6 +106,7 @@ extra_rdoc_files: []
106
106
  files:
107
107
  - ".gitignore"
108
108
  - ".rspec"
109
+ - CHANGELOG.md
109
110
  - CODE_OF_CONDUCT.md
110
111
  - Gemfile
111
112
  - Gemfile.lock
@@ -121,10 +122,14 @@ files:
121
122
  - lib/flex_station_data/concerns/service.rb
122
123
  - lib/flex_station_data/linear_regression.rb
123
124
  - lib/flex_station_data/plate.rb
125
+ - lib/flex_station_data/presenters/linear_regression/plate_hash.rb
126
+ - lib/flex_station_data/presenters/linear_regression/plates_hash.rb
127
+ - lib/flex_station_data/presenters/linear_regression/sample_hash.rb
128
+ - lib/flex_station_data/presenters/linear_regression/sample_regression_hash.rb
129
+ - lib/flex_station_data/presenters/linear_regression/verbose_sample_csv.rb
124
130
  - lib/flex_station_data/presenters/plate_csv.rb
125
131
  - lib/flex_station_data/presenters/plates_csv.rb
126
132
  - lib/flex_station_data/presenters/sample_csv.rb
127
- - lib/flex_station_data/presenters/sample_linear_regression_csv.rb
128
133
  - lib/flex_station_data/readings.rb
129
134
  - lib/flex_station_data/sample.rb
130
135
  - lib/flex_station_data/services/compute_mean.rb
@@ -1,29 +0,0 @@
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