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 +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
|
-
[![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
|
-
|
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: []
|