목차
개발완료보고서 & 요구사항 세부내역
일정표
파일 첨부
실행화면
코드 전문
개발 완료 보고서
일정표
파일 첨부
실행화면
코드 전문
main.py
from data import genres_dict
from genre_recommend import build_chart
from recommend_for_movie import get_recommendations
from movie_recommender import *
from googletrans import Translator
import os
import sys
from PyQt5 import QtWidgets
from PyQt5 import uic
from PyQt5.Qt import *
from PyQt5.QtGui import *
def resource_path(relative_path):
base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
return os.path.join(base_path, relative_path)
# UI 불러오기
form = resource_path('./UI/movie_UI.ui') # 메인창 ui
form_class = uic.loadUiType(form)[0]
loading_form = resource_path('./UI/loading_page.ui')
loading_ui = uic.loadUiType(loading_form)[0]
class Loading(QDialog, loading_ui):
"""로딩창"""
def __init__(self, parent):
super().__init__()
self.setupUi(self)
self.parent = parent
# gif 파일 불러와서 라벨에 불러줌
movie = QMovie('UI/img/search.gif')
self.loading_img.setPixmap(QPixmap('UI/img/search.gif'))
self.loading_img.setMovie(movie)
movie.start()
# 타이머 1: 창 닫아주는 타이머
timer = QTimer(self)
timer.timeout.connect(self.close_and_move)
timer.start(10000)
# 타이머2: 남은 초 알려주는 타이머 -> 10초 기준으로 함
self.DEFAULT_TIME = 10
timer_2 = QTimer(self)
timer_2.timeout.connect(self.label_change)
timer_2.setInterval(1000) # 1초에 한번씩 연결함
timer_2.start()
def close_and_move(self):
"""닫아주고 스택위젯 페이지 옮김"""
self.close()
self.parent.stackedWidget.setCurrentWidget(self.parent.main_page_2)
def label_change(self):
self.DEFAULT_TIME -= 1 # 1초씩 깎아준다.
if self.DEFAULT_TIME == 0: # 만약 0이라면
self.DEFAULT_TIME = 10 # 다시 10으로 만들어줌
self.label_2.setText(f'로딩 중입니다....{self.DEFAULT_TIME}')
class WindowClass(QMainWindow, form_class):
"""추천 메인 화면"""
def __init__(self):
super( ).__init__( )
self.setupUi(self)
self.setWindowTitle('초간단 영화추천기')
self.setWindowIcon(QIcon('UI/img/bono_face.png'))
# self.setWindowIcon()
self.setCursor(QCursor(QPixmap('UI/img/bono_face.png').scaled(80, 80)))
self.label_3.setPixmap(QPixmap('UI/img/remove_bono.png'))
# 버튼 시그널 연결
self.stackedWidget.setCurrentIndex(0) # 기본 페이지는 0(오프닝 페이지)
self.re_check_btn.clicked.connect(lambda : self.stackedWidget.setCurrentWidget(self.open_page))
self.start_btn.clicked.connect(lambda :self.stackedWidget.setCurrentWidget(self.main_page_0))
self.quit_btn.clicked.connect(lambda: self.close())
self.made_by.clicked.connect(lambda: self.stackedWidget.setCurrentWidget(self.contributer_page)) # 만든사람들 창
self.background_lab.mousePressEvent = lambda event: self.stackedWidget.setCurrentWidget(self.open_page) # 이미지 클릭하면 오프닝 페이지로 이동
# self.go_to_openpage.mousePressEvent = lambda event: self.stackedWidget.setCurrentWidget(self.open_page) # 이미지 클릭하면 오프닝 페이지로 이동
self.go_to_openpage.mousePressEvent = lambda event: self.remove_btns() # 이미지 클릭하면 오프닝 페이지로 이동
self.recommend_for_movie.clicked.connect(lambda: self.show_result_page('movie'))
self.recommend_for_genre.clicked.connect(lambda: self.show_result_page('genre'))
self.user_choice = None
# 콤보박스에 숫자 넣기
self.comboBox.addItems([str(e) for e in range(1, 101)])
self.comboBox.setCurrentIndex(9) # 콤보박스의 기본값은 10으로 지정
# 불러오기 테스트
# print(build_chart('Romance').head(15)) 장르
# print(improved_recommendations('Mean Girls')) #영화
self.check_btn.clicked.connect(self.show_result)
def remove_btns(self):
self.stackedWidget.setCurrentWidget(self.open_page)
button_list = self.scrollAreaWidgetContents.findChildren(QPushButton)
for btn in button_list:
btn.deleteLater()
def show_result_page(self, name):
self.button_list = self.widget.findChildren(QPushButton)
if name == 'movie':
self.user_choice = 'movie'
# 라벨에 텍스트 입력
self.text_info.setText('재미있게 본 영화를 선택하세요.')
# 페이지 이동
self.stackedWidget.setCurrentWidget(self.main_page_1)
# 그리드 레이아웃 생성 및 그리드 영역에 버튼 넣기
self.button_group = QButtonGroup() # 버튼 그룹 생성
grid = QGridLayout(self.scrollAreaWidgetContents)
cnt = 0
for i in range(1, 41):
for j in range(1, 6):
# print(cnt, qualified_list[cnt])
cnt += 1
button = QPushButton(qualified_list[cnt])
self.button_group.addButton(button)
button.setFixedSize(140, 75)
button.setCheckable(True)
grid.addWidget(button, i, j)
elif name == 'genre':
self.user_choice = 'genre'
self.text_info.setText('추천받고 싶은 영화 장르를 선택하세요!')
self.stackedWidget.setCurrentWidget(self.main_page_1)
# 위젯에 버튼 넣기
genres_kor_list = list(genres_dict.keys()) # 20개
self.button_group = QButtonGroup() # 버튼 그룹 생성
grid = QGridLayout(self.scrollAreaWidgetContents)
cnt = 0
for i in range(1, 6):
for j in range(1, 5):
button = QPushButton(genres_kor_list[cnt]) # 버튼 생성 및 이름 넣어줌
button.setFixedSize(150, 75) # 버튼의 크기 고정
button.setCheckable(True) # 선택할 수 있게 설정
self.button_group.addButton(button) # 버튼 그룹에 버튼 추가
grid.addWidget(button, i, j)
cnt += 1
# self.widget.setLayout(grid)
for btn in self.button_list:
btn.clicked.connect(self.btn_event_func)
def btn_event_func(self):
"""버튼 한 번만 눌리게 """
self.button_group.setExclusive(True)
def show_loading_page(self):
"""로딩 페이지 가져와서 보여주기"""
loading_page = Loading(self)
loading_page.show()
loading_page.exec_()
def show_result(self):
"""정보 받아와서 결과를 보여주는 부분"""
self.button_list = self.widget.findChildren(QPushButton)
try:
# 정보 넣어주기
for btn in self.button_list:
if btn.isChecked():
btn_name = btn.text()
combobox_text = self.comboBox.currentText() # 콤보박스 정보
if self.user_choice == 'movie':
result = get_recommendations(btn_name).head(int(combobox_text))
else:
result = build_chart(genres_dict[btn_name]).head(int(combobox_text)) #
self.insert_data_in_tablewidget(self.user_choice, result, btn_name, int(combobox_text)) # 테이블 위젯에 값 넣어주기
self.show_loading_page() # 로딩 페이지 보여주기
except UnboundLocalError:
QMessageBox.warning(self, "Warning", "버튼이 눌려지지 않았습니다.")
def insert_data_in_tablewidget(self, type, data, btn_name, row):
self.movie_result.setText(f'{btn_name} 영화의 추천 결과는...')
self.tableWidget.setRowCount(row)
translator = Translator()
for idx, title in enumerate(data['title']):
word = translator.translate(title, dest='ko')
# print(word.text)
self.tableWidget.setItem(idx, 0, QtWidgets.QTableWidgetItem(title))
self.tableWidget.setItem(idx, 1, QtWidgets.QTableWidgetItem(word.text))
if type == 'movie':
for idx, year in enumerate(data['release_date']):
self.tableWidget.setItem(idx, 2, QtWidgets.QTableWidgetItem(year))
for idx, wr in enumerate(data['vote_average']):
self.tableWidget.setItem(idx, 3, QtWidgets.QTableWidgetItem(str(round(wr, 2))))
else:
for idx, year in enumerate(data['year']):
self.tableWidget.setItem(idx, 2, QtWidgets.QTableWidgetItem(year))
for idx, wr in enumerate(data['wr']):
self.tableWidget.setItem(idx, 3, QtWidgets.QTableWidgetItem(str(round(wr, 2))))
self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) # 열 너비를 조정합니다.
if __name__ == '__main__':
app = QApplication(sys.argv)
myWindow = WindowClass( )
myWindow.show( )
app.exec_( )
genre_recommend.py
import pandas as pd
import numpy as np
from ast import literal_eval
from googletrans import Translator
# 영화 추천 시스템 만들기(단순버전, 평점기준 - 알고리즘이 들어가지 않는)
# 장르 추천하는 코드
# 자료 읽어오기
md2 = pd.read_csv('data/tmdb_5000_credits.csv')
md = pd.read_csv('data/tmdb_5000_movies.csv')
md2.columns = ['id','tittle','cast','crew']
md= md.merge(md2,on='id')
# stack 메서드를 사용해 새로운 시리즈를 하나의 열로 쌓음.
# reset_index 메소드를 사용해 새로운 데이터프레임의 인덱스를 레벨 1로 재설정하고 원래 genres 열 삭제
# 장르의 결측지를 제거하고 다시 md의 값으로 넣어줌
md['genres'] = md['genres'].fillna('[]').apply(literal_eval).apply(lambda x: [i['name'] for i in x] if isinstance(x, list) else [])
# release_date 값에서 year 값을 분리하여 md 데이터프레임의 year 열에 저장(새로 생성)
for idx, date in enumerate(md['release_date']):
if date != np.nan:
year = str(date).split('-')[0]
else:
year = np.nan
md.loc[idx, 'year'] = year
s = md.apply(lambda x: pd.Series(x['genres']), axis=1).stack().reset_index(level=1, drop=True) # genres열에서 새로운 시리즈를 생성함
s.name = 'genre'
gen_md = md.drop('genres', axis=1).join(s) # md의 genres열을 분리해서 새 gen_md 열을 생성함.
# 위에서 했던 자료들을 바탕으로 장르를 입력하면 순위 데이터프레임을 반환하는 함수 제작
def build_chart(genre, percentile=0.85):
"""각 장르별 차트 생성기"""
df = gen_md[gen_md['genre'] == genre] # 장르로 데이터 뽑아 옴
vc_notnull = df['vote_count'].notnull() # 투표 개수 null값 없는 것들만 변수에 담아 줌
va_notnull = df['vote_average'].notnull() # 투표 평균 null값 없는 것들만 변수에 담아 줌
vote_counts = df[vc_notnull]['vote_count'].astype('int') #int로 데이터타입 변경
vote_averages = df[va_notnull]['vote_average'].astype('int') # int로 데이터타입 변경
C = vote_averages.mean() # 투표의 평균값 계산
m = vote_counts.quantile(percentile) # 투표갯수의 상위 15프로만 가져옴
con1 = (df['vote_count'] >= m) # 조건1: 투표수가 상위 25프로 이내 있는 것
# con2 = (df['vote_count'].notnull()) # 조건2: 투표수가 빈값이 없는 것(위에서 만들어온 조건과 같아서 생략)
# con3 = (df['vote_average'].notnull()) # 조건3: 투표 평균중 빈값이 없는 것
qualified = df[con1 & vc_notnull & va_notnull][['title', 'year', 'vote_count', 'vote_average', 'popularity']]
# qualified = df[(df['vote_count'] >= m) & (df['vote_count'].notnull()) & (df['vote_average'].notnull())][
# ['title', 'year', 'vote_count', 'vote_average', 'popularity']]
qualified['vote_count'] = qualified['vote_count'].astype('int') #int로 데이터타입 변경
qualified['vote_average'] = qualified['vote_average'].astype('int')
qualified['wr'] = qualified.apply(lambda x: calculate_wr(x['vote_count'], x['vote_average'], m, C), axis=1)
# qualified['wr'] = qualified.apply( lambda x: (x['vote_count'] / (x['vote_count'] + m) * x['vote_average']) + (m / (m + x['vote_count']) * C),
# axis=1)
qualified = qualified.sort_values('wr', ascending=False).head(250) #wr열(평점)에 따른 점수 오름차순으로 250개를 뽑아옴
return qualified[['title', 'year', 'wr']]
def calculate_wr(vote_count, vote_average, m, C):
"""영화 평점에 대한 가중치 계산(tmdb에 따름)"""
return (vote_count / (vote_count + m) * vote_average) + (m / (m + vote_count) * C)
movie_recommender.py
genres = ['Action', 'Adventure', 'Fantasy', 'Science Fiction', 'Crime', 'Drama', 'Thriller',
'Animation', 'Family', 'Western', 'Comedy', 'Romance', 'Horror', 'Mystery', 'History',
'War', 'Music', 'Documentary', 'Foreign', 'TV Movie']
genres_dict = {
'액션': 'Action',
'어드벤쳐': 'Adventure',
'판타지': 'Fantasy',
'SF(공상과학)': 'Science Fiction',
'범죄': 'Crime',
'드라마': 'Drama',
'스릴러': 'Thriller',
'애니메이션': 'Animation',
'가족': 'Family',
'서부': 'Western',
'오락': 'Comedy',
'로맨스': 'Romance',
'공포': 'Horror',
'미스터리': 'Mystery',
'역사': 'History',
'전쟁': 'War',
'음악': 'Music',
'다큐멘터리': 'Documentary',
'외국': 'Foreign',
'TV쇼': 'TV Movie',
}
import pandas as pd
import numpy as np
# 자료 읽어오기
md2 = pd.read_csv('./data/tmdb_5000_credits.csv')
md = pd.read_csv('./data/tmdb_5000_movies.csv')
md2.columns = ['id','tittle','cast','crew']
md= md.merge(md2,on='id')
# 전체 정보의 투표 평균을 재 준다. 투표 갯수와 투표 평균에서 빈 값이 없는 자료만 가져와 준다.
vote_counts = md[md['vote_count'].notnull()]['vote_count'].astype('int')
vote_averages = md[md['vote_average'].notnull()]['vote_average'].astype('int')
C = vote_averages.mean() # 투표 평균값을 만들어 주고
# print(C) # 평균값: 5.xxx
m = vote_counts.quantile(0.80) # m은 차트에 들어갈 최소 투표수, 백분율 95까지 넣어준다. (상위 5프로의 값)
# print(m) # 상위 갯수는 3040.xxx
# datetime 값에서 year 값을 분리하여 md 데이터프레임의 year 열에 저장
md['year'] = pd.to_datetime(md['release_date'], errors='coerce').apply(
lambda x: str(x).split('-')[0] if x != np.nan else np.nan) # 변환중에 발생한 오류가 모두 NaT 값으로 변경됨
# 제목, 년도, 투표수, 평점, 인기도, 장르 열을 선택 / 조건: 투표수와 평점 열이 null이 아니고, 최소 투표 수 이상의 값을 가진 영화만 필터링
qualified = md[(md['vote_count'] >= m) & (md['vote_count'].notnull()) & (md['vote_average'].notnull())][
['title', 'year', 'vote_count', 'vote_average', 'popularity', 'genres']]
qualified['vote_count'] = qualified['vote_count'].astype('int') # 투표수를 데이터 타입을 정수형으로 변환
qualified['vote_average'] = qualified['vote_average'].astype('int') # 투표 평균을 정수형으로 변환. 위와 같음.
# print(qualified.shape) # 데이터프레임의 쉐입을 반환한다. (241, 6) ->
def weighted_rating(x):
"""평점 구하기(상대적)"""
v = x['vote_count']
R = x['vote_average']
return (v / (v + m) * R) + (m / (m + v) * C)
qualified['wr'] = qualified.apply(weighted_rating,
axis=1) # weighted_rating 을 wr이라는 열로 새로 생성해서 만든다. axis = 1값은 행값만 반환한다.
qualified = qualified.sort_values('wr', ascending=False).head(250)
qualified_list = qualified['title'].tolist()
# for idx, i in enumerate(qualified['title'].tolist()):
# print(i, end=", ")
# if idx % 10 == 9:
# print()
recommend_for_movie.py
import pandas as pd
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
from sklearn.metrics.pairwise import linear_kernel, cosine_similarity
from nltk.stem.snowball import SnowballStemmer
from ast import literal_eval
# 자료 읽어오기
md2 = pd.read_csv('./data/tmdb_5000_credits.csv')
md = pd.read_csv('./data/tmdb_5000_movies.csv')
md2.columns = ['id', 'tittle', 'cast', 'crew'] # 4가지의 열만 지정
md = md.merge(md2, on='id') # md2의 id 기준으로 두 자료를 병합한다.
id_not_null_df = pd.read_csv('./data/tmdb_5000_movies.csv')
con_id_not_null = id_not_null_df['id'].notnull() # 조건 생성(id가 null값이면 안 됨)
id_not_null_df = id_not_null_df[con_id_not_null]['id'].astype('int')
# print(id_not_null_df)
md['id'] = md['id'].astype('int') # str값인 id를 int값으로 변경해주기
smd = md[md['id'].isin(id_not_null_df)] # 그리고 빈값이 포함되지 않는 데이터를 smd데이터로 담아둠
# print(smd.shape) # 컬럼 열 출력하기
# 태그라인, 개요의 결측값을 ''로 채워줌
smd['tagline'] = smd['tagline'].fillna('') # 결측값을 ''로 채워줌
smd['overview'] = smd['overview'] + smd['tagline'] # 개요 열에 태그라인 열까지 합해주고
smd['overview'] = smd['overview'].fillna('') # 결측값을 ''로 채워줌
# print(smd['overview'])
#=============================================================================================
# # 등장인물과 배우들의 키워드를 뽑아서 비교한다.
# smd['cast'] = smd['cast'].apply(literal_eval) # 배우들 - 여기서 메인 캐릭터들만 추출 / literal_eval을 사용해서 리스트화
# smd['crew'] = smd['crew'].apply(literal_eval) # 영화제작자들 - 여기서 감독만 추출 / literal_eval을 사용해서 리스트화
# smd['keywords'] = smd['keywords'].apply(literal_eval) # 키워드 / literal_eval을 사용해서 리스트화
# smd['cast_size'] = smd['cast'].apply(lambda x: len(x)) # 배우들의 수를 세서 cast_size라는 새 열을 생성함
# smd['crew_size'] = smd['crew'].apply(lambda x: len(x)) # 제작자들의 수를 세서 crew_size라는 새 열을 생성함
#
#
# # 제작자에서 감독 이름만 추출하는 부분
# def get_director(x):
# """특정 영화에서 감독 이름만 반환함"""
# for i in x: # json 형식이기 때문에 딕셔너리 형식으로 값들이 들어 있음
# if i['job'] == 'Director': # 만약 감독이 있다면
# return i['name'] # 이름 반환
# return np.nan # 만약 감독이 없다면 none값 반환함
#
#
# smd['director'] = smd['crew'].apply(get_director) # 위에서 제작한 함수를 사용해 감독만 추출해서 director열을 생성하여 그 값을 넣음
#
# # cast 열에서 list 자료형임을 확인하고 이름만 담아줌
# for i, x in smd['cast'].items(): # cast Series에서 자료 확인
# if isinstance(x, list): # 만약 type이 list이면
# cast_list = [] # cast_list 라는 빈 리스트 생성하고
# for j in x: # 들어있는 자료들을 for문 돌려서(json형식이라 딕셔너리 안에 딕셔너리 들어있는 구조)
# cast_list.append(j['name']) # 빈 리스트에 이름만 추가해 준다.
# smd.at[i, 'cast'] = cast_list # at- 패스트 인덱싱. loc와 유사하며 1개의 값만 가진 자료구조에 접근하려 할 때 사용한다. loc보다 조금 더 빠른 속도가 나옴
# else:
# smd.at[i, 'cast'] = []
# # smd['cast'] = smd['cast'].apply(lambda x: [i['name'] for i in x] if isinstance(x, list) else []) #이렇게도 사용할 수 있음. 위의 for문과 속도는 비슷한 것 같음
# smd['cast'] = smd['cast'].apply(lambda x: x[:3] if len(x) >= 3 else x) # 배우들의 수를 세서 3명까지만 이름을 잘라줌.
#
# # cast 열에서 list 자료형임을 확인하고 내용 소문자로 변경해줌
# for i, x in smd['cast'].items():
# if isinstance(x, list): # 자료들이 리스트에 담겨있으면
# cast_list = [] # 빈 리스트를 만들고
# for j in x: # 소문자로 변경시켜서 이름들을 넣는다
# cast_list.append(str.lower(j.replace(", ", "")))
# smd.at[i, 'cast'] = cast_list # 그리고 smd 테이블 cast 열에 추가해 준다.
# else:
# smd.at[i, 'cast'] = []
#
# # keyword 열에서 list 자료형임을 확인하고 이름만 담아줌
# # print(smd.keywords)
# for i, x in smd['keywords'].items():
# if isinstance(x, list): # 자료가 리스트형이면
# keyword_list = [] # 빈 리스트를 생성하고
# for j in x: # 그 자료들 안에 있는 값들을 for문 돌려서
# keyword_list.append(j['name']) # 빈 리스트에 이름들을
# smd.at[i, 'keywords'] = keyword_list
# else:
# smd.at[i, 'keywords'] = []
#
# # 자료 중 감독의 열을 모두 str 타입으로 형변환 해주고 apply 함수를 사용하여 열값이 빈값이라면 없애준다.
# smd['director'] = smd['director'].astype('str').apply(lambda x: str.lower(x.replace(" ", "")))
# smd['director'] = smd['director'].apply(lambda x: [x, x, x]) # 감독의 값을 3개 넣어주어서 리스트에 담아준다.
# # print(smd['director']) # 감독이 3번씩 나옴
#
# '''
# 'smd'에서 'keywords' 컬럼에 있는 모든 값을 하나의 시리즈로 변환하고, 이를 스택으로 쌓은 뒤 인덱스를 레벨1로 리셋하는 코드
# 즉, 모든 영화의 키워드를 하나의 열로 쌓아놓고, 이를 인덱스로 구분하여 사용하기 쉬운 형태로 만드는 것.
# '''
# s = pd.Series() # 시리즈 하나를 생성함
# for index, row in smd.iterrows(): #for문을 돌려 keywords 열 값만 시리즈에 넣어줌
# s = s._append(pd.Series(row['keywords']), ignore_index=True)
# # print(s)
# s.name = 'keyword' # s의 이름을 keyword로 설정해줌
# s = s.value_counts() # 키워드 값 순으로 정렬해줌(가장 많이 나온 순위대로)
# s = s[s>1] # 1이 가장 많이 나오므로 1이 나오는 값은 생략해준다.
# # print(s)
#
# stemmer = SnowballStemmer('english') # 영어 어간을 잘라주는 변수 stemmer 생성 예) eaten -> eat
#
# # 키워드 하나의 리스트에 넣기
# def filter_keywords(x):
# """제시된 모든 키워드를 리스트에 넣고 리스트 리턴함."""
# words = []
# for i in x:
# if i in s:
# words.append(i)
# return words
#
# smd['keyword'] = smd['keywords'].apply(filter_keywords) # 키워드 열을 모두 적용한다.
# for i in range(len(smd['keywords'])):
# keywords = smd['keywords'][i] #키워드 열의 i번째 값들을 가져와서
# stemmed_keywords = [stemmer.stem(keyword) for keyword in keywords] #어간 잘라주는 stemmer 사용해서 키워드들을 모두 잘라서 리스트에
# smd['keyword'] = smd['keywords'].apply(filter_keywords) # 키워드 열을 모두 적용한다.
# for i in range(len(smd['keywords'])):
# keywords = smd['keywords'][i] #키워드 열의 i번째 값들을 가져와서
# stemmed_keywords = [stemmer.stem(keyword) for keyword in keywords] #어간 잘라주는 stemmer 사용해서 키워드들을 모두 잘라서 리스트에 넣음
# # smd['keyword'][i] = stemmed_keywords # 만든 리스트 값을 다시 키워드 열값에 저장함.
# smd.loc[i, 'keyword'] = stemmed_keywords
# '''SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame 오류'''
#
# ## 여기서부터 이해 시작 ====================
# smd['keyword'] = smd['keywords'].apply(lambda x: [str.lower(i.replace(" ", "")) for i in x])
# smd['genres'] = smd['genres'].apply(lambda x: [str.lower(i.replace(", ","")) for i in x])
#
#
# # 새로운 열 soup를 제작한다. keyword, cast, director, genres 다 있는 행
# # smd['keyword'] + smd['cast'] + smd['director'] + smd['genres']
# # print(smd['keyword']) # -- 리스트화 완
# # print(smd['cast']) # -- 리스트화 완
# # print(smd['director']) # --리스트화 되어 있음
# # print(smd['genres']) # -- 이게 튜플 형식으로 되어 있었음! 리스트화 시킴
# ==================================================================================================
# 개요, 태그라인에서 단어들을 뽑아서 분석
# analyer: 학습단위 결정 - word(단어로 설정)
# ngram_range: 단어의 묶음 - (1,2) 1개부터 2개까지
# min_df: 정수 또는 [0.0, 1.0] 사이의 실수. 디폴트는 1, 단어장에 포함되기 위한 최소 빈도
# stop_words: 문자열 {‘english’}, 리스트 또는 None (디폴트)
tf = TfidfVectorizer(analyzer='word', ngram_range=(1, 2), min_df=0, stop_words='english')
tfidf_matrix = tf.fit_transform(smd['overview']) # 개요의 텍스트 벡터화
# 코사인 유사도 구하기 - 모든 영화의 데이터에서 코사인 유사도를 구한다.
cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix) # fit_transform으로 구한 벡터 2가지의 유사성을 비교한다.
# 유사도를 기준으로 영화를 추천한다.
smd = smd.reset_index() # 인덱스 리셋
titles = smd['title'] # smd의 title 열을 가져온다.
indices = pd.Series(smd.index, index=smd['title']) # 인덱스값을 영화명으로 바꾸고, 내용을 인덱스 값으로 바꿔준다.
# print(indices)
def get_recommendations(title):
"""영화 제목에 따라 추천해주기"""
idx = indices[title] # 영화명에 따른 인덱스 값을 가져옴
sim_scores = list(
enumerate(cosine_sim[idx])) # 숫자와 유사도를 튜플 형식으로 모은 후 이를 리스트에 담는다. -> [(0, 0.0), (1, 0.000223434323242)]
sim_scores = sorted(sim_scores, key=lambda x: x[1],
reverse=True) # 튜플 형식 중 1번째 값, 즉 유사도 높은 순으로 정렬해서 다시 sorted_score에 담는다.
sim_scores = sim_scores[1:31] # 이 자료들을 1부터 30위까지만 다시 sorted_score에 담는다.
movie_indices = [i[0] for i in sim_scores] # 유사도 기준으로 오름차순한 자료에서 인덱스 값만 다시 담는다 -> 이를 비교해서 영화 정보를 담기 위함
results = []
for idx, i in enumerate(movie_indices):
condition = (smd['index'] == movie_indices[idx])
result = smd.loc[condition, ['title', 'release_date', 'vote_average']]
results.append(result)
df = pd.concat(results, ignore_index=True)
return df # 영화 타이틀에서 인덱스 값을 기준으로 제목, 출시일자, 평균 투표 점수를 리턴함(데이터프레임으로 만들어서)
이번 프로젝트를 위해 보노보노를 직접 그려봤다.
감사합니다.
'공부_백업용 > 프로젝트 모음' 카테고리의 다른 글
[팀프로젝트] (23.7.10 ~ 7.16) 채팅 프로그램 만들기 개발완료보고서 (2) | 2023.08.19 |
---|---|
[팀프로젝트] (23.7.3 ~ 7.8) 서울 관광 프로그램 개발완성보고서 (0) | 2023.08.19 |
[개인프로젝트] (23.6.13 ~ 6.23) 메가커피 키오스크 만들기 개발완료보고서 (5) | 2023.08.18 |
[프로젝트] 개인프로젝트 - 키오스크 제작: 요구분석서 작성하기 (0) | 2023.06.12 |
[팀프로젝트] (23.5.11 ~ 5.25) Pyqt, Python 사용해 RPG 게임 만들기 (2) | 2023.05.29 |