Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
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
Tags
more
Archives
Today
Total
관리 메뉴

류동균의 R 공부방입니다.

R로 카카오톡 채팅 분석하기 #1 (텍스트마이닝, 비정형데이터의 정형화) 본문

Data Analysis

R로 카카오톡 채팅 분석하기 #1 (텍스트마이닝, 비정형데이터의 정형화)

R쟁이 2019. 8. 31. 06:14

R로 카카오톡 채팅방 분석을 해보려 한다. 우선 결과적으로 R로 카카오톡 채팅방 분석을 통해 얻을 수 있는 자료들은

채팅이 가장 활발한 달, 일, 시간대 그리고 채팅방에서 가장 많이 나오는 단어, 유저별 채팅 점유율 등 분석목적에 따라 다양한 데이터를 얻을 수 있다. 카카오톡 채팅 분석글은 우선 카카오톡을 분석하기위해 필요한 RAW데이터를 정제시키는 1편 정제된 데이터들을 분석하고 시각화하는 2편으로 나누어 작성할 예정이다. 그럼 시작해보겠다.

 

우선 필요한 4가지 패키지들에 대한 간단한 설명이다

#텍스트마이닝을 하고 분석에 필요한 dplyr패키지

library(dplyr)

 

#문자로 되어있는 날짜를 날짜형식의 데이터로 바꿔줄 lubridate
library(lubridate)

 

#문자열 데이터를 다루는데 필요한 stringr
library(stringr)

 

#데이터를 시각화하는데 필요한 ggplot2
library(ggplot2)

 

#데이터를 분석하려면 단톡방 데이터가 필요하다 필자는 데이터를 rstudy_talk.txt 라는 이름으로 저장하였다

#데이터를 불러오기위해 txt_name이라는 변수에 단톡방데이터파일의 이름을 저장한다.
txt_name <- "rstudy_talk"

 

#dir은 단톡방데이터가 있는 경로이다.
dir <- "/cloud/project/Textfile/"

 

#paste0함수를 이용하여 데이터의 경로와 파일의 이름을 합쳐 파일의 위치를 불러온다.

#paste0와 유사하게 paste라는 함수가 있는데 둘의 차이는 paste0는 공백없이 문자열을 붙이고 paste함 수는 sep라는  #속성을 이용하여 문자를 합칠때 합친 문자를 구분하는 문자를 넣을 수 있다. 가령 "a"와 "b"를 띄어쓰기로 "a b"와 같이 #합치고 싶다면 paste("a","b",sep = " ")와 같이 사용하면 된다.
text_value <- paste0(dir,text_file,".txt")

 


# readLines()는 text를 읽는 함수
raw_df <- readLines(text_value)

 

#raw_df을 가져오고 str(), head() 함수로 기본적인 구조와 자료형식이 어떻게 되어있는지 한번 살펴보는 것이 좋다.
#카카오톡 데이터는 다음과 같이 하나의 컬럼으로 되어있다.

"2019년 4월 9일 오전 9:43, 회원명 : 내용"

 

#나는 이것을 날짜, 회원명, 내용 3개의 컬럼으로 data.frame화 시킬 예정이다.

#그러기 위해서는 우선 데이터를 파악해야한다. 처음에 날짜가 나오고 ","가 찍히고 한칸뛰고 회원명 한칸뛰고 ":"

#한칸뛰고 내용이 나오는 형식으로 되어있다. 톡방에 따라 관리자의 메세지나 여러 알림 메세지의 경우 날짜가 나오지
#우선 ","를 기준으로 날짜를 추출할 것인데 이떄 str_sub()함수가 사용된다.

#str_sub()는 str_sub(data, start num, end num)의 형식으로 사용되는데 예를 들자면

#str(sub("1234567890", 1, 5) 는 "12345"가 str(sub("1234567890", 6, 10)는 "67890"이 결과로 나온다.
#위의 데이터를 보면 날짜는 "2019년 xx월 xx일 오전/오후 xx:xx,와 같이 되어있다. 

#날짜텍스트에서 가장 짧은 경우와 가장 긴 경우를 모두 고려해야한다. 월, 일, 시 가 모두 1의자리 혹은 10의 자릿

#수가 올 수 있기떄문에 가장짧은경우 2019년 x월 x일 오전/오후 x:xx 긴경우 2019년 xx월 xx일 오전/오후 xx:xx로

