본문 바로가기
프로젝트

[프로젝트] 국내 주요 게임사 텍스트 데이터 분석 프로젝트 리뷰 (2)

by 단깅수 2024. 7. 12.
728x90

2024년 1학기 텍스트 데이터 분석 과목을 수강하면서 진행했던 프로젝트에 대해 리뷰해보려고 한다.

해당 프로젝트는 데이터 분석 프로젝트이고 분야는 NLP, 데이터 분석이 되겠다.

이번 포스팅에서는 실질적인 텍스트 데이터 분석 내용을 담았다.

 

GitHub - junhoeKu/Game-Company-Analysis.github.io: 국내 주요 게임사에 대한 텍스트 데이터 분석 (알고리즘 |

국내 주요 게임사에 대한 텍스트 데이터 분석 (알고리즘 | 정형 | NLP | 데이터분석) - junhoeKu/Game-Company-Analysis.github.io

github.com

 

분석 기법은 총 3가지를 사용했다.

  • WordCloud
  • LDA, 토픽 모델링
  • 게임사 별 유사도 분석

 

1. WordCloud

첫 번째로, 뉴스 기사와 블로그 글 따로 워드클라우드를 생성하였다.

아무래도 본문 내용은 불용어 등 중요성이 낮은 단어가 빈번하게 등장한다고 판단해 제목과 본문 내용 중 제목 column만 활용해서 분석을 진행하였다.

게임사 5곳 (크래프톤, 넥슨, 넷마블, 엔씨소프트, 라이엇) 에 대해 워드 클라우드를 생성했는데 이번 포스팅에서는 그 중 넷마블과 라이엇의 예시만 볼 예정이다.

여담이지만, 기존의 워드클라우드 글씨체가 너무 구려서 글씨체를 찾아보았는데 넷마블체 너무 귀엽다.. 자주 애용할 예정이다.

아래는 넷마블 워드클라우드 시각화 코드 및 워드클라우드이다.

from wordcloud import WordCloud
import matplotlib.pyplot as plt
%matplotlib inline
from kiwipiepy import Kiwi

## 넷마블체 다운로드 받아서 사용
font_path = 'C:\\Users\\82109\\appdata\\local\\microsoft\\windows\\fonts\\netmarbleM.ttf'
kiwi = Kiwi(typos='basic')  ## 오탈자 제거 기능 추가

def extract_pos(text):
    tokens = []
    results = kiwi.analyze(text)
    for result in results:
        for token in result[0]:
            if token.tag in ['NNG', 'NNP', 'VV', 'VA', 'SL', 'SN']:  ## 동사, 명사, 형용사, 영어, 숫자만 남기기 
                tokens.append(token.form)
    return ' '.join(tokens)

## netmarble 내용만 추출
netmarble_news = df.loc[df.category == 'netmarble_news']
netmarble_blog = df.loc[df.category == 'netmarble_blog']
news = ''
blog = ''

for _, row in netmarble_news.iterrows():
    news += extract_pos(row['title'])

for _, row in netmarble_blog.iterrows():
    blog += extract_pos(row['title'])

## WordCloud 설정
news_cloud = WordCloud(font_path = font_path, background_color='white', width=800, height=500).generate_from_text(news)
blog_cloud = WordCloud(font_path = font_path, background_color='white', width=800, height=500).generate_from_text(blog)

netmarble_news_arr = news_cloud.to_array()
netmarble_blog_arr = blog_cloud.to_array()

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 10))
ax1.imshow(netmarble_news_arr)
ax1.set_title('Netmarble News WordCloud')

ax2.imshow(netmarble_blog_arr)
ax2.set_title('Netmarble Blog WordCloud')

plt.show()

  • 워드클라우드 분석 결과
    • 신작 - 아스달 연대기, 나 혼자만 레벨업 등 지속적인 모바일 게임 출시를 통해 모바일 게임 시작 개척에 박차를 가하고 있음
    • 마블 챌린저 - 대학생 서포터즈 활동을 운영해 인재를 발굴하고 게임 업계로 꿈을 키울 수 있도록 적극 지원
    • 문화재단 - 넷마블 문화재단을 설립해 게임을 기반으로 다양한 문화적 가치를 창출하고 나눔 문화 확산을 위한 노력을 하고 있음

 

아래는 라이엇게임즈 워드클라우드 시각화 코드 및 워드클라우드이다.

## riot 내용만 추출
riot_news = df.loc[df.category == 'riot_news']
riot_blog = df.loc[df.category == 'riot_blog']
news = ''
blog = ''

for _, row in riot_news.iterrows():
    news += extract_pos(row['title2'])

for _, row in riot_blog.iterrows():
    blog += extract_pos(row['title2'])

