데이터 로드
이번 포스팅에는 개인적으로 맛있게 먹었던 일본식 돈까스 음식점들의 리뷰를 보고, 식당에 방문한 사람들과 나의 생각은 유사한지 확인해보았다. 먼저, 네이버 플레이스에서 3가지 식당의 방문자 리뷰를 크롤링했다. 아래의 사진은 부산대 인근의 "톤쇼우" 네이버 플레이스이다.

톤쇼우외에도 "최강금 돈까스"와 "카와카츠 합정점"의 리뷰도 동일한 방식으로 가져왔다. 결과는 다음과 같다.

톤쇼우의 네이버 방문자 리뷰 2,122개를 가져왔으며, 사용자 닉네임, 리뷰 내용, 작성 날짜, 방문 횟수 4개의 column으로 구성했다. 사용자 이름은 개인정보이므로 masking 처리했다. 이제, KNIME으로 전처리를 진행해보자.
데이터 전처리

데이터 분석 플랫폼 KNIME는 전처리 및 시각화, 분석을 위한 다양한 node를 제공한다. 위의 사진은 전처리 및 시각화를 정리한 workflow이다.
1. 날짜 데이터 처리

- 현재 2023년의 데이터는 올해의 데이터이기 때문에, 연도가 지정되지 않고 월,일,요일만 출력되었다. 월,일,요일의 문자열 길이가 7보다 작거나 같기 때문에, 조건에 해당하면 앞에 23년을 붙여 format을 통일하였다.

- Date column의 type을 바꾸기 위해 전체 열 앞에 "20"을 붙이고, 정규식을 사용해 숫자와 .을 제외한 내용을 제거했다.

- Date column의 Type을 String에서 Date로 변경하였다.


- 날짜, 요일 별 시각화를 위해 Date Column에서 연, 월, 일, 요일을 추출했다.
나머지 음식점들의 리뷰도 동일한 방식으로 전처리를 진행하였다. 이제, 시각화를 통해 리뷰를 작성한 고객의 특성을 파악해보자.
리뷰 시각화(톤쇼우)

리뷰가 작성된 2019년 2월부터 2023년 11월까지, 요일별 방문 횟수를 나타낸 막대그래프이다. 주중보다는 주말 방문자가 많으며, 특히 일요일에 가장 많이 방문했음을 알 수 있다.

그렇다면, 재방문한 손님들은 언제 제일 많이 올까? 전체 손님의 방문 횟수와 대조적으로, 주말보다 평일에 방문하는 경향이 훨씬 강하다. 이유가 뭘까? 다른 음식점의 리뷰도 확인해보자.
리뷰 시각화(최강금)


최강금 돈까스는 전체 방문과 재방문자의 행태가 크게 차이나지 않는다. 톤쇼우와는 대조적인데, 원인은 식당이 위치한 곳의 인구 특성, 밀집도, 방문자의 연령층 등 여러가지 요소가 있을 수 있다.
리뷰 시각화(카와카츠)


