coletivo 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
= coletivo
|
2
2
|
|
3
|
-
|
3
|
+
A simple Rails 3 recommendations engine.
|
4
|
+
Coletivo uses {Euclidean Distance}[http://en.wikipedia.org/wiki/Euclidean_distance] or {Pearson's Correlation Coefficient}[http://en.wikipedia.org/wiki/Pearson_product-moment_correlation_coefficient] to calculate the similarity between persons and their preferences.
|
4
5
|
|
5
6
|
== Installation:
|
6
7
|
|
@@ -18,15 +19,21 @@ At your Rails model that represents a person (can be an _User_, _Member_, or som
|
|
18
19
|
# ...
|
19
20
|
end
|
20
21
|
|
21
|
-
|
22
|
+
So, a person can rate things:
|
23
|
+
|
22
24
|
current_user = User.create(:name => 'Diogenes')
|
23
25
|
movie = Movie.create(:name => 'The Tourist', :year => 2010)
|
24
26
|
|
25
27
|
current_user.rate!(movie, 4.5)
|
26
28
|
|
27
|
-
|
29
|
+
And after a lot of ratings... *recommendations*:
|
30
|
+
|
28
31
|
Movie.find_recommendations_for(current_user) # => movies and more movies...
|
29
32
|
|
33
|
+
By default, the similarity strategy used is Euclidean Distance, but you can change that passing the _strategy_ option:
|
34
|
+
Movie.find_recommendations_for(current_user, :strategy => :pearson)
|
35
|
+
|
36
|
+
|
30
37
|
== Contributing to coletivo
|
31
38
|
|
32
39
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
@@ -41,4 +48,3 @@ At your Rails model that represents a person (can be an _User_, _Member_, or som
|
|
41
48
|
|
42
49
|
Copyright (c) 2011 Diógenes Falcão. See LICENSE.txt for
|
43
50
|
further details.
|
44
|
-
|
@@ -7,57 +7,15 @@ module Coletivo
|
|
7
7
|
end
|
8
8
|
|
9
9
|
module ClassMethods
|
10
|
-
def find_recommendations_for(
|
11
|
-
sim_totals, weighted_means = {}, {}
|
10
|
+
def find_recommendations_for(person, options = {})
|
12
11
|
preferences = options[:preferences] ||=
|
13
|
-
load_preferences_for_recommendation(
|
12
|
+
load_preferences_for_recommendation(person)
|
13
|
+
top = predict_highest_ratings(person, preferences, options)
|
14
|
+
ids = top.collect(&:last)
|
14
15
|
|
15
|
-
|
16
|
-
next if other == model
|
17
|
-
|
18
|
-
sim = model.similarity_with(other, options)
|
19
|
-
next if sim <= 0
|
20
|
-
|
21
|
-
other_prefs.each do |item, weight|
|
22
|
-
unless preferences[model.id].keys.include?(item)
|
23
|
-
sim_totals[item] ||= 0
|
24
|
-
weighted_means[item] ||= 0
|
25
|
-
|
26
|
-
sim_totals[item] += sim
|
27
|
-
weighted_means[item] += weight * sim
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# DESC sort by weighted mean of ratings
|
33
|
-
# e.g: [[5.35, "movie_2"], [2.0, "movie_4"]]
|
34
|
-
top = weighted_means.collect { |i, mean| [mean/sim_totals[i], i] }\
|
35
|
-
.sort { |t_one, t_other| t_other <=> t_one }
|
36
|
-
|
37
|
-
ids = top.collect(&:last) # e.g: ['movie_2', 'movie_4']
|
38
|
-
models = where(:id => ids)
|
39
|
-
|
40
|
-
top.collect { |weight, item| models.detect {|m| m.id == item } }\
|
41
|
-
.compact
|
16
|
+
where(:id => ids).limit(options[:limit]).all
|
42
17
|
end
|
43
18
|
|
44
|
-
# Map a preferences Hash by a collection of ratings.
|
45
|
-
#
|
46
|
-
# Rating objects have a +person+, a +rateable+ object and a +weight+.
|
47
|
-
#
|
48
|
-
# The preferences can be mapped for _persons_, like that:
|
49
|
-
# {
|
50
|
-
# :person_1 => {:movie_1 => 2.0, :movie_2 => 5.0},
|
51
|
-
# :person_2 => {:movie_1 => 3.0, :movie_2 => 4.0}
|
52
|
-
# }
|
53
|
-
#
|
54
|
-
# Or for _rateable_ items, like that:
|
55
|
-
# {
|
56
|
-
# :movie_1 => {:person_1 => 2.0, :person_2 => 3.0},
|
57
|
-
# :movie_2 => {:person_1 => 5.0, :person_2 => 4.0}
|
58
|
-
# }
|
59
|
-
#
|
60
|
-
# Expected keys are :person or :rateable
|
61
19
|
def map_ratings_to_preferences(ratings)
|
62
20
|
#TODO: (???) Item based mapping.
|
63
21
|
key, subkey = :person_id, :rateable_id
|
@@ -71,12 +29,40 @@ module Coletivo
|
|
71
29
|
preferences
|
72
30
|
end
|
73
31
|
|
74
|
-
def load_preferences_for_recommendation(
|
32
|
+
def load_preferences_for_recommendation(person)
|
75
33
|
r = Coletivo::Config.ratings_container\
|
76
|
-
.find_for_recommendation(
|
34
|
+
.find_for_recommendation(person, self)
|
77
35
|
|
78
36
|
map_ratings_to_preferences(r)
|
79
37
|
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def predict_highest_ratings(person, people_preferences, options)
|
42
|
+
data = {}
|
43
|
+
people_preferences.each do |other, other_prefs|
|
44
|
+
next if other == person
|
45
|
+
|
46
|
+
sim = person.similarity_with(other, options)
|
47
|
+
next if sim <= 0
|
48
|
+
|
49
|
+
other_prefs.each do |item, weight|
|
50
|
+
unless people_preferences[person.id].keys.include?(item)
|
51
|
+
data[item] ||= {:total_similarity => 0.0, :weighted_mean => 0.0}
|
52
|
+
data[item][:total_similarity] += sim
|
53
|
+
data[item][:weighted_mean] += weight * sim
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# e.g: [[5.35, "movie_2"], [2.0, "movie_4"]]
|
59
|
+
guessed_rating_and_id = lambda { |item, item_data|
|
60
|
+
[item_data[:weighted_mean] / item_data[:total_similarity], item]
|
61
|
+
}
|
62
|
+
|
63
|
+
# DESC sorting by weighted mean of ratings
|
64
|
+
data.collect(&guessed_rating_and_id).sort_by(&:first).reverse
|
65
|
+
end
|
80
66
|
end
|
81
67
|
|
82
68
|
module InstanceMethods
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: coletivo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- "Di\xC3\xB3genes Falc\xC3\xA3o"
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-09-20 00:00:00 -03:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|