## WordCloud 설정
news_cloud = WordCloud(font_path = font_path, background_color='white', width=800, height=500).generate_from_text(news)
blog_cloud = WordCloud(font_path = font_path, background_color='white', width=800, height=500).generate_from_text(blog)

riot_news_arr = news_cloud.to_array()
riot_blog_arr = blog_cloud.to_array()

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 10))
ax1.imshow(riot_news_arr)
ax1.set_title('Riot News WordCloud')

ax2.imshow(riot_blog_arr)
ax2.set_title('Riot Blog WordCloud')

plt.show()

  • 워드클라우드 분석 결과
    • E-sports - LCK, E-sports 등 키워드를 보아 다른 게임사와는 다르게 대회, 경기 등의 이미지가 강함
    • 주력 컨텐츠 - 리그 오브 레전드, 발로란트, 레전드 오브 룬테라 등 주력 컨텐츠에 집중하고 있음을 알 수 있음
    • 타 게임사와의 차별성 - 타 게임사와 달리 채용, 복지 등의 키워드가 전무해 외국 기업인만큼 채용 활동에 소극적인 것으로 추측하고 있음

2. LDA, Topic Modeling

두 번째로, 토픽 모델링을 진행해주었다.처음에 5가지 검색어를 먼저 설정하고 분석했는데 LDA의 결과도 5개의 구역별로 잘 나올지에 대한 의문점을 가지고 분석을 진행했다.추가로 게임사별 새로운 인사이트를 얻을 수 있으면 더 좋겠다는 생각도 있었다.LDA 에서는 크래프톤과 넥슨에 대해 살펴보도록 하겠다.

아래는 크래프톤 LDA 코드와 LDA 시각화 자료이다.

from kiwipiepy.utils import Stopwords
from gensim import corpora
import gensim
from IPython.core.display import HTML
import pyLDAvis.gensim_models

stopwords = Stopwords()

## 게임사 이름과 뉴스, 블로그 등의 키워드는 포함되지 않도록 불용어 설정
stopwords_set = set(['크래프톤', '넥슨', '넥슨 게임즈', '라이엇', '라이엇 게임즈', '라이엇코리아',
			'엔씨', '엔씨소프트', '넷마블', '뉴스', '블로그'])
for i in stopwords_set:
    stopwords.add(i)

## LDA 토픽 모델링 함수
def preprocess_korean_LDA(text, stopwords):
    result = []
    tokens = kiwi.tokenize(text, normalize_coda=True)
    for token in tokens:
        if token.tag in ['NNG', 'NNP', 'SL'] and token.form not in stopwords:
            result.append(token.form)
    return result
    
krafton_news = df.loc[df.category == 'krafton_news']
krafton_blog = df.loc[df.category == 'krafton_blog']

## DataFrame에서 'title'과 'body' 결합
krafton_news['text_combined'] = krafton_news['title'] + " " + krafton_news['body']
krafton_blog['text_combined'] = krafton_blog['title'] + " " + krafton_blog['body']

## stopwords 리스트를 함수 호출 시 매개변수로 전달
krafton_news['LDA'] = krafton_news['text_combined'].apply(lambda x: preprocess_korean_LDA(x, stopwords_set))
krafton_blog['LDA'] = krafton_blog['text_combined'].apply(lambda x: preprocess_korean_LDA(x, stopwords_set))
krafton = pd.concat([krafton_news, krafton_blog], axis=0, ignore_index=True)

word_dict = corpora.Dictionary(krafton['LDA'])
corpus = [word_dict.doc2bow(text) for text in krafton['LDA']]

## lDA 모델 훈련 - 5개의 토픽
ldamodel = gensim.models.ldamodel.LdaModel(corpus, num_topics = 5, id2word=word_dict)

## 각 토픽별 대표 단어 출력
topics = ldamodel.print_topics(num_words=10)
for topic in topics:
    print(topic)

## LDA 시각화
vis = pyLDAvis.gensim_models.prepare(ldamodel, corpus, word_dict)

## 대시보드
pyLDAvis.enable_notebook()
pyLDAvis.display(vis)

  • 복지에 대한 내용이 두드러지지는 않지만 각 토픽별로 분리가 잘 된 결과를 확인할 수 있음
    • #1. 투자, 주가, 매출, 분기, 이익, 실적, 전망
    • #2. 기업, 회사, 개발, AI, 직원, 카페, 연봉
    • #3. 상승, 경제, 시장, 투자, 하락, 기업
    • #4. 채용, 경력, 지원, 교육, 신입, 개발자, 청년
    • #5. 증권, 경제, 분기, 상장, 글로벌

 

아래는 넥슨 LDA 코드와 LDA 시각화 자료이다.

nexon_news = df.loc[df.category == 'nexon_news']
nexon_blog = df.loc[df.category == 'nexon_blog']

