0. 목표


1-1. 패키지 읽기

In [1]:
# import package
import numpy as np
import pandas as pd
import collections, time, os, re
from itertools import chain
from collections import Counter
from nltk import collocations, everygrams, word_tokenize
from tqdm import tnrange, tqdm_notebook

# mecab 형태소분석 명사만
from konlpy.tag import Mecab
mecab = Mecab().nouns

# Twitter 형태소분석 명사만
from konlpy.utils import pprint
from konlpy.tag import Twitter
twitter = Twitter().nouns

# 시각화 라이브러리 한글폰트 설정
from matplotlib import font_manager, rc, pyplot as plt
import seaborn as sns
%matplotlib inline
font_name = font_manager.FontProperties(fname="/usr/share/fonts/truetype/nanum/NanumBarunGothicBold.ttf").get_name()
rc('font', family=font_name)
plt.rcParams["font.family"]
Out[1]:
['NanumBarunGothic']

1-2. 변수 설정

In [2]:
# ngram 설정하기
# 1어절에서 1어절로 세팅
ngram_start = 1
ngram_end = 3

# 히트맵 N x N matrix의 N 설정하기
heatmap_number = 20

# 히트맵 색상 설정
# https://matplotlib.org/users/colormaps.html
colorset = "YlOrRd"

# 형태소분석기 설정 ( mecab , twitter 둘중 하나 선택, mecab이 빠름)
pos_tool = twitter

2. 데이터 전처리

In [3]:
# pandas로 엑셀 파일 읽기
data = pd.read_excel('Keyword 빈도(Total Data 기준).xlsx', sheetname='Sheet3', header=None)
print("읽은 데이터 행 수 : ", len(data))
data["content"] = data[0].astype(str)
data["content"] = data["content"].str.replace("^"," ")

# 문의글 칼럼의 특수문자를 없애주고 preprocess_question 칼럼에 추가
data['preprocess_question']=[ re.sub('[\{\}\[\]\/?.,;:|\)*~`!^\-_+<>@\#$%&…▶◆\\\=ⓒ\(\'\"]','',i).strip() for i in data["content"] ]

# 2 어절을 추출해서 카운트 하는 방식 (3어절 이상도 가능)
print("ngram_start : ", ngram_start)
print("ngram_end : ", ngram_end)
ngram_data_question = data['preprocess_question'].apply(lambda x: [' '.join(ng) for ng in everygrams(word_tokenize(x), ngram_start, ngram_end)])

# ngram_question 칼럼에 추가
data['ngram_question'] = ngram_data_question

# 형태소 분석 칼럼 추가
data['pos'] = data['preprocess_question'].apply(pos_tool)
def joinlist(list):
    return " ".join(list)

# 형태소 분석 결과 한문장으로된 칼럼 추가
data['string_pos'] = data['pos'].apply(joinlist)


# 형태소 분석 명사만 추출 후 2 어절 카운트 (3어절 이상도 가능)
ngram_data_question_pos = data['string_pos'].apply(lambda x: [' '.join(ng) for ng in everygrams(word_tokenize(x), ngram_start, ngram_end)])

# ngram_question 칼럼에 추가
data['ngram_question_pos'] = ngram_data_question_pos