#짧은경우가 ","포함 20자리 긴경우가 ","포함 23자리이다. 이것을 이용하여 ","의 위치는 20에서 23번째 문자 사이에 위#치함으로 날짜가 없는 형식의 데이터를 거르기 위해 다음과 같이 grepl 함수를 사용하여 날짜가 있는 데이터의 논리값#을 얻을 수 있도록 한다. 또한 날짜는 없지만 ","가 있는 경우를 대비해 전체 데이터에 날짜형식에 포함되는

#"월", "일", "오전", "오후"의 문자가 포함된 행만 추출한다.
index_date <- grepl(",",str_sub(raw_df,20,23)) & 
  grepl("월",raw_df) & 
  grepl("일",raw_df) &
  (grepl("오전",raw_df) |
     grepl("오후",raw_df))

# 이렇게 얻은  index_date로 raw데이터를 재 설정해주도록한다.
raw_df_2 <- raw_df[index_date]

# ","라는 문자를 통해 날짜와 대화내용을 분리하기위해 첫번쨰로오는 ","의 위치를 구해줘야한다 이때 regexpr()라는 함수가 사용된다.
comma_pattern <- regexpr(",",raw_df_2)

#comma_pattern에는 각 raw마다 첫번째 ","가 어디있는지 위치가 나타나있다. 이것을 이용해 str_sub()함수로 다음과

#같이 날짜를 분리시키도록한다. comma_pattern에서 -1을 하는 이유는 불필요한 ","를 제외시키기 위해 그런것이다.

kko_date <- str_sub(raw_df_2,1,comma_pattern-1)

 

#위와 비슷하게 대화내용을 추출해야하는데 이때 nchar()함수가 사용된다 nchar()함수는 말그대로 문자의 길이를

#나타내는 함수인데 카카오톡 대화내용의 최대길이를 알아내기위해 max()와 함께 사용한다. 실상 max(nchar())를

#사용하지않고 1000000000정도로 큰 수를 사용해도 상관없다. 카카오톡 대화가 1000000000개의 문자일리는 없으니 
nchar_max <- max(nchar(raw_df_2))

# 각 comma 기준 뒤에 나오는 text 선정 
kko_text <- str_sub(raw_df_2,comma_pattern + 2,nchar_max)

 

# 우선 kko_text를 2가지로 분류해서 관리할 생각이다. Username과 대화내용이나오는 row와 "~가 ~를 초대했습니다"와 # 같이 초대, 퇴장, 관리자메세지 등 ":" 이 나오지 않는 경우 두가지로 말이다. 
# Username이 나오는 경우 ":" 이 있다는 것은 정상적인 대화라는 뜻이다.
# colon 나오는 텍스트만 추출
head(kko_text)
text_colon_index <- grep(":",kko_text)


kko_text_colon <- kko_text[text_colon_index]

# 날짜와 비슷하게 Username과 Message를 ":" 기준으로 나누기위해 ":"의 위치를 찾아낸다.
colon_grep <- regexpr(":",kko_text_colon)