카와카츠는 톤쇼우와 유사한 행태를 보이고 있다. 일요일은 휴일이기 때문에, 리뷰가 거의 없고 재방문자의 경우 아예 리뷰가 없음을 확인할 수 있다.
요일별 리뷰 횟수를 통해 사람들이 언제 많이 방문하고, 언제 적게 방문하는지를 어느정도 확인할 수 있지만, 사람들의 심리를 정확하게 알 수는 없다. 따라서, 가게별로 감성 분석을 진행하고, 워드 클라우드를 그려서 사람들의 심리를 데이터로 나타내보자.
감성 분석
KNIME에서 정제한 데이터를, python으로 가져왔다. 감성 분석을 하기 위해서는 Text Data에서 불필요한 내용을 제거해야한다. 정규표현식을 사용해서 URL, 해시태그, 멘션, 이모지, 숫자를 제거했다.
import sys
import bareunpy as brn
import google.protobuf.text_format as tf
import matplotlib.pyplot as plt
import pandas as pd
from collections import Counter
import networkx as nx
from gensim.models import Word2Vec
import json
import re
import string
from tqdm import tqdm
from collections import Counter
import numpy as np
import matplotlib.pyplot as plt
from wordcloud import WordCloud
data = pd.read_csv('톤쇼우.csv', encoding='cp949')
data = data.dropna() # 결측치 제거
# 정규표현식 패턴 정의
URL_PATTERN = r'https?://[\w/:%#\$&\?\(\)~\.=\+\-]+'
HASHTAG_PATTERN = r'#(\w+)'
MENTION_PATTERN = r'@(\w+)'
EMOJI_PATTERN = r'[\U00010000-\U0010ffff]'
NUM_PATTERN = r'\d+'
def preprocess_review(review):
review = re.sub(URL_PATTERN, ' ', review) # URL 제거
review = re.sub(HASHTAG_PATTERN, ' ', review) # 해시태그 제거
review = re.sub(MENTION_PATTERN, ' ', review) # 멘션 제거
review = re.sub(EMOJI_PATTERN, ' ', review) # 이모지 제거
review = re.sub(NUM_PATTERN, ' ', review) # 숫자 제거
return review
data['content'] = data['content'].apply(preprocess_review)
필요없는 부분을 정제하면, Text 데이터를 토큰화 시켜서 사전과 비교해야한다. 단, 사전과 비교하기 전 불용어를 선언하여 불필요한 단어를 제거해야한다. 토큰화는 형태소 분석기 "바른"을 사용하였고, 기존 불용어 목록에 "돈까스"라는 말을 추가하였다.
t = brn.Tagger(API_KEY, "localhost", 5656)
morphs_data = t.tags(data['content'].tolist()).nouns()
morphs_data = [word for word in morphs_data if len(word) > 1]
with open('stopwords-ko.txt', 'r', encoding='utf-8') as f:
stopwords = f.readlines()
stopwords = [word.strip() for word in stopwords]
filter_list = ['돈까스']
stopwords = stopwords + filter_list
filtered_text = [word for word in morphs_data if not word in stopwords]
count_values = Counter(filtered_text)
print(count_values.most_common(20))
count_tokens = pd.DataFrame(dict(count_values).items(), columns=['word','count'])
감성사전은 군산대학교의 KNU 한국어 감성사전을 활용하였다. 아래의 링크에서 다운받을 수 있다.
GitHub - park1200656/KnuSentiLex: KNU(케이앤유) 한국어 감성사전
KNU(케이앤유) 한국어 감성사전. Contribute to park1200656/KnuSentiLex development by creating an account on GitHub.
github.com
불용어를 제외한 토큰을 DataFrame으로 만들었으면, 감성사전과 교집합을 찾아서 긍정 혹은 부정 단어의 빈도를 확인해야한다.
# 극성 사전 로드
with open('SentiWord_info.json', encoding='utf-8-sig', mode='r') as f:
sentiment_dic = json.load(f)
sentiword_dict = pd.DataFrame(sentiment_dic)
sentiment_data = pd.merge(left=sentiword_dict, right=count_tokens, how='inner',on='word')
sentiment_data['polarity'] = sentiment_data['polarity'].astype(int)
sentiment_data['sentiment'] = list(map(lambda x:'긍정' if x> 0 else '부정' if x < 0 else '중립',
sentiment_data.polarity))
| word | word_root | polarity | count | sentiment | |
| 0 | 가치 | 가치 | 1 | 14 | 긍정 |
| 1 | 감동 | 감동 | 2 | 7 | 긍정 |
| ... | ... | ... | ... | ... | ... |
| 62 | 환상 | 환상 | -2 | 2 | 부정 |
| 63 | 후회 | 후회 | -2 | 8 | 부정 |
동일한 방식으로 진행한 3곳의 극성 점수는 다음과 같다. 점수는 polarity column의 값을 합산했다.
| 구분 | 점수 |
| 카와카츠(합정) | 33 |
| 최강금 | 31 |
| 톤쇼우(부산대) | 16 |
하지만, 그냥 점수만 합치면 다수의 사람들이 말하고자하는 의도가 묵살될 수 있다는 생각이 들었다. 많은 사람들이 비슷한 내용을 얘기하는 경우에는, 해당 부분에 가중치를 줘야 많은 사람의 의견이 반영된 점수가 도출되기 때문이다. 따라서, 단어가 언급된 횟수에 맞게 가중치를 부여하여 점수를 다시 계산하였다. 단, 그냥 횟수를 곱할 경우 점수가 과하게 편향될 수 있기 때문에 횟수에 min-max normalization을 적용하였다. 가중치를 적용한 결과는 다음과 같다.
| 구분 | 점수 | 점수(가중치) |
| 카와카츠(합정) | 33 | 6.45 |
| 최강금 | 31 | 2.56 |
| 톤쇼우(부산대) | 16 | 6.75 |
결과를 보면, 단순히 점수를 합쳤을 때 가장 점수가 낮았던 톤쇼우가 가중치를 적용하니 가장 높은 점수를 보였다. 여기서 추가로 확인하고 싶은 점이 있다. 리뷰의 개수와 극성 점수는 비례할까? 현재 3가지 음식점 모두 긍정적인 반응이 부정적인 반응보다 훨씬 많은 결과를 보여준다. 그렇다면, 리뷰가 많을수록 점수가 올라갈까? 확인해보자. 1주일 기준으로 리뷰가 가장 많은 날, 가장 적은날, 재방문자가 가장 많은 날 3가지로 분류해서 테이블에 정리했다.
| 구분(가중치 점수) | 리뷰 1위 | 리뷰 7위 | 재방문자 리뷰 1위 |
| 카와카츠(합정) | 4.2 | 7 | 4.94 |
| 최강금 | 1.83 | 2.27 | 2 |
| 톤쇼우(부산대) | 5.28 | 5.23 | 5 |
분석 결과, 리뷰의 개수와 점수는 전혀 관계없음을 확인했다. 손님이 적어도 짧게 기다리고 맛있는 음식을 먹어 더 높은 점수를 부여할 수 있고, 손님이 많아도 웨이팅이 길거나 맛의 편차로 인해 점수가 다소 낮을 수 있다.
워드 클라우드

