active_median 0.2.3 → 0.2.4
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 +4 -4
- data/CHANGELOG.md +13 -9
- data/LICENSE.txt +1 -1
- data/README.md +30 -4
- data/lib/active_median/enumerable.rb +28 -2
- data/lib/active_median/model.rb +25 -5
- data/lib/active_median/mongoid.rb +20 -11
- data/lib/active_median/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 86998cba53771c73de5fcc9c5dc8a869a4ff061029828dcf0ce8fab89852e346
|
4
|
+
data.tar.gz: 23482bad0017c7b16422e9be9961783a6674576a65955a6d228db22f556ef84b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c5f02aa5f51169214536006caa8509b4fcc7041d1be680805388019d092f1517c54fbdb0738f4fa80d89609ea959ba1c741a83460c651da62288916c47e1a5e
|
7
|
+
data.tar.gz: d53d94e2835a54521a17977f035c04d40766d958473890cfcacfd4cae5d99d792fdea5c8f18ff975155f0d25d5854134c7770f1d0e578e70d5c4aceddeed115c
|
data/CHANGELOG.md
CHANGED
@@ -1,19 +1,23 @@
|
|
1
|
-
## 0.2.
|
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
|
data/LICENSE.txt
CHANGED
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
|
-
[](https://travis-ci.org/ankane/active_median)
|
14
|
+
[](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
|
-
|
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
|
-
|
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
|
-
|
11
|
-
|
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
|
data/lib/active_median/model.rb
CHANGED
@@ -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(
|
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},
|
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(
|
38
|
+
select(*group_values, "PERCENTILE_CONT(#{percentile}) WITHIN GROUP (ORDER BY #{column}) OVER (#{over})").unscope(:group)
|
29
39
|
when /sqlite/i
|
30
|
-
|
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(
|
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
|
-
.
|
8
|
-
.
|
9
|
-
.
|
10
|
-
.project(count: {"$
|
11
|
-
.project(
|
12
|
-
.project(
|
13
|
-
.
|
14
|
-
.project(beginValue: {"$arrayElemAt" => ["$values", "$
|
15
|
-
.project(
|
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["
|
27
|
+
res ? res["result"] : nil
|
19
28
|
end
|
20
29
|
end
|
21
30
|
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.
|
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:
|
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.
|
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: []
|