분석하고싶은코코

모바일 4가지 게임 리뷰 토픽 모델링 분석(1) 본문

머신러닝&딥러닝/NLP

모바일 4가지 게임 리뷰 토픽 모델링 분석(1)

코코로코코 2023. 10. 10. 19:26
반응형

블루아카이브, 니케, 원신, 붕괴:스타레일 4가지 게임에 대한 구글 스토어의 리뷰에 대한 토픽 모델링 분석에 대한 내용을 다뤄보겠습니다.

수집  데이터는 10월 3일까지의 리뷰를 수집하였습니다. 

 

모델을 사용한 토픽 모델링 방법은 기존 포스팅들에서 토픽 모델링을 어떻게 하는지에 대해서 작성을 했기 때문에 해당 포스팅을 참고해주시면 될 것 같습니다. 이번 분석의 순서는 다음과 같습니다.

 

  • 데이터 수집
  • 전처리
  • 모델링
    • LDA
    • CTM
    • KeyBERT
    • BERTopic
  •  결론

위 순서로 데이터 분석을 진행하였고 토픽 모델링을 다양하게 진행하고 하나의 어플이 아닌 4개의 어플에 대한 리뷰의 토픽 모델링이기 때문에 하나의 페이지에 다루기 어려워 수집,전처리를 하나의 포스팅, 모델링과 결론을 2개 포스팅으로 총 3개 포스팅으로 분석을 진행해보겠습니다.

 

과정에 대한 이야기에 앞서 해당 프로젝트에서 사용할 데이터 정보는 다음과 같습니다. 긍정의 경우 리뷰의 점수가 4, 5점 부정의 경우 1, 2점 리뷰로 분류하였습니다.

 

< 4가지 게임 리뷰 데이터 정보 >

  긍정 부정 전체
블루 아카이브 6,341 2,673 9,515
니케 5,033 5,255 11,339
원신 26,687 9,638 38,662
붕괴 스타레일 3,232 2,699 6,257

 

 

1) 데이터수집

구글 플레이 스토어에서 리뷰를 쉽게 가져올 수 있는 모듈이 있어서 따로 크롤러를 생성하지 않고 해당 모듈을 사용하여 데이터를 수집하였습니다.

 

관련 링크 : https://github.com/facundoolano/google-play-scraper

 

GitHub - facundoolano/google-play-scraper: Node.js scraper to get data from Google Play

Node.js scraper to get data from Google Play. Contribute to facundoolano/google-play-scraper development by creating an account on GitHub.

github.com

#!pip install google-play-scraper
from google_play_scraper import Sort, reviews_all

reviews = dict()

locs = [
    'com.nexon.bluearchive', # 블루아카이브
    'com.proximabeta.nikke', # 니케
    'com.miHoYo.GenshinImpact', # 원신
    'com.HoYoverse.hkrpgoversea', # 붕괴 : 스타레일
]

for idx, loc in enumerate(locs):
    result = reviews_all(
        loc,
        sleep_milliseconds=0, # defaults to 0
        lang='ko',
        country='kr',
        sort=Sort.NEWEST, # 최신순으로 불러옴
    )

    reviews[idx] = result

 

2) 전처리

 

2-1) 치환, 반복 문자 전처리

전처리는 크개 두 가지로 나뉘어서 진행했습니다. 우선 토픽 모델링에서 중요한 전처리 중 하나는 단어, 용어들의 치환 과정입니다. 예를들어서 '~ 버그에요.', '~ 오류에요.' 라는 두 가지 리뷰가 있을때 두 리뷰 모두 해당 어플이 정상적으로 동작하지 않은 상황입니다. 이에 대해서 토픽 모델링을 할 때 다른 토픽으로 컴퓨터는 분류하게 됩니다. 따라서 이 부분을 컴퓨터가 따로 분류하지 않도록 오류와 버그를 하나의 단어로 친환해줄 필요가 있습니다. 이 작업을 전처리 과정에서 진행합니다. 모든 단어를 치환할 수 없기 때문에 범용적으로 사용하는 단어들에 대해서 치환하는 작업을 진행하였습니다. 주로 리뷰에 등장하는 단어들을 대표적인 단어로 치환하였고 게임마다 사용되는 과금, 컨텐츠에 관련된 단어들 역시 치환하는 작업을 진행하였습니다. 자세한 치환 목록은  아래 텍스트르 참고해주세요. 

