In [1]:
Pràctiques de Nous Usos de la Informàtica


  File "<ipython-input-1-adbe0ac4005d>", line 1
    Pràctiques de Nous Usos de la Informàtica
      ^
SyntaxError: invalid syntax

Requeriments per fer les pràctiques:

1) Una forma simple d'instal·lar tots els paquets necessaris és instal·lar la plataforma de distribució de Python Anaconda: https://store.continuum.io/ Aquesta plataforma instal·la automàticament un conjunt d'eines (matplotlib, NumPy, SciPy, NetworkX, iPython, pandas, etc.) que constitueixen l'entorn de computació científica necessari per desenvolupar les pràctiques d'aquesta assignatura. L'altra opció és instal·lar independentment els paquets matplotlib, NumPy, SciPy, NetworkX, iPython i pandas. 2) Les pràctiques es poden lliurar en forma de "notebook" de iPython que contingui tot el programari desenvolupat per l'alumne o simplement en un mòdul de Python que contingui tot el programari necessari per executar la pràctica. Informació sobre iPython: http://ipython.org/

ENTREGA: El dia límit per a l'entrega d'aquesta pràctica és el dia 18 d'Octubre a les 23.55h

Format de l'entrega L'entrega s'efecturà mitjançant el campus virtual. S'ha de penjar un arxiu per grup. El nom del fitxer ha de seguir el següent patro: NUI_1_PrimeralletranomCognomMembre1_PrimeralletranomCognomMembre2.iypnb

Exemple:
Membre 1: Maria del Carme Vilà
Membre 2: Francesc Castell

Nom de l'arxiu: NUI_1_MVila_FCastell.ipynb

Pràctica 1. Recomanadors

