dersblog

Netflix / Movielens Film Verisi

Movielens 1M Verisi [3] 6000 kullanıcı tarafından yaklaşık 4000 tane filme verilen not / derece (rating) verisini içeriyor, 1 milyon tane not verilmiş. Örnek olarak film isimleri ve o filmlere verilmiş beğeni notlarını taşıyan bir veri tabanını işleyeceğiz. Verimiz üç ayrı dosyaya yayılmış halde, users.dat, ratings.dat, ve movies.dat. Üç tabloyu alttaki şekilde, merge komutu ile birleştiriyoruz - Pandas otomatik olarak ortak kolon ismini bulacak ve onun üzerinden birleştirimi yapacak.

import pandas as pd, zipfile
unames = ['user_id', 'gender', 'age', 'occupation', 'zip']
rnames = ['user_id', 'movie_id', 'rating', 'timestamp']
mnames = ['movie_id', 'title', 'genres']
with zipfile.ZipFile('ml1m.zip', 'r') as z:
    users = pd.read_table(z.open('users.dat'), sep='::', header=None,names=unames)
    ratings = pd.read_table(z.open('ratings.dat'), sep='::', header=None,names=rnames)
    movies = pd.read_table(z.open('movies.dat'), sep='::', header=None,names=mnames)
data = pd.merge(pd.merge(ratings, users), movies)

Eğer erkeklerin en çok sevdiği ama kadınların en az sevdiği (ve hanımlar için tam tersi olan) filmleri bulmak istiyorsak, bu işlemi nasıl yaparız? Bu işlemi Pandas ile yapmak için ilginç bir takla atacağız. "Bir grubun en çok diğerinin en az" sorusu, onların bir filme verdiği ortalama notun farkının en büyük olması demektir. Bunu düşünebilmek önemli.

İkinci olarak bu işlemin kodlaması için ne gerekir? Bir çıkartma işlemi lazım. İdeal olarak bir kolonu (ya da satırı) diğerinden çıkartmak - bu tür toptan işlemler zaten Pandas ile çok hızlı.

Fakat verimiz halen o formatta değil. Her satır, tek bir film, tek bir kişi (cinsiyet) ve tek bir not için kaydedilmiş. Bizim ilgilendiğimiz analiz için film bazında cinsiyet verisini yanyana, değişik kolonlarda görmeliyiz.

Peki nasıl? Cevap pivotlamak.

Pivotlamak bir kolonu (hatta birkaç kolonu) alıp onu x ekseni yapmak, aynı şekilde bir (veya birkaç) kolonu y ekseni yapmak anlamına gelir. Yani bir kolon üzerindeki tüm değerler okunur, ve kordinatmış gibi o eksene yayılır. Aynı şekilde diğer kordinat halledilir. Daha sonra bu iki kordinattaki kesişim değerleri için bir üçüncü nümerik kolon seçilir (ve onun üzerinden ek bir nümerik işlem de tanımlanabilir), ve böylece pivotlama gerçekleşmiş olur.

Bizim pivot için cinsiyet kolona yayılacak, film ismi satıra yayılacak. Kesişim ise not ortalaması (rating mean) olacak.

mean_ratings = data.pivot_table('rating', rows='title', cols='gender',
                                aggfunc='mean')
print mean_ratings[:5]
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

Daha fazla ilerlemeden ufak bir ek işlem daha yapalım, 250'den daha az not almış olan filmleri eleyelim.

