active_median 0.2.3 → 0.2.4

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: 47d759f5155e0a9687eeb845cf25a65daae5fe2a475bed1dcc071b742413b8c4
4
- data.tar.gz: 9eb8b10f9ff8526adcea04beb4fa68fb11e61b544f11413925280c5d37f10e2b
3
+ metadata.gz: 86998cba53771c73de5fcc9c5dc8a869a4ff061029828dcf0ce8fab89852e346
4
+ data.tar.gz: 23482bad0017c7b16422e9be9961783a6674576a65955a6d228db22f556ef84b
5
5
  SHA512:
6
- metadata.gz: af49d340ff2b1298d1706381a7879ac3666e9077a56607c2d8809b132f93eef3eb8beb9996aa7599157a3d900ad3951e606413cb936f23d045115d8baead0121
7
- data.tar.gz: ed82a1e318b79372aace81c56d2ca9bd9c59f373be5795f2e595fc8a60c7a7bdd9f9191e5a46069c4ea7405347e3057abb3c1ad6ae794a252cdce6de7f5411b6
6
+ metadata.gz: 6c5f02aa5f51169214536006caa8509b4fcc7041d1be680805388019d092f1517c54fbdb0738f4fa80d89609ea959ba1c741a83460c651da62288916c47e1a5e
7
+ data.tar.gz: d53d94e2835a54521a17977f035c04d40766d958473890cfcacfd4cae5d99d792fdea5c8f18ff975155f0d25d5854134c7770f1d0e578e70d5c4aceddeed115c
@@ -1,19 +1,23 @@
1
- ## 0.2.3
1
+ ## 0.2.4 (2020-03-12)
2
+
3
+ - Added `percentile` method
4
+
5
+ ## 0.2.3 (2019-09-03)
2
6
 
3
7
  - Added support for Mongoid
4
8
  - Dropped support for Rails 4.2
5
9
 
6
- ## 0.2.2
10
+ ## 0.2.2 (2018-10-29)
7
11
 
8
12
  - Added support for MySQL with udf_infusion
9
13
  - Added support for SQL Server and Redshift
10
14
 
11
- ## 0.2.1
15
+ ## 0.2.1 (2018-10-15)
12
16
 
13
17
  - Added support for arrays and hashes
14
18
  - Added compatibility with Groupdate 4
15
19
 
16
- ## 0.2.0
20
+ ## 0.2.0 (2018-10-15)
17
21
 
18
22
  - Added support for MariaDB 10.3.3+ and SQLite
19
23
  - Use `PERCENTILE_CONT` for 4x performance increase
@@ -22,23 +26,23 @@ Breaking
22
26
 
23
27
  - Dropped support for Postgres < 9.4
24
28
 
25
- ## 0.1.4
29
+ ## 0.1.4 (2016-12-03)
26
30
 
27
31
  - Added `drop_function` method
28
32
 
29
- ## 0.1.3
33
+ ## 0.1.3 (2016-03-22)
30
34
 
31
35
  - Added support for ActiveRecord 5.0
32
36
 
33
- ## 0.1.2
37
+ ## 0.1.2 (2014-12-27)
34
38
 
35
39
  - Added support for ActiveRecord 4.2
36
40
 
37
- ## 0.1.1
41
+ ## 0.1.1 (2014-08-12)
38
42
 
39
43
  - 10x faster median
40
44
  - Added tests
41
45
 
42
- ## 0.1.0
46
+ ## 0.1.0 (2014-03-13)
43
47
 
44
48
  - First release
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013-2019 Andrew Kane
1
+ Copyright (c) 2013-2020 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ActiveMedian
2
2
 
3
- Median for Active Record, Mongoid, arrays, and hashes
3
+ Median and percentile for Active Record, Mongoid, arrays, and hashes
4
4
 
5
5
  Supports:
6
6
 
@@ -11,7 +11,7 @@ Supports:
11
11
 
12
12
  :fire: Uses native functions for blazing performance
13
13
 
