davidrichards-sirb 0.6.14
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.
- data/README.rdoc +227 -0
- data/VERSION.yml +4 -0
- data/bin/sirb +33 -0
- data/lib/overrides/array.rb +6 -0
- data/lib/overrides/file.rb +17 -0
- data/lib/overrides/module.rb +70 -0
- data/lib/overrides/symbol.rb +39 -0
- data/lib/sirb/enumerable_statistics.rb +350 -0
- data/lib/sirb/functional.rb +114 -0
- data/lib/sirb/general_statistics.rb +72 -0
- data/lib/sirb/inter_enumerable_statistics.rb +139 -0
- data/lib/sirb/lib_loader.rb +45 -0
- data/lib/sirb/runner.rb +274 -0
- data/lib/sirb/sproc/proc.rb +5 -0
- data/lib/sirb/sproc/proc_source.rb +130 -0
- data/lib/sirb/sproc/sproc.rb +79 -0
- data/lib/sirb/sproc/usage_notes.txt +25 -0
- data/lib/sirb/sproc.rb +29 -0
- data/lib/sirb/thread_support.rb +20 -0
- data/lib/sirb/unbound_method.rb +5 -0
- data/lib/sirb.rb +52 -0
- data/lib/stored_procedures.rb +10 -0
- data/spec/lib/overrides/array_spec.rb +7 -0
- data/spec/lib/overrides/file_spec.rb +13 -0
- data/spec/lib/overrides/module_spec.rb +86 -0
- data/spec/lib/overrides/symbol_spec.rb +39 -0
- data/spec/lib/sirb/enumerable_statistics_spec.rb +85 -0
- data/spec/lib/sirb/functional_spec.rb +75 -0
- data/spec/lib/sirb/general_statistics_spec.rb +40 -0
- data/spec/lib/sirb/inter_enumerable_statistics_spec.rb +55 -0
- data/spec/lib/sirb/lib_loader_spec.rb +39 -0
- data/spec/lib/sirb/runner_spec.rb +9 -0
- data/spec/lib/sirb/sproc/proc_spec.rb +9 -0
- data/spec/lib/sirb/sproc/sproc_spec.rb +25 -0
- data/spec/lib/sirb/unbound_method_spec.rb +12 -0
- data/spec/lib/sirb_spec.rb +9 -0
- data/spec/spec_helper.rb +15 -0
- metadata +97 -0
@@ -0,0 +1,350 @@
|
|
1
|
+
module Sirb #:nodoc:
|
2
|
+
|
3
|
+
# These are the standard R vector functions that I want to add to any
|
4
|
+
# Enumerable class for Ruby. I started by borrowing heavily from
|
5
|
+
# Gotoken' math/statistics project
|
6
|
+
# (http://raa.ruby-lang.org/project/math-statistics/). There were a few
|
7
|
+
# changes that don't make sense in the idiomatic Ruby that I now use (a
|
8
|
+
# few things have changed since 2001).
|
9
|
+
#
|
10
|
+
# The following is a table of values from R to my methods
|
11
|
+
#
|
12
|
+
# max | max
|
13
|
+
# min | min
|
14
|
+
# sum | sum
|
15
|
+
# mean | mean
|
16
|
+
# median | median
|
17
|
+
# range | range
|
18
|
+
# var | var variance
|
19
|
+
# cor | cor correlation
|
20
|
+
# sort | sort
|
21
|
+
# rank | rank
|
22
|
+
# order | order
|
23
|
+
# quantile | quantile
|
24
|
+
# cumsum | cum_sum cumulative_sum
|
25
|
+
# cumprod | cum_prod cumulative_product
|
26
|
+
# cummax | cum_max cumulative_max
|
27
|
+
# cummin | cum_min cumulative_min
|
28
|
+
# pmax | p_max
|
29
|
+
# pmin | p_min
|
30
|
+
|
31
|
+
module EnumerableStatistics
|
32
|
+
|
33
|
+
# There are issues with this...
|
34
|
+
include GeneralStatistics
|
35
|
+
|
36
|
+
def self.append_features(mod)
|
37
|
+
|
38
|
+
alias :original_max :max
|
39
|
+
alias :original_min :min
|
40
|
+
|
41
|
+
unless mod < Enumerable
|
42
|
+
raise TypeError,
|
43
|
+
"`#{self}' can't be included non Enumerable (#{mod})"
|
44
|
+
end
|
45
|
+
|
46
|
+
def mod.default_block= (block)
|
47
|
+
self.const_set("STAT_BLOCK", block)
|
48
|
+
end
|
49
|
+
|
50
|
+
def mod.default_block
|
51
|
+
defined?(self::STAT_BLOCK) && self::STAT_BLOCK
|
52
|
+
end
|
53
|
+
|
54
|
+
super
|
55
|
+
end
|
56
|
+
|
57
|
+
def default_block
|
58
|
+
@stat_block || self.class.default_block
|
59
|
+
end
|
60
|
+
|
61
|
+
def default_block=(block)
|
62
|
+
@stat_block = block
|
63
|
+
end
|
64
|
+
|
65
|
+
def sum
|
66
|
+
sum = 0.0
|
67
|
+
if block_given?
|
68
|
+
each{|i| sum += yield(i)}
|
69
|
+
elsif default_block
|
70
|
+
each{|i| sum += default_block[*i]}
|
71
|
+
else
|
72
|
+
each{|i| sum += i}
|
73
|
+
end
|
74
|
+
sum
|
75
|
+
end
|
76
|
+
|
77
|
+
def average(&block)
|
78
|
+
sum(&block)/size
|
79
|
+
end
|
80
|
+
alias :mean :average
|
81
|
+
alias :avg :average
|
82
|
+
|
83
|
+
def variance(&block)
|
84
|
+
m = mean(&block)
|
85
|
+
sum_of_differences = if block_given?
|
86
|
+
sum{ |i| j=yield(i); (m - j) ** 2 }
|
87
|
+
elsif default_block
|
88
|
+
sum{ |i| j=default_block[*i]; (m - j) ** 2 }
|
89
|
+
else
|
90
|
+
sum{ |i| (m - i) ** 2 }
|
91
|
+
end
|
92
|
+
sum_of_differences / (size - 1)
|
93
|
+
end
|
94
|
+
alias :var :variance
|
95
|
+
|
96
|
+
def standard_deviation(&block)
|
97
|
+
Math::sqrt(variance(&block))
|
98
|
+
end
|
99
|
+
alias :std :standard_deviation
|
100
|
+
|
101
|
+
def min(&block)
|
102
|
+
list = if block_given?
|
103
|
+
map{|x| yield(x) }
|
104
|
+
elsif default_block
|
105
|
+
map{|x| default_block[*x] }
|
106
|
+
else
|
107
|
+
self
|
108
|
+
end
|
109
|
+
Object.min(*list)
|
110
|
+
end
|
111
|
+
|
112
|
+
def min_index
|
113
|
+
index(min)
|
114
|
+
end
|
115
|
+
|
116
|
+
def max
|
117
|
+
list = if block_given?
|
118
|
+
map{|x| yield(x) }
|
119
|
+
elsif default_block
|
120
|
+
map{|x| default_block[*x] }
|
121
|
+
else
|
122
|
+
self
|
123
|
+
end
|
124
|
+
Object.max(*list)
|
125
|
+
end
|
126
|
+
|
127
|
+
def max_index
|
128
|
+
index(max)
|
129
|
+
end
|
130
|
+
|
131
|
+
# The slow way is to iterate up to the middle point. A faster way is to
|
132
|
+
# use the index, when available. If a block is supplied, always iterate
|
133
|
+
# to the middle point.
|
134
|
+
def median(ratio=0.5, &block)
|
135
|
+
return iterate_midway(ratio, &block) if block_given?
|
136
|
+
begin
|
137
|
+
mid1, mid2 = middle_two
|
138
|
+
sorted = new_sort
|
139
|
+
med1, med2 = sorted[mid1], sorted[mid2]
|
140
|
+
return med1 if med1 == med2
|
141
|
+
return med1 + ((med2 - med1) * ratio)
|
142
|
+
rescue
|
143
|
+
iterate_midway(ratio, &block)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def middle_two
|
148
|
+
mid2 = size.div(2)
|
149
|
+
mid1 = (size % 2 == 0) ? mid2 - 1 : mid2
|
150
|
+
return mid1, mid2
|
151
|
+
end
|
152
|
+
protected :middle_two
|
153
|
+
|
154
|
+
def median_position
|
155
|
+
middle_two.last
|
156
|
+
end
|
157
|
+
protected :median_position
|
158
|
+
|
159
|
+
def first_half(&block)
|
160
|
+
fh = self[0..median_position].dup
|
161
|
+
end
|
162
|
+
protected :first_half
|
163
|
+
|
164
|
+
def second_half(&block)
|
165
|
+
# Total crap, but it's the way R does things, and this will most likely
|
166
|
+
# only be used to feed R some numbers to plot, if at all.
|
167
|
+
sh = size <= 5 ? self[median_position..-1].dup : self[median_position - 1..-1].dup
|
168
|
+
end
|
169
|
+
protected :second_half
|
170
|
+
|
171
|
+
# An iterative version of median
|
172
|
+
def iterate_midway(ratio, &block)
|
173
|
+
mid1, mid2, last_value, j, sorted, sort1, sort2 = middle_two, nil, 0, new_sort, nil, nil
|
174
|
+
|
175
|
+
if block_given?
|
176
|
+
sorted.each do |i|
|
177
|
+
last_value = yield(i)
|
178
|
+
j += 1
|
179
|
+
sort1 = last_value if j == mid1
|
180
|
+
sort2 = last_value if j == mid2
|
181
|
+
break if j >= mid2
|
182
|
+
end
|
183
|
+
elsif default_block
|
184
|
+
sorted.each do |i|
|
185
|
+
last_value = default_block[*i]
|
186
|
+
j += 1
|
187
|
+
sort1 = last_value if j == mid1
|
188
|
+
sort2 = last_value if j == mid2
|
189
|
+
break if j >= mid2
|
190
|
+
end
|
191
|
+
else
|
192
|
+
sorted.each do |i|
|
193
|
+
last_value = i
|
194
|
+
sort1 = last_value if j == mid1
|
195
|
+
sort2 = last_value if j == mid2
|
196
|
+
j += 1
|
197
|
+
break if j >= mid2
|
198
|
+
end
|
199
|
+
end
|
200
|
+
return med1 if med1 == med2
|
201
|
+
return med1 + ((med2 - med1) * ratio)
|
202
|
+
end
|
203
|
+
protected :iterate_midway
|
204
|
+
|
205
|
+
# Just an array of [min, max] to comply with R uses of the work. Use
|
206
|
+
# range_as_range if you want a real Range.
|
207
|
+
def range(&block)
|
208
|
+
[min(&block), max(&block)]
|
209
|
+
end
|
210
|
+
|
211
|
+
# Useful for setting a real range class (FixedRange).
|
212
|
+
def range_class=(klass)
|
213
|
+
@range_class = klass
|
214
|
+
end
|
215
|
+
|
216
|
+
def range_class
|
217
|
+
@range_class ||= Range
|
218
|
+
end
|
219
|
+
|
220
|
+
def range_as_range(&block)
|
221
|
+
range_class.new(min(&block), max(&block))
|
222
|
+
end
|
223
|
+
|
224
|
+
# I don't pass the block to the sort, because a sort block needs to look
|
225
|
+
# something like: {|x,y| x <=> y}. To get around this, set the default
|
226
|
+
# block on the object.
|
227
|
+
def new_sort(&block)
|
228
|
+
if block_given?
|
229
|
+
map { |i| yield(i) }.sort.dup
|
230
|
+
elsif default_block
|
231
|
+
map { |i| default_block[*i] }.sort.dup
|
232
|
+
else
|
233
|
+
sort().dup
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# Doesn't overwrite things like Matrix#rank
|
238
|
+
def rank(&block)
|
239
|
+
|
240
|
+
sorted = new_sort
|
241
|
+
|
242
|
+
if block_given?
|
243
|
+
map { |i| sorted.index(yield(i)) + 1 }
|
244
|
+
elsif default_block
|
245
|
+
map { |i| sorted.index(default_block[*i]) + 1 }
|
246
|
+
else
|
247
|
+
map { |i| sorted.index(i) + 1 }
|
248
|
+
end
|
249
|
+
|
250
|
+
end unless defined?(rank)
|
251
|
+
|
252
|
+
# Given values like [10,5,5,1]
|
253
|
+
# Rank should produce something like [4,2,2,1]
|
254
|
+
# And order should produce something like [4,2,3,1]
|
255
|
+
# The trick is that rank skips as many as were duplicated, so there
|
256
|
+
# could not be a 3 in the rank from the example above.
|
257
|
+
def order(&block)
|
258
|
+
hold= []
|
259
|
+
rank(&block).each_with_index do |x, i|
|
260
|
+
j = i
|
261
|
+
while hold.include?(j) do
|
262
|
+
j += 1
|
263
|
+
end
|
264
|
+
hold << j
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# First quartile: nth_split_by_m(1, 4)
|
269
|
+
# Third quartile: nth_split_by_m(3, 4)
|
270
|
+
# Median: nth_split_by_m(1, 2)
|
271
|
+
# Doesn't match R, and it's silly to try to.
|
272
|
+
# def nth_split_by_m(n, m)
|
273
|
+
# sorted = new_sort
|
274
|
+
# dividers = m - 1
|
275
|
+
# if size % m == dividers # Divides evenly
|
276
|
+
# # Because we have a 0-based list, we get the floor
|
277
|
+
# i = ((size / m.to_f) * n).floor
|
278
|
+
# j = i
|
279
|
+
# else
|
280
|
+
# # This reflects R's approach, which I don't think I agree with.
|
281
|
+
# i = (((size / m.to_f) * n) - 1)
|
282
|
+
# i = i > (size / m.to_f) ? i.floor : i.ceil
|
283
|
+
# j = i + 1
|
284
|
+
# end
|
285
|
+
# sorted[i] + ((n / m.to_f) * (sorted[j] - sorted[i]))
|
286
|
+
# end
|
287
|
+
|
288
|
+
def quantile(&block)
|
289
|
+
[
|
290
|
+
min(&block),
|
291
|
+
first_half(&block).median(0.25, &block),
|
292
|
+
median(&block),
|
293
|
+
second_half(&block).median(0.75, &block),
|
294
|
+
max(&block)
|
295
|
+
]
|
296
|
+
end
|
297
|
+
|
298
|
+
def cum_sum(sorted=false, &block)
|
299
|
+
sum = 0.0
|
300
|
+
obj = sorted ? self.new_sort : self
|
301
|
+
if block_given?
|
302
|
+
obj.map { |i| sum += yield(i) }
|
303
|
+
elsif default_block
|
304
|
+
obj.map { |i| sum += default_block[*i] }
|
305
|
+
else
|
306
|
+
obj.map { |i| sum += i }
|
307
|
+
end
|
308
|
+
end
|
309
|
+
alias :cumulative_sum :cum_sum
|
310
|
+
|
311
|
+
def cum_prod(sorted=false, &block)
|
312
|
+
prod = 1.0
|
313
|
+
obj = sorted ? self.new_sort : self
|
314
|
+
if block_given?
|
315
|
+
obj.map { |i| prod *= yield(i) }
|
316
|
+
elsif default_block
|
317
|
+
obj.map { |i| prod *= default_block[*i] }
|
318
|
+
else
|
319
|
+
obj.map { |i| prod *= i }
|
320
|
+
end
|
321
|
+
end
|
322
|
+
alias :cumulative_product :cum_prod
|
323
|
+
|
324
|
+
def cum_max(&block)
|
325
|
+
current_max = nil
|
326
|
+
if block_given?
|
327
|
+
map {|i| current_max = Object.max(current_max, yield(i)) }
|
328
|
+
elsif default_block
|
329
|
+
map {|i| current_max = Object.max(current_max, default_block[*i]) }
|
330
|
+
else
|
331
|
+
map {|i| current_max = Object.max(current_max, i) }
|
332
|
+
end
|
333
|
+
end
|
334
|
+
alias :cumulative_max :cum_max
|
335
|
+
|
336
|
+
def cum_min(&block)
|
337
|
+
current_min = nil
|
338
|
+
if block_given?
|
339
|
+
map {|i| current_min = Object.min(current_min, yield(i)) }
|
340
|
+
elsif default_block
|
341
|
+
map {|i| current_min = Object.min(current_min, default_block[*i]) }
|
342
|
+
else
|
343
|
+
map {|i| current_min = Object.min(current_min, i) }
|
344
|
+
end
|
345
|
+
end
|
346
|
+
alias :cumulative_min :cum_min
|
347
|
+
|
348
|
+
end
|
349
|
+
|
350
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# This is probably border-line for what O'Reilly meant for using their
|
2
|
+
# code. I grabbed six methods from The Ruby Programming Language,
|
3
|
+
# section 6.8. I want to experiment with how this could change some of
|
4
|
+
# my methods.
|
5
|
+
# This module defines methods and operators for functional programming.
|
6
|
+
module Functional
|
7
|
+
|
8
|
+
# Apply this function to each element of the specified Enumerable,
|
9
|
+
# returning an array of results. This is the reverse of Enumerable.map.
|
10
|
+
# Use | as an operator alias. Read "|" as "over" or "applied over".
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
# a = [[1,2],[3,4]]
|
14
|
+
# sum = lambda {|x,y| x+y}
|
15
|
+
# sums = sum|a # => [3,7]
|
16
|
+
def apply(enum)
|
17
|
+
enum.respond_to?(:map) ? enum.map(&self) : self.call(enum)
|
18
|
+
end
|
19
|
+
alias | apply
|
20
|
+
|
21
|
+
# Use this function to "reduce" an enumerable to a single quantity.
|
22
|
+
# This is the inverse of Enumerable.inject.
|
23
|
+
# Use <= as an operator alias.
|
24
|
+
# Mnemonic: <= looks like a needle for injections
|
25
|
+
# Example:
|
26
|
+
# data = [1,2,3,4]
|
27
|
+
# sum = lambda {|x,y| x+y}
|
28
|
+
# total = sum<=data # => 10
|
29
|
+
def reduce(enum)
|
30
|
+
enum.inject &self
|
31
|
+
end
|
32
|
+
alias <= reduce
|
33
|
+
|
34
|
+
# Return a new lambda that computes self[f[args]].
|
35
|
+
# Use * as an operator alias for compose.
|
36
|
+
# Examples, using the * alias for this method.
|
37
|
+
#
|
38
|
+
# f = lambda {|x| x*x }
|
39
|
+
# g = lambda {|x| x+1 }
|
40
|
+
# (f*g)[2] # => 9
|
41
|
+
# (g*f)[2] # => 5
|
42
|
+
#
|
43
|
+
# def polar(x,y)
|
44
|
+
# [Math.hypot(y,x), Math.atan2(y,x)]
|
45
|
+
# end
|
46
|
+
# def cartesian(magnitude, angle)
|
47
|
+
# [magnitude*Math.cos(angle), magnitude*Math.sin(angle)]
|
48
|
+
# end
|
49
|
+
# p,c = method :polar, method :cartesian
|
50
|
+
# (c*p)[3,4] # => [3,4]
|
51
|
+
#
|
52
|
+
def compose(f)
|
53
|
+
if self.respond_to?(:arity) && self.arity == 1
|
54
|
+
lambda {|*args| self[f[*args]] }
|
55
|
+
else
|
56
|
+
lambda {|*args| self[*f[*args]] }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# * is the natural operator for function composition.
|
61
|
+
alias * compose
|
62
|
+
|
63
|
+
# Return a lambda equivalent to this one with one or more initial
|
64
|
+
# arguments applied. When only a single argument
|
65
|
+
# is being specified, the >> alias may be simpler to use.
|
66
|
+
# Example:
|
67
|
+
# product = lambda {|x,y| x*y}
|
68
|
+
# doubler = product >> 2
|
69
|
+
#
|
70
|
+
def apply_head(*first)
|
71
|
+
lambda {|*rest| self[*first.concat(rest)]}
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Return a lambda equivalent to this one with one or more final arguments
|
76
|
+
# applied. When only a single argument is being specified,
|
77
|
+
# the << alias may be simpler.
|
78
|
+
# Example:
|
79
|
+
# difference = lambda {|x,y| x-y }
|
80
|
+
# decrement = difference << 1
|
81
|
+
#
|
82
|
+
def apply_tail(*last)
|
83
|
+
lambda {|*rest| self[*rest.concat(last)]}
|
84
|
+
end
|
85
|
+
|
86
|
+
# Here are operator alternatives for these methods. The angle brackets
|
87
|
+
# point to the side on which the argument is shifted in.
|
88
|
+
# alias >> apply_head # g = f >> 2 -- set first arg to 2
|
89
|
+
# alias << apply_tail # g = f << 2 -- set last arg to 2
|
90
|
+
|
91
|
+
# Return a new lambda that caches the results of this function and
|
92
|
+
# only calls the function when new arguments are supplied.
|
93
|
+
#
|
94
|
+
def memoize
|
95
|
+
cache = {} # An empty cache. The lambda captures this in its closure.
|
96
|
+
lambda {|*args|
|
97
|
+
# notice that the hash key is the entire array of arguments!
|
98
|
+
unless cache.has_key?(args) # If no cached result for these args
|
99
|
+
cache[args] = self[*args] # Compute and cache the result
|
100
|
+
end
|
101
|
+
cache[args] # Return result from cache
|
102
|
+
}
|
103
|
+
end
|
104
|
+
# A (probably unnecessary) unary + operator for memoization
|
105
|
+
# Mnemonic: the + operator means "improved"
|
106
|
+
alias +@ memoize # cached_f = +f
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
# I add these here, instead of in overrides because it makes things a
|
111
|
+
# lot simpler for sirb.rb to figure out the load order.
|
112
|
+
# Add these functional programming methods to Proc and Method classes.
|
113
|
+
class Proc; include Functional; end
|
114
|
+
class Method; include Functional; end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Sirb #:nodoc:
|
2
|
+
# These are general statistics, things that should stand on their own as
|
3
|
+
# concepts unattached to vectors or scalars or whatever.
|
4
|
+
module GeneralStatistics #:nodoc:
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
archive_method(:max)
|
9
|
+
archive_method(:min)
|
10
|
+
include InstanceMethods
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module InstanceMethods
|
15
|
+
# Returns the max, the non-nil value, or nil (if both are nil). A block
|
16
|
+
# can be passed if a special comparison is wanted (not typically).
|
17
|
+
def max(*x, &block)
|
18
|
+
return x.first if x.size == 1
|
19
|
+
return max2(x[0], x[1], &block) if x.size == 2
|
20
|
+
a = x.first
|
21
|
+
(1...x.size).each { |b|
|
22
|
+
a = max2(a,x[b], &block) }
|
23
|
+
a
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the first index of the max value
|
27
|
+
def max_index(*x, &block)
|
28
|
+
x.index(max(*x, &block))
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the max, the non-nil value, or nil (if both are nil). A block
|
32
|
+
# can be passed if a special comparison is wanted (not typically).
|
33
|
+
def max2(x,y, &block)
|
34
|
+
return y if x.nil?
|
35
|
+
return x if y.nil?
|
36
|
+
if block_given?
|
37
|
+
yield(x,y)
|
38
|
+
else
|
39
|
+
(x <=> y) > 0 ? x : y
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Min of any number of items
|
44
|
+
def min(*x, &block)
|
45
|
+
return x.first if x.size == 1
|
46
|
+
return min2(x[0], x[1], &block) if x.size == 2
|
47
|
+
a = x.first
|
48
|
+
(1...x.size).each { |b|
|
49
|
+
a = min2(a,x[b], &block) }
|
50
|
+
a
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the first index of the min value
|
54
|
+
def min_index(*x, &block)
|
55
|
+
x.index(min(*x, &block))
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the min, the non-nil value, or nil (if both are nil). A block
|
59
|
+
# can be passed if a special comparison is wanted (not typically).
|
60
|
+
def min2(x,y, &block)
|
61
|
+
return y if x.nil?
|
62
|
+
return x if y.nil?
|
63
|
+
if block_given?
|
64
|
+
yield(x,y)
|
65
|
+
else
|
66
|
+
(x <=> y) < 0 ? x : y
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end # InstanceMethods
|
71
|
+
end # GeneralStatistics
|
72
|
+
end # Sirb
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module Sirb #:nodoc:
|
2
|
+
# These are general methods for comparing enumerables. This list seems
|
3
|
+
# to grow quite a bit as I build up other libraries, so expect this to
|
4
|
+
# grow.
|
5
|
+
module InterEnumerableStatistics #:nodoc:
|
6
|
+
|
7
|
+
# Multiplies the values:
|
8
|
+
# >> product(1,2,3)
|
9
|
+
# => 6.0
|
10
|
+
def product(*x)
|
11
|
+
x.inject(1.0) {|sum, a| sum *= a}
|
12
|
+
end
|
13
|
+
|
14
|
+
# There are going to be a lot more of these kinds of things, so pay
|
15
|
+
# attention.
|
16
|
+
def to_pairs(x, y, &block)
|
17
|
+
n = min(x.size, y.size)
|
18
|
+
(0...n).map {|i| block.call(x[i], y[i]) }
|
19
|
+
end
|
20
|
+
|
21
|
+
# Finds the tanimoto coefficient: the intersection set size / union set
|
22
|
+
# size. This is used to find the distance between two vectors.
|
23
|
+
# >> cor([1,2,3], [2,3,5])
|
24
|
+
# => 0.981980506061966
|
25
|
+
# >> tanimoto_pairs([1,2,3], [2,3,5])
|
26
|
+
# => 0.5
|
27
|
+
def tanimoto_pairs(x,y)
|
28
|
+
intersect(x,y).size / union(x,y).size.to_f
|
29
|
+
end
|
30
|
+
|
31
|
+
# Sometimes it just helps to have things spelled out. These are all
|
32
|
+
# part of the Array class.
|
33
|
+
|
34
|
+
# All of the left and right hand sides, excluding duplicates.
|
35
|
+
# "The union of x and y"
|
36
|
+
def union(x,y)
|
37
|
+
x | y
|
38
|
+
end
|
39
|
+
|
40
|
+
# What's shared on the left and right hand sides
|
41
|
+
# "The intersection of x and y"
|
42
|
+
def intersect(x,y)
|
43
|
+
x & y
|
44
|
+
end
|
45
|
+
|
46
|
+
# Everything on the left hand side except what's shared on the right
|
47
|
+
# hand side.
|
48
|
+
# "The relative compliment of y in x"
|
49
|
+
def compliment(x,y)
|
50
|
+
x - y
|
51
|
+
end
|
52
|
+
|
53
|
+
# Everything but what's shared
|
54
|
+
def exclusive_not(x,y)
|
55
|
+
(x | y) - (x & y)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Finds the cartesian product, excluding duplicates items and self-
|
59
|
+
# referential pairs. Yields the block value if given.
|
60
|
+
def cartesian_product(x,y, &block)
|
61
|
+
x,y = x.uniq.dup, y.uniq.dup
|
62
|
+
pairs = x.inject([]) do |cp, i|
|
63
|
+
cp | y.map{|b| i == b ? nil : [i,b]}.compact
|
64
|
+
end
|
65
|
+
return pairs unless block_given?
|
66
|
+
pairs.map{|p| yield p.first, p.last}
|
67
|
+
end
|
68
|
+
alias :cp :cartesian_product
|
69
|
+
alias :permutations :cartesian_product
|
70
|
+
|
71
|
+
# Sigma of pairs. Returns a single float, or whatever object is sent in.
|
72
|
+
# Example: sigma_pairs([1,2,3], [4,5,6], 0) {|x, y| x + y}
|
73
|
+
# returns 21 instead of 21.0.
|
74
|
+
def sigma_pairs(x, y, z=0.0, &block)
|
75
|
+
to_pairs(x,y,&block).inject(z) {|sum, i| sum += i}
|
76
|
+
end
|
77
|
+
|
78
|
+
# Takes any number of enumerables and returns the range for all of them.
|
79
|
+
# This is an O(n*3) operation.
|
80
|
+
def range_for(*args)
|
81
|
+
range_pairs(p_max(*args), p_min(*args))
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns the range of each position between the two pairs.
|
85
|
+
def range_pairs(x,y)
|
86
|
+
to_pairs(x,y) {|a,b| max(a,b) - min(a,b)}
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns the Euclidian distance between all points of a set of enumerables
|
90
|
+
def euclidian_distance(x,y)
|
91
|
+
Math.sqrt(sigma_pairs(x,y) {|a, b| (a - b) ** 2})
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns a random integer in the range for any number of lists. This
|
95
|
+
# is a way to get a random vector that is tenable based on the sample
|
96
|
+
# data. For example, given two sets of numbers:
|
97
|
+
#
|
98
|
+
# a = [1,2,3]; b = [8,8,8]
|
99
|
+
#
|
100
|
+
# rand_in_pair_range will return a value >= 1 and <= 8 in the first
|
101
|
+
# place, >= 2 and <= 8 in the second place, and >= 3 and <= 8 in the
|
102
|
+
# last place.
|
103
|
+
# Works for integers. Rethink this for floats. May consider setting up
|
104
|
+
# FixedRange for floats. O(n*5)
|
105
|
+
def rand_in_range(*args)
|
106
|
+
range = range_for(*args)
|
107
|
+
min = p_min(*args)
|
108
|
+
(0...range.size).inject([]) do |ary, i|
|
109
|
+
ary << (rand(range[i] + 1) + min[i])
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Finds the correlation between two enumerables.
|
114
|
+
# Example: cor([1,2,3], [2,3,5)
|
115
|
+
# return 0.981980506061966
|
116
|
+
def correlation(x, y)
|
117
|
+
n = min(x.size, y.size)
|
118
|
+
( sigma_pairs(x,y) { |a,b| a * b } - (( x.sum * y.sum ) / n.to_f)) / ((n - 1 ) * x.std * y.std)
|
119
|
+
end
|
120
|
+
alias :cor :correlation
|
121
|
+
|
122
|
+
# Returns the max of two or more enumerables.
|
123
|
+
# >> p_max([1,2,3], [4,5,6], [0,2,9])
|
124
|
+
# => [4, 5, 9]
|
125
|
+
def p_max(*enums)
|
126
|
+
n = min(*enums.map{ |x| x.size} )
|
127
|
+
(0...n).map { |i| max(*enums.map{ |x| x[i] }) }
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns the min of two or more enumerables.
|
131
|
+
# >> p_min([1,2,3], [4,5,6], [0,2,9])
|
132
|
+
# => [0, 2, 3]
|
133
|
+
def p_min(*enums)
|
134
|
+
n = min(*enums.map{ |x| x.size} )
|
135
|
+
(0...n).map { |i| min(*enums.map{ |x| x[i] }) }
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|