Pada project sebelumnya kita telah membuat Simple Recommender System yang dibuat hanya dengan menggunakan formula Weighted Rating, yaitu mengurutkan score yang terdapat komponen average rating secara descending, kita dapat mengetahui (secara estimasi) film mana yang menurut para audience paling menarik.

Content Based Recommender System

Kali ini, kita akan membuat sistem rekomendasi yang menggunakan content/feature dari film/entitas tersebut, kemudian melakukan perhitungan terhadap kesamaannya satu dan yang lain sehingga ketika kita menunjuk ke satu film, kita akan mendapat beberapa film lain yang memiliki kesamaan dengan film tersebut. Hal ini biasa kita sebut sebagai Content Based Recommender System.

Sebagai contoh, berdasarkan kesamaan plot yang ada dan genre yang ada, ketika audience lebih menyukai film Narnia, maka sistem rekomendasi ini juga akan merekomendasikan film seperti Harry Potter atau The Lords of The Rings yang memiliki genre yang mirip.

Dataset

Dataset yang akan digunakan dalam pembahasan ini, meliputi:

Library

Library yang digunakan dalam project ini antara lain:

  • numpy untuk perhitungan numerik dengan array atau matriks.
  • pandas untuk manipulasi dan analisis data.
  • sklearn untuk pemodelan data.
# Load library
import pandas as pd
import numpy as np

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.metrics.pairwise import cosine_similarity

import warnings
warnings.filterwarnings('ignore')

Load Dataset

Melakukan pembacaan file csv ke dalam bentuk dataframe, kemudian melakukan preview data dan info data nya.

Table Movies

# Load file movie_rating.csv
movie_rating_df = pd.read_csv('data/movie_rating.csv')

# Print first five rows
movie_rating_df.head()
tconst titleType primaryTitle originalTitle isAdult startYear endYear runtimeMinutes genres averageRating numVotes
0 tt0000001 short Carmencita Carmencita 0 1894.0 NaN 1.0 Documentary,Short 5.6 1608
1 tt0000002 short Le clown et ses chiens Le clown et ses chiens 0 1892.0 NaN 5.0 Animation,Short 6.0 197
2 tt0000003 short Pauvre Pierrot Pauvre Pierrot 0 1892.0 NaN 4.0 Animation,Comedy,Romance 6.5 1285
3 tt0000004 short Un bon bock Un bon bock 0 1892.0 NaN 12.0 Animation,Short 6.1 121
4 tt0000005 short Blacksmith Scene Blacksmith Scene 0 1893.0 NaN 1.0 Comedy,Short 6.1 2050


# View info data movie_rating_df
movie_rating_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 751614 entries, 0 to 751613
Data columns (total 11 columns):
 #   Column          Non-Null Count   Dtype  
---  ------          --------------   -----  
 0   tconst          751614 non-null  object 
 1   titleType       751614 non-null  object 
 2   primaryTitle    751614 non-null  object 
 3   originalTitle   751614 non-null  object 
 4   isAdult         751614 non-null  int64  
 5   startYear       751614 non-null  float64
 6   endYear         16072 non-null   float64
 7   runtimeMinutes  751614 non-null  float64
 8   genres          486766 non-null  object 
 9   averageRating   751614 non-null  float64
 10  numVotes        751614 non-null  int64  
dtypes: float64(4), int64(2), object(5)
memory usage: 63.1+ MB

Terlihat pada kolom endYear dan genres terdapat missing values karena jumlah data nya lebih sedikit dari keseluruhan data.

Table Actors

# Load file actor_name.csv
actor_df = pd.read_csv('data/actor_name.csv')

