Tag Archives: R

KBO 팀간 승패 자료를 github.com 에 올렸습니다.

안녕하세요. 외대 철학과 스터디 그룹 LOPES의 추선식 입니다.

데이터 분석을 공부하면서 느낀 점은 학습에 맞게 구성된 데이터가 적고 이런 형태의 자료를 현실적으로 찾아보기 어렵다는 점입니다. 그래서 대중적으로도 인기가 있는 한국의 프로야구 자료로 데이터 분석을 공부해보기로 했습니다.

자료를 모으기 위해 KBO 홈페이지에 등록된 2010년도부터 2016년도까지의 야구 자료를 모으고 보기 좋게 정리하기 시작했습니다.

저희가 수집하고 가공한 자료는 약 7년 치의 한국 프로야구 데이터가 있습니다. 이 데이터는 점수와 홈 & 원정팀, 시즌정보 등을 구분하여 경기정보를 쉽게 확인해 볼 수 있습니다. 또한 7년치의 데이터로 다양한 기록들을 시각화해볼 수 있습니다. 아래 링크로 가시면 자료를 받으실 수 있습니다.

choosunsick/KBO_data

##응용사례

  1. 경기 결과열을 만들어 줍니다.
kbo_total$경기결과 <- ifelse(kbo_total$홈팀점수>kbo_total$원정팀점수,"홈승",ifelse(kbo_total$홈팀점수<kbo_total$원정팀점수,"원정승","무"))
  1. 정규시즌만 뽑아 줍니다.
normal_season <- subset(kbo_total,kbo_total$비고=="정규시즌")
doosan <- subset(normal_season,normal_season$홈팀=="두산"|normal_season$원정팀=="두산")
  1. 홈팀과 원정팀의 경기 수를 경기결과에 따라 분류합니다.
home <- aggregate(홈팀점수~홈팀+원정팀+경기결과,doosan,length)
doosan_home <- home[home$홈팀=="두산",]
away <- aggregate(원정팀점수~홈팀+원정팀+경기결과,doosan,length)
doosan_away <- away[away$원정팀=="두산",]
  1. 두산이 홈일 때와 원정일 때의 승리만 따로 뽑아줍니다.
doosan_home_win <- doosan_home[doosan_home$경기결과=="홈승",]
doosan_away_win <- doosan_away[doosan_away$경기결과=="원정승",]
  1. 두산의 열이 중복되므로 지워줍니다.
doosan_home_win <- doosan_home_win[,-1]
doosan_away_win <- doosan_away_win[,-2]
  1. 하나의 자료로 만들어 줍니다.
colnames(doosan_home_win) <- c("상대팀","경기결과","승리횟수")
colnames(doosan_away_win) <- c("상대팀","경기결과","승리횟수")
doosan_win <- rbind(doosan_home_win,doosan_away_win)
  1. 이제 두산의 상대전적(승리)그래프를 그려봅시다.
install.packages("ggplot2")
library(ggplot2)
p <- ggplot(doosan_win,aes(x=상대팀,y=승리횟수,fill= 경기결과))+
geom_bar(stat="identity",colour = "black")+
scale_x_discrete(limits=c("한화","KIA", "SK", "LG", "넥센", "삼성", "롯데", "NC","kt" ))+
theme(axis.text.x= element_text(angle=90, hjust=1))
p+labs(title="두산상대전적그래프")

ps. R에서 플롯을 그렸을 때 한글이 깨질 수 있습니다.

R에서 Gephi 없이 igraph을 이용하여 군집화 하기

안녕하세요. 외대 철학과 스터디그룹 LOPES의 추선식입니다. 이번에는 오직 R만을 이용하여 와인데이터를 분석하고 군집화 해보겠습니다. 앞서 같은 스터디 그룹인 배서연 학우가 쓴 “와인 구매 데이터를 R로 분석하여 Gephi로 군집화, 시각화 하기“에서 Gephi를 이용해서 군집화 했었습니다. 그렇지만 저는 이 글에서 Gephi 없이 R을 이용해서 와인데이터를 군집화하고 그 결과를 서로 비교해보겠습니다.

준비 작업: 『DATA SMART』의 예제 와인데이터 이용하기