## DataFrame에서 'title'과 'body' 결합
nexon_news['text_combined'] = nexon_news['title'] + " " + nexon_news['body']
nexon_blog['text_combined'] = nexon_blog['title'] + " " + nexon_blog['body']

## stopwords 리스트를 함수 호출 시 매개변수로 전달
nexon_news['LDA'] = nexon_news['text_combined'].apply(lambda x: preprocess_korean_LDA(x, stopwords_set))
nexon_blog['LDA'] = nexon_blog['text_combined'].apply(lambda x: preprocess_korean_LDA(x, stopwords_set))
nexon = pd.concat([nexon_news, nexon_blog], axis=0, ignore_index=True)

word_dict = corpora.Dictionary(nexon['LDA'])
corpus = [word_dict.doc2bow(text) for text in nexon['LDA']]

## lDA 모델 훈련 - 5개의 토픽
ldamodel = gensim.models.ldamodel.LdaModel(corpus, num_topics = 5, id2word=word_dict)

## 각 토픽별 대표 단어 출력
topics = ldamodel.print_topics(num_words=10)
for topic in topics:
    print(topic)

## LDA 시각화
vis = pyLDAvis.gensim_models.prepare(ldamodel, corpus, word_dict)

## 대시보드
pyLDAvis.enable_notebook()
pyLDAvis.display(vis)

  • 어린이, 병원 등 넥슨의 사회적 가치 창출을 확인할 수 있었고 상품권, 결제 등 결제 관련 토픽이 새롭게 확인되었음
    • #1. 주가, 게임, 개발, 시장, 사람, 메이플스토리, 유저
    • #2. 채용, 지원, 복지, 회사, 연봉, 면접, 프로젝트
    • #3. 기업, 회사, 교육, 복지, 어린이, 장애인, 재활
    • #4. 병원, 어린이, 재활, 복지, 모바일, 학원, 치료, 푸르메재단
    • #5. 상품권, 페이, 카드, 상품, 구매, 결제

3. Similary, 유사도 분석

세 번째로, 게임사별 유사도 분석을 진행해주었다.

앞선 워드클라우드 및 토픽모델링 분석 과정을 거치면서 비슷한 게임사, 매우 다른 게임사 등 게임 업계 내에서도 가치 창출 전략이나 중점 사업 등 비교 분석을 해보고자 하였다.

유사도 분석에서는 게임사 문서의 자카드 유사도와 코사인 유사도로 각각 분석하였다.

아래는 제목 column에 대한 자카드 유사도 분석 코드와 히트맵 시각화 자료이다.

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from collections import Counter
from kiwipiepy import Kiwi
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

## 형태소 분석기 초기화
kiwi = Kiwi()

def extract_important_words(text, n=1000):
    ## 텍스트를 입력받아 명사, 동사, 형용사, 외국어를 추출하고 빈도수를 계산하여 상위 n개를 반환
    tokens = kiwi.analyze(text)
    words = []
    for token_list in tokens:
        for token in token_list[0]:
            if token.tag in ['NNG', 'NNP', 'VV', 'VA' 'SL']:  ## 명사, 동사, 형용사, 외국어
                words.append(token.form)
    return Counter(words).most_common(n)

def get_top_words(df, n=1000):
    ## DataFrame의 title 컬럼에 대해 형태소 분석을 적용
    all_words = df['title'].apply(lambda x: extract_important_words(x, n))
    all_words = [word for sublist in all_words for word, count in sublist]
    top_words = Counter(all_words).most_common(n)
    return set(dict(top_words).keys())
    
## 자카드 유사도 함수 정의
def jaccard_similarity(set1, set2):
    intersection = len(set1 & set2)
    union = len(set1 | set2)
    if union == 0 : return 0
    return intersection / union

## 유사도 행렬 생성
news_dfs = [krafton_news_top_words, nexon_news_top_words, netmarble_news_top_words, ncsoft_news_top_words, riot_news_top_words]
blog_dfs = [krafton_blog_top_words, nexon_blog_top_words, netmarble_blog_top_words, ncsoft_blog_top_words, riot_blog_top_words]
news_sim_matrix = pd.DataFrame(index=range(1, 6), columns=range(1, 6))
blog_sim_matrix = pd.DataFrame(index=range(1, 6), columns=range(1, 6))

for i in range(5):
    for j in range(5):
        news_sim_matrix.iloc[i, j] = jaccard_similarity(news_dfs[i], news_dfs[j])
        blog_sim_matrix.iloc[i, j] = jaccard_similarity(blog_dfs[i], blog_dfs[j])