# Print five first rows
actor_df.head()
nconst primaryName birthYear deathYear primaryProfession knownForTitles
0 nm1774132 Nathan McLaughlin 1973 \N special_effects,make_up_department tt0417686,tt1713976,tt1891860,tt0454839
1 nm10683464 Bridge Andrew \N \N actor tt7718088
2 nm1021485 Brandon Fransvaag \N \N miscellaneous tt0168790
3 nm6940929 Erwin van der Lely \N \N miscellaneous tt4232168
4 nm5764974 Svetlana Shypitsyna \N \N actress tt3014168
# View info data actor_df
actor_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000 entries, 0 to 999
Data columns (total 6 columns):
 #   Column             Non-Null Count  Dtype 
---  ------             --------------  ----- 
 0   nconst             1000 non-null   object
 1   primaryName        1000 non-null   object
 2   birthYear          1000 non-null   object
 3   deathYear          1000 non-null   object
 4   primaryProfession  891 non-null    object
 5   knownForTitles     1000 non-null   object
dtypes: object(6)
memory usage: 47.0+ KB

Terlihat pada kolom birthYear dan deathYear terdapat nilai \\N yang berarti bernilai NULL karena kesalahan pembacaan data. Lalu pada info data kolom primaryProfession juga terdapat missing values karena jumlah datanya lebih sedikit dibandingkan keseluruhan data.

Table Director_Writers

# Load file directors_writers.csv
director_writers_df = pd.read_csv('data/directors_writers.csv')

# Print first five rows
director_writers_df.head()
tconst director_name writer_name
0 tt0011414 David Kirkland John Emerson,Anita Loos
1 tt0011890 Roy William Neill Arthur F. Goodrich,Burns Mantle,Mary Murillo
2 tt0014341 Buster Keaton,John G. Blystone Jean C. Havez,Clyde Bruckman,Joseph A. Mitchell
3 tt0018054 Cecil B. DeMille Jeanie Macpherson
4 tt0024151 James Cruze Max Miller,Wells Root,Jack Jevne
# View info data director_writers_df
director_writers_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 986 entries, 0 to 985
Data columns (total 3 columns):
 #   Column         Non-Null Count  Dtype 
---  ------         --------------  ----- 
 0   tconst         986 non-null    object
 1   director_name  986 non-null    object
 2   writer_name    986 non-null    object
dtypes: object(3)
memory usage: 23.2+ KB

Terlihat bahwa tidak ditemukan missing values karena info jumlah data sesuai.

Update Dataframe

Melakukan manipulasi data agar siap diolah lebih lanjut sesuai kebutuhan.

Update Table Director_Writers

Kita akan mengubah kolom director_name dan writer_name dari string menjadi list.

# Change values string to list
director_writers_df['director_name'] = director_writers_df['director_name'].apply(lambda row: row.split(','))
director_writers_df['writer_name'] = director_writers_df['writer_name'].apply(lambda row: row.split(','))