원본 와인데이터는 아래 링크로 가서 chapter2를 클릭하여 자료를 다운받으시고, WineKMC 파일 안의 Matrix시트를 R에서 지정한 디렉토리 폴더에 csv형식으로 내보내 사용하시거나 혹은 chapter10를 클릭하여 자료를 다운 받은 다음 그 안의 WineKMC.csv 파일을 사용하시면 됩니다.
Wiley: Data Smart: Using Data Science to Transform Information into Insight – John W. Foreman

R로 와인데이터 분석: (코사인 유사도 행렬 구하기 및 Cut-off 하기)

와인데이터의 군집화를 위해 먼저 해야 할 작업은 코사인 유사도 행렬을 만드는 것입니다. 코사인유사도는 각 구매자 간 구매데이터의 유사성에 비중을 둔 거리 계산법으로 이 예제에서는 와인 데이터의 구매 유사성을 토대로 구매자들 간의 거리를 측정할 수 있습니다. 코사인 유사도는 0과 1 사이의 값을 가지는데 코사인 유사도로 나타난 구매자들 간의 거리는 구매 데이터의 유사성 정도에 따라서 유사성이 강하면 그값이 1에 가까우며 유사성이 약할수록 0에 가까워지게 됩니다. 즉 이것으로 구매 데이터를 사람과 사람 간의 관계로 바꾸어 이후 군집화의 초석을 쌓는 것입니다.

다음 작업으로는 컷오프를 하는 것입니다. 이것을 하는 이유로는 와인 구매 데이터의 유사성을 통해 고객들 간의 모든 관계를 파악할 수 있지만, 그 관계가 너무 많아 데이터가 복잡하기 때문입니다. 따라서 중요하지 않은 관계는 지우고 강한 관계가 있는 데이터만 남기는 과정이 바로 컷오프입니다. 여기서 중요한 관계만 남기는 방법으로는 앞선 글애서와 똑같이 유사성이 강한 상위 20%만 남기는 것 입니다.

군집화하기: Igraph 패키지를 이용해서 군집화하기

군집화를 위해서는 Igraph 패키지가 필요합니다. 이 패키지가 기존의 Gephi의 기능인 네트워크 그래프를 다루는 일을 수행하기 때문입니다. Igraph 패키지는 컷오프 한 와인데이터 구매자들 간의 관계를 보고 군집화해주는 역할을 합니다. 이것을 이용해 R로 시각화를 해볼 순 있지만, 아직 모듈성 최적화가 되지 않은 상태이기에 군집 간 구분이 힘들기에 모듈성 최적화의 과정을 필요로 합니다.

따라서 Igraph로 군집화 된 것의 모듈성 최적화를 위해walktrap.community 나 multilevel.community 함수를 사용해야 합니다. 각 함수를 사용해서 군집의 최적화 즉 모듈성을 최적화하게 되면 모듈성은 walktrap.community 방식이 더 극대화되지만 두 방식의 모듈성 값 차이는 미세합니다. 두 가지 방식에 대한 더 자세한 설명을 알고 싶다면 이 두 기능이 내포된 Igraph 패키지와 각 함수에 대하여 Inside-r 링크에서 직접 찾아보실 수 있습니다.

R 코드 내용: 다음은 전체과정에 대한 R 코드입니다.

R 코드 설명

먼저 코사인 유사도를 구하는 데 필요한 패키지인 lsa를 다운 받습니다. 그 후 군집화에 사용할 패키지인 Igraph패키지를 다운 받습니다. 이제 와인 데이터를 읽어오고 코사인 유사도 그래프를 만들기 위한 작업(행,렬 변환 과 NA값 변환)을 합니다. 기본 작업이 완료되면 cosine 함수를 이용하여 코사인 유사도를 만들고 for 구문으로 대각선의 값을 0으로 바꿔 줍니다. 코사인 유사도 그래프가 완성되었으면 그것에 대한 컷오프 과정을 진행합니다. 컷오프 과정은 앞서 배서연 학우가 올린 글에서 설명되었기에 넘어가겠습니다.

이제 Igraph 패키지를 사용하게 되는데 가장 처음 할 일은 인접성 행렬 그래프를 만드는 것입니다. 인접성 행렬 그래프는 네트워크 데이터를 나타내는 그래프 중 하나로서 인접성 행렬을 통해 네트워크 데이터가 드러내는 관계를 보여주는 그래프입니다. 간단히 말해서 네트워크 데이터를 노드와 에지로 표현한 그래프라 할 수 있습니다.