## 히트맵 생성
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 8))
sns.heatmap(news_sim_matrix.astype(float), annot=True, fmt=".2f", cmap='GnBu', 
            vmin=0.15, vmax=0.35, annot_kws={"size": 25}, ax=ax1,
            xticklabels=["Krafton", "Nexon", "Netmarble", "NCSoft", "Riot"], 
             yticklabels=["Krafton", "Nexon", "Netmarble", "NCSoft", "Riot"])
ax1.set_title("Jaccard Similarity Heatmap for News Titles")
sns.heatmap(blog_sim_matrix.astype(float), annot=True, fmt=".2f", cmap='YlGn',
            vmin=0.25, vmax=0.45, annot_kws={"size": 25}, ax=ax2,
            xticklabels=["Krafton", "Nexon", "Netmarble", "NCSoft", "Riot"], 
            yticklabels=["Krafton", "Nexon", "Netmarble", "NCSoft", "Riot"])
ax2.set_title("Jaccard Similarity Heatmap for Blog Titles")
plt.show()

  • 뉴스, 블로그 모두 크래프톤 - 엔씨소프트가 가장 유사한 경향
    • 크래프톤, 엔씨소프트 모두 글로벌 기업 시장 진출을 중점적으로 기획하고 있어 유사도가 높은 것으로 추측
  • 라이엇게임즈가 다른 게임사와 모두 유사하지 않은 경향
    • 라이엇게임즈 특성상 채용 빈도가 적고 상대적으로 E스포츠 관련 소식이 많아 상호 유사도가 낮은 것으로 추측

 

아래는 본문 column에 대한 코사인 유사도 분석 코드와 히트맵 시각화 자료이다.

# 텍스트 데이터를 TF-IDF 벡터로 변환하는 함수
def tfidf_transform(list_of_docs):
    vectorizer = TfidfVectorizer()
    tfidf_matrix = vectorizer.fit_transform(list_of_docs)
    return tfidf_matrix

## 유사도 행렬 생성
news_dfs = [krafton_news_top_words, nexon_news_top_words, netmarble_news_top_words, ncsoft_news_top_words, riot_news_top_words]
blog_dfs = [krafton_blog_top_words, nexon_blog_top_words, netmarble_blog_top_words, ncsoft_blog_top_words, riot_blog_top_words]

# 각 게임사의 title 데이터를 합친 뒤 TF-IDF 벡터로 변환
news_titles = [" ".join(words) for words in news_dfs]  # news_dfs는 단어 리스트의 리스트
blog_titles = [" ".join(words) for words in blog_dfs]

news_tfidf_matrix = tfidf_transform(news_titles)
blog_tfidf_matrix = tfidf_transform(blog_titles)

# 코사인 유사도 행렬 계산
news_cos_sim_matrix = cosine_similarity(news_tfidf_matrix)
blog_cos_sim_matrix = cosine_similarity(blog_tfidf_matrix)

# 히트맵 생성
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 8))
sns.heatmap(news_sim_matrix.astype(float), annot=True, fmt=".2f", cmap='YlOrBr', 
            vmin=0.4, vmax=0.65, annot_kws={"size": 25}, ax=ax1,
            xticklabels=["Krafton", "Nexon", "Netmarble", "NCSoft", "Riot"], 
             yticklabels=["Krafton", "Nexon", "Netmarble", "NCSoft", "Riot"])
ax1.set_title("Cosine Similarity Heatmap for News Contents")
sns.heatmap(blog_sim_matrix.astype(float), annot=True, fmt=".2f", cmap='OrRd',
            vmin=0.6, vmax=0.75, annot_kws={"size": 25}, ax=ax2,
            xticklabels=["Krafton", "Nexon", "Netmarble", "NCSoft", "Riot"], 
            yticklabels=["Krafton", "Nexon", "Netmarble", "NCSoft", "Riot"])
ax2.set_title("Cosine Similarity Heatmap for Blog Contents")
plt.show()

  • title에 비해 body column의 유사도가 전체적으로 높은 수치를 보이는 경향
    • 공통적으로 쓰이는 키워드가 많은 영향으로 추측
  • 넥슨과 넷마블이 유사한 경향
    • 넥슨과 넷마블은 모두 강력한 모바일 게임 포트폴리오를 가지고 있어 유사도가 높은 것으로 추측

 

 

이상으로 게임사 텍스트 분석 프로젝트 포스팅을 마치려고 한다.

다음 프로젝트에 대해서도 조만간 찾아오도록 하겠다.

 

 

[프로젝트] HR_면접자 정보 맞추기 프로젝트 (1)

논문 읽는 학회에 멤버로 참여해 매주 논문을 하나씩 읽어보면서 공부했던 시절에는 매주 블로그 소재가 하나씩 생겼는데 이 활동이 끝나니까 블로그 소재가 뚝 떨어졌네요.. ㅠㅠ 그래서 새로

dangingsu.tistory.com

 

 

728x90
반응형