data = data.iloc[:,1:]
data.head(3)
읽은 데이터 행 수 :  9766
ngram_start :  1
ngram_end :  3
Out[3]:
content preprocess_question ngram_question pos string_pos ngram_question_pos
0 시각장애인보도블럭 안전보안관 시각장애인보도블럭 안전보안관 [시각장애인보도블럭, 안전보안관, 시각장애인보도블럭 안전보안관] [시각장애인, 보도, 블럭, 안전, 보안관] 시각장애인 보도 블럭 안전 보안관 [시각장애인, 보도, 블럭, 안전, 보안관, 시각장애인 보도, 보도 블럭, 블럭 안...
1 벚꽃나무 안전보안관 벚꽃나무 안전보안관 [벚꽃나무, 안전보안관, 벚꽃나무 안전보안관] [벚꽃, 나무, 안전, 보안관] 벚꽃 나무 안전 보안관 [벚꽃, 나무, 안전, 보안관, 벚꽃 나무, 나무 안전, 안전 보안관, 벚꽃 나무 ...
2 정비 요청 안전보안관 파손도로 파손도 정비 요청 안전보안관 파손도로 파손도 [정비, 요청, 안전보안관, 파손도로, 파손도, 정비 요청, 요청 안전보안관, 안전... [정비, 요청, 안전, 보안관, 파손, 도로, 파손] 정비 요청 안전 보안관 파손 도로 파손 [정비, 요청, 안전, 보안관, 파손, 도로, 파손, 정비 요청, 요청 안전, 안전...

3. heatmap용 데이터 설정

In [4]:
heatmap_raw_data = data.ngram_question_pos.tolist()

# 카운터 사용
d = Counter(list(chain(*list(heatmap_raw_data))))
df_q = pd.DataFrame.from_dict(d, orient='index').reset_index()
df_q = df_q.sort_values(by=0, ascending=False)
print(ngram_start, "~", ngram_end, "어절 ngram 수", len(df_q))

print(heatmap_number, "x", heatmap_number, "히트맵 설정")

number = df_q['index'][:heatmap_number].tolist()
heatmap_word = []
heatmap_cnt = []

# 히트맵용 카운트
for i in tqdm_notebook(range(len(number))):
    heatmap_cnt = []
    
    for j in range(len(number)):
        if number[i] == number[j]:
            heatmap_cnt.append(0)
            break
        else:
            heatmap_cnt.append(len(data[(data['string_pos'].str.contains(number[i])) & (data['string_pos'].str.contains(number[j]))]))
    heatmap_word.append(heatmap_cnt)

# 히트맵용 카운트를 dataframe으로 변경
heatmap_data = pd.DataFrame(heatmap_word, index=number, columns=number)
heatmap_data = heatmap_data.fillna(0)
heatmap_data[heatmap_data.columns] = heatmap_data[heatmap_data.columns].astype(int)
heatmap_data.iloc[:heatmap_number,:heatmap_number]
1 ~ 3 어절 ngram 수 103361
20 x 20 히트맵 설정

Out[4]:
안전 파손 도로 차량 인도 불법 사고 주차 요청 통행 블럭 보수 설치 보도 횡단보도 공사 정비 운영 보행자
안전 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
파손 323 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
도로 207 381 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
차량 212 169 150 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
인도 172 171 38 73 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
불법 138 9 47 132 33 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
사고 335 155 145 217 63 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0
주차 121 30 59 181 78 221 77 0 0 0 0 0 0 0 0 0 0 0 0 0
요청 120 150 119 86 38 114 70 29 0 0 0 0 0 0 0 0 0 0 0 0
통행 126 89 96 234 102 69 102 86 43 0 0 0 0 0 0 0 0 0 0 0
블럭 102 191 26 11 170 1 50 10 31 38 0 0 0 0 0 0 0 0 0 0
보수 162 146 89 74 66 0 71 14 104 47 42 0 0 0 0 0 0 0 0 0
132 103 77 76 62 49 58 52 50 34 42 75 0 0 0 0 0 0 0 0
설치 152 32 55 110 52 61 116 46 70 67 9 15 38 0 0 0 0 0 0 0
보도 197 247 51 89 115 41 125 63 52 57 453 64 111 68 0 0 0 0 0 0
횡단보도 88 74 30 80 20 41 70 52 18 30 30 17 74 61 477 0 0 0 0 0
공사 119 33 57 71 50 17 71 31 29 60 13 43 17 38 23 12 0 0 0 0
정비 68 136 92 27 37 7 15 9 202 36 36 4 32 12 55 14 9 0 0 0
운영 3 154 115 0 26 81 0 15 1 2 3 6 1 1 2 0 2 6 0 0
보행자 177 79 61 105 58 21 102 34 33 70 58 31 19 57 122 68 24 21 0 0
In [5]:
fig, ax = plt.subplots(figsize=(heatmap_number/1.5, heatmap_number/2))
sns.heatmap(heatmap_data.iloc[:heatmap_number,:heatmap_number], cmap=colorset, annot=True, fmt="d", linewidths=.5)
Out[5]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f6735d1f588>

4. 결과 저장, 기타

In [6]:
# 특정단어를 포함한 ngram만 추출
df_q = df_q.rename(index=str, columns={"index": "ngram", 0: "count"})

# 전체 데이터 저장
df_q.to_csv("ngram.csv")

df_q.head(20)
Out[6]:
ngram count
3 안전 1934
20 파손 1755
21 도로 1113
416 차량 1061
165 인도 900
460 불법 853
96 사고 816
167 주차 690
19 요청 667
471 통행 637
2 블럭 626
73 보수 609
614 533
442 설치 502
1 보도 496
95 횡단보도 494
227 공사 475
18 정비 475
629 운영 463
36 보행자 454
In [7]:
df_q[df_q["ngram"].str.contains("파손")][:20].plot(x='ngram', y='count', kind='barh')
Out[7]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f66fd566940>
In [8]:
df_q[df_q["ngram"].str.contains("파손")].head()
Out[8]:
ngram count
20 파손 1755
26 도로 파손 270
2900 파손 운영 154
1668 파손 안전 125
1667 블럭 파손 116
In [9]:
# 파손만 저장
df_q[df_q["ngram"].str.contains("파손")].to_csv("ngram_word.txt")