package org.easyrec.mahout.model;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.mahout.cf.taste.common.Refreshable;
import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.impl.common.FastIDSet;
import org.apache.mahout.cf.taste.impl.common.LongPrimitiveIterator;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.model.PreferenceArray;
import org.easyrec.mahout.store.MahoutDataModelMappingDAO;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import java.util.Collection;
import java.util.Date;
/**
*
*/
public class EasyrecDataModel extends JdbcDaoSupport implements DataModel {
int tenantId;
int actionTypeId;
Date cutoffDate;
boolean hasRatingValues;
MahoutDataModelMappingDAO mahoutDataModelMappingDAO;
private final Log logger = LogFactory.getLog(this.getClass());
/**
* This constructor can be used to create a Data Model which is usable for Mahout if you want to create a boolean recommender
* just use a actionType without rating values.
*
* @param tenantId the tenantId of the dataset you want to load
* @param actionTypeId the INT id of the actionType you want to use for your recommender
* @param hasRatingValues a boolean which tells the DataModel about your Data Set having rating values or not.
* @param mahoutDataModelMappingDAO an instance of MahoutDataModelMappingDAO
*/
public EasyrecDataModel(int tenantId, int actionTypeId, boolean hasRatingValues, MahoutDataModelMappingDAO mahoutDataModelMappingDAO) {
this.tenantId = tenantId;
this.actionTypeId = actionTypeId;
this.mahoutDataModelMappingDAO = mahoutDataModelMappingDAO;
this.hasRatingValues = hasRatingValues;
cutoffDate = new Date();
}
/**
* @return all user IDs in the model, in order
* @throws TasteException if an error occurs while accessing the data
*/
public LongPrimitiveIterator getUserIDs() throws TasteException {
return mahoutDataModelMappingDAO.getUserIDs(tenantId, cutoffDate, actionTypeId);
}
/**
* @param userID ID of user to get prefs for
* @return user's preferences, ordered by item ID
* @throws org.apache.mahout.cf.taste.common.NoSuchUserException
* if the user does not exist
* @throws TasteException if an error occurs while accessing the data
*/
public PreferenceArray getPreferencesFromUser(long userID) throws TasteException {
if (hasRatingValues) {
return mahoutDataModelMappingDAO.getPreferencesFromUser(tenantId, cutoffDate, userID, actionTypeId);
} else {
return mahoutDataModelMappingDAO.getBooleanPreferencesFromUser(tenantId, cutoffDate, userID, actionTypeId);
}
}
/**
* @param userID ID of user to get prefs for
* @return IDs of items user expresses a preference for
* @throws org.apache.mahout.cf.taste.common.NoSuchUserException
* if the user does not exist
* @throws TasteException if an error occurs while accessing the data
*/
public FastIDSet getItemIDsFromUser(long userID) throws TasteException {
return mahoutDataModelMappingDAO.getItemIDsFromUser(tenantId, cutoffDate, userID, actionTypeId);
}
/**
* @return a {@link LongPrimitiveIterator} of all item IDs in the model, in order
* @throws TasteException if an error occurs while accessing the data
*/
public LongPrimitiveIterator getItemIDs() throws TasteException {
return mahoutDataModelMappingDAO.getItemIDs(tenantId, cutoffDate, actionTypeId);
}
/**
* @param itemID item ID
* @return all existing Preference's expressed for that item, ordered by user ID, as an array
* @throws org.apache.mahout.cf.taste.common.NoSuchItemException
* if the item does not exist
* @throws TasteException if an error occurs while accessing the data
*/
public PreferenceArray getPreferencesForItem(long itemID) throws TasteException {
if (hasRatingValues) {
return mahoutDataModelMappingDAO.getPreferencesForItem(tenantId, cutoffDate, itemID, actionTypeId);
} else {
return mahoutDataModelMappingDAO.getBooleanPreferencesForItem(tenantId, cutoffDate, itemID, actionTypeId);
}
}
/**
* Retrieves the preference value for a single user and item.
*
* @param userID user ID to get pref value from
* @param itemID item ID to get pref value for
* @return preference value from the given user for the given item or null if none exists
* @throws org.apache.mahout.cf.taste.common.NoSuchUserException
* if the user does not exist
* @throws TasteException if an error occurs while accessing the data
*/
public Float getPreferenceValue(long userID, long itemID) throws TasteException {
if (hasRatingValues) {
return mahoutDataModelMappingDAO.getPreferenceValue(tenantId, cutoffDate, userID, itemID, actionTypeId);
} else {
return mahoutDataModelMappingDAO.getBooleanPreferenceValue(tenantId, cutoffDate, userID, itemID, actionTypeId);
}
}
/**
* Retrieves the time at which a preference value from a user and item was set, if known.
* Time is expressed in the usual way, as a number of milliseconds since the epoch.
*
* @param userID user ID for preference in question
* @param itemID item ID for preference in question
* @return time at which preference was set or null if no preference exists or its time is not known
* @throws org.apache.mahout.cf.taste.common.NoSuchUserException
* if the user does not exist
* @throws TasteException if an error occurs while accessing the data
*/
public Long getPreferenceTime(long userID, long itemID) throws TasteException {
return mahoutDataModelMappingDAO.getPreferenceTime(tenantId, cutoffDate, userID, itemID, actionTypeId);
}
/**
* @return total number of items known to the model. This is generally the union of all items preferred by
* at least one user but could include more.
* @throws TasteException if an error occurs while accessing the data
*/
public int getNumItems() throws TasteException {
return mahoutDataModelMappingDAO.getNumItems(tenantId, cutoffDate, actionTypeId);
}
/**
* @return total number of users known to the model.
* @throws TasteException if an error occurs while accessing the data
*/
public int getNumUsers() throws TasteException {
return mahoutDataModelMappingDAO.getNumUsers(tenantId, cutoffDate, actionTypeId);
}
/**
* @param itemID item ID to check for
* @return the number of users who have expressed a preference for the item
* @throws TasteException if an error occurs while accessing the data
*/
public int getNumUsersWithPreferenceFor(long itemID) throws TasteException {
return mahoutDataModelMappingDAO.getNumUsersWithPreferenceFor(tenantId, cutoffDate, itemID, actionTypeId);
}
/**
* @param itemID1 first item ID to check for
* @param itemID2 second item ID to check for
* @return the number of users who have expressed a preference for the items
* @throws TasteException if an error occurs while accessing the data
*/
public int getNumUsersWithPreferenceFor(long itemID1, long itemID2) throws TasteException {
return mahoutDataModelMappingDAO.getNumUsersWithPreferenceFor(tenantId, cutoffDate, itemID1, itemID2, actionTypeId);
}
/**
* <p>
* Sets a particular preference (item plus rating) for a user.
* <b> Not implemented yet. </b>
* </p>
*
* @param userID user to set preference for
* @param itemID item to set preference for
* @param value preference value
* @throws org.apache.mahout.cf.taste.common.NoSuchItemException
* if the item does not exist
* @throws org.apache.mahout.cf.taste.common.NoSuchUserException
* if the user does not exist
* @throws TasteException if an error occurs while accessing the data
*/
public void setPreference(long userID, long itemID, float value) throws TasteException {
throw new UnsupportedOperationException();
}
/**
* <p>
* Removes a particular preference for a user.
* <b> Not implemented yet. </b>
* </p>
*
* @param userID user from which to remove preference
* @param itemID item to remove preference for
* @throws org.apache.mahout.cf.taste.common.NoSuchItemException
* if the item does not exist
* @throws org.apache.mahout.cf.taste.common.NoSuchUserException
* if the user does not exist
* @throws TasteException if an error occurs while accessing the data
*/
public void removePreference(long userID, long itemID) throws TasteException {
throw new UnsupportedOperationException();
}
/**
* @return true iff this implementation actually stores and returns distinct preference values;
* that is, if it is not a 'boolean' DataModel
*/
public boolean hasPreferenceValues() {
return hasRatingValues;
}
/**
* @return the maximum preference value that is possible in the current problem domain being evaluated. For
* example, if the domain is movie ratings on a scale of 1 to 5, this should be 5. While a
* {@link org.apache.mahout.cf.taste.recommender.Recommender} may estimate a preference value above 5.0, it
* isn't "fair" to consider that the system is actually suggesting an impossible rating of, say, 5.4 stars.
* In practice the application would cap this estimate to 5.0. Since evaluators evaluate
* the difference between estimated and actual value, this at least prevents this effect from unfairly
* penalizing a {@link org.apache.mahout.cf.taste.recommender.Recommender}
*/
public float getMaxPreference() {
return mahoutDataModelMappingDAO.getMaxPreference(tenantId, cutoffDate, actionTypeId);
}
/**
* @see #getMaxPreference()
*/
public float getMinPreference() {
return mahoutDataModelMappingDAO.getMinPreference(tenantId, cutoffDate, actionTypeId);
}
/**
* <p>
* Triggers "refresh" -- whatever that means -- of the implementation. The general contract is that any
* should always leave itself in a consistent, operational state, and that the refresh
* atomically updates internal state from old to new.
* </p>
*
* @param alreadyRefreshed s that are known to have already been
* refreshed as a result of an initial call to a method on some
* object. This ensure that objects in a refresh dependency graph aren't refreshed twice
* needlessly.
*/
public void refresh(Collection<Refreshable> alreadyRefreshed) {
cutoffDate = new Date();
}
}