replace_text.txt
0.01MB

import pandas as pd
import re
import json

review_df = pd.read_csv('./data/reivews_df.csv')

# 치환 리스트
with open('./data/replace_text.txt', 'r') as f:
    replace_words = f.readlines()
    
replace_dict = dict()
for r_words in replace_words:
    b_word, a_word = re.sub('\n', '', r_words).split('/')
    replace_dict[b_word] = a_word
    

# 단어 치환
def replace_review_text(txt):
    for origin_word, replace_word in replace_dict.items():
        txt = re.sub(origin_word, replace_word, txt)
    return txt

 

 

2-2) 조사, 접속사 전처리

두 번째 전처리는 조사와 접속사에 대한 전처리를 진행하였습니다. 한국어 자연어처리에 있어서 전처리 작업에서 중요하게 작업해줘야할 부분이 조사에 대한 처리입니다. 조사에서는 모든 조사를 처리하기에는 원본 데이터를 너무 훼손하는 경우가 발생하여서 조사 리스트 중에서 주로 많이 사용되는 조사 리스트를 추려서 제거해주는 방식으로 진행했습니다. 또한 접속사 역시 토픽모델링에서 불필요한 단어이기에 이 역시 삭제하는 작업을 진행하였습니다. 이후에는 토픽 모델링에 수치에 관련된 내용은 필요가 없고 한국에서 남긴 리뷰에 대한 분석이기에 한국어를 제외한 모든 문자를 제거하여 전처리를 진행하였습니다. 마지막으로 최종 반환 텍스트에서 연속된 문자가 3개이상이라면 축약해주는 soynlp-normalizer를 사용하였습니다.

from soynlp.normalizer import repeat_normalize

def pp_stopwords_pposition(txt, stopwords = stopwords_pPosition):
    
    split_words = txt.split()

    result = []
    for word in split_words:
        for length in range(max(map(len, stopwords)),0 , -1):
            if word[-length:] in stopwords:
                result.append(word[:-length])
                break
            elif length == 1:
                result.append(word)

    result = ' '.join(result)

    return result



def pp_stopwords_conjunction(txt, stopwords = conjunction_lst):
    for stopword in stopwords:
        if stopword in txt:

            # Stopword의 위치 찾기
            check_before_idx = re.search(stopword, txt).start() -1
            check_after_idx = re.search(stopword, txt).end() # idx가 아니라 번째 개념으로 자동으로 +1 되어있음

            # 시작위치가 첫번째일떄 예외처리
            if check_before_idx == -1:
                check_before_blank = True
            else:
                check_before_blank = True if txt[check_before_idx] == ' ' else False
            
            #종료지점이 끝위치일떄 예외처리
            if check_after_idx == len(txt):
                check_after_blank = True
            else:
                check_after_blank = True if txt[check_after_idx] == ' ' else False
            
            if check_before_blank and check_after_blank:
                txt = re.sub(stopword, ' ', txt).strip()
        
    return txt

def del_stopwords(txt):
    txt = pp_stopwords_conjunction(txt) # 접속사 제거
    txt = pp_stopwords_pposition(txt) # 조사 제거
    txt = re.sub('[^가-힣]', ' ', txt).strip() # 한글 제외 제거
	txt = repeat_normalize(txt, num_repeats=3)
    
    return txt

data['content'] = data['content'].apply(del_stopwords)

 

반응형