def test_vertical_slope(self): """Attempt to calculate a vertical slope. Assert :class:`movie_recommender.exceptions.VerticalLineOfBestFitGraphError` is raised. """ graph = Graph((Point(1, 1), Point(1, 2))) with self.assertRaises(exceptions.VerticalLineOfBestFitGraphError): graph.slope # pylint:disable=pointless-statement
def setUpClass(cls): """Define a non-trivial graph.""" # The line of best fit is `y = -1x + 10`. cls.graph = Graph(( # Define a line 2 above the line of best fit. Point(-2, 14), Point(2, 10), # Define a line 2 below the line of best fit. Point(-2, 10), Point(2, 6), ))
def make_genre_predictor(genre, user_id, forbidden_movie=None): """Make a predictor for the given genre for the given user. :param genre: A genre name, as a string. :param user_id: A user ID. The user for which a predictor is being created. :param forbidden_movie: A movie ID. A movie to ignore when creating the predictor. :return: A function which accepts a movie ID and returns a predicted rating. """ query = """ SELECT movies.genres, ratings.rating FROM movies JOIN ratings USING (movieId) WHERE ratings.userId == ? """ params = [user_id] if forbidden_movie: query += 'AND movieId != ?' params.append(forbidden_movie) # Iterate through movies this user has rated. For each movie, create a # Cartesian point, where X is whether the move has the given genre, and Y # is the rating this user has given to this movie. points = [] with common.get_db_conn() as conn: for row in conn.execute(query, params): genres = row[0].split('|') genre_present = 1 if genre in genres else 0 rating = row[1] points.append(Point(genre_present, rating)) graph = Graph(points) def predictor(movie_id): """Predict a user's rating for the given movie. :param movie_id: A movie ID. :return: A predicted rating for the given movie. """ genres = read.genres(movie_id) genre_present = 1 if genre in genres else 0 try: rating = graph.predict_y(genre_present) except exceptions.VerticalLineOfBestFitGraphError: rating = graph.avg_point.y return clamp_rating(rating) return predictor
def get_points(input_stream, columns, *, header_rows=0): """Consume an input stream, yielding an x,y point for each line. :param input_stream: A `text I/O`_ stream, where eacah line is CSV data in "x,y" format. :param Columns columns: The columns to read from the CSV file. :param header_rows: The number of header rows in the input file. Header rows are ignored. :returns: A generator yielding :class:`movie_recommender.graph.Point` instances. :raises: A ``TypeError`` if an input line doesn't have exactly two fields. .. _text I/O: https://docs.python.org/3/library/io.html#text-i-o """ current_line = 0 reader = csv.reader(input_stream) for row in reader: current_line += 1 if current_line <= header_rows: continue x = float(row[columns.x]) y = float(row[columns.y]) yield Point(x, y)
def test_positive_intercept(self): """Verify the method can calculate a positive y-intercept.""" graph = Graph((Point(1, 11), Point(2, 12))) self.assertEqual(graph.y_intercept, 10)
def test_flat_slope(self): """Verify the method can calculate a flat slope.""" graph = Graph((Point(1, 1), Point(2, 1))) self.assertEqual(graph.slope, 0)
def test_negative_slope(self): """Verify the method can calculate a negative slope.""" graph = Graph((Point(1, 2), Point(2, 1))) self.assertEqual(graph.slope, -1)
def test_positive_slope(self): """Verify the method can calculate a positive slope.""" graph = Graph((Point(1, 1), Point(2, 2))) self.assertEqual(graph.slope, 1)
def test_two_points(self): """Assert the method behaves correctly when given two points.""" graph = Graph((Point(10, -1), Point(20, -2))) self.assertEqual(graph.avg_point, Point(15, -1.5))
def test_two_points(self): """Create a graph with two points.""" points = (Point(1.2, 3.4), Point(5.6, 7.8)) graph = Graph(points) self.assertEqual(graph.points, points)
def test_one_point(self): """Create a graph with one point.""" points = (Point(-1, 1), ) graph = Graph(points) self.assertEqual(graph.points, points)
def setUpClass(cls): """Define a trivial graph.""" # y = 0.5x + 1 cls.graph = Graph((Point(0, 1), Point(2, 2)))
def make_year_predictor(user_id, forbidden_movie=None): """Make a year-based predictor for the given user. If year information can't be extracted from a movie's title, then that movie is skipped when generating a predictor. This is done because so few movies have this issue. See: `class:`movie_recommender.exceptions.NoMovieYearError`. :param user_id: A user ID. The user for which a predictor is being created. :param forbidden_movie: A movie ID. A movie to ignore when creating the predictor. :return: A function which accepts a movie ID and returns a predicted rating. """ query = """ SELECT movies.title, ratings.rating FROM movies JOIN ratings USING (movieId) WHERE ratings.userId == ? """ params = [user_id] if forbidden_movie: query += 'AND movieId != ?' params.append(forbidden_movie) # Iterate through movies this user has rated. For each movie, create a # Cartesian point, where X is the movie's year, and Y is the rating this # user has given to this movie. points = [] with common.get_db_conn() as conn: for row in conn.execute(query, params): try: year = read.year(row[0]) except exceptions.NoMovieYearError: continue rating = row[1] points.append(Point(year, rating)) graph = Graph(points) def predictor(movie_id): """Predict a user's rating for the given movie. :param movie_id: A movie ID. :return: A predicted rating for the given movie. :raise movie_recommender.exceptions.NoMovieYearError: If the given movie's title doesn't include a year, and this predictor makes use of year data. :raise movie_recommender.exceptions.EmptyGraphError: If this predictor can't predict movie ratings at all, due to a lack of relevant data. For example, this will occur if the given movie's title does include a year, but all of the movies this user has rated lack a year. """ title = read.title(movie_id) year = read.year(title) try: rating = graph.predict_y(year) except exceptions.VerticalLineOfBestFitGraphError: rating = graph.avg_point.y return clamp_rating(rating) return predictor
def test_negative_intercept(self): """Verify the method can calculate a positive y-intercept.""" graph = Graph((Point(1, -9), Point(2, -8))) self.assertEqual(graph.y_intercept, -10)
def test_one_point(self): """Assert the method behaves correctly when given one point.""" graph = Graph((Point(25, -1.5), )) self.assertEqual(graph.avg_point, graph.points[0])
def test_zero_intercept(self): """Verify the method can calculate a zero y-intercept.""" graph = Graph((Point(1, 0.5), Point(2, 1))) self.assertEqual(graph.y_intercept, 0)
def setUpClass(cls): """Set class-wide variables.""" cls.points = (Point(0.25, 4.4), Point(0.265, 4.62), Point(0.25, 3.84), Point(0.256, 4.16), Point(0.27, 4.28), Point(0.25, 4.5), Point(0.269, 4.47))