abanalyzer 0.1.0 → 1.0.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
  SHA1:
3
- metadata.gz: f300363efbaf45233112dd30f6092192dcda30d9
4
- data.tar.gz: 3ac739cdcac5e47cc38a1a1009591f023c95a36b
3
+ metadata.gz: 9cbf5e6bc121a608cab28ea506580a3cbae02998
4
+ data.tar.gz: f897ff9d1f557e831d779d7ee6f87937dc414ec3
5
5
  SHA512:
6
- metadata.gz: e5d78e898a261f4cb50a3db03431fc262428718e17e2b6922b9fecd71fbed9ed38b6be7a8ec307c831a7930fd2bb5ac3d4620f0fa8779257860ade9aa1fb2965
7
- data.tar.gz: e8daaacde12cdc610de4726889678f077527ee73d9f0137fcb8aa359d91915b65d9bf86203baf267c51bfe40dfe0de368feb43511b1905fa0b29013eedff5d2f
6
+ metadata.gz: f78dfa9350b44b072a34c4d6d230dd415d3b84eb53a9e0d5b1f1e7bf7f5bf146858a2e03ecabe528a26bb52f4f7a80c0458c82948f830454cddc93c050069ef7
7
+ data.tar.gz: 5d54f21901a039d64e8aec4d1c3c32b56e8edd2b29e7a7fbeb54c258ca3b6f8ed5afa1546a8a7a8338f99acc182ad2176deb806ce5d5a23855304013ddb591d8
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
+ .bundle
1
2
  docs
2
3
  pkg
3
4
  Gemfile.lock
@@ -0,0 +1,11 @@
1
+ Metrics/LineLength:
2
+ Max: 140
3
+
4
+ Style/Documentation:
5
+ Enabled: false
6
+
7
+ Metrics/MethodLength:
8
+ Max: 19
9
+
10
+ Metrics/AbcSize:
11
+ Max: 43
@@ -1,7 +1,5 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
4
- - 2.0.0
5
- - 2.1.0
6
- - 2.1.1
7
- - 2.1.2
3
+ - 2.2.8
4
+ - 2.3.5
5
+ - 2.4.2
data/Gemfile CHANGED
@@ -1,4 +1,3 @@
1
- source "http://rubygems.org"
1
+ source 'http://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in bandit.gemspec
4
3
  gemspec
@@ -55,7 +55,7 @@ You can additionally get the actual score for either a Chi-Square test for indep
55
55
 
56
56
 
57
57
  == Sample Size Calculations
