Collaborative Filtering¶
Introducción¶
Recommendation System: User-Based Collaborative Filtering o Filtrado Colaborativo Basado en el Usuario es un tipo de algoritmo de sistema de recomendación que utiliza la similitud del usuario para hacer recomendaciones de productos.
En este tutorial, hablaremos sobre
- ¿Qué es el filtrado colaborativo basado en usuarios (usuario-usuario)?
- ¿Cómo crear una matriz usuario-producto?
- ¿Cómo procesar los datos para el filtrado colaborativo basado en el usuario?
- ¿Cómo identificar usuarios similares?
- ¿Cómo reducir el grupo de elementos?
- ¿Cómo clasificar los artículos para la recomendación?
- ¿Cómo predecir la puntuación de calificación?
Nota: Para entender a profundidad este algoritmo, se recomienda leer el documento oficial.
Algoritmo¶
En primer lugar, comprendamos cómo funciona el filtrado colaborativo basado en usuarios.
El filtrado colaborativo basado en el usuario hace recomendaciones basadas en las interacciones del usuario con el producto en el pasado. La suposición detrás del algoritmo es que a usuarios similares les gustan productos similares.
El algoritmo de filtrado colaborativo basado en el usuario generalmente tiene los siguientes pasos:
- Encuentre usuarios similares en función de las interacciones con elementos comunes.
- Identifique los elementos con una calificación alta por parte de usuarios similares pero que no han sido expuestos al usuario activo de interés.
- Calcular la puntuación media ponderada de cada elemento.
- Clasifique los elementos según la puntuación y elija los n mejores elementos para recomendar.
Este gráfico ilustra cómo funciona el filtrado colaborativo basado en elementos mediante un ejemplo simplificado.
- A la Sra. Blond le gustan las manzanas, las sandías y las piñas. A la Sra. Black le gusta la sandía y la piña. A la Sra. Púrpura le gustan las sandías y las uvas.
- Debido a que a la Sra. Black y la Sra. Purple les gustan tanto las sandías como las piñas, consideramos que las sandías y las piñas son artículos similares.
- Dado que a la Sra. Púrpura le gustan las sandías y aún no ha estado expuesta a la piña, el sistema de recomendación recomienda la piña a la Sra. Púrpura.
Importar librerías¶
En el primer paso, importaremos las bibliotecas de Python pandas
, numpy
y scipy.stats
. Estas tres bibliotecas son para procesamiento de datos y cálculos.
También importamos seaborn
para la visualización y cosine_similarity
para calcular el puntaje de similitud.
# Data processing
import pandas as pd
import numpy as np
import scipy.stats
# Visualization
import seaborn as sns
# Similarity
from sklearn.metrics.pairwise import cosine_similarity
import warnings
warnings.simplefilter('ignore')
Descargar y leer datos¶
Este tutorial utiliza el conjunto de datos movielens. Este conjunto de datos contiene calificaciones reales de usuarios de películas.
Seguiremos los pasos a continuación para obtener los conjuntos de datos:
- Vaya a https://grouplens.org/datasets/movielens/
- Descargue el conjunto de datos de 100k con el nombre de archivo "ml-latest-small.zip"
- Descomprima "ml-latest-small.zip"
- Copie la carpeta "ml-latest-small" en la carpeta de su proyecto
Hay varios conjuntos de datos en la carpeta 100k movielens. Para este tutorial, utilizaremos dos clasificaciones y películas. Ahora vamos a leer en los datos de calificación (ratings.csv
).
# Read in data
ratings=pd.read_csv('data/ml-latest-small/ratings.csv')
# Take a look at the data
ratings.head()
userId | movieId | rating | timestamp | |
---|---|---|---|---|
0 | 1 | 1 | 4.0 | 964982703 |
1 | 1 | 3 | 4.0 | 964981247 |
2 | 1 | 6 | 4.0 | 964982224 |
3 | 1 | 47 | 5.0 | 964983815 |
4 | 1 | 50 | 5.0 | 964982931 |
Hay cuatro columnas en el conjunto de datos de calificaciones:
- userID
- movieID
- rating
- timestamp
El conjunto de datos tiene más de 100 000 registros y no falta ningún dato.
# Get the dataset information
ratings.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 100836 entries, 0 to 100835 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 userId 100836 non-null int64 1 movieId 100836 non-null int64 2 rating 100836 non-null float64 3 timestamp 100836 non-null int64 dtypes: float64(1), int64(3) memory usage: 3.1 MB
Las calificaciones de 100k son de 610 usuarios en 9724 películas. La calificación tiene diez valores únicos de 0.5 a 5.
# Number of users
print('The ratings dataset has', ratings['userId'].nunique(), 'unique users')
# Number of movies
print('The ratings dataset has', ratings['movieId'].nunique(), 'unique movies')
# Number of ratings
print('The ratings dataset has', ratings['rating'].nunique(), 'unique ratings')
# List of unique ratings
print('The unique ratings are', sorted(ratings['rating'].unique()))
The ratings dataset has 610 unique users The ratings dataset has 9724 unique movies The ratings dataset has 10 unique ratings The unique ratings are [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0]
A continuación, leamos los datos de las películas para obtener los nombres de las películas (movies.csv
).
El conjunto de datos de películas tiene:
- movieId
- title
- genres
# Read in data
movies = pd.read_csv('data/ml-latest-small/movies.csv')
# Take a look at the data
movies.head()
movieId | title | genres | |
---|---|---|---|
0 | 1 | Toy Story (1995) | Adventure|Animation|Children|Comedy|Fantasy |
1 | 2 | Jumanji (1995) | Adventure|Children|Fantasy |
2 | 3 | Grumpier Old Men (1995) | Comedy|Romance |
3 | 4 | Waiting to Exhale (1995) | Comedy|Drama|Romance |
4 | 5 | Father of the Bride Part II (1995) | Comedy |
Usando movieID
como clave coincidente, agregamos información de la película al conjunto de datos de calificación y lo llamamos df
. ¡Así que ahora tenemos el título de la película y la calificación de la película en el mismo conjunto de datos!
# Merge ratings and movies datasets
df = pd.merge(ratings, movies, on='movieId', how='inner')
# Take a look at the data
df.head()
userId | movieId | rating | timestamp | title | genres | |
---|---|---|---|---|---|---|
0 | 1 | 1 | 4.0 | 964982703 | Toy Story (1995) | Adventure|Animation|Children|Comedy|Fantasy |
1 | 5 | 1 | 4.0 | 847434962 | Toy Story (1995) | Adventure|Animation|Children|Comedy|Fantasy |
2 | 7 | 1 | 4.5 | 1106635946 | Toy Story (1995) | Adventure|Animation|Children|Comedy|Fantasy |
3 | 15 | 1 | 2.5 | 1510577970 | Toy Story (1995) | Adventure|Animation|Children|Comedy|Fantasy |
4 | 17 | 1 | 4.5 | 1305696483 | Toy Story (1995) | Adventure|Animation|Children|Comedy|Fantasy |
Análisis exploratorio de datos¶
Debemos filtrar las películas y mantener solo aquellas con más de 100 calificaciones para el análisis. Esto es para que el cálculo sea manejable por la memoria de Google Colab.
Para hacerlo, primero agrupamos las películas por título, contamos el número de calificaciones y mantenemos solo las películas con más de 100 calificaciones.
Las calificaciones promedio de las películas también se calculan.
Desde la salida .info()
, podemos ver que quedan 134 películas.
# Aggregate by movie
agg_ratings = df.groupby('title').agg(mean_rating = ('rating', 'mean'),
number_of_ratings = ('rating', 'count')).reset_index()
# Keep the movies with over 100 ratings
agg_ratings_GT100 = agg_ratings[agg_ratings['number_of_ratings']>100]
agg_ratings_GT100.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 134 entries, 74 to 9615 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 title 134 non-null object 1 mean_rating 134 non-null float64 2 number_of_ratings 134 non-null int64 dtypes: float64(1), int64(1), object(1) memory usage: 4.2+ KB
Veamos cuáles son las películas más populares y sus calificaciones.
# Check popular movies
agg_ratings_GT100.sort_values(by='number_of_ratings', ascending=False).head()
title | mean_rating | number_of_ratings | |
---|---|---|---|
3158 | Forrest Gump (1994) | 4.164134 | 329 |
7593 | Shawshank Redemption, The (1994) | 4.429022 | 317 |
6865 | Pulp Fiction (1994) | 4.197068 | 307 |
7680 | Silence of the Lambs, The (1991) | 4.161290 | 279 |
5512 | Matrix, The (1999) | 4.192446 | 278 |
A continuación, usemos un jointplot
para verificar la correlación entre la calificación promedio y el número de calificaciones.
Podemos ver una tendencia ascendente en el diagrama de dispersión, que muestra que las películas populares obtienen calificaciones más altas.
La distribución de calificación promedio muestra que la mayoría de las películas en el conjunto de datos tienen una calificación promedio de alrededor de 4.
El número de distribución de calificaciones muestra que la mayoría de las películas tienen menos de 150 calificaciones.
# Visulization
sns.jointplot(
x='mean_rating',
y='number_of_ratings',
data=agg_ratings_GT100,
color='gray'
)
<seaborn.axisgrid.JointGrid at 0x263dc2b6070>
Para mantener solo las 134 películas con más de 100 calificaciones, debemos unir la película con el dataframe del nivel de calificación del usuario.
how='inner'
y on='title'
aseguran que solo se incluyan las películas con más de 100 calificaciones.
# Merge data
df_GT100 = pd.merge(df, agg_ratings_GT100[['title']], on='title', how='inner')
df_GT100.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 19788 entries, 0 to 19787 Data columns (total 6 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 userId 19788 non-null int64 1 movieId 19788 non-null int64 2 rating 19788 non-null float64 3 timestamp 19788 non-null int64 4 title 19788 non-null object 5 genres 19788 non-null object dtypes: float64(1), int64(3), object(2) memory usage: 1.1+ MB
Después de filtrar las películas con más de 100 calificaciones, tenemos 597 usuarios que calificaron 134 películas.
# Number of users
print('The ratings dataset has', df_GT100['userId'].nunique(), 'unique users')
# Number of movies
print('The ratings dataset has', df_GT100['movieId'].nunique(), 'unique movies')
# Number of ratings
print('The ratings dataset has', df_GT100['rating'].nunique(), 'unique ratings')
# List of unique ratings
print('The unique ratings are', sorted(df_GT100['rating'].unique()))
The ratings dataset has 597 unique users The ratings dataset has 134 unique movies The ratings dataset has 10 unique ratings The unique ratings are [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0]
Matriz películas de usuario¶
Transformaremos el conjunto de datos en un formato de matriz. Las filas de la matriz son usuarios y las columnas de la matriz son películas. El valor de la matriz es la calificación de usuario de la película si hay una calificación. De lo contrario, muestra NaN
.
# Create user-item matrix
matrix = df_GT100.pivot_table(index='userId', columns='title', values='rating')
matrix.head()
title | 2001: A Space Odyssey (1968) | Ace Ventura: Pet Detective (1994) | Aladdin (1992) | Alien (1979) | Aliens (1986) | Amelie (Fabuleux destin d'Amélie Poulain, Le) (2001) | American Beauty (1999) | American History X (1998) | American Pie (1999) | Apocalypse Now (1979) | ... | True Lies (1994) | Truman Show, The (1998) | Twelve Monkeys (a.k.a. 12 Monkeys) (1995) | Twister (1996) | Up (2009) | Usual Suspects, The (1995) | WALL·E (2008) | Waterworld (1995) | Willy Wonka & the Chocolate Factory (1971) | X-Men (2000) |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
userId | |||||||||||||||||||||
1 | NaN | NaN | NaN | 4.0 | NaN | NaN | 5.0 | 5.0 | NaN | 4.0 | ... | NaN | NaN | NaN | 3.0 | NaN | 5.0 | NaN | NaN | 5.0 | 5.0 |
2 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
3 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
4 | NaN | NaN | 4.0 | NaN | NaN | NaN | 5.0 | NaN | NaN | NaN | ... | NaN | NaN | 2.0 | NaN | NaN | NaN | NaN | NaN | 4.0 | NaN |
5 | NaN | 3.0 | 4.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | 2.0 | NaN | NaN | NaN | NaN | 4.0 | NaN | NaN | NaN | NaN |
5 rows × 134 columns
Normalización de datos¶
Dado que algunas personas tienden a dar una calificación más alta que otras, normalizamos la calificación extrayendo la calificación promedio de cada usuario.
Después de la normalización, las películas con una calificación inferior a la calificación promedio del usuario obtienen un valor negativo y las películas con una calificación superior a la calificación promedio del usuario obtienen un valor positivo.
# Normalize user-item matrix
matrix_norm = matrix.subtract(matrix.mean(axis=1), axis = 'rows')
matrix_norm.head()
title | 2001: A Space Odyssey (1968) | Ace Ventura: Pet Detective (1994) | Aladdin (1992) | Alien (1979) | Aliens (1986) | Amelie (Fabuleux destin d'Amélie Poulain, Le) (2001) | American Beauty (1999) | American History X (1998) | American Pie (1999) | Apocalypse Now (1979) | ... | True Lies (1994) | Truman Show, The (1998) | Twelve Monkeys (a.k.a. 12 Monkeys) (1995) | Twister (1996) | Up (2009) | Usual Suspects, The (1995) | WALL·E (2008) | Waterworld (1995) | Willy Wonka & the Chocolate Factory (1971) | X-Men (2000) |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
userId | |||||||||||||||||||||
1 | NaN | NaN | NaN | -0.392857 | NaN | NaN | 0.607143 | 0.607143 | NaN | -0.392857 | ... | NaN | NaN | NaN | -1.392857 | NaN | 0.607143 | NaN | NaN | 0.607143 | 0.607143 |
2 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
3 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
4 | NaN | NaN | 0.617647 | NaN | NaN | NaN | 1.617647 | NaN | NaN | NaN | ... | NaN | NaN | -1.382353 | NaN | NaN | NaN | NaN | NaN | 0.617647 | NaN |
5 | NaN | -0.461538 | 0.538462 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | -1.461538 | NaN | NaN | NaN | NaN | 0.538462 | NaN | NaN | NaN | NaN |
5 rows × 134 columns
Identifique Usuarios Similares¶
Hay diferentes formas de medir las similitudes. La correlación de Pearson y la similitud del coseno son dos métodos ampliamente utilizados.
En este tutorial, calcularemos la matriz de similitud del usuario utilizando la correlación de Pearson.
# User similarity matrix using Pearson correlation
user_similarity = matrix_norm.T.corr()
user_similarity.head()
userId | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ... | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
userId | |||||||||||||||||||||
1 | 1.000000 | NaN | NaN | 0.391797 | 0.180151 | -0.439941 | -0.029894 | 0.464277 | 1.0 | -0.037987 | ... | 0.091574 | 0.254514 | 0.101482 | -0.500000 | 0.780020 | 0.303854 | -0.012077 | 0.242309 | -0.175412 | 0.071553 |
2 | NaN | 1.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 1.000000 | ... | -0.583333 | NaN | -1.000000 | NaN | NaN | 0.583333 | NaN | -0.229416 | NaN | 0.765641 |
3 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
4 | 0.391797 | NaN | NaN | 1.000000 | -0.394823 | 0.421927 | 0.704669 | 0.055442 | NaN | 0.360399 | ... | -0.239325 | 0.562500 | 0.162301 | -0.158114 | 0.905134 | 0.021898 | -0.020659 | -0.286872 | NaN | -0.050868 |
5 | 0.180151 | NaN | NaN | -0.394823 | 1.000000 | -0.006888 | 0.328889 | 0.030168 | NaN | -0.777714 | ... | 0.000000 | 0.231642 | 0.131108 | 0.068621 | -0.245026 | 0.377341 | 0.228218 | 0.263139 | 0.384111 | 0.040582 |
5 rows × 597 columns
Aquellos que estén interesados en usar la similitud del coseno pueden consultar este código. Dado que cosine_similarity
no toma valores perdidos, necesitamos imputar los valores perdidos con 0 antes del cálculo.
# User similarity matrix using cosine similarity
user_similarity_cosine = cosine_similarity(matrix_norm.fillna(0))
user_similarity_cosine
array([[ 1. , 0. , 0. , ..., 0.14893867, -0.06003146, 0.04528224], [ 0. , 1. , 0. , ..., -0.04485403, -0.25197632, 0.18886414], [ 0. , 0. , 0. , ..., 0. , 0. , 0. ], ..., [ 0.14893867, -0.04485403, 0. , ..., 1. , 0.14734568, 0.07931015], [-0.06003146, -0.25197632, 0. , ..., 0.14734568, 1. , -0.14276787], [ 0.04528224, 0.18886414, 0. , ..., 0.07931015, -0.14276787, 1. ]])
Ahora usemos el ID de usuario 1 como ejemplo para ilustrar cómo encontrar usuarios similares.
Primero debemos excluir el ID de usuario 1 de la lista de usuarios similares y decidir el número de usuarios similares.
# Pick a user ID
picked_userid = 1
# Remove picked user ID from the candidate list
user_similarity.drop(index=picked_userid, inplace=True)
# Take a look at the data
user_similarity.head()
userId | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ... | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
userId | |||||||||||||||||||||
2 | NaN | 1.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 1.000000 | ... | -0.583333 | NaN | -1.000000 | NaN | NaN | 0.583333 | NaN | -0.229416 | NaN | 0.765641 |
3 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
4 | 0.391797 | NaN | NaN | 1.000000 | -0.394823 | 0.421927 | 0.704669 | 0.055442 | NaN | 0.360399 | ... | -0.239325 | 0.562500 | 0.162301 | -0.158114 | 0.905134 | 0.021898 | -0.020659 | -0.286872 | NaN | -0.050868 |
5 | 0.180151 | NaN | NaN | -0.394823 | 1.000000 | -0.006888 | 0.328889 | 0.030168 | NaN | -0.777714 | ... | 0.000000 | 0.231642 | 0.131108 | 0.068621 | -0.245026 | 0.377341 | 0.228218 | 0.263139 | 0.384111 | 0.040582 |
6 | -0.439941 | NaN | NaN | 0.421927 | -0.006888 | 1.000000 | 0.000000 | -0.127385 | NaN | 0.957427 | ... | -0.292770 | -0.030599 | -0.123983 | -0.176327 | 0.063861 | -0.468008 | 0.541386 | -0.337129 | 0.158255 | -0.030567 |
5 rows × 597 columns
En la matriz de similitud del usuario, los valores varían de -1 a 1, donde -1 significa la preferencia de película opuesta y 1 significa la misma preferencia de película.
n = 10
significa que nos gustaría elegir los 10 usuarios más similares para el ID de usuario 1.
El filtrado colaborativo basado en usuarios hace recomendaciones basadas en usuarios con gustos similares, por lo que debemos establecer un umbral positivo. Aquí configuramos user_similarity_threshold
en 0,3, lo que significa que un usuario debe tener un coeficiente de correlación de Pearson de al menos 0,3 para ser considerado como un usuario similar.
Después de establecer la cantidad de usuarios similares y el umbral de similitud, clasificamos el valor de similitud del usuario del más alto al más bajo, luego imprimimos la ID de los usuarios más similares y el valor de correlación de Pearson.
# Number of similar users
n = 10
# User similarity threashold
user_similarity_threshold = 0.3
# Get top n similar users
similar_users = user_similarity[user_similarity[picked_userid]>user_similarity_threshold][picked_userid].sort_values(ascending=False)[:n]
# Print out top n similar users
print(f'The similar users for user {picked_userid} are', similar_users)
The similar users for user 1 are userId 108 1.000000 9 1.000000 550 1.000000 598 1.000000 502 1.000000 401 0.942809 511 0.925820 366 0.872872 154 0.866025 595 0.866025 Name: 1, dtype: float64
Restringir el grupo de artículos¶
Reduciremos el grupo de artículos haciendo lo siguiente:
- Elimine las películas que ha visto el usuario de destino (ID de usuario 1 en este ejemplo).
- Guarde solo las películas que otros usuarios similares hayan visto.
Para eliminar las películas vistas por el usuario objetivo, mantenemos solo la fila para userId=1 en la matriz de elementos de usuario y eliminamos los elementos con valores faltantes.
# Movies that the target user has watched
picked_userid_watched = matrix_norm[matrix_norm.index == picked_userid].dropna(axis=1, how='all')
picked_userid_watched
title | Alien (1979) | American Beauty (1999) | American History X (1998) | Apocalypse Now (1979) | Back to the Future (1985) | Batman (1989) | Big Lebowski, The (1998) | Braveheart (1995) | Clear and Present Danger (1994) | Clerks (1994) | ... | Star Wars: Episode IV - A New Hope (1977) | Star Wars: Episode V - The Empire Strikes Back (1980) | Star Wars: Episode VI - Return of the Jedi (1983) | Stargate (1994) | Terminator, The (1984) | Toy Story (1995) | Twister (1996) | Usual Suspects, The (1995) | Willy Wonka & the Chocolate Factory (1971) | X-Men (2000) |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
userId | |||||||||||||||||||||
1 | -0.392857 | 0.607143 | 0.607143 | -0.392857 | 0.607143 | -0.392857 | 0.607143 | -0.392857 | -0.392857 | -1.392857 | ... | 0.607143 | 0.607143 | 0.607143 | -1.392857 | 0.607143 | -0.392857 | -1.392857 | 0.607143 | 0.607143 | 0.607143 |
1 rows × 56 columns
Para mantener solo las películas de los usuarios similares, mantenemos los ID de usuario en las 10 listas de usuarios similares principales y eliminamos la película con todos los valores faltantes. Todo valor faltante para una película significa que ninguno de los usuarios similares ha visto la película.
# Movies that similar users watched. Remove movies that none of the similar users have watched
similar_user_movies = matrix_norm[matrix_norm.index.isin(similar_users.index)].dropna(axis=1, how='all')
similar_user_movies
title | Aladdin (1992) | Alien (1979) | Amelie (Fabuleux destin d'Amélie Poulain, Le) (2001) | Back to the Future (1985) | Batman Begins (2005) | Beautiful Mind, A (2001) | Beauty and the Beast (1991) | Blade Runner (1982) | Bourne Identity, The (2002) | Braveheart (1995) | ... | Shrek (2001) | Silence of the Lambs, The (1991) | Spider-Man (2002) | Star Wars: Episode I - The Phantom Menace (1999) | Terminator 2: Judgment Day (1991) | Titanic (1997) | Toy Story (1995) | Up (2009) | Usual Suspects, The (1995) | WALL·E (2008) |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
userId | |||||||||||||||||||||
9 | NaN | NaN | NaN | 0.333333 | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
108 | NaN | NaN | 0.466667 | 0.466667 | NaN | 0.466667 | NaN | 0.466667 | NaN | NaN | ... | NaN | NaN | 0.466667 | NaN | NaN | -0.533333 | NaN | NaN | NaN | NaN |
154 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.214286 | NaN | NaN |
366 | NaN | NaN | NaN | NaN | -0.205882 | NaN | NaN | NaN | NaN | -0.205882 | ... | NaN | NaN | NaN | NaN | -0.205882 | NaN | NaN | NaN | NaN | NaN |
401 | -0.382353 | NaN | NaN | NaN | NaN | NaN | -0.382353 | NaN | NaN | NaN | ... | 0.117647 | NaN | NaN | NaN | NaN | NaN | 0.117647 | 0.617647 | NaN | 0.617647 |
502 | NaN | -0.375 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
511 | NaN | NaN | -0.653846 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | -1.153846 | -0.653846 | NaN | NaN | NaN | -0.153846 | NaN | NaN |
550 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | -0.277778 | 0.222222 | NaN | -0.277778 |
595 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | -0.333333 | NaN | NaN | NaN | NaN | 0.666667 | NaN |
598 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.888889 | NaN | ... | -2.111111 | -2.611111 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
10 rows × 62 columns
A continuación, eliminaremos las películas que el usuario ID 1 vio de la lista de películas de usuarios similares. errors='ignore'
elimina columnas si existen sin dar un mensaje de error.
# Remove the watched movie from the movie list
similar_user_movies.drop(picked_userid_watched.columns,axis=1, inplace=True, errors='ignore')
# Take a look at the data
similar_user_movies
title | Aladdin (1992) | Amelie (Fabuleux destin d'Amélie Poulain, Le) (2001) | Batman Begins (2005) | Beautiful Mind, A (2001) | Beauty and the Beast (1991) | Blade Runner (1982) | Bourne Identity, The (2002) | Breakfast Club, The (1985) | Catch Me If You Can (2002) | Dark Knight, The (2008) | ... | Monsters, Inc. (2001) | Ocean's Eleven (2001) | Pirates of the Caribbean: The Curse of the Black Pearl (2003) | Shawshank Redemption, The (1994) | Shrek (2001) | Spider-Man (2002) | Terminator 2: Judgment Day (1991) | Titanic (1997) | Up (2009) | WALL·E (2008) |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
userId | |||||||||||||||||||||
9 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
108 | NaN | 0.466667 | NaN | 0.466667 | NaN | 0.466667 | NaN | -0.533333 | 0.466667 | NaN | ... | NaN | NaN | NaN | NaN | NaN | 0.466667 | NaN | -0.533333 | NaN | NaN |
154 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.214286 | NaN |
366 | NaN | NaN | -0.205882 | NaN | NaN | NaN | NaN | NaN | NaN | -0.205882 | ... | NaN | NaN | -0.205882 | NaN | NaN | NaN | -0.205882 | NaN | NaN | NaN |
401 | -0.382353 | NaN | NaN | NaN | -0.382353 | NaN | NaN | NaN | NaN | NaN | ... | 0.117647 | NaN | 0.117647 | NaN | 0.117647 | NaN | NaN | NaN | 0.617647 | 0.617647 |
502 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | 0.125000 | NaN | NaN | NaN | NaN | NaN | NaN |
511 | NaN | -0.653846 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | 0.346154 | NaN | -1.153846 | NaN | NaN | -0.153846 | NaN |
550 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | -0.277778 | -0.277778 | ... | NaN | NaN | NaN | 0.222222 | NaN | NaN | NaN | NaN | 0.222222 | -0.277778 |
595 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
598 | NaN | NaN | NaN | NaN | NaN | NaN | 0.888889 | NaN | NaN | NaN | ... | NaN | 0.888889 | NaN | NaN | -2.111111 | NaN | NaN | NaN | NaN | NaN |
10 rows × 38 columns
Recomendar artículos¶
Decidiremos qué película recomendar al usuario objetivo. Los elementos recomendados están determinados por el promedio ponderado del puntaje de similitud del usuario y la calificación de la película. Las calificaciones de las películas están ponderadas por las puntuaciones de similitud, por lo que los usuarios con mayor similitud obtienen una mayor ponderación.
Este código recorre los elementos y los usuarios para obtener la puntuación del elemento, clasificar la puntuación de mayor a menor y elegir las 10 mejores películas para recomendar al ID de usuario 1.
# A dictionary to store item scores
item_score = {}
# Loop through items
for i in similar_user_movies.columns:
# Get the ratings for movie i
movie_rating = similar_user_movies[i]
# Create a variable to store the score
total = 0
# Create a variable to store the number of scores
count = 0
# Loop through similar users
for u in similar_users.index:
# If the movie has rating
if pd.isna(movie_rating[u]) == False:
# Score is the sum of user similarity score multiply by the movie rating
score = similar_users[u] * movie_rating[u]
# Add the score to the total score for the movie so far
total += score
# Add 1 to the count
count +=1
# Get the average score for the item
item_score[i] = total / count
# Convert dictionary to pandas dataframe
item_score = pd.DataFrame(item_score.items(), columns=['movie', 'movie_score'])
# Sort the movies by score
ranked_item_score = item_score.sort_values(by='movie_score', ascending=False)
# Select top m movies
m = 10
ranked_item_score.head(m)
movie | movie_score | |
---|---|---|
16 | Harry Potter and the Chamber of Secrets (2002) | 1.888889 |
13 | Eternal Sunshine of the Spotless Mind (2004) | 1.888889 |
6 | Bourne Identity, The (2002) | 0.888889 |
29 | Ocean's Eleven (2001) | 0.888889 |
18 | Inception (2010) | 0.587491 |
3 | Beautiful Mind, A (2001) | 0.466667 |
5 | Blade Runner (1982) | 0.466667 |
12 | Donnie Darko (2001) | 0.466667 |
10 | Departed, The (2006) | 0.256727 |
31 | Shawshank Redemption, The (1994) | 0.222566 |
Predecir puntuaciones (opcional)¶
Si el objetivo es elegir los elementos recomendados, basta con tener el rango de los elementos. Sin embargo, si el objetivo es predecir la calificación del usuario, debemos sumar la calificación promedio de la película del usuario a la calificación de la película.
# Average rating for the picked user
avg_rating = matrix[matrix.index == picked_userid].T.mean()[picked_userid]
# Print the average movie rating for user 1
print(f'The average movie rating for user {picked_userid} is {avg_rating:.2f}')
The average movie rating for user 1 is 4.39
La calificación promedio de la película para el usuario 1 es 4.39, por lo que agregamos 4.39 nuevamente a la calificación de la película.
# Calcuate the predicted rating
ranked_item_score['predicted_rating'] = ranked_item_score['movie_score'] + avg_rating
# Take a look at the data
ranked_item_score.head(m)
movie | movie_score | predicted_rating | |
---|---|---|---|
16 | Harry Potter and the Chamber of Secrets (2002) | 1.888889 | 6.281746 |
13 | Eternal Sunshine of the Spotless Mind (2004) | 1.888889 | 6.281746 |
6 | Bourne Identity, The (2002) | 0.888889 | 5.281746 |
29 | Ocean's Eleven (2001) | 0.888889 | 5.281746 |
18 | Inception (2010) | 0.587491 | 4.980348 |
3 | Beautiful Mind, A (2001) | 0.466667 | 4.859524 |
5 | Blade Runner (1982) | 0.466667 | 4.859524 |
12 | Donnie Darko (2001) | 0.466667 | 4.859524 |
10 | Departed, The (2006) | 0.256727 | 4.649584 |
31 | Shawshank Redemption, The (1994) | 0.222566 | 4.615423 |
Podemos ver que las 10 mejores películas recomendadas tienen calificaciones pronosticadas superiores a 4.5.
Resumen¶
En este tutorial, analizamos cómo crear un sistema de recomendación de filtrado colaborativo basado en el usuario. Aprendiste
- ¿Qué es el filtrado colaborativo basado en usuarios (usuario-usuario)?
- ¿Cómo crear una matriz usuario-producto?
- ¿Cómo procesar los datos para el filtrado colaborativo basado en el usuario?
- ¿Cómo identificar usuarios similares?
- ¿Cómo reducir el grupo de elementos?
- ¿Cómo clasificar los artículos para la recomendación?
- ¿Cómo predecir la puntuación de calificación?