코드에서 graph.adjacency 함수가 인접 행렬 그래프를 만들어 줍니다. graph.adjacency 함수는 방향성과 가중치에 따라서 인접 행렬을 다양하게 해석할 수 있기에 여기서는 가중치는 주지 않고 무방향성(undirected)을 설정해 줍니다.
다음으로 Igraph의 walktrap.community 함수를 사용합니다. 이 함수는 무작위 걸음(random walk)을 통해 그 걸음 길이가 짧을수록 같은 군집으로 묶는 역할을 합니다. 따라서 각 사람 간의 군집이 형성되는데 이것을 membership 함수를 통해 확인할 수 있습니다. 이어서 이 군집화 결과를 as.integer 함수를 통해 정수형 벡터로 바꾸어 줍니다.
남은 과정은 이제 기존의 정보들을 덧붙이는 과정입니다. 이것은 기존 와인에 대한 정보를 붙이고 하나의 데이터 벡터로 바꾸는 과정입니다. 이 과정을 진행하고 나면 최종 결과인 각 군집별 구매한 와인의 특성을 파악할 수 있습니다.

결과 비교해보기: Gephi 와 두 가지 방식(walktrap,multilevel)의 함수 결과 비교

비교하는 것에 앞서 결과를 추측해보자면 같은 군집화 알고리즘을 사용한다면 군집화의 결과가 같을 것이고 그렇지 않으면 차이가 있을 것이라 생각되어집니다.

군집화의 결과를 R로 비교해 보기 위해서는 19번째 코드까지 진행하고 그 이후 20번째 코드부터 다르게 진행합니다. 우선 walktrap.community 함수부터 사용해서 사람과 할당 군집목록을 만듭니다. 다음의 코드를 이용합니다.

winedata.wtc <- walktrap.community(winedata.graph)
membership(winedata.wtc)
walktrap.membership <- print(membership(winedata.wtc))
walktrap.community <- data.frame(walktrap.membership)

위 코드로 walktrap 함수의 구매자 별 군집 목록을 만듭니다. 같은 방식으로 multilevel.community 함수를 적용해 구매자 별 군집 목록을 완성합니다. 다음으로는 맨 위 링크에서 Gephi 로 작업한 파일을 Gephi 내부에서 csv 파일 형식으로 내보냅니다. Gephi에서 파일을 내보내는 방법은 gephi의 Data laboratory 섹션에서 Export table을 클릭하여 내보낼 수 있습니다. csv 형식으로 내보낸 파일을 R에서 읽어옵니다. R에서 csv 파일을 읽는 방법은 read.csv 함수를 사용하면 됩니다. 다음과 같이 gephi <- read.csv("CommunitiesGephi.csv") 사용합니다. R에서 불러온 다음엔 Gephi의 군집데이터를 이름을 토대로 다시 정렬해줍니다. 다음의 코드로 정렬할 수 있습니다.

library(plyr)
arrange(gephi,label)
gephi.community <- arrange(gephi,label)

이제 3개 방식의 군집 목록을 R에서 한 번에 비교해보도록 하겠습니다. 3개 방식을 한 번에 보는 방법은 cbind()함수를 사용하여 하나의 목록으로 합칠 수 있습니다.

walktrap.class <- walktrap.community$walktrap.membership
comparison.class <- cbind(gephi.community,walktrap.class)
multilevel.class <- multilevel.community$multilevel.membership
comparison.class <- cbind(comparison.class,multilevel.class)

위의 코드를 통해 하나의 목록으로 만들었다면 arrange() 함수를 통해서 방식별 정렬을 하여 비교할 수 있습니다. 예를 들면 arrange(comparison.class,modularity_class) 와 같이 Gephi의 군집을 기준으로 다른 방식과 비교한 것을 볼 수 있습니다.

3가지 방식의 군집화를 비교해본 결과 처음에는 모듈성이 거의 유사하기에 Gephi와 Igraph의 방식들의 군집화 결과가 서로 같을 것으로 생각했지만, 정렬을 통해 비교해본 결과 분명히 다르게 군집화가 이루어졌음을 확인할 수 있었습니다. 같은 자료, 같은 모듈성이지만 군집화 방식 도구에 따라 군집의 결과가 다르게 나누어진다는 사실에 놀랐습니다. 읽어주셔서 감사합니다

