ar_result_calculations 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.textile +35 -0
- data/Rakefile +2 -0
- data/ar_result_calculations.gemspec +25 -0
- data/lib/ar_result_calculations.rb +6 -0
- data/lib/ar_result_calculations/ar_result_calculations.rb +159 -0
- data/lib/ar_result_calculations/version.rb +3 -0
- metadata +76 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 [name of plugin creator]
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
h1. Description
|
2
|
+
|
3
|
+
p. ar_result_calculations adds methods to Array for when Array is an ActiveRecord result set. For example:
|
4
|
+
|
5
|
+
bc. Product.all.sum(:price)
|
6
|
+
Product.all.average(:price)
|
7
|
+
Product.all.regression(:sales_volumn)
|
8
|
+
|
9
|
+
p. Will return the sum of the attribute :price from all rows. Note this is not the same as Product.sum(:price) which will create an SQL statement that does the summing.
|
10
|
+
|
11
|
+
h2. Methods created on Array
|
12
|
+
|
13
|
+
p. All methods take one parameter (column name).
|
14
|
+
|
15
|
+
|_. Method|_. Description|
|
16
|
+
|sum|Sum the given column. Delegates to super() if not an AR result set|
|
17
|
+
|mean|Mean of the given column. Delegates to super() if not an AR result set. Aliases of **avg** and **average**|
|
18
|
+
|count|Count the given column. Delegates to super() if not an AR result set|
|
19
|
+
|min|Min the given column. Delegates to super() if not an AR result set|
|
20
|
+
|max|Max the given column. Delegates to super() if not an AR result set|
|
21
|
+
|regression|Ordinary Least Squares regression on the given column. Returns the regression as an array|
|
22
|
+
|slope|Returns the slope of a regression on a given column|
|
23
|
+
|make_numeric_|Coerces a column to be numeric. Useful if you have derived columns returned from a query that are not in the model definition and hence are otherwise returned as strings. Returns **self** so is composable|
|
24
|
+
|
25
|
+
h1. License
|
26
|
+
|
27
|
+
(The MIT License)
|
28
|
+
|
29
|
+
Copyright © 2010 Kip Cole
|
30
|
+
|
31
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
32
|
+
|
33
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
34
|
+
|
35
|
+
THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "ar_result_calculations/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "ar_result_calculations"
|
7
|
+
s.version = ArResultCalculations::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Kip Cole"]
|
10
|
+
s.email = ["kipcole9@gmail.com"]
|
11
|
+
s.homepage = "http://github.com/kipcole9/ar_result_calculations"
|
12
|
+
s.summary = %q{Calculations on ActiveRecord result sets.}
|
13
|
+
s.description = <<-EOF
|
14
|
+
Defines Array#calculation methods for ActiveRecord result sets. Provides
|
15
|
+
#sum, #min, #max, #count, #mean, #regression, #slope. Delegates to super()
|
16
|
+
if not an AR result set where appropriate.
|
17
|
+
EOF
|
18
|
+
|
19
|
+
s.rubyforge_project = "ar_result_calculations"
|
20
|
+
|
21
|
+
s.files = `git ls-files`.split("\n")
|
22
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
23
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
24
|
+
s.require_paths = ["lib"]
|
25
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module ArResultCalculations
|
2
|
+
module Calculations
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
extend ClassMethods
|
6
|
+
include InstanceMethods
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module InstanceMethods
|
11
|
+
# Return the sum of a column from an active record result set
|
12
|
+
# Calls super() if not a result set
|
13
|
+
#
|
14
|
+
# column: The column name to sum
|
15
|
+
def sum(column = nil)
|
16
|
+
return super() unless column && first && first.class.respond_to?(:descends_from_active_record?)
|
17
|
+
inject( 0 ) { |sum, x| x[column].nil? ? sum : sum + x[column] }
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return the average of a column from an active record result set
|
21
|
+
# Calls super() if not a result set
|
22
|
+
#
|
23
|
+
# column: The column name to average
|
24
|
+
def mean(column = nil)
|
25
|
+
return super() unless column && first && first.class.respond_to?(:descends_from_active_record?)
|
26
|
+
(length > 0) ? sum(column) / length : 0
|
27
|
+
end
|
28
|
+
alias :avg :mean
|
29
|
+
alias :average :mean
|
30
|
+
|
31
|
+
# Return the count of a column from an active record result set
|
32
|
+
# Calls super() if not a result set. nil values are not counted
|
33
|
+
#
|
34
|
+
# column: The column name to count
|
35
|
+
def count(column = nil)
|
36
|
+
return super() unless column && first && first.class.respond_to?(:descends_from_active_record?)
|
37
|
+
inject( 0 ) { |sum, x| x[column].nil? ? sum : sum + 1 }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Return the max of a column from an active record result set
|
41
|
+
# Calls super() if not a result set
|
42
|
+
#
|
43
|
+
# column: The column name to max
|
44
|
+
def max(column = nil)
|
45
|
+
return super() unless column && first && first.class.respond_to?(:descends_from_active_record?)
|
46
|
+
map(&column.to_sym).max
|
47
|
+
end
|
48
|
+
alias :maximum :max
|
49
|
+
|
50
|
+
# Return the min of a column from an active record result set
|
51
|
+
# Calls super() if not a result set
|
52
|
+
#
|
53
|
+
# column: The column name to sum
|
54
|
+
def min(column = nil)
|
55
|
+
return super() unless column && first && first.class.respond_to?(:descends_from_active_record?)
|
56
|
+
map(&column.to_sym).min
|
57
|
+
end
|
58
|
+
alias :minimum :min
|
59
|
+
|
60
|
+
# Return a regression (OLS) of a column from an active record result set
|
61
|
+
# Calls super() if not a result set
|
62
|
+
#
|
63
|
+
# column: The column name to regress
|
64
|
+
def regression(column = nil)
|
65
|
+
return nil unless first
|
66
|
+
unless is_numeric?(first)
|
67
|
+
raise ArgumentError, "Regression needs an array of ActiveRecord objects" unless column && first && first.class.respond_to?(:descends_from_active_record?)
|
68
|
+
series = map { |x| x[column] }
|
69
|
+
end
|
70
|
+
Array::LinearRegression.new(series || self).fit
|
71
|
+
end
|
72
|
+
|
73
|
+
# Return the slope of a regression on a column from an active record result set
|
74
|
+
# Calls super() if not a result set
|
75
|
+
#
|
76
|
+
# column: The column name to regress
|
77
|
+
def slope(column = nil)
|
78
|
+
return nil unless first
|
79
|
+
unless is_numeric?(first)
|
80
|
+
column ||= first_numeric_column
|
81
|
+
series = map { |x| x[column] }
|
82
|
+
end
|
83
|
+
Array::LinearRegression.new(series || self).slope
|
84
|
+
end
|
85
|
+
alias :trend :slope
|
86
|
+
|
87
|
+
# Force a column to be numeric. Useful if you have derived
|
88
|
+
# columns from a query that is not part of the base model.
|
89
|
+
#
|
90
|
+
# column: The column name to sum
|
91
|
+
#
|
92
|
+
# returns self so you can compose other methods.
|
93
|
+
def make_numeric(column)
|
94
|
+
return self unless column && first && first.class.respond_to?(:descends_from_active_record?)
|
95
|
+
each do |row|
|
96
|
+
next if is_numeric?(row[column])
|
97
|
+
row[column] = row[column] =~ /[-+]?[0-9]+(\.[0-9]+)/ ? row[column].to_f : row[column].to_i
|
98
|
+
end
|
99
|
+
self
|
100
|
+
end
|
101
|
+
alias :coerce_numeric :make_numeric
|
102
|
+
|
103
|
+
private
|
104
|
+
def first_numeric_column
|
105
|
+
raise ArgumentError, "Slope needs an array of ActiveRecord objects" unless first && first.class.respond_to?(:descends_from_active_record?)
|
106
|
+
first.attributes.each {|attribute, value| return attribute if is_numeric?(value) }
|
107
|
+
raise ArgumentError, "Slope could not detect a numberic attribute. Please provide an attribute name as an argument"
|
108
|
+
end
|
109
|
+
|
110
|
+
def is_numeric?(val)
|
111
|
+
is_integer?(val) || is_float?(val)
|
112
|
+
end
|
113
|
+
|
114
|
+
def is_integer?(val)
|
115
|
+
val.is_a?(Fixnum) || val.is_a?(Integer) || val.is_a?(Bignum)
|
116
|
+
end
|
117
|
+
|
118
|
+
def is_float?(val)
|
119
|
+
val.is_a?(Float) || val.is_a?(Rational)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
module ClassMethods
|
124
|
+
# Courtesy of http://blog.internautdesign.com/2008/4/21/simple-linear-regression-best-fit
|
125
|
+
class Array::LinearRegression
|
126
|
+
attr_accessor :slope, :offset
|
127
|
+
|
128
|
+
def initialize dx, dy=nil
|
129
|
+
@size = dx.size
|
130
|
+
dy,dx = dx,axis() unless dy # make 2D if given 1D
|
131
|
+
raise ArgumentError, "[regression] Arguments are not same length!" unless @size == dy.size
|
132
|
+
sxx = sxy = sx = sy = 0
|
133
|
+
dx.zip(dy).each do |x,y|
|
134
|
+
sxy += x*y
|
135
|
+
sxx += x*x
|
136
|
+
sx += x
|
137
|
+
sy += y
|
138
|
+
end
|
139
|
+
@slope = ( @size * sxy - sx * sy ) / ( @size * sxx - sx * sx ) rescue 0
|
140
|
+
@offset = (sy - @slope * sx) / @size
|
141
|
+
end
|
142
|
+
|
143
|
+
def fit
|
144
|
+
return axis.map{|data| predict(data) }
|
145
|
+
end
|
146
|
+
|
147
|
+
def predict( x )
|
148
|
+
y = @slope * x + @offset
|
149
|
+
end
|
150
|
+
|
151
|
+
def axis
|
152
|
+
(0...@size).to_a
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ar_result_calculations
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Kip Cole
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-30 00:00:00 +08:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: " Defines Array#calculation methods for ActiveRecord result sets. Provides\n #sum, #min, #max, #count, #mean, #regression, #slope. Delegates to super()\n if not an AR result set where appropriate.\n"
|
23
|
+
email:
|
24
|
+
- kipcole9@gmail.com
|
25
|
+
executables: []
|
26
|
+
|
27
|
+
extensions: []
|
28
|
+
|
29
|
+
extra_rdoc_files: []
|
30
|
+
|
31
|
+
files:
|
32
|
+
- .gitignore
|
33
|
+
- Gemfile
|
34
|
+
- MIT-LICENSE
|
35
|
+
- README.textile
|
36
|
+
- Rakefile
|
37
|
+
- ar_result_calculations.gemspec
|
38
|
+
- lib/ar_result_calculations.rb
|
39
|
+
- lib/ar_result_calculations/ar_result_calculations.rb
|
40
|
+
- lib/ar_result_calculations/version.rb
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://github.com/kipcole9/ar_result_calculations
|
43
|
+
licenses: []
|
44
|
+
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
hash: 3
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
hash: 3
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
version: "0"
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project: ar_result_calculations
|
71
|
+
rubygems_version: 1.3.7
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: Calculations on ActiveRecord result sets.
|
75
|
+
test_files: []
|
76
|
+
|