
공부하기 싫어서 해보는 아주아주 간단한 데이터 분석ㅋㅋㅋ
BTS 노래 듣다가 삘받아서 해봤다.
한글 가사보다 영어로 된 가사가 전처리하기 쉬울 것 같아 Permission to dance로 하기로 했다.
Permission to dance 는 노래 제목이 말하듯이 '현실의 벽에 부닥치고 고단한 하루를 보낸 모두에게 "춤은 마음가는 대로, 허락 없이 마음껏 춰도 된다"' 라는 메시지를 전한다고 한다.
과연 가사 속에 이러한 메시지가 들어있는지 확인해보자!
1. 데이터 수집
구글에서 'permission to dance 가사' 검색. 가사 복사하기 후 메모장에 저장함
2. 데이터 불러오기 및 라이브러리 설치
Colab 환경에서 진행했다.
먼저, 라이브러리를 불러왔다. 나는 필요한 패키지를 한 곳에 몰아 넣는것을 좋아하는데, 한 눈에 관리하기 쉽기 때문이다!
import pandas as pd
import numpy as np
from wordcloud import WordCloud # 워드클라우드
from collections import Counter
import pickle
import nltk
from nltk.corpus import stopwords
import re
nltk.download('all')
import matplotlib.pyplot as plt
from PIL import Image
from os import path
FONT_PATH = '/content/ARIALUNI.TTF'
txt 파일 불러오기.
파이썬 내장 함수 open을 사용했다.
f.open('파일.txt', 'r')
로 파일을 읽기 모드로 연 후 readlines()
을 사용해서 파일의 모든 줄을 읽어온다.
# open으로 txt 파일 읽기 ('r' : 읽기모드)
file = open("permissiontodance.txt", "r")
ptd = file.readlines()
print(ptd)
file.close()
3. 전처리
3-1. 리스트 -> 문자열
위 함수 open으로 txt 파일을 읽으면 리스트로 저장되는데, 이후 단계에서 리스트보다 문자열로 사용하기 때문에 미리 문자열로 변환했다.
# 1. 리스트를 문자열로 변환
ptd_str = ' '.join(map(str, ptd))
print(ptd_str)
3-2. 불필요한 심볼 없애기
txt에 'Cause when we fall, we know how to land 이렇게 ' 나 , 이런 것들이 존재하기 때문에 다 지워준다.
# 2. 불필요한 심볼 없애기
cleaned_ptd = re.sub(r'[^\.\?\!\w\d\s]','',ptd_str)
print(cleaned_ptd)
3-3. 대문자 -> 소문자
가사에 대문자를 소문자로 바꿔준다.
이 단계가 꼭 필요한 건지 모르겠지만, 항상 이렇게 바꿔왔어서 이번에도 바꿔줬다.
통일성 때문인가..?
>> 찾아보니까 'LOVE'와 'love'를 다르게 취급하듯이 데이터의 복잡성을 낮추기 위해 필요하다고 함
# 3. case conversion 대문자 -> 소문자
cleaned_ptd = cleaned_ptd.lower()
3-4. 워드 -> 토큰
단어들을 다 쪼갤 시간. 한국어와는 다르게 영어는 단어 쪼개기가 쉽다.
한국어는 교착어라 어간과 접사를 다 쪼개야 하고 띄어쓰기 규칙도 일정하지 않아서 토큰화시키기 어려운데, 영어는 단어의 문법적 기능이 비교적 일정하다.
영어로된 문자열들을 분석을 위해 일단 작은 단위로 쪼갠다. (토큰화)
토큰 생성 함수 word\_tokenize
는 문자열을 입력받아 토큰 문자열의 리스트를 출력해준다.
# 4, word tokenization 워드 -> 토큰
word_tokens = nltk.word_tokenize(cleaned_ptd)
print(word_tokens)
print(len(word_tokens))
print(set(word_tokens))
참고로, 토큰화된 단어 총 개수는 401개였는데, 중복을 다 제거하면 130개 밖에 안된다.
약 1/4이 같은 단어로 되어있다. 생각보다 단어가 다양하지 않은 듯..
3-5. 품사 분리
쪼개진 토큰들이 어떤 품사를 가지고 있는지 알아야 한다.
nltk.tag
의 pos_tag
를 이용하면 단어 토큰을 자동으로 품사를 부착해 준다.
# 5. POS tagging 품사 분리 `pos_tag()`
tokens_pos = nltk.pos_tag(word_tokens)
print(tokens_pos)
이때, 튜플 2번째에 나온 것이 품사 태그인데 내가 가지고 갈 품사를 정해야 한다.
- NN : 명사
- VB : 동사
- JJ : 형용사
기타 품사 정보는 아래 링크에서 확인할 수 있다.
https://www.ling.upenn.edu/courses/Fall_2003/ling001/penn_treebank_pos.html
Penn Treebank P.O.S. Tags
31. VBP Verb, non-3rd person singular present
www.ling.upenn.edu
3-6. 명사, 형용사, 동사 추출
일단 품사별로 따로 리스트를 담아줬다. 한꺼번에 담아도 되긴 하는데, 불문자 제거도 해야 되고 품사별로 어떤 단어가 있는지도 궁금해서 그냥 따로 담아줌.
# 6. 명사, 형용사, 동사 추출
NN_words = []
JJ_words = []
VB_words = []
for word, pos in tokens_pos:
if 'NN' in pos:
NN_words.append(word)
if 'JJ' in pos:
JJ_words.append(word)
if 'VB' in pos:
VB_words.append(word)
print(NN_words)
print(JJ_words)
print(VB_words)
3-7. Lemmatization (원형 찾기)
토큰 단어들이 모양은 다른데 같은 의미를 가지는 경우가 있다. 그래서 여러 단어들을 사전형으로 통일하는 과정이다.
이 글을 쓰다보니까 이 단계를 품사 태깅 단계 전에 했어야 한 듯 싶다. 좀 더 정확한 원형을 찾을 수 있을 것 같은데..
그래도 어차피 리스트들을 하나로 합칠 생각이기도 하고, 불문자도 제거할 생각이라 그냥 진행했다.
# 7. Lemmatization(원형 찾기)
wnl = nltk.WordNetLemmatizer()
lem_NN_words = []
lem_JJ_words = []
lem_VB_words = []
for word in NN_words:
new_word = wnl.lemmatize(word)
lem_NN_words.append(new_word)
for word in JJ_words:
new_word = wnl.lemmatize(word)
lem_JJ_words.append(new_word)
for word in VB_words:
new_word = wnl.lemmatize(word)
lem_VB_words.append(new_word)
print(lem_NN_words)
print(lem_JJ_words)
print(lem_VB_words)
3-8. 불문자 제거
분석하는 데 도움이 안되는 단어는 제거해야 한다.
danananananana, mmm, yeah 이런 쓸모 없는 단어들이 있기도 하고
youll, dont 같은 잘못 분류된 단어나 딱히 의미 없는 단어들도 있기 때문이다.
일단 nltk에서 제공하는 불용어사전을 이용해서 일반적으로 제거하는 불용어를 일차로 제거한 뒤,
추가적으로 내가 지우고 싶은 단어들을 지운다. (이부분은 주관적 판단이 들어가는 부분임)
# 8. Stopwords removal
stopwords_list = stopwords.words('english') # nltk에서 제공하는 불용어사전 이용
# 명사 불용어 제거
unique_NN_words = set(lem_NN_words)
final_NN_words = lem_NN_words
for word in unique_NN_words:
if word in stopwords_list:
while word in final_NN_words:
final_NN_words.remove(word)
# 형용사 불용어 제거
unique_JJ_words = set(lem_JJ_words)
final_JJ_words = lem_JJ_words
for word in unique_JJ_words:
if word in stopwords_list:
while word in final_JJ_words:
final_JJ_words.remove(word)
# 동사 불용어 제거
unique_VB_words = set(lem_VB_words)
final_VB_words = lem_VB_words
for word in unique_VB_words:
if word in stopwords_list:
while word in final_VB_words:
final_VB_words.remove(word)
# 추가 불용어 정의
custom_NN_stopwords = ['danananananana', 'aint','youll','yeah','mmm','let']
unique_NN_words2 = set(final_NN_words)
for word in unique_NN_words2:
if word in custom_NN_stopwords:
while word in final_NN_words:
final_NN_words.remove(word)
print(final_NN_words)
# 추가 불용어 정의
custom_JJ_stopwords = ['ya', 'aint','hey']
unique_JJ_words2 = set(final_JJ_words)
for word in unique_JJ_words2:
if word in custom_JJ_stopwords:
while word in final_JJ_words:
final_JJ_words.remove(word)
print(final_JJ_words)
# 추가 불용어 정의
custom_VB_stopwords = ['danananananana','get','seems','elton','wan','got','dont','till']
unique_VB_words2 = set(final_VB_words)
for word in unique_VB_words2:
if word in custom_VB_stopwords:
while word in final_VB_words:
final_VB_words.remove(word)
print(final_VB_words)
(살짝 NGD 였다..)
3-9. 리스트 하나로 합치기 - 전처리 끝!
# 9. 리스트 합치기
final_words = []
final_words = final_NN_words + final_JJ_words + final_VB_words
4. 빈도 분석
실제로 빈도를 이용하진 않았지만, 그냥 해본 분석
전처리가 끝난 뒤의 단어들 중 겹치는 단어가 생각보다 많지 않았다.
# 빈도 분석
counter = Counter(final_words)
print(counter)
k = 20
print(counter.most_common(k)) # 빈도 수 기준 상위 k개 단어 출력
cause
는 8개나 나왔닼ㅋㅋㅋ
Cause when we fall we know how to land ~ 여기랑
Cause we don't need permission to dance 이부분 반복 등등 때문에 많은듯
'춤은 마음가는 대로, 허락 없이 마음껏 춰도 된다' 라고 주장하고 있는데, 그 이유는 ~ 때문이다. 라고 설명하고 있어서 'Cause'가 많이 나온 것으로 보인다.
5. 워드클라우드
`WordCloud` 를 이용하면 워드클라우드를 쉽게 만들 수 있다.
# 워드클라우드
text = ''
for word in final_words:
text = text +' '+word
wc = WordCloud(max_font_size=60, background_color="white").generate(text)
plt.figure()
plt.imshow(wc, interpolation='bilinear')
plt.axis('off')
plt.show()
이건 좀 심심하니까 모양있는 워드클라우드를 만들어보자.
먼저 bts 로고를 다운받았다. 이것도 구글에서 검색하면 많이 나옴.
jpg로 저장한 다음 image.open
을 이용해서 이미지 파일을 불러온다.
plt.imshow()
는 이미지 출력할 때 쓰임.
그다음 이미지가 흰 배경이 많아서 내가 쓰고 싶은 부분만 사용하기 위해 자른다.
사진.size
를 통해 해당 사진의 픽셀 사이즈를 알 수 있고, .crop(left_down, left_up, right_down, right_up
) 을 이용해 내가 사용하고 싶은 부분만 지정
그 뒤, 워드클라우드가 들어갈 부분을 mask를 딴다.
# 마스크 만들기
icon = Image.open('bts.jpg')
plt.imshow(icon)
# print(icon.size)
crop_icon = icon.crop((115,60,310,390))
plt.imshow(crop_icon, cmap=plt.cm.gray, interpolation='bilinear')
# crop_icon.save('crop_bts_logo.PNG')
mask = Image.new("RGB", crop_icon.size, (255,255,255))
mask.paste(crop_icon)
mask = np.array(mask)
# 워드클라우드
def color_func(word, font_size, position,orientation,random_state=None, **kwargs):
return("hsl({:d},{:d}%, {:d}%)".format(np.random.randint(212,313),np.random.randint(26,32),np.random.randint(45,80)))
text = ''
for word in final_words:
text = text +' '+word
wc = WordCloud(max_font_size=60,
relative_scaling=.5,
mask=mask,
width=800, height=600,
font_path = FONT_PATH,
background_color = 'black').generate(text)
plt.figure(figsize=(8,8))
plt.imshow(wc.recolor(color_func = color_func),interpolation="bilinear")
plt.axis('off')
plt.show()
# 내컴퓨터에 저장
wc.to_file("permission_to_dance_bts.png")
최종 결과!!
이 노래는 '현실의 벽에 부닥치고 고단한 하루를 보낸 모두에게 "춤은 마음가는 대로, 허락 없이 마음껏 춰도 된다"' 라는 메시지를 포함하고 있다고 했는데, 확실히 cause, need, permission이런 단어들이 눈에 띄고, dance, music 단어들이 많이 나왔다.
또 going이나 talk, walk처럼 긍정적인 단어들이 많이 보였다.
워드클라우드는 해당 단어의 빈도만 분석하는 것이기 때문에 이 분석만으로 해당 메시지를 완전히 알 순 없지만, BTS가 어떤 단어를 강조하고 싶은지 알 수 있었다.
다음엔 단어 간 연관성 분석이나, 감성 분석 등을 더해보면 더 많은 결과가 나오지 않을까?