R로 나이브 베이즈 규칙을 사용하여 트위터 분류 모델 만들기

안녕하세요~ LOPES의 배서연 입니다. 저번에 올린 글이 반응이 나쁘지 않아 용기 내어 올려 봅니다. LOPES 스터디 그룹에서도 각자 R을 이용하여 정리한 내용을 공유 차원에서 올릴 예정입니다. 어설프더라도 예쁘게 봐 주세요~^^

이번에는 『DATA SMART』 3장에 나오는 나이브 베이즈(Naive Bayes)를 이용한 트윗 분류하기를 R로 구현해봤습니다. 저희 팀에서는 R로 구현하는 방법을 독자적으로 해결하고자 하다가 다음과 같은 곳을 발견하여 R로 쉽게 구현할 수 있었습니다. 나중에 나오는 설명과 코드 또한 아래 링크에서 많은 부분 빚졌음을 밝혀둡니다. 그리고 두 번째 링크는 동일한 내용을 다르게 해결하는 곳이 있어 추가합니다.

Tweet Bayes Classification: Excel and R : Salem Marafi

Data Smart, Ch3, Classifying Tweets using Naive Bayes – Working Analytics

R로 풀어볼 나이브 베이즈(Naïve Bayes) 모델은 소위 ‘지도 학습법(supervised learning)’에 속합니다. 주로 스팸 메일을 구분할 때 사용합니다. 스팸 메일에서 자주 등장하는 단어들의 확률 값을 통해 다른 메일이 스팸 메일인지 아닌지를 판단할 수 있는 방법입니다.

『DATA SMART』 3장에서는 나이브 베이즈(Naïve Bayes) 모델을 이용해 트위터에서 맨드릴(Mandrill)이라는 단어가 언급되는 경우, 새로 출시한 앱(app)인 맨드릴Mandrill에 대한 트윗인지 아니면 관련없는 것들을 언급하는 것인지 설명하고 있습니다. 진행 과정은 다음과 같습니다.

새로 출시한 ‘맨드릴(Mandrill)’이라는 서비스를 이야기하는 트윗은 (App)이라고 표기하고 다른 것들은 이야기하는 트윗은 (Other)으로 표기하겠습니다. ‘맨드릴(Mandrill)’이라는 단어를 언급하는 트윗을 조사했더니, 예를 들어 그 트윗에는 단어1, 단어2, 단어3 등등으로 구성되었다고 생각해 봅시다. 이때 맨드릴 서비스를 이야기하고 있는 150개의 트윗들을 가지고 있는 문장 안의 단어들을 다 조사할 수 있습니다. 이때 각각의 맨드릴 서비스를 이야기하고 있는 각각의 트윗이 단어1, 단어2, 단어3 등등을 가지고 있는 확률과 다른 것을 이야기하고 있는 트윗이 단어 2, 단어 4, 단어 6 등등을 가질 확률은 다음과 같습니다.

P(App | 단어 1, 단어 3, 단어 5, …)

P(Other | 단어 2, 단어 4, 단어 6, …)

이 확률 조건표를 가지고 후에 테스트할 트윗이 앱에 대해 이야기하는 것인지 아니면 다른 것을 이야기하고 있는지 확인할 수 있습니다. 테스트 할 트윗 단어들이 앱에 관련될 확률과 아니면 다른 것에 관련될 확률을 비교해 가장 확률이 높은 클래스로 분류시키는 것이 바로 최대 후험 규칙(MAP rule, Maxium A Posteriori rule)입니다. 이렇게 만들기 위해서는 베이즈 규칙을 적용하되 바보같이 각 단어들이 문맥과 상관없이 독립적인 사건이라고 가정하여 확률을 구합니다. 이를 나이브 베이즈 규칙이라고 합니다. 예를 들어 감기와 콧물이 문맥 상 전혀 관계없는 단어가 아니더라도 일단 둘 다 독립적인 사건으로 가정하면 다음과 같이 규칙을 쓸 수 있습니다.

P(스팸메일|감기, 콧물 ) = P(감기|스팸메일) * P(콧물|스팸메일)

