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
- An awesome, flexible, powerful, useful, tricky and liar Rails 3 recommendations engine.
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
- # ratings
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
- # after a lot of ratings... recommendations.
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(model, options = {})
11
- sim_totals, weighted_means = {}, {}
10
+ def find_recommendations_for(person, options = {})
12
11
  preferences = options[:preferences] ||=
13
- load_preferences_for_recommendation(model)
12
+ load_preferences_for_recommendation(person)
13
+ top = predict_highest_ratings(person, preferences, options)
14
+ ids = top.collect(&:last)
14
15
 
15
- preferences.each do |other, other_prefs|
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(model)
32
+ def load_preferences_for_recommendation(person)
75
33
  r = Coletivo::Config.ratings_container\
76
- .find_for_recommendation(model, self)
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
@@ -14,5 +14,5 @@ module Coletivo
14
14
  end
15
15
  end
16
16
  end
17
- end # Similarity
17
+ end
18
18
 
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: 29
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 1
10
- version: 0.0.1
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-07-20 00:00:00 -03:00
18
+ date: 2011-09-20 00:00:00 -03:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency