flex-station-data 0.2.0 → 0.3.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: 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