[성동3기 전Z전능 데이터 분석가] DAY 25 _ 파이썬 실무 프로젝트_뉴스 데이터 전처리 / 번역, 감성분석 _ Vader/FinBERT
프로젝트 발표 시 고려사항
- 과정보다는 목적과 결과를 중심으로 이야기 (10장 내외)
- 분석을 나열하지 말고, 하나의 이야기로 전달
- 문제제기, 분석과정, 결과도출, 해결책 제시의 흐름
- 청중에게 필요한 정보 제공(ex 도메인 지식)
- 기타 노력을 보여주고 싶다면 Appendix 에 넣어두기
- 데이터, 코드 출처 꼭 남기기
To Do
- 데이터 전처리 (한👉영 번역)
- 뉴스 기사 감성분석
- 주식 + 뉴스 데이터 결합
- 가설 검증 계획
- PPT 역할 분배
데이터 전처리 _ 번역
전일 뉴스 데이터 수집이 완료된 후, 통합하여 전처리를 하니 총 약 7만개 정도였다. 예상보다 많이 수집되어 놀라웠다. 하지만, 아직 전처리는 끝나지 않았다. 감성분석을 하기 위해 뉴스 Summary 열의 내용을 영문으로 통일시켜야 했다. 영문으로 번역하는 코드를 GPT의 힘을 빌어 google 에서 지원하는 translator 라이브러리로 제작한 뒤, 800rows 를 테스트 해보았다. 시간은 약 30분 소요되었다. 한 사람이 통째로 번역코드를 돌리기에는 시간적 리스크가 컸다. 따라서, 인당 13,500rows 정도를 담당하여 동일한 번역코드로 돌렸다. 소요시간은 개인별 편차가 존재했지만, 난 대략 6시간이 걸렸다. 😶 살면서 처음으로 교육기관에서 집까지 코드가 돌아가고 있는 노트북을 조심스레 들고서 이동했다. 당시에는 이미 소요된 시간이 아까웠고, 과연 결과물이 언제 어떻게 나올지 궁금했다. (결과는 성공적..!)
import pandas as pd
from googletrans import Translator
# Colab에서 파일 업로드 후 경로 설정
input_file = "merged_data_3.csv" # 업로드한 파일 이름
output_file = "trans_54001_67387_file.csv" # 번역 결과를 저장할 파일 이름
# 데이터 읽기
df = pd.read_csv(input_file)
# 번역할 열 이름
columns_to_translate = ["summary"]
# Translator 초기화
translator = Translator()
# 특정 열 번역하기
def translate_text(text):
if pd.notnull(text): # 텍스트가 비어 있지 않을 경우 번역
try:
return translator.translate(text, src='ko', dest='en').text
except Exception as e:
print(f"Error translating text: {text}. Error: {e}")
return text # 번역 실패 시 원문 반환
return text # 비어 있는 경우 그대로 반환
# 각 열 번역 적용
for column in columns_to_translate:
print(f"Translating column: {column}")
df[column] = df[column].apply(translate_text)
# 결과 저장
df.to_csv(output_file, index=False, encoding='utf-8-sig')
print(f"번역 완료. 파일은 {output_file}로 저장되었습니다.")
특수 문자 제거
감성분석을 하기 전, 보다 퀄리티를 높이기 위한 전처리 작업으로 결측치 제거, 특수 문자 제거의 과정을 거쳤다.
import pandas as pd
# 데이터 불러오기
data = pd.read_csv("trans_13501~27000_복사본.csv")
# 결측치 처리: 결측치가 있는 행 전체 삭제
data.dropna(how="any", inplace=True)
# 특수문자 추출을 위한 정규표현식 처리
punctdf = data.copy()
punctdf['summary'] = punctdf['summary'].str.replace(pat=r'[.,0-9a-zA-Zㄱ-ㅣ가-힣]+', repl=r'', regex=True)
# 특수문자 목록 추출
list_a = []
for a in punctdf["summary"]:
list_a.append(a)
list_b = []
for b in list_a:
bb = set(b)
for c in bb:
list_b.append(c)
list_b = set(list_b)
# 특수문자 제거 함수 정의
punct = list_b
def clean_punct(text, punct):
for p in punct:
text = text.replace(p, " ")
return text.strip()
# 특수문자 제거 및 전처리 열 추가
clean_text = []
for s in data['summary']:
clean_text.append(clean_punct(s, punct))
data['전처리'] = clean_text # 기존 데이터 형식 유지 + 전처리 열 추가
# 파일 저장 (한글 깨짐 방지)
data.to_csv('감성분석_전처리_결과_1.csv', index=False, encoding='utf-8-sig')
print("전처리 완료 및 파일 저장 완료: 감성분석_전처리_결과_1.csv")
뉴스 기사 감성분석
번역을 마친 후 곧바로 감성분석에 대한 서치에 돌입했다. 다양한 라이브러리와 DL 모델이 존재했다. 난이도가 낮은 Rule-based, Statistical-based의 Textblob 부터 사용해보았고, 정확도는 주관적으로 봤을 때 신통치 않았다. 다음으로는 Lexicon-based, Rule-based 의 VADER 를 테스트해보았다. 긍정적/부정적 단어에 상당히 민감하게 반응하는 특징을 가졌고, 그래서 중립적인 결과보다는 양극화된 결과가 도출되었다. 이를 보완할 만한 대책으로 금융 도메인에 특화된 Huggingface 에서 지원하는 FinBERT 모델을 테스트 해보았다. 설계상 보수적으로 감정을 판단하여, 결과에서도 긍정/부정일 만한 기사들도 중립적 결과가 도출되었다.
VADER + FinBERT ?
그렇다면, 직관적인 VADER 와 금융도메인에 특화되며 보수적인 FinBERT 를 적절히 섞으면 더 나은 분석 결과가 나올 것이라는 아이디어가 떠올랐다. 아래는 최종적으로 뉴스 기사를 감성분석하기 위해 사용한 코드이다.
!pip install vaderSentiment
# 라이브러리 불러오기
import pandas as pd
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
from transformers import pipeline
# VADER 초기화
vader_analyzer = SentimentIntensityAnalyzer()
# FinBERT 초기화
finbert = pipeline("sentiment-analysis", model="yiyanghkust/finbert-tone")
# CSV 파일 불러오기
# '파일경로.csv'를 업로드한 CSV 파일 경로로 교체하세요.
file_path = 'VADER_FinBERT_TEST_Panasonic.csv'
data = pd.read_csv(file_path)
# 데이터프레임 컬럼 확인
print("컬럼 목록:", data.columns)
# '전처리' 열에 텍스트 데이터가 있다고 가정
# 필요한 경우, '전처리' 대신 텍스트가 포함된 열 이름으로 바꾸세요.
text_column = '전처리'
# 분석 결과를 저장할 리스트
sentiment_scores = []
sentiment_labels = []
# 감성 분석 수행
for text in data[text_column]:
# VADER 분석
vader_score = vader_analyzer.polarity_scores(text)
vader_compound = round(vader_score['compound'], 2) # 소수점 둘째 자리까지 반올림
# FinBERT 분석
finbert_result = finbert(text)[0]
finbert_label = finbert_result['label']
# 중립적이거나 명확한 어조를 가진 경우 VADER 또는 FinBERT를 신뢰하는 로직
if abs(vader_compound) <= 0.05:
# 중립적이면 FinBERT 사용
final_label = finbert_label
else:
# 명확한 어조이면 VADER 사용
if vader_compound > 0.05:
final_label = "Positive"
elif vader_compound < -0.05:
final_label = "Negative"
# 결과 저장
sentiment_scores.append(vader_compound)
sentiment_labels.append(final_label)
# 결과를 데이터프레임에 추가
data['감성점수'] = sentiment_scores
data['감성결과'] = sentiment_labels
# 결과 미리보기
print(data.head())
# 결과를 CSV로 저장
output_file_path = '감성분석_결과.csv'
data.to_csv(output_file_path, index=False, encoding='utf-8-sig')
print(f"감성 분석 결과가 '{output_file_path}'에 저장되었습니다.")
감성분석 결과, Positive 비율이 현저히 높았다. 팀원들과 공유하여 이야기를 나눠보니, 우리가 다루는 segment 가 관심도와 기대심리가 급증하여, 현실적으로 가능한 시나리오라는 결론을 지었다. 비율적으로 밸런스가 좋았으면 했지만, 가설검증을 통해 이 치우친 비율이 영향을 끼치게 될 지를 확인해봐야겠다.
주식 + 뉴스 데이터 결합
드디어, 주식과 뉴스의 전처리 프로세스가 완료되었다. 이제 이 두 데이터셋을 결합하면 되는데, 문제는 주식 같은 경우 5개 기업의 주가를 다루고 뉴스는 하루에도 수십건의 기사가 포함될 수 있다는 점이었다. 날짜를 기준으로 결합하기로는 했지만, 결합 결과가 쉽사리 상상되지 않았다. 팀원의 도움으로 날짜를 기준으로 데이터셋을 merge 했다.
300,000 rows .. ?
이게 어찌된 일인가.. 뉴스기사는 분명 약 7만 행 정도였는데, 주가데이터와 결합 후 상상치 못한 30만 행으로 늘어나게 되었다. 기사 하나 당 5개 기업 주가 데이터가 결합되어 기존 대비 5배 정도 불려진 것이다. 데이터 다루기가 더 어려워진 듯 하다.🥲
가설 검증 시 적절히 추출하고 그룹화 및 정렬하여 용량을 줄여야 되겠다. 이제 가설검증 시간이 다가왔다. 조금만 더 힘내자 !