이를 활용하여 새로 테스트할 트윗의 각 단어들(단어1′, 단어2′, 단어3’…)이 앱에 관련된 것인지 아닌 지에 대해 비교할 확률은 다음과 같이 구할 수 있습니다. 이 두 개의 값 중 더 큰 값에 따라 클래스를 분류할 수 있습니다.

P(App | 단어1′ , 단어2′, 단어3′ …)
= P(App) * (단어1’|App) * P(단어2’|App) * P(단어3’|App)…

P(Other |단어1′ , 단어2′, 단어3′ …)
=P(Other) * 단어1’|Other) * P(단어2’|Other) * P(단어3’|Other)…

재밌게도 나이브 베이즈 모델은 대충 부정확한 가정을 세워도 후에 어느 것이 더 큰 확률이냐를 비교하기 때문에 이상하지만 비교적 정확한 모델이라고 평가 받고 있습니다. 그럼 앞의 과정에 따라 R 로 예제 트윗을 분류해봅시다.

1) 첫 번째 단계: 데이터를 가져와 깨끗하게 정리하기

『DATA SMART』 3장에서는 엑셀로 작업하고 있지만, 우리는 R에서 작업을 하기 때문에 엑셀 파일에 들어 있는 자료를 CSV 형식 파일로 바꿔서 가져오겠습니다. 이 자료는 맨드릴(Mandrill) 앱(App)에 관련된 트윗과 관련되지 않은(Other) 트윗, 각각 150개로 되어 있습니다. 사용환경에 따라 아래 링크에서 다운 받으시면 됩니다. Mandrill.csv, Other.csv, Test.csv 이렇게 파일 3개가 있을겁니다.
윈도우 사용자분들은 아래 링크에서 다운 받을 수 있습니다.

https://www.dropbox.com/sh/1k3df26mbwwbtcn/AACnrKwvjg6GGtriRAWXWZ-6a?dl=0

맥 사용자분들은 아래 링크에서 다운 받을 수 있습니다.

https://drive.google.com/file/d/0B7Ov50qkZFF5NnJHNzZLalYtcVU/view

혹여 파일에 깨져서 실행이 안되는 분들은 책 홈페이지인 Wiley: Data Smart: Using Data Science to Transform Information into Insight – John W. Foreman에서 Chapter 3 파일을 다운 받으시면 됩니다. 아니면 아래 여기, ch03.zip을 눌러서 다운 받으시면 됩니다. 받으신 파일을 압축을 푼 다음 엑셀에서 열어서 csv 파일로 바꿔주세요. 위의 파일을 다운 받으시면 그 안에 AboutMandrillApp / AboutOther / AppTokens 시트에 A열 만 떼내 각각 Mandrill.csv / Other.csv / Test.csv 파일로 저장한 뒤 진행하면 됩니다 ^^

2) 가져온 파일을 컴퓨터가 이해할 수 있도록 처리하기

가져온 파일은 단순하게 각각 150열로 되어 있는, 즉 각각 150개 트윗으로 되어 있는 자료입니다. 앞에서도 이야기했지만, 우리는 각각의 트윗이 가지고 있는 단어를 가지고 분석할 예정입니다. 따라서 우리가 가지고 있는 모든 트윗에서 단어를 다 분리해야 합니다. 『DATA SMART』에서는 엑셀을 이용하여 조금은 복잡하게 단어로 분리하고 있습니다. 그러나 R에서는 ‘tm’이라는 패키지를 이용해서 엑셀에서 하는 것보다는 쉽게 분리할 수 있습니다. 우선 단어로 쪼개기 전에 트윗이 가지고 있는 모든 단어를 소문자로 바꾸고 문장 부호를 빈칸으로 교체합니다. 아래 코드에서는 replacePunctuation()이라는 함수를 만들어서 대문자를 소문자로 만들고, 문장 부호, “.”, “: ”, “?”, “!”, “;”, “,”을 모두 빈칸으로 바꿉니다. 이 함수 안에서는 tolower()는 대문자를 소문자로, gsub()는 문장 부호를 빈칸으로 바꿔주는 역할을 합니다. 이렇게 하는 이유는 ~ 그 다음 빈칸을 기준으로 단어들을 분류 시켜 하나의 matrix로 만들어줍니다.