58
- Let's say you want to determine how large your sample size needs to be for an A/B test. Let's say your baseline is 10%, and you want to be able to determine if there's at least a 10% relative lift (1% absolute) to 11%. Let's assume you want a power[http://en.wikipedia.org/wiki/Statistical_power] of 0.8 and a {significance level}[http://en.wikipedia.org/wiki/Statistical_significance] of 0.05 (that is, an 80% chance of that you'll fail to recognize a difference when there is one, and a 5% chance of a false negative).
58
+ Let's say you want to determine how large your sample size needs to be for an A/B test. Let's say your baseline is 10%, and you want to be able to determine if there's at least a 10% relative lift (1% absolute) to 11%. Let's assume you want a power[http://en.wikipedia.org/wiki/Statistical_power] of 0.8 and a {significance level}[http://en.wikipedia.org/wiki/Statistical_significance] of 0.05 (that is, an 80% chance of that you'll succeed in recognizing a difference when there is one, and a 5% chance of a false negative).
59
59
 
60
60
  ...
61
61
  ABAnalyzer.calculate_size(0.1, 0.11, 0.05, 0.8)
data/Rakefile CHANGED
@@ -1,18 +1,21 @@
1
+ require 'rake/testtask'
1
2
  require 'bundler/gem_tasks'
2
3
  require 'rdoc/task'
4
+ require 'rubocop/rake_task'
5
+
6
+ RuboCop::RakeTask.new
3
7
 
4
- RDoc::Task.new("doc") { |rdoc|
5
- rdoc.title = "ABAnalyzer - A/B test analysis library for Ruby"
8
+ RDoc::Task.new('doc') do |rdoc|
9
+ rdoc.title = 'ABAnalyzer - A/B test analysis library for Ruby'
6
10
  rdoc.rdoc_dir = 'docs'
7
11
  rdoc.rdoc_files.include('README.rdoc')
8
12
  rdoc.rdoc_files.include('lib/**/*.rb')
9
- }
10
-
11
- require 'rake/testtask'
13
+ end
12
14
 
13
15
  Rake::TestTask.new do |t|
16
+ t.libs << 'test'
14
17
  t.test_files = FileList['test/*_test.rb']
15
18
  t.verbose = true
16
19
  end
17
20
 
18
- task :default => :test
21
+ task default: %i[rubocop test]
@@ -1,20 +1,21 @@
1
- $:.push File.expand_path("../lib", __FILE__)
2
- require "abanalyzer/version"
3
- require "date"
1
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
2
+ require 'abanalyzer/version'
3
+ require 'date'
4
4
 
5
5
  Gem::Specification.new do |s|
6
- s.name = "abanalyzer"
6
+ s.name = 'abanalyzer'
7
7
  s.version = ABAnalyzer::VERSION
8
- s.authors = ["Brian Muller"]
8
+ s.authors = ['Brian Muller']
9
+ s.license = 'GPL-3.0'
9
10
  s.date = Date.today.to_s
10
- s.description = "A/B test analysis library for Ruby"
11
- s.summary = "A/B test analysis library for Ruby"
12
- s.email = "bamuller@gmail.com"
13
- s.files = `git ls-files`.split($/)
11
+ s.summary = 'A/B test analysis library for Ruby'
12
+ s.email = 'bamuller@gmail.com'
13
+ s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
14
14
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
15
- s.homepage = "https://github.com/bmuller/abanalyzer"
16
- s.require_paths = ["lib"]
17
- s.add_development_dependency("rake")
18
- s.add_development_dependency("rdoc")
19
- s.add_dependency('statistics2', '>= 0.54')
15
+ s.homepage = 'https://github.com/bmuller/abanalyzer'
16
+ s.require_paths = ['lib']
17
+ s.add_development_dependency('rake', '~> 12.1')
18
+ s.add_development_dependency('minitest', '~> 5.10')
19
+ s.add_development_dependency('rubocop', '~> 0.50')
20
+ s.add_dependency('statistics2', '= 0.54')
20
21
  end
@@ -1,7 +1,6 @@
1
1
  require 'statistics2'
2
2
 
3
3
  module ABAnalyzer
4
-
5
4
  class ABTest
6
5
  # values should be hash of hashes, with top level hash the group names:
7
6
  # { :groupa => { :yes => 20, :no => 10 }, :groupb => { :yes => 18, :no => 8 } }
@@ -9,42 +8,43 @@ module ABAnalyzer
9
8
  @values = Matrix.new values
10
9
  end
11
10
 
12
- def different?(sig=0.05)
11
+ def different?(sig = 0.05)
13
12
  gtest_p < sig
14
13
  end
15
-
14
+
16
15
  def chisquare_score
17
- sum=0
18
- @values.each_cell { |colname, rowname, value|
16
+ sum = 0
17
+ @values.each_cell do |colname, rowname, value|
19
18
  ex = expected(colname, rowname)
20
19
  test_sufficient_data(colname, rowname, ex, value)
21
- sum += ((value - ex) ** 2) / ex
22
- }
23
- return sum
20
+ sum += ((value - ex)**2) / ex
21
+ end
22
+ sum
24
23
  end
25
-
24
+
26
25
  def gtest_score
27
- sum=0
28
- @values.each_cell { |colname, rowname, value|
26
+ sum = 0
27
+ @values.each_cell do |colname, rowname, value|
29
28
  ex = expected(colname, rowname)
30
29
  test_sufficient_data(colname, rowname, ex, value)
31
30
  sum += value * Math.log(value / ex)
32
- }
33
- return sum
31
+ end
32
+ sum
34
33
  end
35
34
 
36
35
  def chisquare_p
37
- ABTest.chi2dist(df, self.chisquare_score)
36
+ 1 - Statistics2.chi2dist(df, chisquare_score)
38
37
  end
39
38
 
40
39
  def gtest_p
41
- ABTest.chi2dist(df, 2*self.gtest_score)
40
+ 1 - Statistics2.chi2dist(df, 2 * gtest_score)
42
41
  end
43
-
42
+
44
43
  private
44
+
45
45
  def test_sufficient_data(colname, rowname, expected, value)
46
46
  msg = "Insufficient data size for column #{colname} row #{rowname}. Expected value must be >= 5, and value must be > 0."
47
- raise InsufficientDataError, msg if expected < 5 or value <= 0
47
+ raise InsufficientDataError, msg if (expected < 5) || (value <= 0)
48
48
  end
49
49
 
50
50
  def expected(colname, rowname)
@@ -54,10 +54,5 @@ module ABAnalyzer
54
54
  def df
55
55
  (@values.columns.length - 1) * (@values.rows.length - 1)
56
56
  end
57
-
58
- def self.chi2dist(degrees, score)
59
- 1 - Statistics2.chi2dist(degrees, score)
60
- end
61
57
  end
62
-
63
58
  end
@@ -1,5 +1,4 @@
1
1
  module ABAnalyzer
2
-
3
2
  class Matrix
4
3
  attr_reader :columns, :rows
5
4
  def initialize(values)
@@ -10,14 +9,14 @@ module ABAnalyzer
10
9
  end
11
10
 
12
11
  def validate
13
- @values.each { |colname, column|
14
- if column.keys.map { |s| s.to_s }.sort != @rows.map { |s| s.to_s }.sort
12
+ @values.each do |colname, column|
13
+ if column.keys.map(&:to_s).sort != @rows.map(&:to_s).sort
15
14
  raise MatrixFormatError, "Column #{colname} has row names that don't match the first column's."
16
15
  end
17
- }
18
- coltotal = @columns.map { |col| column_sum(col) }.inject { |a,b| a+b }
19
- rowtotal = @rows.map { |col| row_sum(col) }.inject { |a,b| a+b }
20
- raise MatrixFormatError, "Column sums do not equal row sums" if coltotal != rowtotal
16
+ end
17
+ coltotal = @columns.map { |col| column_sum(col) }.inject { |a, b| a + b }
18
+ rowtotal = @rows.map { |col| row_sum(col) }.inject { |a, b| a + b }
19
+ raise MatrixFormatError, 'Column sums do not equal row sums' if coltotal != rowtotal
21
20
  end
22
21
 
23
22
  def get_column(name)
@@ -25,17 +24,17 @@ module ABAnalyzer
25
24
  end
26
25
 
27
26
  def get_row(name)
28
- @values.map { |colname, rows|
27
+ @values.map do |_colname, rows|
29
28
  rows[name]
30
- }
29
+ end
31
30
  end
32
31
 
33
32
  def each_cell
34
- @columns.each { |colname|
35
- @rows.each { |rowname|
33
+ @columns.each do |colname|
34
+ @rows.each do |rowname|
36
35
  yield colname, rowname, get(colname, rowname)
37
- }
38
- }
36
+ end
37
+ end
39
38
  end
40
39
 
41
40
  def get(colname, rowname)
@@ -43,16 +42,15 @@ module ABAnalyzer
43
42
  end
44
43
 
45
44
  def column_sum(name)
46
- get_column(name).inject { |a,b| a+b }
45
+ get_column(name).inject { |a, b| a + b }
47
46
  end
48
47
 
49
48
  def row_sum(name)
50
- get_row(name).inject { |a,b| a+b }
49
+ get_row(name).inject { |a, b| a + b }
51
50
  end
52
51
 
53
52
  def total_sum
54
- @columns.map { |col| column_sum(col) }.inject { |a,b| a+b }
53
+ @columns.map { |col| column_sum(col) }.inject { |a, b| a + b }
55
54
  end
56
55
  end
57
-
58
56
  end
@@ -1,14 +1,13 @@
1
1
  require 'statistics2'
2
2
 
3
3
  module ABAnalyzer
4
-
5
4
  # Calculate the minimum sample size (per group) based on the desire to detect
6
5
  # a increase from proportion p1 to proportion p2. Significance is generally
7
6
  # safe at 0.05 (why? just because) and a power of 0.8 (why? just because)
8
7
  def self.calculate_size(p1, p2, significance, power)
9
- [ p1, p2, significance, power ].each { |a|
10
- raise "All arguments to calculate_size must be Floats" unless a.is_a?(Float)
11
- }
8
+ [p1, p2, significance, power].each do |a|
9
+ raise 'All arguments to calculate_size must be Floats' unless a.is_a?(Float)
10
+ end
12
11
 
13
12
  pbar = (p1 + p2) / 2.0
14
13
  sides = 2.0
@@ -16,8 +15,8 @@ module ABAnalyzer
16
15
  zcrit = Statistics2.pnormaldist(1 - (significance / sides))
17
16
  zpow = Statistics2.pnormaldist(power)
18
17
 
19
- numerator = (zcrit * Math.sqrt(2 * pbar * (1 - pbar)) + zpow * Math.sqrt(p2 * (1 - p2) + p1 * (1 - p1))) ** 2
20
- denominator = (p2 - p1) ** 2
18
+ numerator = (zcrit * Math.sqrt(2 * pbar * (1 - pbar)) + zpow * Math.sqrt(p2 * (1 - p2) + p1 * (1 - p1)))**2
19
+ denominator = (p2 - p1)**2
21
20
  (numerator / denominator).ceil
22
21
  end
23
22
 
@@ -39,5 +38,4 @@ module ABAnalyzer
39
38
  ci = confidence_interval(successes, trials, confidence)
40
39
  [(ci.first - compared_proportion) / compared_proportion, (ci.last - compared_proportion) / compared_proportion]
41
40
  end
42
-
43
41
  end
@@ -1,3 +1,3 @@
1
1
  module ABAnalyzer
2
- VERSION = "0.1.0"
2
+ VERSION = '1.0.0'.freeze
3
3
  end
@@ -1,24 +1,25 @@
1
- require_relative 'helper'
1
+ require 'minitest/autorun'
2
+ require 'abanalyzer'
2
3
 
3
- class ABTestTest < Test::Unit::TestCase
4
+ class ABTestTest < MiniTest::Test
4
5
  def setup
5
- @values = { :rep => { :male => 200, :female => 250 }, :dem => { :male => 150, :female => 300}, :ind => { :male => 50, :female => 50 }}
6
+ @values = { rep: { male: 200, female: 250 }, dem: { male: 150, female: 300 }, ind: { male: 50, female: 50 } }
6
7
  end
7
8
 
8
9
  def test_test_creation
9
- assert_raise ABAnalyzer::InsufficientDataError do
10
- m = ABAnalyzer::ABTest.new({ :one => { :a => 10, :b => 20 }, :two => { :a => 5, :b => 0 } })
11
- p = m.gtest_p
10
+ assert_raises ABAnalyzer::InsufficientDataError do
11
+ m = ABAnalyzer::ABTest.new(one: { a: 10, b: 20 }, two: { a: 5, b: 0 })
12
+ m.gtest_p
12
13
  end
13
14
 
14
- assert_raise ABAnalyzer::InsufficientDataError do
15
- m = ABAnalyzer::ABTest.new({ :one => { :a => 10, :b => 20 }, :two => { :a => 5, :b => -6 } })
16
- p = m.gtest_p
15
+ assert_raises ABAnalyzer::InsufficientDataError do
16
+ m = ABAnalyzer::ABTest.new(one: { a: 10, b: 20 }, two: { a: 5, b: -6 })
17
+ m.gtest_p
17
18
  end
18
19
 
19
- assert_raise ABAnalyzer::InsufficientDataError do
20
- m = ABAnalyzer::ABTest.new({ :one => { :a => 1, :b => 1 }, :two => { :a => 1, :b => 1 } })
21
- p = m.gtest_p
20
+ assert_raises ABAnalyzer::InsufficientDataError do
21
+ m = ABAnalyzer::ABTest.new(one: { a: 1, b: 1 }, two: { a: 1, b: 1 })
22
+ m.gtest_p
22
23
  end
23
24
  end
24
25
 
@@ -28,7 +29,7 @@ class ABTestTest < Test::Unit::TestCase
28
29
  chisquare = 1 - Statistics2.chi2dist(2, 16.2037037037037)
29
30
  assert_equal abt.chisquare_p, chisquare
30
31
 
31
- gtest = 1 - Statistics2.chi2dist(2, 2*8.13286375180066)
32
+ gtest = 1 - Statistics2.chi2dist(2, 2 * 8.13286375180066)
32
33
  assert_equal abt.gtest_p, gtest
33
34
  end
34
35
  end
@@ -1,13 +1,14 @@
1
- require_relative 'helper'
1
+ require 'minitest/autorun'
2
+ require 'abanalyzer'
2
3
 
3
- class MatrixTest < Test::Unit::TestCase
4
+ class TestMatrix < MiniTest::Test
4
5
  def setup
5
- @values = { :rep => { :male => 200, :female => 250 }, :dem => { :male => 150, :female => 300}, :ind => { :male => 50, :female => 50 }}
6
+ @values = { rep: { male: 200, female: 250 }, dem: { male: 150, female: 300 }, ind: { male: 50, female: 50 } }
6
7
  end
7
8
 
8
9
  def test_matrix_creation
9
- assert_raise ABAnalyzer::MatrixFormatError do
10
- ABAnalyzer::Matrix.new({ :one => { :a => 10, :b => 20 }, :two => { :a => 5 } })
10
+ assert_raises ABAnalyzer::MatrixFormatError do
11
+ ABAnalyzer::Matrix.new(one: { a: 10, b: 20 }, two: { a: 5 })
11
12
  end
12
13
  end
13
14
 
metadata CHANGED
@@ -1,65 +1,80 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abanalyzer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Muller
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-28 00:00:00.000000000 Z
11
+ date: 2017-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '12.1'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '12.1'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rdoc
28
+ name: minitest
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '5.10'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '5.10'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.50'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.50'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: statistics2
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
- - - '>='
59
+ - - '='
46
60
  - !ruby/object:Gem::Version
47
61
  version: '0.54'
48
62
  type: :runtime
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
- - - '>='
66
+ - - '='
53
67
  - !ruby/object:Gem::Version
54
68
  version: '0.54'
55
- description: A/B test analysis library for Ruby
69
+ description:
56
70
  email: bamuller@gmail.com
57
71
  executables: []
58
72
  extensions: []
59
73
  extra_rdoc_files: []
60
74
  files:
61
- - .gitignore
62
- - .travis.yml
75
+ - ".gitignore"
76
+ - ".rubocop.yml"
77
+ - ".travis.yml"
63
78
  - Gemfile
64
79
  - LICENSE
65
80
  - README.rdoc
@@ -72,10 +87,10 @@ files:
72
87
  - lib/abanalyzer/sample.rb
73
88
  - lib/abanalyzer/version.rb
74
89
  - test/abtest_test.rb
75
- - test/helper.rb
76
90
  - test/matrix_test.rb
77
91
  homepage: https://github.com/bmuller/abanalyzer
78
- licenses: []
92
+ licenses:
93
+ - GPL-3.0
79
94
  metadata: {}
80
95
  post_install_message:
81
96
  rdoc_options: []
@@ -83,21 +98,20 @@ require_paths:
83
98
  - lib
84
99
  required_ruby_version: !ruby/object:Gem::Requirement
85
100
  requirements:
86
- - - '>='
101
+ - - ">="
87
102
  - !ruby/object:Gem::Version
88
103
  version: '0'
89
104
  required_rubygems_version: !ruby/object:Gem::Requirement
90
105
  requirements:
91
- - - '>='
106
+ - - ">="
92
107
  - !ruby/object:Gem::Version
93
108
  version: '0'
94
109
  requirements: []
95
110
  rubyforge_project:
96
- rubygems_version: 2.2.2
111
+ rubygems_version: 2.6.13
97
112
  signing_key:
98
113
  specification_version: 4
99
114
  summary: A/B test analysis library for Ruby
100
115
  test_files:
101
116
  - test/abtest_test.rb
102
- - test/helper.rb
103
117
  - test/matrix_test.rb
@@ -1,6 +0,0 @@
1
- require 'rubygems'
2
- require 'statistics2'
3
- require 'test/unit'
4
-
5
- $:.unshift(File.join File.dirname(__FILE__), '..', 'lib')
6
- require 'abanalyzer'