# Print update rows data
director_writers_df.head()
tconst director_name writer_name
0 tt0011414 [David Kirkland] [John Emerson, Anita Loos]
1 tt0011890 [Roy William Neill] [Arthur F. Goodrich, Burns Mantle, Mary Murillo]
2 tt0014341 [Buster Keaton, John G. Blystone] [Jean C. Havez, Clyde Bruckman, Joseph A. Mitc...
3 tt0018054 [Cecil B. DeMille] [Jeanie Macpherson]
4 tt0024151 [James Cruze] [Max Miller, Wells Root, Jack Jevne]

Update Table Actors

Kita hanya akan membutuhkan kolom nconst, primaryName, dan knownForTitles untuk mencocokkan aktor/aktris ini dengan film yang ada.

# Selecting columns
actor_df = actor_df[['nconst','primaryName','knownForTitles']]

# Print update rows data
actor_df.head()
nconst primaryName knownForTitles
0 nm1774132 Nathan McLaughlin tt0417686,tt1713976,tt1891860,tt0454839
1 nm10683464 Bridge Andrew tt7718088
2 nm1021485 Brandon Fransvaag tt0168790
3 nm6940929 Erwin van der Lely tt4232168
4 nm5764974 Svetlana Shypitsyna tt3014168

Kita ingin tahu mengenai variasi dari jumlah film yang dapat dibintangi oleh seorang aktor. Tentunya seorang aktor dapat membintangi lebih dari 1 film, bukan? maka akan diperlukan untuk membuat table yang mempunyai relasi 1-1 ke masing-masing title movie tersebut. Kita akan melakukan unnest terhadap table tersebut. Langkah yang dilakukan antara lain,

  • Melakukan pengecekan variasi jumlah film yang dibintangi oleh aktor.
  • Mengubah kolom knownForTitles menjadi list of list.
# Check counts
print(actor_df['knownForTitles'].apply(lambda x: len(x.split(','))).unique())
[4 1 2 3]

Terlihat jumlah isi data pada kolom knownForTitles paling banyak adalah 4.

# Change values column knownForTitles to list of list
actor_df['knownForTitles'] = actor_df['knownForTitles'].apply(lambda x: x.split(','))
# Print update rows data
actor_df.head()
nconst primaryName knownForTitles
0 nm1774132 Nathan McLaughlin [tt0417686, tt1713976, tt1891860, tt0454839]
1 nm10683464 Bridge Andrew [tt7718088]
2 nm1021485 Brandon Fransvaag [tt0168790]
3 nm6940929 Erwin van der Lely [tt4232168]
4 nm5764974 Svetlana Shypitsyna [tt3014168]

Karena pada data sebelumnya dapat dilihat bahwa seorang aktor dapat membintangi 1 sampai 4 film, diperlukan untuk membuat table yang mempunyai relasi 1-1 dari aktor ke masing-masing judul film tersebut.

# Create empty list
new_df = []
for x in ['knownForTitles']:
    # Repeat index
    idx = actor_df.index.repeat(actor_df['knownForTitles'].str.len())   
    # Slicing values to create new rows
    temp_df = pd.DataFrame({x: np.concatenate(actor_df[x].values)})   
    # Change values of index
    temp_df.index = idx
    # Append to new_df
    new_df.append(temp_df)
    
# Concat the result
df_concat = pd.concat(new_df, axis=1)
# Join values with old data
unnested_df = df_concat.join(actor_df.drop(['knownForTitles'], 1), how='left')
# Convert into list
unnested_df = unnested_df[actor_df.columns.tolist()]
# Print update new rows
unnested_df.head()
nconst primaryName knownForTitles
0 nm1774132 Nathan McLaughlin tt0417686
0 nm1774132 Nathan McLaughlin tt1713976
0 nm1774132 Nathan McLaughlin tt1891860
0 nm1774132 Nathan McLaughlin tt0454839
1 nm10683464 Bridge Andrew tt7718088

Selanjutnya, kita akan mengelompokkan isi data primaryName menjadi list group berdasarkan kolom knownForTitles.

# Drop column nconst
unnested_df = unnested_df.drop(['nconst'], axis=1)
# Create empty list
new_df2 = []
for col in ['primaryName']:
    # Grouping by column knownForTitles
    temp_df2 = unnested_df.groupby(['knownForTitles'])[col].apply(list)
    # Append to empty list
    new_df2.append(temp_df2)

# Concat data
cast_df = pd.concat(new_df2, axis=1).reset_index()
# Rename columns
cast_df.columns = ['knownForTitles','cast_name']
cast_df.head()
knownForTitles cast_name
0 tt0008125 [Charles Harley]
1 tt0009706 [Charles Harley]
2 tt0010304 [Natalie Talmadge]
3 tt0011414 [Natalie Talmadge]
4 tt0011890 [Natalie Talmadge]

Terlihat bahwa ada nilai dari kolom cast_name yang sama tetapi memiliki nilai knownForTitles yang berbeda. Hal membuktikan bahwa seorang aktor/aktris pernah membintangi film yang berbeda.

Join Tables

Tahap berikutnya adalah melakukan penggabungan tabel atau dataframe yang telah di update sebelumnya, yakni:

  • Inner Join antara cast_df dan movie_rating_df (field knownForTitles dan tconst)
  • Left Join antara cast_movies_df dengan director_writer_df (field tconst dan tconst)
# Join cast_df and movie_rating_df
cast_movies_df = pd.merge(cast_df, movie_rating_df, left_on='knownForTitles', right_on='tconst', how='inner')
# Join base_df and director_writers_df
base_df = pd.merge(cast_movies_df, director_writers_df, left_on='tconst', right_on='tconst', how='left')

# Print first five rows
base_df.head()
knownForTitles cast_name tconst titleType primaryTitle originalTitle isAdult startYear endYear runtimeMinutes genres averageRating numVotes director_name writer_name
0 tt0011414 [Natalie Talmadge] tt0011414 movie The Love Expert The Love Expert 0 1920.0 NaN 60.0 Comedy,Romance 4.9 136 [David Kirkland] [John Emerson, Anita Loos]
1 tt0011890 [Natalie Talmadge] tt0011890 movie Yes or No Yes or No 0 1920.0 NaN 72.0 NaN 6.3 7 [Roy William Neill] [Arthur F. Goodrich, Burns Mantle, Mary Murillo]
2 tt0014341 [Natalie Talmadge] tt0014341 movie Our Hospitality Our Hospitality 0 1923.0 NaN 65.0 Comedy,Romance,Thriller 7.8 9621 [Buster Keaton, John G. Blystone] [Jean C. Havez, Clyde Bruckman, Joseph A. Mitc...
3 tt0018054 [Reeka Roberts] tt0018054 movie The King of Kings The King of Kings 0 1927.0 NaN 155.0 Biography,Drama,History 7.3 1826 [Cecil B. DeMille] [Jeanie Macpherson]
4 tt0024151 [James Hackett] tt0024151 movie I Cover the Waterfront I Cover the Waterfront 0 1933.0 NaN 80.0 Drama,Romance 6.3 455 [James Cruze] [Max Miller, Wells Root, Jack Jevne]


Data Cleaning

Setelah melakukan join table sebelumnya, sekarang hal yang akan kembali kita lakukan adalah melakukan cleaning pada data yang sudah dihasilkan.

# View info data base_df
base_df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1060 entries, 0 to 1059
Data columns (total 15 columns):
 #   Column          Non-Null Count  Dtype  
---  ------          --------------  -----  
 0   knownForTitles  1060 non-null   object 
 1   cast_name       1060 non-null   object 
 2   tconst          1060 non-null   object 
 3   titleType       1060 non-null   object 
 4   primaryTitle    1060 non-null   object 
 5   originalTitle   1060 non-null   object 
 6   isAdult         1060 non-null   int64  
 7   startYear       1060 non-null   float64
 8   endYear         110 non-null    float64
 9   runtimeMinutes  1060 non-null   float64
 10  genres          745 non-null    object 
 11  averageRating   1060 non-null   float64
 12  numVotes        1060 non-null   int64  
 13  director_name   986 non-null    object 
 14  writer_name     986 non-null    object 
dtypes: float64(4), int64(2), object(9)
memory usage: 132.5+ KB
# Check NULL columns
null_cols = base_df.columns[base_df.isnull().any()]
base_df[null_cols].isnull().sum()
endYear          950
genres           315
director_name     74
writer_name       74
dtype: int64

Dari hasil diatas diketahui bahwa:

  • Kolom knownForTitles dan tconst memiliki nilai yang sama.
  • Kolom endYear, genres, director_name, writer_name terdapat missing values.

Untuk mengatasi hal tersebut yang akan kita lakukan antara lain:

  • Menghapus kolom knownForTitles
  • Mengisi missing values dengan Unknown kecuali kolom endYear.
# Drop colomn knownForTitles
base_df = base_df.drop(['knownForTitles'], axis=1)
# Fill missing values column genres with 'Unknown'
base_df['genres'] = base_df['genres'].fillna('Unknown')
# Fill missing values column director_name and writer_name with 'Unknown'
base_df[['director_name','writer_name']] = base_df[['director_name','writer_name']].fillna('Unknown')
# Convert values column genres into list
base_df['genres'] = base_df['genres'].apply(lambda x: x.split(','))

Reformat Table

Hal selanjutnya yang akan kita lakukan adalah melakukan reformat pada table base_df, seperti menhapus kolom yang tidak diperlukan dan mengubah nama kolom.

# Drop unnecessary columns
clean_df = base_df.drop(['tconst','isAdult','endYear','originalTitle'], axis=1)
# Ordering columns
clean_df = clean_df[['primaryTitle','titleType','startYear','runtimeMinutes',
                     'genres','averageRating','numVotes','cast_name',
                     'director_name','writer_name']]
# Rename columns
clean_df.columns = ['title','type','start','duration','genres','rating','votes',
                    'cast_name','director_name','writer_name']
# Print clean data
clean_df.head()
title type start duration genres rating votes cast_name director_name writer_name
0 The Love Expert movie 1920.0 60.0 [Comedy, Romance] 4.9 136 [Natalie Talmadge] [David Kirkland] [John Emerson, Anita Loos]
1 Yes or No movie 1920.0 72.0 [Unknown] 6.3 7 [Natalie Talmadge] [Roy William Neill] [Arthur F. Goodrich, Burns Mantle, Mary Murillo]
2 Our Hospitality movie 1923.0 65.0 [Comedy, Romance, Thriller] 7.8 9621 [Natalie Talmadge] [Buster Keaton, John G. Blystone] [Jean C. Havez, Clyde Bruckman, Joseph A. Mitc...
3 The King of Kings movie 1927.0 155.0 [Biography, Drama, History] 7.3 1826 [Reeka Roberts] [Cecil B. DeMille] [Jeanie Macpherson]
4 I Cover the Waterfront movie 1933.0 80.0 [Drama, Romance] 6.3 455 [James Hackett] [James Cruze] [Max Miller, Wells Root, Jack Jevne]

Metadata

Untuk membuat sistem rekomendasi kita akan mengambil metadata yang dibutuhkan, yaitu kolom title, cast_name, genres, director_name, dan writer_name.

# Selecting metadata
feature_df = clean_df[['title', 'cast_name', 'genres', 'director_name','writer_name']]
# Print first five rows
feature_df.head()
title cast_name genres director_name writer_name
0 The Love Expert [Natalie Talmadge] [Comedy, Romance] [David Kirkland] [John Emerson, Anita Loos]
1 Yes or No [Natalie Talmadge] [Unknown] [Roy William Neill] [Arthur F. Goodrich, Burns Mantle, Mary Murillo]
2 Our Hospitality [Natalie Talmadge] [Comedy, Romance, Thriller] [Buster Keaton, John G. Blystone] [Jean C. Havez, Clyde Bruckman, Joseph A. Mitc...
3 The King of Kings [Reeka Roberts] [Biography, Drama, History] [Cecil B. DeMille] [Jeanie Macpherson]
4 I Cover the Waterfront [James Hackett] [Drama, Romance] [James Cruze] [Max Miller, Wells Root, Jack Jevne]

Building Content Based Recommender System

Step 1

Membuat fungsi untuk menghilangkan spasi dari setiap baris dan setiap elemennya, dengan cara membuat lower case terlebih dahulu kemudian mengecek apakah bertipe list atau string biasa.

# Step 1
def sanitize(x):
    try:
        # Check if values is list
        if isinstance(x, list):
            return [i.replace(' ','').lower() for i in x]
        # If values is string
        else:
            return [x.replace(' ','').lower()]
    except:
        print(x)
        
# Column with list group       
feature_cols = ['cast_name','genres','writer_name','director_name']

# Apply function sanitize 
for col in feature_cols:
    feature_df[col] = feature_df[col].apply(sanitize)

Step 2

Membuat fungsi untuk membuat metadata soup (menggabungkan semua feature menjadi 1 bagian kalimat) untuk setiap judulnya.

# Create function metadata soup
def soup_feature(x):
    return ' '.join(x['cast_name']) + ' ' + ' '.join(x['genres']) + ' ' + ' '.join(x['director_name']) + ' ' + ' '.join(x['writer_name'])

# Create new column soup
feature_df['soup'] = feature_df.apply(soup_feature, axis=1)
# Check soup result
feature_df['soup'].head()
0    natalietalmadge comedy romance davidkirkland j...
1    natalietalmadge unknown roywilliamneill arthur...
2    natalietalmadge comedy romance thriller buster...
3    reekaroberts biography drama history cecilb.de...
4    jameshackett drama romance jamescruze maxmille...
Name: soup, dtype: object

Step 3

Menyiapkan CountVectorizer (stop_words = english) dan fit dengan soup yang kita buat sebelumnya. CountVectorizer adalah tipe paling sederhana dari vectorizer.

Sebagai contoh terdapat 3 text A, B, dan C, dimana text nya adalah

  • A: The Sun is a star
  • B: My Love is like a red, red rose
  • C : Mary had a little lamb

Untuk mengkonversi teks berikut menjadi bentuk vector menggunakan CountVectorizer. Langkah-langkahnya adalah:

  • Menghitung ukuran dari vocabulary. Vocabulary adalah jumlah dari kata unik yang ada dari text tersebut. Maka hasil vocabulary dari set ketiga text tersebut adalah: the, sun, is, a, star, my, love, like, red, rose, mary, had, little, lamb. Secara total, ukuran vocabulary adalah 14.
  • Tidak include stop words (english), seperti as, is, a, the, dan sebagainya karena kata tersebut sudah umum sekali. Dengan mengeliminasi stop words, maka clean size vocabulary kita adalah like, little, lamb, love, mary, red, rose, sun, star (sorted alphabet ascending).

Dengan menggunakan CountVectorizer, maka hasil yang kita dapatkan adalah sebagai berikut:

  • A : (0,0,0,0,0,0,0,1,1), terdiri atas sun:1, star:1
  • B : (1,0,0,1,0,2,1,0,0), terdiri atas like:1, love:1, red:2, rose:1
  • C : (0,1,1,0,1,0,0,0,0), terdiri atas little:1, lamb:1, mary:1
# Create CountVectorizer
count = CountVectorizer(stop_words='english')
count_matrix = count.fit_transform(feature_df['soup'])

print(count_matrix.shape)
(1060, 10026)

Step 4

Membuat model similarity antara count matrix. Pada langkah ini, kita akan menghitung score cosine similarity dari setiap pasangan judul (berdasarkan semua kombinasi pasangan yang ada, dengan kata lain kita akan membuat 675 x 675 matrix, dimana cell di kolom i dan j menunjukkan similarity score antara judul i dan j. Kita dapat dengan mudah melihat bahwa matrix ini simetris dan setiap elemen pada diagonal adalah 1, karena itu adalah similarity score dengan dirinya sendiri.

Kita akan menggunakan formula cosine similarity untuk membuat model. Score ini sangatlah berguna dan mudah untuk dihitung. Formula untuk perhitungan cosine similarity antara 2 text, adalah sebagai berikut:

$cosine(x,y)=\frac{x.y^T}{   x   .   y   }$

Output yang didapat antara range -1 sampai 1. Score yang hampir mencapai 1 artinya kedua entitas tersebut sangatlah mirip sedangkan score yang hampir mencapai -1 artinya kedua entitas tersebut adalah beda.

# Using cosine similarity
cosine_sim = cosine_similarity(count_matrix, count_matrix)

# Print output
print(cosine_sim)
[[1.         0.15430335 0.35355339 ... 0.         0.         0.13608276]
 [0.15430335 1.         0.10910895 ... 0.         0.         0.        ]
 [0.35355339 0.10910895 1.         ... 0.         0.08703883 0.09622504]
 ...
 [0.         0.         0.         ... 1.         0.         0.        ]
 [0.         0.         0.08703883 ... 0.         1.         0.10050378]
 [0.13608276 0.         0.09622504 ... 0.         0.10050378 1.        ]]

Step 5

Selanjutnya yang harus dilakukan adalah reverse mapping dengan judul sebagai index nya.

# Create indices from column title
indices = pd.Series(feature_df.index, index=feature_df['title']).drop_duplicates()

# Create function recommender system
def content_based_recommender(title):
    # Get index title
    idx = indices[title]
    
    # Convert array similarity score to list
    sim_scores = list(enumerate(cosine_sim[idx]))
    # Sort by highest score
    sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)
    # Get similarity score (index 1 to 11)
    sim_scores = sim_scores[1:11]

    # Get index title based similarity score
    movie_indices = [i[0] for i in sim_scores]
    # Get rows with index title
    return base_df.iloc[movie_indices]

# Apply function recommender system
content_based_recommender('The Love Expert')
cast_name tconst titleType primaryTitle originalTitle isAdult startYear endYear runtimeMinutes genres averageRating numVotes director_name writer_name
344 [Anat Dychtwald] tt0237123 tvSeries Coupling Coupling 0 2000.0 2004.0 30.0 [Comedy, Romance] 8.5 41571 [Martin Dennis] [Steven Moffat]
1052 [Metin Namlisesli] tt9124840 movie Organik Ask Organik Ask 0 2018.0 NaN 100.0 [Comedy, Romance] 4.3 89 [Kamil Cetin] [Volkan Girgin]
2 [Natalie Talmadge] tt0014341 movie Our Hospitality Our Hospitality 0 1923.0 NaN 65.0 [Comedy, Romance, Thriller] 7.8 9621 [Buster Keaton, John G. Blystone] [Jean C. Havez, Clyde Bruckman, Joseph A. Mitc...
24 [Constance De Mattiazzi] tt0043762 movie Lullaby of Broadway Lullaby of Broadway 0 1951.0 NaN 92.0 [Comedy, Musical, Romance] 6.8 893 [David Butler] [Earl Baldwin]
398 [Wai Chi Wong] tt0308670 movie Oi ching bak min bau Oi ching bak min bau 0 2001.0 NaN 101.0 [Comedy, Romance] 6.8 47 [Steven Lo] [Canny Leung, Chi Shan Leung]
441 [Matthew Fuchs] tt0396269 movie Wedding Crashers Wedding Crashers 0 2005.0 NaN 119.0 [Comedy, Romance] 6.9 323737 [David Dobkin] [Steve Faber, Bob Fisher]
142 [Harvey J. Alperin] tt0094889 movie Cocktail Cocktail 0 1988.0 NaN 104.0 [Comedy, Drama, Romance] 5.9 76694 [Roger Donaldson] [Heywood Gould]
325 [Tim Horsely] tt0198284 movie After Sex After Sex 0 2000.0 NaN 96.0 [Comedy, Drama, Romance] 4.4 753 [Cameron Thor] [Thomas M. Kostigen]
345 [Ngan-Ying Poon] tt0237501 movie Ninth Happiness Gau sing bou hei 0 1998.0 NaN 86.0 [Comedy, Musical, Romance] 5.9 118 [Clifton Ko] [Raymond To]
410 [Catherine May] tt0340109 movie Fast Food High Fast Food High 0 2003.0 NaN 92.0 [Comedy, Drama, Romance] 5.2 174 [Nisha Ganatra] [Tassie Cameron, Jackie May]


Hasil diatas adalah rekomendasi 10 film terbaik berdasarkan kemiripan data film The Love Expert.