토큰화 된 단어의 길이를 구해 의미 없는 단어들은 Cut-off 하기

컷 오프하는 방식은 데이터를 다룰 때 언제나 중요하게 다뤄지는 것 같습니다 ^^. 여기서는 글자 수 3개 이하의 토큰들을 다 컷오프시킵니다. 단순하게 말하자면 영어 단어 중 3글자 아래는 모두 지웁니다. 영어 단어 중 3글자보다 작은 단어는 통상적으로 의미가 없는 단어이기 때문입니다. 보통 이런 작업에서는 몇 개 이하의 짧은 단어를 제거하는 대신 because, instead와 같이 멈춤 단어(stop words)라고 불리는 단어들을 컷오프 하기도 합니다.

3) 나이브 베이즈 모델을 이용하여 분류 모델 만들기 – 각 단어의 App/Other에서 조건부 확률 구하기

본격적으로 앱에 관련된 단어들과 아닌 단어들의 빈도를 확률로 계산하기 전에 크게 두 가지 문제를 풀어야 합니다. 첫 번째로 트윗에 거의 나오지 않은 드문 단어들을 처리 해야 하고, 두 번째로 이런 드문 단어들의 아주 작은 확률 값이 컴퓨터가 잘 처리할 수 있도록 처리해야 합니다.

가산 평활

앱에 관련된 트윗과 아닌 트윗에서 나오지 않은 단어가 후에 나왔다면 이 단어는 어느 쪽에 속해야할까요? 이런 단어는 나이브 베이즈 확률값을 계산할 때 전체 확률 값을 0으로 만들기 때문에 일단 이런 드문 단어는 한 번 나온 것으로 가정합니다. 그러면 정말 한 번 나타난 단어들의 확률이 불공정해지기 때문에 모든 단어들의 빈도 수에 1씩 더해줍니다. 이렇게 쭉 1씩 더해주는 방식을 가산 평활법(addictive smoothing)이라고 합니다. (다른 책에서는 라플라스 추정기라고 하기도 하더군요^^)

자연 로그 값으로 변환

예컨대 확률이 0.001인 단어들이 여러 개 있다면 후에 단어들의 확률을 곱해서 클래스를 비교할 때 소숫점 수 백 개 이하의 0이 나오게 됩니다. 이 경우 컴퓨터가 제대로 처리하지 못하기 때문에 언더 플로라고 하는데 이를 해결하기 위해 0과 1 사이의 값에 대해서 자연 로그를 취합니다, 그러면 소수점 몇 백 개 이하의 수가 아니라 비교적 잘 와 닿는 음수가 되게 됩니다. 그 다음 최대 후험 비교를 수행하면 컴퓨터가 무리가 가지 않게 처리할 수 있습니다.

이제 2 가지 문제를 해결했으면 이제 R에서 트윗의 각 단어들이 App에 들어갈 확률과 그 외에 들어갈 확률을 구해줍니다.

4) 앞에서 만든 모델을 가지고 새로운 트윗들이 어디에 속하는지 예측해보기

앞에서 나이브 베이즈 모델을 이용하여 만든 분류 모델은 사실상 두 개의 조건부 확률 표를 만드는 것에 불과합니다. 이제 이 확률표라는 증거들을 가지고 새로운 트윗이 App에 관련된 트윗인지 아닌지 분류해봅시다. 테스트할 20의 트윗은 반은 App에 관련된 트윗이고 반은 상관 없는 트윗으로 이루어져 있습니다. 앞에서 한 토큰화, 가산 평활, 자연 로그 과정을 거친 뒤 이미 만들어 놓은 조건부 확률을 통해 그 단어들이 app에 관련될 확률과 그 외에 관련된 확률을 구해 비교합니다. 비교했을 때 확률 값이 더 큰 쪽으로 클래스를 분류합니다. 이제 정말 이 모델이 정확한 지 확인해 봅시다. 20개 중에 하나 빼고는 다 맞혔네요^^ R로 구현하는 코드는 다음과 같습니다. 윈도우 사용자 분들은 R코드 마지막 줄에 View(cbind(classified,tweets.test$Tweet))으로 하시면 더 멋진 결과를 보실 수 있습니다. 긴 글 읽어주셔서 감사합니다~!