14
- [![Build Status](https://travis-ci.org/ankane/active_median.svg)](https://travis-ci.org/ankane/active_median)
14
+ [![Build Status](https://travis-ci.org/ankane/active_median.svg?branch=master)](https://travis-ci.org/ankane/active_median)
15
15
 
16
16
  ## Getting Started
17
17
 
@@ -25,11 +25,19 @@ For MySQL and SQLite, also follow [these instructions](#additional-instructions)
25
25
 
26
26
  ## Models
27
27
 
28
+ Median
29
+
28
30
  ```ruby
29
31
  Item.median(:price)
30
32
  ```
31
33
 
32
- Works with grouping, too.
34
+ Percentile
35
+
36
+ ```ruby
37
+ Item.percentile(:price, 0.95)
38
+ ```
39
+
40
+ Works with grouping, too
33
41
 
34
42
  ```ruby
35
43
  Order.group(:store_id).median(:total)
@@ -37,11 +45,19 @@ Order.group(:store_id).median(:total)
37
45
 
38
46
  ## Arrays and Hashes
39
47
 
48
+ Median
49
+
40
50
  ```ruby
41
51
  [1, 2, 3].median
42
52
  ```
43
53
 
44
- You can also pass a block.
54
+ Percentile
55
+
56
+ ```ruby
57
+ [1, 2, 3].percentile(0.75)
58
+ ```
59
+
60
+ You can also pass a block
45
61
 
46
62
  ```ruby
47
63
  {a: 1, b: 2, c: 3}.median { |k, v| v }
@@ -106,3 +122,13 @@ Everyone is encouraged to help improve this project. Here are a few ways you can
106
122
  - Fix bugs and [submit pull requests](https://github.com/ankane/active_median/pulls)
107
123
  - Write, clarify, or fix documentation
108
124
  - Suggest or add new features
125
+
126
+ To get started with development and testing:
127
+
128
+ ```sh
129
+ git clone https://github.com/ankane/active_median.git
130
+ cd active_median
131
+ createdb active_median_test
132
+ bundle install
133
+ bundle exec rake test
134
+ ```
@@ -6,9 +6,35 @@ module Enumerable
6
6
  elsif !block && respond_to?(:with_scope)
7
7
  with_scope(self) { klass.median(*args) }
8
8
  else
9
+ raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0)" if args.any?
10
+ percentile(0.5, &block)
11
+ end
12
+ end
13
+ end
14
+
15
+ unless method_defined?(:percentile)
16
+ def percentile(*args, &block)
17
+ if !block && respond_to?(:scoping)
18
+ scoping { @klass.percentile(*args) }
19
+ elsif !block && respond_to?(:with_scope)
20
+ with_scope(self) { klass.percentile(*args) }
21
+ else
22
+ raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 1)" if args.size != 1
23
+
24
+ percentile = args[0].to_f
25
+ raise ArgumentError, "percentile is not between 0 and 1" if percentile < 0 || percentile > 1
26
+
27
+ # uses C=1 variant, like percentile_cont
28
+ # https://en.wikipedia.org/wiki/Percentile#The_linear_interpolation_between_closest_ranks_method
9
29
  sorted = map(&block).sort
10
- len = sorted.length
11
- (sorted[(len - 1) / 2] + sorted[len / 2]) / 2.0
30
+ x = percentile * (sorted.size - 1)
31
+ r = x % 1
32
+ i = x.floor
33
+ if i == sorted.size - 1
34
+ sorted[-1]
35
+ else
36
+ sorted[i] + r * (sorted[i + 1] - sorted[i])
37
+ end
12
38
  end
13
39
  end
14
40
  end
@@ -1,6 +1,16 @@
1
1
  module ActiveMedian
2
2
  module Model
3
3
  def median(column)
4
+ percentile(column, 0.5)
5
+ end
6
+
7
+ def percentile(column, percentile)
8
+ percentile = percentile.to_f
9
+ raise ArgumentError, "percentile is not between 0 and 1" if percentile < 0 || percentile > 1
10
+
11
+ # prevent SQL injection
12
+ percentile = connection.quote(percentile)
13
+
4
14
  group_values = all.group_values
5
15
 
6
16
  relation =
@@ -15,21 +25,31 @@ module ActiveMedian
15
25
  over = "PARTITION BY #{group_values.join(", ")}"
16
26
  end
17
27
 
18
- select(*group_values, "PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY #{column}) OVER (#{over})").unscope(:group)
28
+ select(*group_values, "PERCENTILE_CONT(#{percentile}) WITHIN GROUP (ORDER BY #{column}) OVER (#{over})").unscope(:group)
19
29
  else
20
30
  # if mysql gets native function, check (and memoize) version first
21
- select(*group_values, "PERCENTILE_CONT(#{column}, 0.50)")
31
+ select(*group_values, "PERCENTILE_CONT(#{column}, #{percentile})")
22
32
  end
23
33
  when /sqlserver/i
24
34
  if group_values.any?
25
35
  over = "PARTITION BY #{group_values.join(", ")}"
26
36
  end
27
37
 
28
- select(*group_values, "PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY #{column}) OVER (#{over})").unscope(:group)
38
+ select(*group_values, "PERCENTILE_CONT(#{percentile}) WITHIN GROUP (ORDER BY #{column}) OVER (#{over})").unscope(:group)
29
39
  when /sqlite/i
30
- select(*group_values, "MEDIAN(#{column})")
40
+ case percentile.to_f
41
+ when 0
42
+ select(*group_values, "MIN(#{column})")
43
+ when 0.5
44
+ select(*group_values, "MEDIAN(#{column})")
45
+ when 1
46
+ select(*group_values, "MAX(#{column})")
47
+ else
48
+ # LOWER_QUARTILE and UPPER_QUARTILE use different calculation than 0.25 and 0.75
49
+ raise "SQLite only supports 0, 0.5, and 1 percentiles"
50
+ end
31
51
  when /postg/i, /redshift/i # postgis too
32
- select(*group_values, "PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY #{column})")
52
+ select(*group_values, "PERCENTILE_CONT(#{percentile}) WITHIN GROUP (ORDER BY #{column})")
33
53
  else
34
54
  raise "Connection adapter not supported: #{connection.adapter_name}"
35
55
  end
@@ -1,21 +1,30 @@
1
1
  module ActiveMedian
2
2
  module Mongoid
3
- # https://www.compose.com/articles/mongo-metrics-finding-a-happy-median/
4
3
  def median(column)
4
+ percentile(column, 0.5)
5
+ end
6
+
7
+ # https://www.compose.com/articles/mongo-metrics-finding-a-happy-median/
8
+ def percentile(column, percentile)
9
+ percentile = percentile.to_f
10
+ raise ArgumentError, "percentile is not between 0 and 1" if percentile < 0 || percentile > 1
11
+
5
12
  relation =
6
13
  all
7
- .group(_id: nil, count: {"$sum" => 1}, values: {"$push" => "$#{column}"})
8
- .unwind("$values")
9
- .asc(:values)
10
- .project(count: {"$subtract" => ["$count", 1]}, values: 1)
11
- .project(count: 1, values: 1, midpoint: {"$divide" => ["$count", 2]})
12
- .project(count: 1, values: 1, midpoint: 1, high: {"$ceil" => "$midpoint"}, low: {"$floor" => "$midpoint"})
13
- .group(_id: nil, values: {"$push" => "$values"}, high: {"$avg" => "$high"}, low: {"$avg" => "$low"})
14
- .project(beginValue: {"$arrayElemAt" => ["$values", "$high"]}, endValue: {"$arrayElemAt" => ["$values", "$low"]})
15
- .project(median: {"$avg" => ["$beginValue", "$endValue"]})
14
+ .asc(column)
15
+ .group(_id: nil, values: {"$push" => "$#{column}"}, count: {"$sum" => 1})
16
+ .project(values: 1, count: {"$subtract" => ["$count", 1]})
17
+ .project(values: 1, count: 1, x: {"$multiply" => ["$count", percentile]})
18
+ .project(values: 1, count: 1, r: {"$mod" => ["$x", 1]}, i: {"$floor" => "$x"})
19
+ .project(values: 1, count: 1, r: 1, i: 1, i2: {"$add" => ["$i", 1]})
20
+ .project(values: 1, count: 1, r: 1, i: 1, i2: {"$min" => ["$i2", "$count"]})
21
+ .project(r: 1, beginValue: {"$arrayElemAt" => ["$values", "$i"]}, endValue: {"$arrayElemAt" => ["$values", "$i2"]})
22
+ .project(r: 1, beginValue: 1, result: {"$subtract" => ["$endValue", "$beginValue"]})
23
+ .project(beginValue: 1, result: {"$multiply" => ["$result", "$r"]})
24
+ .project(result: {"$add" => ["$beginValue", "$result"]})
16
25
 
17
26
  res = collection.aggregate(relation.pipeline).first
18
- res ? res["median"] : nil
27
+ res ? res["result"] : nil
19
28
  end
20
29
  end
21
30
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveMedian
2
- VERSION = "0.2.3"
2
+ VERSION = "0.2.4"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_median
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-04 00:00:00.000000000 Z
11
+ date: 2020-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -141,8 +141,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
141
141
  - !ruby/object:Gem::Version
142
142
  version: '0'
143
143
  requirements: []
144
- rubygems_version: 3.0.3
144
+ rubygems_version: 3.1.2
145
145
  signing_key:
146
146
  specification_version: 4
147
- summary: Median for Active Record, Mongoid, arrays, and hashes
147
+ summary: Median and percentile for Active Record, Mongoid, arrays, and hashes
148
148
  test_files: []