slope_one 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +9 -0
- data/README.markdown +58 -0
- data/Rakefile +15 -0
- data/lib/slope_one.rb +52 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/test/test_helper.rb +3 -0
- data/test/test_slope_one.rb +60 -0
- metadata +74 -0
data/Manifest.txt
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# Slope One (Recommendation Algorithm)
|
2
|
+
|
3
|
+
## DESCRIPTION:
|
4
|
+
|
5
|
+
Implementation of the [Slope One](http://en.wikipedia.org/wiki/Slope_One) collaborative filtering/recommendation algorithm.
|
6
|
+
|
7
|
+
Ported to Ruby from Bryan O’Sullivan's [Python implementation](http://www.serpentine.com/blog/2006/12/12/collaborative-filtering-made-easy/). All credit goes to him.
|
8
|
+
|
9
|
+
## SYNOPSIS:
|
10
|
+
|
11
|
+
user_data = {
|
12
|
+
"rob" => {
|
13
|
+
"24" => 9.5,
|
14
|
+
"Lost" => 8.2,
|
15
|
+
"House" => 6.8
|
16
|
+
},
|
17
|
+
|
18
|
+
"bob" => {
|
19
|
+
"24" => 3.7,
|
20
|
+
"Big Bang Theory" => 2.1,
|
21
|
+
"House" => 8.3
|
22
|
+
},
|
23
|
+
|
24
|
+
"tod" => {
|
25
|
+
"24" => 9.5,
|
26
|
+
"Lost" => 3.4,
|
27
|
+
"House" => 5.5,
|
28
|
+
"Big Bang Theory" => 9.3
|
29
|
+
},
|
30
|
+
|
31
|
+
"dod" => {
|
32
|
+
"24" => 7.2,
|
33
|
+
"Lost" => 5.1,
|
34
|
+
"House" => 8.4,
|
35
|
+
"The Event" => 7.8,
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
slope_one = SlopeOne.new
|
40
|
+
slope_one.update(user_data)
|
41
|
+
puts slope_one.predict({"House" => 3, "Big Bang Theory" => 7.5}).inspect
|
42
|
+
|
43
|
+
## INSTALL:
|
44
|
+
|
45
|
+
gem install slope_one
|
46
|
+
|
47
|
+
or in your Gemfile:
|
48
|
+
|
49
|
+
gem 'slope_one'
|
50
|
+
|
51
|
+
## LICENSE:
|
52
|
+
|
53
|
+
Copyright 2006 Bryan O'Sullivan <bos@serpentine.com> (Original implementation)
|
54
|
+
Copyright 2010 Ashley Williams <hi@ashleyw.co.uk> (Ruby port)
|
55
|
+
|
56
|
+
This software may be used and distributed according to the terms
|
57
|
+
of the GNU General Public License, version 2 or later, which is
|
58
|
+
incorporated herein by reference.
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'hoe', '>= 2.1.0'
|
3
|
+
require 'hoe'
|
4
|
+
require 'fileutils'
|
5
|
+
require './lib/slope_one'
|
6
|
+
|
7
|
+
Hoe.plugin :newgem
|
8
|
+
|
9
|
+
$hoe = Hoe.spec 'slope_one' do
|
10
|
+
self.developer 'Ashley Williams', 'hi@ashleyw.co.uk'
|
11
|
+
self.rubyforge_name = self.name # TODO this is default value
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'newgem/tasks'
|
15
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
data/lib/slope_one.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
VERSION = '0.1'
|
2
|
+
|
3
|
+
class SlopeOne
|
4
|
+
attr_accessor :diffs, :freqs
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
self.diffs = {}
|
8
|
+
self.freqs = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def insert(user_data)
|
12
|
+
user_data.each do |name, ratings|
|
13
|
+
ratings.each do |item1, rating1|
|
14
|
+
self.freqs[item1] ||= {}
|
15
|
+
self.diffs[item1] ||= {}
|
16
|
+
ratings.each do |item2, rating2|
|
17
|
+
self.freqs[item1][item2] ||= 0
|
18
|
+
self.diffs[item1][item2] ||= 0.0
|
19
|
+
self.freqs[item1][item2] += 1
|
20
|
+
self.diffs[item1][item2] += (rating1 - rating2)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
self.diffs.each do |item1, ratings|
|
26
|
+
ratings.each do |item2, rating2|
|
27
|
+
ratings[item2] = ratings[item2] / self.freqs[item1][item2]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def predict(user)
|
33
|
+
preds, freqs = {}, {}
|
34
|
+
|
35
|
+
user.each do |item, rating|
|
36
|
+
self.diffs.each do |diff_item, diff_ratings|
|
37
|
+
next if self.freqs[diff_item].nil? or diff_ratings[item].nil?
|
38
|
+
freq = self.freqs[diff_item][item]
|
39
|
+
preds[diff_item] ||= 0.0
|
40
|
+
freqs[diff_item] ||= 0
|
41
|
+
preds[diff_item] += freq * (diff_ratings[item] + rating)
|
42
|
+
freqs[diff_item] += freq
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
results = {}
|
47
|
+
preds.each do |item, value|
|
48
|
+
results[item] = sprintf('%.3f', (value / freqs[item])).to_f unless user.include?(item) && freqs[item] > 0
|
49
|
+
end
|
50
|
+
return results
|
51
|
+
end
|
52
|
+
end
|
data/script/console
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# File: script/console
|
3
|
+
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
4
|
+
|
5
|
+
libs = " -r irb/completion"
|
6
|
+
# Perhaps use a console_lib to store any extra methods I may want available in the cosole
|
7
|
+
# libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
|
8
|
+
libs << " -r #{File.dirname(__FILE__) + '/../lib/slope_one.rb'}"
|
9
|
+
puts "Loading slope_one gem"
|
10
|
+
exec "#{irb} #{libs} --simple-prompt"
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
class TestSlopeOne < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
user_data = {
|
6
|
+
"rob" => {
|
7
|
+
"24" => 9.5,
|
8
|
+
"Lost" => 8.2,
|
9
|
+
"House" => 6.8
|
10
|
+
},
|
11
|
+
|
12
|
+
"bob" => {
|
13
|
+
"24" => 3.7,
|
14
|
+
"Big Bang Theory" => 2.1,
|
15
|
+
"House" => 8.3
|
16
|
+
},
|
17
|
+
|
18
|
+
"tod" => {
|
19
|
+
"24" => 9.5,
|
20
|
+
"Lost" => 3.4,
|
21
|
+
"House" => 5.5,
|
22
|
+
"Big Bang Theory" => 9.3
|
23
|
+
},
|
24
|
+
|
25
|
+
"dod" => {
|
26
|
+
"24" => 7.2,
|
27
|
+
"Lost" => 5.1,
|
28
|
+
"House" => 8.4,
|
29
|
+
"The Event" => 7.8,
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
@slope_one = SlopeOne.new
|
34
|
+
@slope_one.insert(user_data)
|
35
|
+
end
|
36
|
+
|
37
|
+
def input_test(input, output)
|
38
|
+
assert @slope_one.predict(input) == output
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_predict
|
42
|
+
input = {"House" => 3, "Big Bang Theory" => 7.5}
|
43
|
+
output = {"24"=>4.95, "Lost"=>1.65, "The Event"=>2.4}
|
44
|
+
input_test(input, output)
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_irrelevent_input
|
48
|
+
input = {"Eastenders" => 7.25}
|
49
|
+
output = {}
|
50
|
+
input_test(input, output)
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_insufficient_data
|
54
|
+
@slope_one = SlopeOne.new
|
55
|
+
# < here there nay be data insertion >
|
56
|
+
input = {"Eastenders" => 7.25}
|
57
|
+
output = {}
|
58
|
+
input_test(input, output)
|
59
|
+
end
|
60
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: slope_one
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
version: "0.1"
|
9
|
+
platform: ruby
|
10
|
+
authors:
|
11
|
+
- Ashley Williams
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
|
16
|
+
date: 2010-12-13 00:00:00 +00:00
|
17
|
+
default_executable:
|
18
|
+
dependencies: []
|
19
|
+
|
20
|
+
description: Implementation of the weighted Slope One collaborative filtering/recommendation algorithm.
|
21
|
+
email:
|
22
|
+
- hi@ashleyw.co.uk
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- Manifest.txt
|
31
|
+
- README.markdown
|
32
|
+
- Rakefile
|
33
|
+
- lib/slope_one.rb
|
34
|
+
- script/console
|
35
|
+
- script/destroy
|
36
|
+
- script/generate
|
37
|
+
- test/test_helper.rb
|
38
|
+
- test/test_slope_one.rb
|
39
|
+
has_rdoc: true
|
40
|
+
homepage: http://github.com/ashleyw/slope_one
|
41
|
+
licenses: []
|
42
|
+
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
segments:
|
54
|
+
- 0
|
55
|
+
version: "0"
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
segments:
|
62
|
+
- 1
|
63
|
+
- 3
|
64
|
+
- 6
|
65
|
+
version: 1.3.6
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project: slope_one
|
69
|
+
rubygems_version: 1.3.7
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: Implementation of the Slope One recommendation algorithm.
|
73
|
+
test_files: []
|
74
|
+
|