Exemple #1
0
    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
Exemple #2
0
 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),
     ))
Exemple #3
0
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
Exemple #4
0
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)
Exemple #5
0
 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)
Exemple #6
0
 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)
Exemple #7
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)
Exemple #8
0
 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)
Exemple #9
0
 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))
Exemple #10
0
 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)
Exemple #11
0
 def test_one_point(self):
     """Create a graph with one point."""
     points = (Point(-1, 1), )
     graph = Graph(points)
     self.assertEqual(graph.points, points)
Exemple #12
0
 def setUpClass(cls):
     """Define a trivial graph."""
     # y = 0.5x + 1
     cls.graph = Graph((Point(0, 1), Point(2, 2)))
Exemple #13
0
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
Exemple #14
0
 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)
Exemple #15
0
 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])
Exemple #16
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))