La base de dades movielens 1M (http://www.grouplens.org/node/73) conté 1,000,209 puntuacions de 3.900 pel·licules fetes l'any 2000 per 6.040 usuaris anònims del recomanador online MovieLens (http://www.movielens.org/).
Els continguts de la base de dades són: ================================================================================ All ratings are contained in the file "ratings.dat" and are in the following format: UserID::MovieID::Rating::Timestamp - UserIDs range between 1 and 6040 - MovieIDs range between 1 and 3952 - Ratings are made on a 5-star scale (whole-star ratings only) - Timestamp is represented in seconds since the epoch as returned by time(2) - Each user has at least 20 ratings USERS FILE DESCRIPTION ================================================================================ User information is in the file "users.dat" and is in the following format: UserID::Gender::Age::Occupation::Zip-code All demographic information is provided voluntarily by the users and is not checked for accuracy. Only users who have provided some demographic information are included in this data set. - Gender is denoted by a "M" for male and "F" for female - Age is chosen from the following ranges: * 1: "Under 18" * 18: "18-24" * 25: "25-34" * 35: "35-44" * 45: "45-49" * 50: "50-55" * 56: "56+" - Occupation is chosen from the following choices: * 0: "other" or not specified * 1: "academic/educator" * 2: "artist" * 3: "clerical/admin" * 4: "college/grad student" * 5: "customer service" * 6: "doctor/health care" * 7: "executive/managerial" * 8: "farmer" * 9: "homemaker" * 10: "K-12 student" * 11: "lawyer" * 12: "programmer" * 13: "retired" * 14: "sales/marketing" * 15: "scientist" * 16: "self-employed" * 17: "technician/engineer" * 18: "tradesman/craftsman" * 19: "unemployed" * 20: "writer" MOVIES FILE DESCRIPTION ================================================================================ Movie information is in the file "movies.dat" and is in the following format: MovieID::Title::Genres - Titles are identical to titles provided by the IMDB (including year of release) - Genres are pipe-separated and are selected from the following genres: * Action * Adventure * Animation * Children's * Comedy * Crime * Documentary * Drama * Fantasy * Film-Noir * Horror * Musical * Mystery * Romance * Sci-Fi * Thriller * War * Western - Some MovieIDs do not correspond to a movie due to accidental duplicate entries and/or test entries - Movies are mostly entered by hand, so errors and inconsistencies may exist
Baixa't la base de dades i còpia-la a un directori local de la teva màquina (p.e. "C:\Documents and Settings\UB\Escritorio\My Dropbox\iPython").
Llegeix les tres taules de la base de dades en tres DataFrames de pandas:

In [13]:
import pandas as pd
unames = ['user_id', 'gender', 'age', 'occupation', 'zip']
#users = pd.read_table('I:\\p1\\users.dat', sep='::', header=None, names=unames)
users = pd.read_table('users.dat', sep='::', header=None, names=unames)

rnames = ['user_id', 'movie_id', 'rating', 'timestamp']
#ratings = pd.read_table('I:\\p1\\ratings.dat', sep='::', header=None, names=rnames)
ratings = pd.read_table('ratings.dat', sep='::', header=None, names=rnames)

mnames = ['movie_id', 'title', 'genres']
#movies = pd.read_table('I:\\p1\\movies.dat', sep='::', header=None, names=mnames)
movies = pd.read_table('movies.dat', sep='::', header=None, names=mnames)

In [2]:
print users[:10]


   user_id gender  age  occupation    zip
0        1      F    1          10  48067
1        2      M   56          16  70072
2        3      M   25          15  55117
3        4      M   45           7  02460
4        5      M   25          20  55455
5        6      F   50           9  55117
6        7      M   35           1  06810
7        8      M   25          12  11413
8        9      M   25          17  61614
9       10      F   35           1  95370

In [3]:
users[:10]


Out[3]:
user_id gender age occupation zip
0 1 F 1 10 48067
1 2 M 56 16 70072
2 3 M 25 15 55117
3 4 M 45 7 02460
4 5 M 25 20 55455
5 6 F 50 9 55117
6 7 M 35 1 06810
7 8 M 25 12 11413
8 9 M 25 17 61614
9 10 F 35 1 95370

In [4]:
ratings[:10]


Out[4]:
user_id movie_id rating timestamp
0 1 1193 5 978300760
1 1 661 3 978302109
2 1 914 3 978301968
3 1 3408 4 978300275
4 1 2355 5 978824291
5 1 1197 3 978302268
6 1 1287 5 978302039
7 1 2804 5 978300719
8 1 594 4 978302268
9 1 919 4 978301368

In [5]:
ratings


Out[5]:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1000209 entries, 0 to 1000208
Data columns (total 4 columns):
user_id      1000209  non-null values
movie_id     1000209  non-null values
rating       1000209  non-null values
timestamp    1000209  non-null values
dtypes: int64(4)

In [6]:
ratings.sort_index(by='movie_id')[:8]


Out[6]:
user_id movie_id rating timestamp
427702 2599 1 4 973796689
1966 18 1 4 978154768
683688 4089 1 5 965428947
596207 3626 1 4 966594018
465902 2873 1 5 972784317
78200 528 1 5 976245400
106468 701 1 3 979094230
434718 2652 1 5 973535127

In [7]:
movies[:5]


Out[7]:
movie_id title genres
0 1 Toy Story (1995) Animation|Children's|Comedy
1 2 Jumanji (1995) Adventure|Children's|Fantasy
2 3 Grumpier Old Men (1995) Comedy|Romance
3 4 Waiting to Exhale (1995) Comedy|Drama
4 5 Father of the Bride Part II (1995) Comedy

In [8]:
ratings[0:5]


Out[8]:
user_id movie_id rating timestamp
0 1 1193 5 978300760
1 1 661 3 978302109
2 1 914 3 978301968
3 1 3408 4 978300275
4 1 2355 5 978824291

1.1 Exemple: Càlcul de les puntuacions mitjanes per sexe i edat dels usuaris.

Suposa que volem calcular les puntuacions mitjanes d'una pel·licula per sexe i edat. El primer pas a obtebir una única estructura que contingui tota la informació. Per fer-ho podem usar la funció "merge" de pandas. Aquesta funció infereix automàticament quines columnes ha d'usar per fer el "merge" basant-se en els noms que fan interesecció:

In [14]:
data = pd.merge(pd.merge(ratings, users), movies)
#print data[2000:2200][['user_id','title','rating']]
La funció "ix" ens permet obtenir un subconjunt de files i/o columnes:

In [15]:
print data.ix[1]


user_id                                            2
movie_id                                        1193
rating                                             5
timestamp                                  978298413
gender                                             M
age                                               56
occupation                                        16
zip                                            70072
title         One Flew Over the Cuckoo's Nest (1975)
genres                                         Drama
Name: 1, dtype: object
Per obtenir les puntuacions mitjanes de cada pel·licula agrupada per gènere podem usar el mètode 'pivot_table':

In [12]:
mean_ratings = data.pivot_table('rating', rows='title',cols='gender', aggfunc='mean')
mean_ratings[:10]


Out[12]:
gender F M
title
$1,000,000 Duck (1971) 3.375000 2.761905
'Night Mother (1986) 3.388889 3.352941
'Til There Was You (1997) 2.675676 2.733333
'burbs, The (1989) 2.793478 2.962085
...And Justice for All (1979) 3.828571 3.689024
1-900 (1994) 2.000000 3.000000
10 Things I Hate About You (1999) 3.646552 3.311966
101 Dalmatians (1961) 3.791444 3.500000
101 Dalmatians (1996) 3.240000 2.911215
12 Angry Men (1957) 4.184397 4.328421
Podem filtrar les pel·licules que han rebut al menys 250 puntuacions. Per fer-ho, agrupem les dades per títol i usem "size()" per obtebir una sèrie de mides per cada títol:

In [13]:
ratings_by_title = data.groupby('title').size()
active_titles = ratings_by_title.index[ratings_by_title >= 250]
L'índex de títols que reben al menys 250 puntuacions es pot fer servir per seleccionar les files de "mean_ratings":

In [14]:
mean_ratings = mean_ratings.ix[active_titles]
mean_ratings[:10]


Out[14]:
gender F M
title
'burbs, The (1989) 2.793478 2.962085
10 Things I Hate About You (1999) 3.646552 3.311966
101 Dalmatians (1961) 3.791444 3.500000
101 Dalmatians (1996) 3.240000 2.911215
12 Angry Men (1957) 4.184397 4.328421
13th Warrior, The (1999) 3.112000 3.168000
2 Days in the Valley (1996) 3.488889 3.244813
20,000 Leagues Under the Sea (1954) 3.670103 3.709205
2001: A Space Odyssey (1968) 3.825581 4.129738
2010 (1984) 3.446809 3.413712
Per veure els films més valorats per les dones, podem ordenar per la columna F de forma descendent:

In [15]:
top_female_ratings = mean_ratings.sort_index(by='F', ascending=False)
top_female_ratings[:10]


Out[15]:
gender F M
title
Close Shave, A (1995) 4.644444 4.473795
Wrong Trousers, The (1993) 4.588235 4.478261
Sunset Blvd. (a.k.a. Sunset Boulevard) (1950) 4.572650 4.464589
Wallace & Gromit: The Best of Aardman Animation (1996) 4.563107 4.385075
Schindler's List (1993) 4.562602 4.491415
Shawshank Redemption, The (1994) 4.539075 4.560625
Grand Day Out, A (1992) 4.537879 4.293255
To Kill a Mockingbird (1962) 4.536667 4.372611
Creature Comforts (1990) 4.513889 4.272277
Usual Suspects, The (1995) 4.513317 4.518248
Suposem ara que volem les pel·licules que estan valorades de forma més diferent entre homes i dones. Una forma d'obtenir-ho és afegir una columna a "mean_ratings" que contingui la diferència en mitjana i llavors ordenar:

In [16]:
mean_ratings['diff'] = mean_ratings['M'] - mean_ratings['F']
Ordenant per 'diff' ens dóna les pel·licules amb valoració més diferent i que són ben valorades per les dones:

In [17]:
sorted_by_diff = mean_ratings.sort_index(by='diff')
sorted_by_diff[:15]


Out[17]:
gender F M diff
title
Dirty Dancing (1987) 3.790378 2.959596 -0.830782
Jumpin' Jack Flash (1986) 3.254717 2.578358 -0.676359
Grease (1978) 3.975265 3.367041 -0.608224
Little Women (1994) 3.870588 3.321739 -0.548849
Steel Magnolias (1989) 3.901734 3.365957 -0.535777
Anastasia (1997) 3.800000 3.281609 -0.518391
Rocky Horror Picture Show, The (1975) 3.673016 3.160131 -0.512885
Color Purple, The (1985) 4.158192 3.659341 -0.498851
Age of Innocence, The (1993) 3.827068 3.339506 -0.487561
Free Willy (1993) 2.921348 2.438776 -0.482573
French Kiss (1995) 3.535714 3.056962 -0.478752
Little Shop of Horrors, The (1960) 3.650000 3.179688 -0.470312
Guys and Dolls (1955) 4.051724 3.583333 -0.468391
Mary Poppins (1964) 4.197740 3.730594 -0.467147
Patch Adams (1998) 3.473282 3.008746 -0.464536
Invertint l'ordre de les files i fent un "slicing" de les 15 files superiors obtenim les pel·licules preferides pels homes que no han agradat a les dones:

In [18]:
sorted_by_diff[::-1][:15]


Out[18]:
gender F M diff
title
Good, The Bad and The Ugly, The (1966) 3.494949 4.221300 0.726351
Kentucky Fried Movie, The (1977) 2.878788 3.555147 0.676359
Dumb & Dumber (1994) 2.697987 3.336595 0.638608
Longest Day, The (1962) 3.411765 4.031447 0.619682
Cable Guy, The (1996) 2.250000 2.863787 0.613787
Evil Dead II (Dead By Dawn) (1987) 3.297297 3.909283 0.611985
Hidden, The (1987) 3.137931 3.745098 0.607167
Rocky III (1982) 2.361702 2.943503 0.581801
Caddyshack (1980) 3.396135 3.969737 0.573602
For a Few Dollars More (1965) 3.409091 3.953795 0.544704
Porky's (1981) 2.296875 2.836364 0.539489
Animal House (1978) 3.628906 4.167192 0.538286
Exorcist, The (1973) 3.537634 4.067239 0.529605
Fright Night (1985) 2.973684 3.500000 0.526316
Barb Wire (1996) 1.585366 2.100386 0.515020
Si volguéssim les pel·licules que han generat puntuacions més discordants, independentment del gènere, podem fer servir la variança o la desviació estàndard de les puntuacions:

In [19]:
# Standard deviation of rating grouped by title
rating_std_by_title = data.groupby('title')['rating'].std()
# Filter down to active_titles
rating_std_by_title = rating_std_by_title.ix[active_titles]
rating_std_by_title.order(ascending=False)[:10]


Out[19]:
title
Dumb & Dumber (1994)                     1.321333
Blair Witch Project, The (1999)          1.316368
Natural Born Killers (1994)              1.307198
Tank Girl (1995)                         1.277695
Rocky Horror Picture Show, The (1975)    1.260177
Eyes Wide Shut (1999)                    1.259624
Evita (1996)                             1.253631
Billy Madison (1995)                     1.249970
Fear and Loathing in Las Vegas (1998)    1.246408
Bicentennial Man (1999)                  1.245533
Name: rating, dtype: float64

EXERCICI 1: Calcula la puntuació mitjana de cada usuari. Quina és la pel·lícula més ben puntuada?


In [20]:
# Dos possibles formes:

# mean_rating_by_user ha de ser un dataframe que conté el nom del usuari i la mitja de les seves puntiacions
mean_rating_by_user = data.groupby('user_id')['rating'].mean()
print mean_rating_by_user[:5]

print '\n',data.pivot_table(values='rating',rows='user_id',aggfunc='mean')[:5]

# critic_higher_mean ha de ser el registre de l'usuari amb la mitja més elevada
critic_higher_mean = mean_rating_by_user[mean_rating_by_user.argmax()+1]
print '\n',critic_higher_mean

# Pel·lícula millor puntuada:
mean_rating_by_movie = data.groupby('title')['rating'].mean()
movie_higher_mean = mean_rating_by_movie.index[mean_rating_by_movie.argmax()]
print movie_higher_mean


user_id
1          4.188679
2          3.713178
3          3.901961
4          4.190476
5          3.146465
Name: rating, dtype: float64

user_id
1          4.188679
2          3.713178
3          3.901961
4          4.190476
5          3.146465
Name: rating, dtype: float64

4.96296296296
Baby, The (1973)

EXERCICI 2: Defineix una funció anomenada top_movie que donat un usuari ens retorni quina és la pel·lícula millor puntuada.
def top_movie(user)


In [21]:
def top_movie(dataFrame,usr):
    #Obtenim les pel·lícules valorades del usuari 'usr'.
    data_usr = dataFrame[ dataFrame['user_id'] == usr]
    return data_usr.ix[data_usr.index[data_usr['rating'].argmax()]]['title']


print top_movie(data,1)


One Flew Over the Cuckoo's Nest (1975)

1.2 Construcció d'un recomanador.

Exercici 3:

Construeix dues funcions, distEuclid(x,y) i coefPearson(x,y), que implementin la distància Euclidiana i el coeficient de correlació de Pearson entre dos vectors. Escriu les funcions que calculin la semblança entre dos usuaris segons aquesta estructura:

def SimEuclid (DataFrame, User1, User2) Calcular els vectors representatius de cada usuari, C1 i C2, amb les puntuacions dels ítems comuns que han puntuat el dos usuaris.
Si no hi ha puntuacions en comú, retornar 0. Retornar 1/(1+distEuclid(C1, C2))

def SimPearson (DataFrame, User1, User2) Calcular els vectors representatius de cada usuari, C1 i C2, amb les puntuacions dels ítems comuns que han puntuat el dos usuaris.
Si no hi ha puntuacions en comú, retornar 0.> Retornar coefPearson(C1,C2)

Utilizeu el panda per a realització d'aquest exercici.


In [16]:
import math
import time
import numpy as np
import pandas as pd

# Returns the euclidean distance of to vectors
def distEuclid(x, y):
    if len(x)!=len(y):
        return "Error: vectors size does not match."   
    return np.sqrt(sum((x[i]-y[i])**2 for i in range(len(x))))


 # Returns the euclidean pearson of to vectors 
def coefPearson(x, y):
    '''Calcula el coeficient de correlacio de Pearson donats els vectors x e y.'''
    if len(x)!=len(y):
        return "Error: vectors size does not match."
    mx = np.mean(x)
    my = np.mean(y)
    
    num = den_x = den_y = 0
    for i in range(len(x)): #obtenim el sumatori per el numerador i denomidaor
        num += (x[i]-mx)*(y[i]-my)
        den_x += (x[i]-mx)**2
        den_y += (y[i]-my)**2
        
    if den_x == 0 or den_y == 0: #evitam dividir per 0 (nan)
        return 0
    else:
        return num/(np.sqrt(den_x)*np.sqrt(den_y))
        

# Returns a distance-based similarity score for person1 and person2 based on euclidean distance
def SimEuclid(DataFrame,User1,User2):

    #Obtenim el dataFrames amb les pelicules i votació de cada usuari
    data_usr1 = DataFrame[ DataFrame['user_id'] == User1][['movie_id','rating']]
    data_usr2 = DataFrame[ DataFrame['user_id'] == User2][['movie_id','rating']]
    
    #Finalment obtenim únicament les votacions de les pel·lícules evaluades per els dos usuaris
    ints = pd.merge(data_usr1, data_usr2, how='inner',on='movie_id', suffixes=('_usr1', '_usr2')) 

    return 1/(1+distEuclid(ints['rating_usr1'], ints['rating_usr2']))




# Returns a distance-based similarity score for person1 and person2 based on pearson distance
def SimPearson(DataFrame,User1,User2):
    
    #Obtenim el dataFrames amb les pelicules i votació de cada usuari
    data_usr1 = DataFrame[ DataFrame['user_id'] == User1][['movie_id','rating']]
    data_usr2 = DataFrame[ DataFrame['user_id'] == User2][['movie_id','rating']]
    
    #Finalment obtenim únicament les votacions de les pel·lícules evaluades per els dos usuaris
    ints = pd.merge(data_usr1, data_usr2, how='inner',on='movie_id', suffixes=('_usr1', '_usr2'))

    return coefPearson(ints['rating_usr1'], ints['rating_usr2'])




# ============= PROVES ===================
#print "distEulid([1,2,8],[1,2,8]) --> " + str(distEuclid([1,2,8],[1,2,8]))
#print "\n---- Proves Pearson ----"
#print coefPearson([1,2,3],[3,2,1]) #---> -1
#print coefPearson([1,2,3],[1,2,3]) #---> 1
#print coefPearson([1,2,3],[2,3,4]) #---> 1
#print coefPearson([1,1,1,1],[1,2,3,4]) #---> 0

In [23]:
# Execute functions
print SimEuclid(data,1,2)
print SimPearson(data,1,2)


0.333333333333
0.416666666667

Exercici 4:

Feu dues funcions, getNBestEuclid(DataFrame, user,n) i getNBestPearson(DataFrame, user,n), que retornin els n usuaris més semblants segons aquestes dues mesures de similitud.


In [24]:
import time

# return the N most similar users to given used based on euclidean distance
def getNBestEuclid(DataFrame,user,n):
    # Per tal de fer-ho més eficient reduim el DataFrame a unicament les pel·licules votades per 'user'
    data2 = pd.merge(DataFrame,DataFrame[DataFrame['user_id']==user][['movie_id','title']],how='inner',on='movie_id')[['user_id','movie_id','rating']]  
    
    # Obtenim una llista dels usuaris excepte el propi a comparar
    users = set(data2[data2['user_id'] != user]['user_id'])
    
    # Calculem la dist. Euclid. entre usuaris (i,user) per tots els users necessaris i els ordenem.
    rank = [(SimEuclid(data2,user,i),i) for i in users]   
    rank = sorted(rank, key=lambda pearson_rtg: pearson_rtg[0], reverse=True)
    
    return [ rank[i] for i in range(n) ]


    
# return the N most similar users to given used based on pearson distance
def getNBestPearson(DataFrame,user,n):
    # Per tal de fer-ho més eficient reduim el DataFrame a unicament les pel·licules votades per 'user'
    data2 = pd.merge(DataFrame,DataFrame[DataFrame['user_id']==user][['movie_id','title']],how='inner',on='movie_id')[['user_id','movie_id','rating']]  
    
    # Obtenim una llista dels usuaris excepte el propi a comparar
    users = set(data2[data2['user_id'] != user]['user_id'])
    
    # Calculem el coef entre usuaris (i,user) per tots els users.
    rank = [(SimPearson(data2,user,i),i) for i in users]
    rank = sorted(rank, key=lambda pearson_rtg: pearson_rtg[0], reverse=True)
    
    return [ rank[i] for i in range(n) ]

In [25]:
# ======= EXECUTE FUNCTIONS ========
t0=time.clock()
print getNBestEuclid(data,1,5)
t1=time.clock()
print "Time ",(t1-t0),'\n'

t0=time.clock()
print getNBestPearson(data,1,5)
t1=time.clock()
print "Time ",(t1-t0)


[(1.0, 16), (1.0, 50), (1.0, 61), (1.0, 171), (1.0, 185)]
Time  47.5069661819 

[(1.0000000000000002, 298), (1.0000000000000002, 448), (1.0000000000000002, 565), (1.0000000000000002, 994), (1.0000000000000002, 1076)]
Time  40.8547947838

Exercici 5:

Desenvolupa un sistema de recomanació col·laboratiu basat en usuaris. La funció principal, getRecommendationsUser, ha de tenir com a entrada una taula de puntuacions, un "user_id", el tipus de mesura de semblança (Euclidiana o Pearson) que volem usar i el nombre n màxim de recomanacions que volem. Com a sortida ha de donar la llista de les n millors pel·lícules que li podriem recomanar segons la seva semblança amb altres usuaris.

Nota: S'ha d'evitar comparar "user_id" a ell mateix.


In [34]:
# Gets recommendations for a person by using a weighted average
# of every other user's rankings
def getRecommendationsUser(DataFrame,person,n,similarity=SimPearson):
    
    #1) Obtenim un DataFrame de user_id - similituts, mitjançant la creació d'un diccionari:
    users = list(set(DataFrame[DataFrame['user_id']!=person]['user_id']))
    sim_d = {'similar':[similarity(DataFrame,person,i) for i in  users], 'user_id':users }
    sim_df = pd.DataFrame(sim_d)
    #print sim_df[:100]
    
    
    #2) Trobem les pel·lícules no vistes per 'person'
    vistes = set(DataFrame[DataFrame['user_id']==person]['movie_id'])
    totes = set(DataFrame['movie_id'])
    no_vistes = list(totes.difference(vistes))
    
    #print "==================== VISTES ==================\n",list(vistes)
    #print "================== NO VISTES =================\n",no_vistes[:50] 
    
    #3) Obtenim totes les valoracions per cada una de les pel. no_vistes per tal de fer intersecció amb el dataFrame de similituts (sim_df). 
    # A partir d'aquest, podem calcular el valor de recomanació i guardar-ho junt al seu 'movie_id' a un array 'resu' (posterioriment ordenat
    # per treure les N millors).
    resu = []  #resu contindra tuples '(valoracio, movie_id)'
    for i in no_vistes:
        mov_df = DataFrame[DataFrame['movie_id']==i][['user_id','rating']]
        # Eliminam les similituts dels usuaris que no ens interesen (a la matriu serien els que tenen '?')
        m_df = pd.merge(mov_df,sim_df, on='user_id', how='inner')
        #print '\n======== Movies MERGE_DataFrame =======\n',m_df[:10]
        
        # Comprovam si el denominador és 0 --> Evitar NaN
        den = sum(m_df['similar'])
        if den != 0:
            resu = resu + [(sum(m_df['rating'] * m_df['similar'])/den, i )]
        
    #print "\n\nEl màxim:", max([i[0] for i in resu])
    resu = sorted(resu, key=lambda rtg:rtg[0], reverse=True)[:n]
    #print resu
    
    return [ list(set(DataFrame[DataFrame['movie_id']==i[1]]['title']))[0] for i in resu ]

In [36]:
t1 = time.clock()
print getRecommendationsUser(data, 1, 10, SimEuclid)
t2 = time.clock()
print 'Time: ',t2-t1

# EUCLIDES OUTPUT getRecommendationsUser(data, 1, 10, SimEuclid):
#['Schlafes Bruder (Brother of Sleep) (1995)', 'Follow the Bitch (1998)', 'Ulysses (Ulisse) (1954)', 'Smashing Time (1967)', 'Baby, The (1973)', 'Song of Freedom (1936)', 'One Little Indian (1973)', 'Lured (1947)', 'Bittersweet Motel (2000)', 'Gate of Heavenly Peace, The (1995)']
#Time:  127.41

# PEARSON OUTPUT  getRecommendationsUser(data, 1, 10, SimPearson):
#['Wing Commander (1999)', 'Badlands (1973)', 'American Dream (1990)', 'First Kid (1996)', 'Come See the Paradise (1990)', 'Stardust Memories (1980)', 'Newsies (1992)', "Squanto: A Warrior's Tale (1994)", 'Hotel de Love (1996)', 'How I Won the War (1967)']
#Time:  129.28


['Schlafes Bruder (Brother of Sleep) (1995)', 'Follow the Bitch (1998)', 'Ulysses (Ulisse) (1954)', 'Smashing Time (1967)', 'Baby, The (1973)', 'Song of Freedom (1936)', 'One Little Indian (1973)', 'Lured (1947)', 'Bittersweet Motel (2000)', 'Gate of Heavenly Peace, The (1995)']
Time:  142.730518055

Exercici 6:

Desenvolupa un sistema de recomanació col·laboratiu basat en ítems.

Primer, escriu una funció CalcSimItems(DataFrame), que construeixi i retorni una taula, itemsim, amb les semblances entre els ítems. Després escriu la funció principal, getRecommendationsItem(DataFrame, itemsim, user, n), ha de tenir com a entrada les puntuacions dels usuaris, la taula de semblança entre ítems, un "user_id" i el nombre n màxim de recomanacions que volem. Com a sortida ha de donar les n millors pel·lícules.


In [79]:
def CalcSimItems(DataFrame):
    movs = list(set(DataFrame['movie_id']))
    mida = len(movs)
    #Creació de Matriu de similituts
    matrix = [ [0] * mida for i in range(mida) ]
    
    #Anem Omplim de la matriu de similituts entre Items considerant la simetria respecte la diagonal:
    for i in range(mida):
        for j in range(i,mida):
            #obtenim els usuaris que han votat la peli 'i' i la 'j'
            users1 = DataFrame[DataFrame['movie_id']==i+1][['user_id','rating']]
            users2 = DataFrame[DataFrame['movie_id']==j+1][['user_id','rating']]
            sim = 0
            
            
            #Ens quedem amb les valoracions dels usuaris que han votat les dues pel·licules
            ratings = pd.merge(users1,users2,on='user_id',how='inner')
            #print ratings[:10]
            if len(ratings['rating_x']) != 0:
            #Calculam la similitut d'una pel·licula en funció de les valoracions dels usuaris que han votat les dues:
            
                sim = 1/(1+distEuclid(ratings['rating_x'],ratings['rating_y']))
            matrix[i][j] = sim
            matrix[-i-1][-j-1]=sim
            
    return matrix



def getRecommendationsItem(DataFrame, itemsim, usr, n):
    
    #Primerament trobem les pel·lícules NO vistes per 'usr' (pel·lícules a recomanar)
    vistes = set(DataFrame[DataFrame['user_id']==usr]['movie_id'])
    totes = set(DataFrame['movie_id'])
    no_vistes = list(totes.difference(vistes)) 
    #print "==================== VISTES ==================\n",list(vistes)
    #print "================== NO VISTES =================\n",no_vistes[:50]
    
    # Obtenim les puntuacions de totes les pelicules vistes per l'usuari
    mov_ratings = DataFrame[DataFrame['user_id']==usr][['movie_id','rating']]
    resu = []
    for i in no_vistes:
        estim = 0 
        #print '\n===Movie i:',i,'=='
        for j in  vistes:
            rating = list(mov_ratings[mov_ratings['movie_id']== j]['rating'])[0]
            similarity = itemsim[i-1][j-1]
            #print '>>rating:',rating,'\n>>similarity:',similarity
            estim = estim + (rating*similarity )
        #print '>>ESTIMACIO:',estim
        resu = resu + [(estim, i )]    
    print "\n\nEl màxim:", max([i[0] for i in resu])
    resu = sorted(resu, key=lambda rtg:rtg[0], reverse=True)[:n]
    #print resu
    
    return [ list(set(DataFrame[DataFrame['movie_id']==i[1]]['title']))[0] for i in resu ]

In [80]:
#Executions
data_aux = data[data['movie_id']<=50]
t0 = time.clock()
itemsim=CalcSimItems(data_aux)
t1 = time.clock()
print 'Matriu Similituts__Time: ',(t1-t0)
print getRecommendationsItem(data_aux, itemsim, 1, 10)
t2 = time.clock()
print 'Recomanacions__Time: ',(t2-t1)


Matriu Similituts__Time:  22.6963547747


El màxim: 2.75439132718
['Wings of Courage (1995)', 'Nixon (1995)', 'Four Rooms (1995)', 'Assassins (1995)', 'Cry, the Beloved Country (1995)', 'It Takes Two (1995)', 'American President, The (1995)', 'Restoration (1995)', 'Shanghai Triad (Yao a yao yao dao waipo qiao) (1995)', 'Carrington (1995)']
Recomanacions__Time:  0.118789954096

Exercici 7: Creu una funcio, EvaluateRecommendationsUser(DataFrame,DataFrameTest,similarity=SimPearson), que donat un conjunt de dades d'entrenament i un conjunt de dades de test ens avalua la precisió dels sistema. Per a cadascun dels elements del conjunt de test haurem de pronosticar el seu valor i comparar-lo amb el valor real que l'usuari li ha asignat.
Els mesura que utilizarem per avaluar el sistema és la següent: $$accuracy = 1/N\sum_{i=0}^N abs(rating_i - rating_i^*) $$ on rating és la puntaució real que l'usuari va asginar a la pel·lícula i rating* és el valor pronoticat pel sistema de recomanacio desenvolupat.


In [41]:
# evaluate the recomender given a training and a testing set. Return the accuracy of the system 
def EvaluateRecommendationsUser(DataFrame,DataFrameTest,similarity=SimPearson):
    test_usr = list(DataFrameTest['user_id'])
    
    resu = 0
    for i in range(len(DataFrameTest)):
        usr =  DataFrameTest.ix[i]['user_id']
        
        users = list(set(DataFrame[DataFrame['user_id']!=usr]['user_id']))
        sim_d = {'similar':[similarity(DataFrame,usr,u) for u in users], 'user_id':users }
        sim_df = pd.DataFrame(sim_d)
        
        peli = DataFrameTest.ix[i]['movie_id']
        mov_df = DataFrame[DataFrame['movie_id']==peli][['user_id','rating']]
        # Eliminam les similituts dels usuaris que no ens interesen
        m_df = pd.merge(mov_df,sim_df, on='user_id', how='inner')
        
        # Comprovam si el denominador és 0
        den = sum(m_df['similar'])
        if den != 0:
            estimacio = sum(m_df['rating'] * m_df['similar'])/den
            real = DataFrameTest.ix[i]['rating']
            
            print '>>> Real:',real,'\n>>> Estimació:',estimacio,'\n'
            resu += abs(estimacio - real) #la valoracio generada
            
    return resu/len(DataFrameTest)




In [44]:
t0=time.clock()
print EvaluateRecommendationsUser(data[5:],data[:5],SimEuclid)
t1=time.clock()
print 'Time: ',(t1-t0)


>>> Real: 5 
>>> Estimació: 4.38088909351 

0.619110906492
Time:  238.901307279

In [79]:


In [88]:
len(data)


Out[88]:
1000209