# Username, text 별 DF 생성
text_only_df <- cbind(
  text_colon_index,
  str_sub(kko_text_colon,1,colon_grep-2), # Username
  str_sub(kko_text_colon,colon_grep + 2,nchar_max) # Message



# colon이 나오지 않는 텍스트 선정
text_notcolon_index <- which(!grepl(":",kko_text))

kko_text[text_notcolon_index]

# colon나오지 않는 패턴
text_pattern <- c("님이 들어왔습니다.",
                  "님이 나갔습니다.",
                  "채팅방 관리자가 메시지를 가렸습니다.",
                  "님을 내보냈습니다.",
                  "삭제된 메시지입니다.",
                  "변경되었습니다.",
                  "초대했습니다.")

# 각 패턴별 df를 생성하여 빈 공간인 pattern_df에 rbind()한다.

# 스크립트를 보면 text_notcolon_index와 pattern_index를 교집합 함수인 intersect()하는 것이 보인다.

# 그 이유는 텍스트에 어떤사람이 "~를 초대했습니다." 와 같이 대화를 한경우 제대로 원하는 데이터를 걸러낼 수 없기# # 때문이다.
pattern_df <- c()

for(pattern_msg in text_pattern) {
  pattern_index <- grep(pattern_msg,kko_text)

# which() 함수는 벡터에서 x가 가지고 있는 위치를 알 수 있는 함수  pattern_index <- intersect(text_notcolon_index,pattern_index)
  
  pattern_grep <- regexpr(pattern_msg,kko_text[pattern_index])
  

# pattern_index를 cbind()로 같이해주고있는데 이는 매우 중요하다 위에서작성한 kko_date와 순서가 맞게 하기위해

# 초기의 index를 보존하여 나중에 index를 기준으로 정렬을 하고 kko_date와 합쳐야 순서가 맞기때문에 꼭 필요하다.

  pattern_raw_df <- cbind(
    pattern_index,
    str_sub(kko_text[pattern_index],1,pattern_grep-1),
    str_sub(kko_text[pattern_index],pattern_grep,nchar_max)
  )
  pattern_df <- rbind(pattern_df,pattern_raw_df)
}

head(pattern_df)
head(text_only_df)
# username이 있는 경우 없는 경우 합치기 \
total_df <- as.data.frame(
  rbind(
    text_only_df,
    pattern_df 
  )
)

head(total_df)
tail(total_df)
#완성된 total_df의 데이터는 factor로 되어있다. 날짜의 경우 날짜형식의 데이터로 유저명, 내용은 문자형식으로

# 다음과 같이 바꿔준다.
str(total_df)
#factor -> character -> numeric
total_df[,1] <- as.numeric(as.character(total_df[,1]))
total_df[,2:3] <- sapply(total_df[,2:3], as.character)
# total_df[,3] <- as.character(total_df[,3])

 


total_df <- total_df %>% 
  arrange(text_colon_index)

total_df <- cbind(kko_date,total_df[,2:3])
str(total_df)
# 날짜형 변환을위해 gsub() 함수로 kko_date컬럼의 년, 월, 일, 오전, 오후, 등 날짜형식에 알맞게 바꾸어준다.
total_df[,1] <- gsub(",","",total_df[,1])
total_df[,1] <- gsub("년 ","-",total_df[,1])
total_df[,1] <- gsub("월 ","-",total_df[,1])
total_df[,1] <- gsub("일 "," ",total_df[,1])
total_df[,1] <- gsub("오전 ","",total_df[,1])

 

# pm_index 오전, 오후의 경우 12시간이 차이가 나기때문에 오후의 경우 index를 설정하여 밑에서 12시간을 더해줘야한# 다.
pm_index <- grep("오후",total_df[,1])
total_df[,1] <- gsub("오후 ","",total_df[,1])



total_df[,1] <- as.POSIXct(total_df[,1])

total_df[pm_index,1] <- total_df[pm_index,1] + 60*60*12
# hour(total_df[pm_index,1]) <- hour(total_df[pm_index,1]) + 12

 

#필요없는 rownames을 null로 만들어준다.
rownames(total_df) <- NULL

#colnames()함수를 사용하여 컬럼에 맞게 이름을 작성
colnames(total_df) <- c("Date","User","Message")

# 카카오톡을 다운받은 본인의 경우 닉네임이아닌 "회원님"으로 표시되기때문에 필요한 경우 알맞게 변경해준다.

total_df$User[grep("회원님",total_df$User)] <- "xxx"

#Username의 공백을 제거
head_null_index <- grep(" ",str_sub(total_df$User,1,1))

total_df$User[head_null_index] <- gsub(" ","",total_df$User[head_null_index])

str(total_df)


다음과같이 하면 우선 카카오톡 데이터를 받아서 raw데이터를 분석을 하기에 알맞은 형식으로 dataframe을 만들었다.

이제 2번째 글에서는 이렇게 만들어진 데이터를 토대로 실제로 분석을 하고 그래프를 만드는 등 시각화를 해 볼 예정이다.

'Data Analysis' 카테고리의 다른 글

서울시 먹거리분석 #1  (0) 2019.10.16
K-겹 교차검증(K- fold Cross Validation)  (0) 2019.10.02
캐글(Kaggle)  (0) 2019.09.28
혼동행렬(Confusion Maxtrix)  (0) 2019.09.22
프리미어리그 데이터분석(feat 크롤링)  (0) 2019.08.06