ratings_by_title = data.groupby('title').size()
active_titles = ratings_by_title.index[ratings_by_title >= 250]
print active_titles[:10]
Index([u''burbs, The (1989)', u'10 Things I Hate About You (1999)', u'101 Dalmatians (1961)', u'101 Dalmatians (1996)', u'12 Angry Men (1957)', u'13th Warrior, The (1999)', u'2 Days in the Valley (1996)', u'20,000 Leagues Under the Sea (1954)', u'2001: A Space Odyssey (1968)', u'2010 (1984)'], dtype=object)

Yapılan harekete dikkat: ratings_by_title.index üzerinde bir boolean filtreleme yaptık, yani [True, False..., True] gibi bir filtreleyiciyi Index objesi üzerinde kullandık. Bu niye işledi? Çünkü .index çağrısı da sonuçta bir dizindir, ve dizinler üzerinde istenen boolean filtrelemesi yapılabilir (her iki taraf ta aynı boyutta olduğu sürece).

Devam edelim, şimdi ortalama notları üstteki yeni Index'e göre azaltalım (ve .ix kullanacağız, çünkü Index objesi satırlar üzerinde işlem yapar ve .ix çağrısı satırlara erişmek için kullanılır), ve hanımların en çok sevdiği filmlere bakalım,

mean_ratings = mean_ratings.ix[active_titles]
top_female_ratings = mean_ratings.sort_index(by='F', ascending=False)
print top_female_ratings[:4]
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

Baylara pek tanıdık gelmeyen bir liste. Şimdi erkekler ve hanımlar beğeni farkını hesaplayalım ve en büyük farklar en üstte olacak şekilde sıralama (sort) yapalım,

mean_ratings['diff'] = mean_ratings['M'] - mean_ratings['F']
sorted_by_diff = mean_ratings.sort_index(by='diff')
print sorted_by_diff[:6] 
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

Dirty Dancing, Grease gibi romantik filmler üstte çıktı. Şimdi listeyi ters çevirelim ve en alta bakalım, orada bayların en çok hanımların en az sevdiği filmler olmalı,

print sorted_by_diff[::-1][:15]
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

Burada da Good, The Bad and The Ugly gibi kovboy filmleri, ve buna benzer vurdulu kırdılı filmler ya da enseye tokat türünden Aptal ve Daha Aptal (Dumb and Dumber) gibi filmler çıktı. İlginç bir analiz oldu!

Yapay Öğrenim geniş bir alandır, ama regresyon, sınıflama gibi işlemlerden önce hala yapılabilecek ilginç ve önemli, üstteki gibi veri analizler var.

Seyrek Matris

Movielens verisini seyrek matris formuna döndümek için basit bir kod

from scipy.sparse import csr_matrix
d = "/tmp" # verinin oldugu dizin
ratings = pd.read_csv(d + "/ratings.csv")
utility_csr = csr_matrix((ratings.rating, (ratings.userId , ratings.movieId)))

Bu matris ile kendi beğenilerimizi temsile eden bir vektör arasında kosinüs uzaklığı şöyle hesaplayabiliriz,

from sklearn.metrics.pairwise import cosine_similarity

my_ratings = np.zeros((1,utility_csr.shape[1]))
my_ratings[0,movId1] = rating1
my_ratings[0,movId2] = rating2
...
similarities = cosine_similarity(utility_csr, tst)
close_people = np.argsort(similarities[:,0])
...

Bizim beğenilerle bize en yakın seyircileri bulduktan sonra o seyircilerin en yüksek not verdiği filmleri tavsiye olarak listeleyebiliriz.

JSON

Bir diğer seyrek sayılabilecek format her satırda ayrı kullanıcı için o kullanıcının beğeni raporunu JSON olarak vermek. Böylece kişi bazlı veri satır satır okunabilir ve her satır üzerinde json.loads işletilerek veri Python sözlüğüne çevirilebilir.

import json, csv

fin = d + "/ratings.csv"
fout = d + "/ratings-json.csv"
curruser = 0
row_dict = {}
fout = open(fout, "w")
with open(fin) as csvfile:   
    rd = csv.reader(csvfile,delimiter=',')
    headers = {k: v for v, k in enumerate(next(rd))}
    for row in rd:
        if row[headers['userId']] != curruser:
            fout.write(str(curruser) + "|")
            fout.write(json.dumps(row_dict))
            fout.write("\n")
            fout.flush()
            curruser = row[headers['userId']]
            row_dict = {}       
        row_dict[int(row[headers['movieId']])] = float(row[headers['rating']])
fout.close()

Kaynaklar

[1] [Grouplens](https://grouplens.org/datasets/movielens/latest), MovieLens Latest Datasets

[2] McKinney, W., Python for Data Analysis

[3] [Movielens 1M Veri](https://drive.google.com/uc?export=view&id=1AfnThq72GCP2NkJk_w5nGFqTZjl7lPTA)


Yukarı