파란색은 긍정적인 단어, 빨강색은 부정적인 단어이며, 회색은 중립적인 경향의 단어이다. 하지만, 감성 사전에 사용자 정의 요소를 넣지 않아 다소 부정확한 결과가 도출됨을 확인할 수 있다. 톤쇼우 워드클라우드에서 주로 도출되는 내용은 웨이팅, 버크셔, 테이블링, 맛집 등으로 테이블링 이라는 웨이팅 플랫폼을 사용하며, 시그니쳐 메뉴 버크셔K를 활용한 돈까스 맛집임을 유추할 수 있다.

최강금 돈까스의 경우 안심이라는 단어가 가장 돋보인다. 해당 단어는 긍정적인 의미의 안심으로 출력되어있지만, 실제로는 시그니쳐 메뉴인 안심카츠에 대한 호감도가 높다고 해석해야한다. 동음이의어 문제로, 한국어 뿐만 아니라 텍스트 마이닝에서 필수적으로 확인해야하는 요소이다. 그 외에 요소로 웨이팅이 있으며, 들기름과 된장국, 소금으로 카츠를 보조함을 확인할 수 있다.

카와카츠 합정점은 고객들이 고기에 초점을 두고, 특히 안심 돈까스를 극찬함을 확인할 수 있다. 그 외에 튀김, 카레, 등심 등 다양한 메뉴가 있으며, 여기도 웨이팅이 있음을 유추할 수 있다.
분석 결과, 개인적인 입맛과 다수의 의견이 꽤나 일치함을 확인했다. 3가지 음식점 모두 맛있지만, 리뷰를 작성한 사용자들의 의견 기준으로는 톤쇼우가 제일 맛있다고 해석할 수 있다. 하지만, 동음이의어와 같은 한국어 텍스트 데이터 문제를 해소하지 못했고, 모든 사람의 리뷰를 분석한 것이 아니기 때문에 무조건 맞는 결과라고 볼 수 없다. 또한, 개인의 입맛 역시 다르기 때문에 이 결과는 하나의 지표로만 참고하는 것이 바람직하다.
'데이터 분석 > 리뷰 분석' 카테고리의 다른 글
| [리뷰] 네이버 플레이스 카츠 - RFM (0) | 2023.11.30 |
|---|