소연의_개발일지
article thumbnail

 

목차

개발완료보고서 & 요구사항 세부내역

일정표

파일 첨부

실행화면

코드 전문

 


개발 완료 보고서

 

 

 

 

일정표

 

파일 첨부

실행화면

오픈화면

 

선택 화면

 

장르추천/ 유사한 영화 추천

 

커서 눌리는 부분 / 로딩화면

 

영화 추천 결과 / 장르 추천 결과
 

 

코드 전문

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  # 영화 타이틀에서 인덱스 값을 기준으로 제목, 출시일자, 평균 투표 점수를 리턴함(데이터프레임으로 만들어서)
 

 

이번 프로젝트를 위해 보노보노를 직접 그려봤다.

감사합니다.

 

 

profile

소연의_개발일지

@ssoyxon

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!