심심해서 하는 블로그 :: '워드클라우드' 태그의 글 목록

rGithub > https://github.com/ssooni/data_mining_practice

- 자세한 소스코드는 Github에 올렸습니다.

- 데이터 파일도 올려 드리고 싶지만 실제 카카오톡 대화내용이라 일부분만 올렸습니다.

- 소스코드는 짬짬히 업로드할 예정입니다.




카카오톡 시리즈의 2번째로 워드클라우드를 만들어 보았습니다.

이전 시간에서 만들었던 명사 추출 결과를 사용하여 워드클라우드를 그릴 것입니다.

 

1. 단어의 빈도 수를 측정  

워드클라우드에서 글자 크기를 정하는 기준을 저는 단어의 빈도수로 결정하였습니다. 

특정 컬럼을 그룹으로 하여 집계하는 방법은 pandas에서 간단하게 구현이 가능합니다.


mwordcloud.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def draw_wordcloud(kkma_result):
    # List로 되어있는 열을 Row 단위로 분리
    tokens = pd.DataFrame(kkma_result["token"].apply(lambda x: ast.literal_eval(x)).tolist())
 
    tokens["Date"= kkma_result["Date"]
    tokens["Speaker"= kkma_result["Speaker"]
    tokens["timetype"= kkma_result["timetype"]
    tokens["time"= kkma_result["time"]
    tokens["contents"= kkma_result["contents"]
    
    tokens = tokens.set_index(["Date""Speaker""timetype""time""contents"])
    tokens = tokens.T.unstack().dropna().reset_index()
 
    tokens.columns = ["Date""Person""time_type""time""sntc""index""token"]
    print(tokens.head())
 
    # 빈도수 집계
    summary = tokens.groupby(["token"])["index"].count().reset_index()
    summary = summary.sort_values(["index"], ascending=[False]).reset_index(drop=True)
 
    """ 이하 생략 """ 
cs


모든 소스코드는 GitHub를 참조바랍니다.


제가 정의한 draw_wordcloud()는 집계를 하고 빈도수가 많은 순서대로 정렬한 후 워드클라우드를 생성합니다.


line 2-15

시리즈 1의 결과를 보면 token 컬럼 안에 리스트로 토큰들을 저장하도록 구성되어 있습니다. 


이렇게 구성되어 있으면 분석하기 상당히 불편하므로 리스트 안에 있는 원소를 풀어서 새로운 행을 구성하는 것이 line2 - 14 입니다.

만약 시리즈를 이어서 하는 것이 아니라면 line 2-14번은 생략하셔도 됩니다.


line 18 - 19

pandas.DataFrame의 groupby는 특정한 컬럼을 그룹으로 묶어줍니다.

그리고 그룹을 대상으로 min(), max(), count()등의 집계함수를 제공합니다. 

이 경우에는 모든 구간에 대하여 token을 기준으로 발생한 빈도수를 집계하였습니다. 

순위를 측정하기 위해서 집계한 결과를 내림차순으로 정렬까지 완료합니다.


2. 워드클라우드  

데이터과학에서 시각화는 생각보다 큰 비중을 차지하는 분야입니다. 

어떻게 보여줘야 분석에 용이하고 데이터 분석결과를 받아 드리는 사람들이 쉽게 받아 드릴수 있기 때문입니다.

측정한 빈도수를 기반으로 Line나 막대 그래프도 그릴 수 있습니다만, 몇 번 나왔는지에 큰 의미를 두지 않는다면 빈도수는 TMI가 될 수 있습니다.

키워드에만 집중하기 위한 시각화 표현 기법으로 주로 워드클라우드를 사용합니다.

그리고 Python의 WordCloud 모듈은 개발자들에게 쉽고 빠른 방법으로 워드클라우드를 생성하는 것을 도와줍니다.


>> pip install wordcloud


워드클라우드 모듈을 설치가 완료되었다면 이제 사용하는 소스 코드를 작성해 봅시다.




mwordcloud.py

1
2
3
4
5
6
7
8
def draw_wordcloud(kkma_result):
 
    """ 상단 생략 """
 
    wc = WordCloud(font_path='./font/NanumBrush.ttf', background_color='white', width=800, height=600).generate(" ".join(summary["token"]))
    plt.imshow(wc)
    plt.axis("off")
    plt.show()
cs


line 4

WordCloud의 generate() 함수는 공백으로 분리된 문자열 리스트를 받습니다.

그리고 가장 첫 번째의 단어를 가장 크게, 가장 마지막 단어를 가장 작게 표현합니다.

앞서 빈도 수를 기준으로 token을 정렬하였기 때문에 가장 많은 빈도 수가 나오는 문자가 가장 크게 나올 것입니다. 

아래 그림처럼 말이죠.


 

카카오톡은 이모티콘과 사진, 동영상을 전송한 채팅 기록에 대하여는 (이모티콘) 이런 식으로 저장하니까 이모티콘과 사진이 압도적으로 많이 나오는 걸 볼 수 있습니다.


3. 특정 단어를 제외하자  

정말 직업으로 이모티콘을 개발하는 사람끼리 얘기하는 것이라면 이모티콘 단어가 큰 의미로 받아 지겠지만, 그냥 개발자와 그 여자친구간에 얘기에서 이모티콘은 큰 의미가 있는 단어는 아닙니다.

이번 단계에서는 노이즈라고 생각하는 단어들을 제외하는 과정을 진행해서 유효한 단어들만 보여주고 싶습니다.


mwordcloud.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def get_except_keyword(filename):
    keyword_list = list()
    with open(filename, encoding='utf-8') as f:
        for keyword in f.readlines():
            keyword_list.append(keyword.strip())
    print(keyword_list)
    return keyword_list
 
def draw_wordcloud(kkma_result):
 
    """ 상단 생략 """
    
    # 특정 단어 필터링
    except_keyword = get_except_keyword("./raw_data/except_word.txt")
    summary = summary[summary["token"].apply(lambda x: x not in except_keyword)]
    summary = summary[summary["token"].apply(lambda x: len(x) > 1)]
 
    wc = WordCloud(font_path='./font/NanumBrush.ttf', background_color='white', width=800, height=600).generate(" ".join(summary["token"]))
    plt.imshow(wc)
    plt.axis("off")
    plt.show()
 
cs



./raw_data/except_word.txt 에는 아래와 같이 제외하고자 하는 단어들의 리스트가 저장 되어있습니다.


./raw_data/except_word.txt


line 14 - 16

get_except_word() 함수에서 해당 파일을 읽어서 리스트 형태로 제외 문자열을 가지고 있습니다.

line 15에서 해당 문자열 리스트 안에 token이 포함되어 있으면 제외하는 로직이 적용됩니다.

그리고 추가적으로 line 16에서 token의 글자 수가 1개인 경우도 제외하였습니다.



아까보다는 저한테는 필요한 것이 나오긴 했지만, 좀 더 필터링을 한다면 더 좋을 거 같아요.

근데 사각형 너무 식상하지 않나요?


4. 워드클라우드 Mask 적용

Mask를 적용하면 워드클라우드가 이미지에 따라서 맞춰서 단어를 배열해 줍니다. 

바탕색이 투명한 PNG 파일을 하나 준비합니다.

저는 여자친구가 좋아하는 데덴네 이미지를 하나 준비하였습니다.


mwordcloud.py

1
2
3
4
5
6
7
8
9
10
11
12
13
from PIL import Image
 
def draw_wordcloud(kkma_result):
 
    """ 상단 생략 """
    
    denne_mask = np.array(Image.open("./font/denne.png"))
 
    wc = WordCloud(font_path='./font/NanumBrush.ttf', background_color='white', width=800, height=600, mask=denne_mask).generate(" ".join(summary["token"]))
    plt.imshow(wc)
    plt.axis("off")
    plt.show()
 
cs

 

line 7

마스크로 사용할 이미지를 읽어서 Numpy.Array로 저장합니다. 


line 9

WordCloud 생성자에 mask 파라미터에 line 7에 만들어 둔 마스크를 넣어 줍니다.

 


실행한 결과입니다. 

원하는 이미지를 사용해서 적용해 보는 것이 좋을 거 같아요.

다음 시리즈는 Word2Vec을 적용해본것을 올려 볼까 합니다.


전체적인 소스코드는 GitHub를 참조하시고, 궁금한 사항이 있으시면 댓글 달아 주시면 최대한 빨리 답변드리겠습니다.


긴 글 읽어주셔서 감사합니다.

공감버튼은 작성자에게 큰 동기부여가 됩니다.

,

이클립스와 R의 연동은 http://ssoonidev.tistory.com/10 을 참고해주세요


     


1. 준비물 

  1) 카카오톡 대화내용 File

      컴퓨터용 카카오톡 메신저에서 분석하고자하는 대화방에 들어가서 [Ctrl + S]를 누르면 

      대화내용을 Text 파일로 저장할 수 있다. 파일명은 편의상 kakao_temp.txt라고 하였다.


   2) R 스크립트

      주의해야할 것은 아무리 함수가 길더라도 줄바꿈을 하면 안된다. 

      이거 때매 결과가 안 나와서 뻘짓 정말 많이 했다 ㅠㅠ 이름은 form.R로 저장했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
library(KoNLP)
library(wordcloud)
library(RColorBrewer)
library(stringr)
useSejongDic()
 
data1 <- readLines("kakao_temp.txt", encoding = "UTF-8")
data1 <- gsub("손찬호""", data1) # 대화명1
data1 <- gsub("여보♥""",data1)   # 대화명2
data1 <- str_replace_all(data1, "[^[:alpha:]]"""#특수문자 Bye~~ 
data1 <- str_replace_all(data1, "[A-Za-z0-9]""")  #영문, 숫자도 Bye~~
data1 <- gsub("ㅋ""", data1)
data1 <- gsub("ㅠ""", data1)
data1 <- gsub("ㅜ""", data1)
data1 <- gsub("ㅎ""", data1)
data1 <- gsub("오전""", data1)
data1 <- gsub("오후""", data1)
data1 <- gsub("이모티콘""", data1)
data1 <- gsub("월요일""", data1)
data1 <- gsub("화요일""", data1)
data1 <- gsub("수요일""", data1)
data1 <- gsub("목요일""", data1)
data1 <- gsub("금요일""", data1)
data1 <- gsub("토요일""", data1)
data1 <- gsub("일요일""", data1)
data1 <- gsub("년""", data1)
data1 <- gsub("월""", data1)
data1 <- gsub("일""", data1)
data1 <- gsub("음성메세지""", data1)
data1 <- gsub("사진""", data1)
data1 <- gsub("프렌즈팝""", data1)
write(data1,"kakao_1.txt"
 
data2 <- readLines("kakao_1.txt")
data2 <- sapply(data1, extractNoun, USE.NAMES = F)
 
data3 <- unlist(data2)
data3 <- Filter(function(x){nchar(x)>=2 && nchar(x) <= 4}, data3)
 
wordcount <- table(data3)
<- head(sort(wordcount, decreasing = T), 100
write(a,"kakao_count.txt")
 
palete <- brewer.pal(8"Set2")
jpeg(filename = "cloud.jpg", width = 1000, height = 1000)
wordcloud(names(a), freq = a, scale=c(20,2), rot.per = 0.25, min.freq = 1, random.order = F, random.color = T, colors = palete)
dev.off()
cs


   3) 이클립스로 프로젝트를 생성하고 해당 자바 프로젝트 폴더 안에  준비물을 넣어 둔다.

     



2. REnginManager.java

   R 스크립트를 읽어 실행하는 클래스이다. 

   Rengine의 멤버 함수 eval(String text)에서 R스크립트 내용을 수행한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import java.io.BufferedReader;
import java.io.FileReader;
 
import org.rosuda.JRI.Rengine;
 
public class REngineManager {
    
    /**
     * @class R스크립트 파일을 REngine으로 실행하는 클래스
     */
 
    private Rengine re;
    private boolean isRunning = false;
    private enum SCRIPT_PROCESSING {sucess, fail};
    private SCRIPT_PROCESSING state;
    
    public REngineManager(){
        
    }
 
    public boolean isSucessful(){
        if(state == SCRIPT_PROCESSING.sucess)
            return true;
        else
            return false;
    }
    
    public boolean isRunning(){
        return isRunning;
    }
    
    public void createREngine(){
        String[] Rargs = {"--no-save"};
        re = new Rengine(Rargs, falsenull);
        System.out.println("Create R Engine...");
        
        if(!re.waitForR()){
            System.out.println("Loading R engine was failed");
            isRunning = false;
            return;
        }
        isRunning = true;
        System.out.println("Create R Engine Sucess !!");
        init();
    }
    
    private void init(){
        System.out.println("Install Package....");
        re.eval("install.packages(\"KoNLP\")");
        re.eval("install.packages(\"wordcloud\")");
        re.eval("install.packages(\"RColorBrewer\")");
        re.eval("install.packages(\"httr\")");
        System.out.println("Install Package Complete!!!");
 
    }
    
    public void readScript(String filePath){
        try{
            BufferedReader reader = new BufferedReader(new FileReader(filePath));
            String textLine;
            while(true){
                textLine = reader.readLine();
                if(textLine == null)
                    break;
                re.eval(textLine);
            }
            state = SCRIPT_PROCESSING.sucess;
            reader.close();
        }
        catch(Exception e){
            state = SCRIPT_PROCESSING.fail;
            e.printStackTrace();
        }
    }
    public void close(){
        re.eval("q()");
    }
}
cs

 

3. REnginTest01.java
   이 소스코드는 REngineManager가 잘 운영하는지 보기위해 작성되었다

   form.R의 결과물을 위의 R스크립트에서 cloud.jpg로 저장되어진다.. 

   싱글 쓰레드에서 자바코드와 REngine가 실행되어져서 

   REngine이 실행되어지는 동안에는 자바 코드의 수행이 이루어 지지 않는다. 

   좀 더 효율적인 운영을 위해서 멀티쓰레드 방식을 사용하는 것을 고려해야겠다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import java.awt.BorderLayout;
import java.awt.Color;import java.awt.Component;
import java.awt.Graphics;
import java.awt.Insets;
 
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.Border;
 
import REngine.REngineManager;
 
public class REngineTest01 extends JFrame {
    
    JLabel Image = new JLabel();
    public REngineTest01(){
        init();
    }
    
    private void init(){
        setSize(10001000);
        setLayout(new BorderLayout());
        setLocationRelativeTo(null); // 화면 중앙으로
        setTitle("카카오 워드클라우드");
        setResizable(false);
    }
    
    public void setImg(ImageIcon img){
        Image.setIcon(img);
        add("Center", Image);
        setVisible(true);
    }
    
    public static void main(String[] args){
        
        REngineTest01 testFrame = new REngineTest01();
        
        REngineManager manager = new REngineManager();
        manager.createREngine();
        System.out.println("Read form.R");
        manager.readScript("form.R");
        
        if(manager.isSucessful()){
            System.out.println("Complete");
            System.out.println("Loading Wordcloud Image...");
 
            ImageIcon cloudImg = new ImageIcon("cloud.jpg");
            testFrame.setImg(cloudImg);
 
        }
        else{
            System.out.println("Fail");
        }
 
    }
}
cs



,

학교에서 간단하게 배운 R로 일상생활에서 무엇을 해 볼 수 있을까? 고민하다가

"그 동안 여자친구랑 했던 카톡들 중에 어떤 말이 가장 많았을까?" 하는 궁금증에 야심차게(?) 수행해보았다. 


1. 데이터 수집

   컴퓨터 버전 카카오톡에서 [Ctrl + S]를 누르면 이 때까지 상대방과 대화하였던 내용을 .txt파일 형태로 

   저장시켜준다. 그 후 R의 Working Directory 위치에 이 txt파일을 옮겨준다.

   이제 파일을 열어보면....

   허.. 완전 추위에 오돌오돌 떨던 시절의 대화부터 저장되어있다.. txt파일이 12.6MB라니... 

   양이 방대한지라 분석하는데 오래 걸리길래 7월부터 현재까지 대화내용을 분석해 보기로 했다.


2. 데이터 정제

   우선 한글과 워드클라우드로 출력하기 위한 라이브러리를  설치한다.


1
2
3
4
5
6
7
8
9
10
install.packages("KoNLP")
install.packages("wordcloud")
install.packages("RColorBrewer")
install.packages('httr')
library(KoNLP)
library(wordcloud)
library(RColorBrewer)
library(stringr)
 
useSejongDic()
cs


   7월부터 현재까지의 대화내용은 kakao1.txt 파일에 저장되어 있다. 

   특수문자나 숫자, 분석에 필요없는 내용에 대하하여 삭제를 해주는 과정이 필요하다. 

   data1 <- str_replace_all(data1, "[^[:alpha:]]", "") 에서 이 과정을 처리한다.

   카카오톡 계정명인 내 이름과 여자친구 저장명을 삭제하고 

   각 종 게임 아이템 주고 받을 때마다 나오는 메세지 내용을 삭제하고 ㅋㅋ, ㅎㅎ, ㅠㅠ 등등을 

   gsub("대체당할 문자", "대체 하고자하는 문자", 변수)를 활용하여 제거해준다.

   (우리는 프랜즈팝을 즐겨해서 프랜즈팝이 온데간데 등장한다)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
data1 <- readLines("kakao1.txt")
data1
data1 <- gsub("손찬호""", data1)
data1 <- gsub("여보♥""",data1)
data1 <- str_replace_all(data1, "[^[:alpha:]]""")
data1 <- gsub("부끄럽지만.. 내 (하트)받아주면 안될까?(아잉)""", data1)
data1 <- gsub("어디서 났냐고 다들 물어봐~""", data1)
data1 <- gsub("뭘좋아할지몰""", data1)
data1 <- gsub("단준비한선물""", data1)
data1 <- gsub("님의 핫한 (선물)""", data1)
data1 <- gsub("ㅋ""", data1)
data1 <- gsub("ㅠ""", data1)
data1 <- gsub("ㅜ""", data1)
data1 <- gsub("ㅎ""", data1)
data1 <- gsub("오전""", data1)
data1 <- gsub("오후""", data1)
data1 <- gsub("이모티콘""", data1)
data1 <- gsub("월요일""", data1)
data1 <- gsub("화요일""", data1)
data1 <- gsub("수요일""", data1)
data1 <- gsub("목요일""", data1)
data1 <- gsub("금요일""", data1)
data1 <- gsub("토요일""", data1)
data1 <- gsub("일요일""", data1)
data1 <- gsub("년""", data1)
data1 <- gsub("월""", data1)
data1 <- gsub("일""", data1)
write(data1,"kakao_1.txt") # 정제한 결과 저장
cs

3. 데이터 분석

    어느 정도로 필터링이 완료되면 이제 이것들의 갯수를 새어주는 코드를 작성한다.

    이 때 2글자 이상의 단어만 카운트가 되도록 하였다.

   

1
2
3
4
5
6
data2 <- readLines("kakao_1.txt")
data2 <- sapply(data1, extractNoun, USE.NAMES = F)
data3 <- unlist(data2)
data3 <- Filter(function(x){nchar(x)>=2}, data3)
wordcount <- table(data3)
wordcount
cs

4. 워드클라우드로 표현하기

   이제 집계된 결과를 기반으로 워드클라우드를 만들어 본다.


1
2
3
4
5
<- head(sort(wordcount, decreasing = T), 30)
palete <- brewer.pal(9"Set2")
wordcloud(names(a), freq = wordcount, scale=c(150,0.5), 
        rot.per = 0.25, min.freq = 1, random.order = F, 
        random.color = T, colors = palete)
cs

5. 결 과

   


7월 달에 여자친구가 시험때문에 같이 한숨을 많이 쉬었던지라 ㅋㅋㅋ 에휴가 1등이다 ㅋㅋㅋ

그외에도 곡성을 보고나서 뭐시중헌디 뭐시중헌디 거렸던것이  임팩트가 있었나보다..ㅋㅋ

차후에 다른 그래프로도 표현을 해보고 매달마다 자동적으로 분석해주는 프로그램을 만들어보고 싶다.

,