coletivo 0.0.1 → 0.0.2

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 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