• Home

카카오톡 보이스톡 데이터 시각화

R에는 다양한 웹의 데이터를 끌어와 분석을 할 수 있는 장점이 있다. 흥미롭게도 카카오톡의 홈페이지는 군더더기 없이 깔끔하게 웹 테이블을 구성해 놓고 있어 생각보다 빨리 시각화를 할 수 있었다.

군산에서 서울로 가는 무궁화호 기차 안에서 카카오톡 보이스톡 품질 관련 글을 읽고 생각이 나서 시각화 및 관련 글을 써봤다(애들이 좀 크니 예전처럼 기차에서 글을 보거나 블로깅을 하는것도 이젠 어느정도 가능해졌다.ㅋ).

suppressPackageStartupMessages({
library(XML)
library(data.table)
library(stringi)
library(lubridate)
library(ggvis)
library(ggplot2)
library(reshape2)
})

vtalks_raw <- readHTMLTable("http://www.kakao.com/services/talk/voices/data")
vtalks <- data.frame(data.table(vtalks_raw[[1]]))


accum <- data.frame(dt=c(), SKT=c(), KT=c(), LGU=c(),JAP=c(), US=c())

for(n in 1:nrow(vtalks)){
  if(n %% 2 == 0){
    accum <- rbind(accum, data.frame(dt=vtalks[n-1,]$V1, SKT=vtalks[n,]$V2, KT=vtalks[n,]$SKT, 
                                     LGU=vtalks[n,]$KT, JAP=vtalks[n,]$`LGU.`, US=vtalks[n,]$`Japan`))
  }
}


dt_vtalks <- data.frame(
  apply(accum, 2, function(x){
    stri_trim_both(stri_replace_all_regex(x, "[ |%]",""))})
)

y <- year(Sys.Date())

chgdt <- function(dt){
  ret <- paste0(dt,"/", y)
  if('01/Jan' == dt){
    y <<- y - 1
  }
  ret
}


dt_vtalks <- within(dt_vtalks, {
  SKT<-as.numeric(as.character(SKT)) 
  KT<-as.numeric(as.character(KT))
  LGU<-as.numeric(as.character(LGU))
  JAP<-as.numeric(as.character(JAP))
  US<-as.numeric(as.character(US))
  ymd<-dmy(sapply(dt, chgdt))
  })



dt_vtalks_melts <- melt(dt_vtalks, id.vars = "ymd",measure.vars = c("SKT", "KT", "LGU", "JAP", "US"), 
                        variable.name="service_provider",value.name = "loss_rate")


ggplot(dt_vtalks_melts, aes(ymd, loss_rate, colour=service_provider)) + geom_line() + 
  ggtitle("2012 ~ now")

plot of chunk unnamed-chunk-1

ggplot(with(dt_vtalks_melts, dt_vtalks_melts[ymd >= ymd('2014-01-01'),]), aes(ymd, loss_rate, colour=service_provider)) + geom_line() + 
  ggtitle("2014 >= ")

plot of chunk unnamed-chunk-1

웹에 있는 테이블을 가져와서 몇몇 전처리 이후 간단하게 시각화를 해보았다. 시각화 결과를 보면 통신 3사가 과거에 어느정도 제한을 걸다가 현재는 거의 mVoIP제한을 하지 않는 것과 같은 양상을 보이고 있다는 것을 볼 수 있다. 물론 이 결과는 앞으로 장기적으로 지켜봐야 될 거란 생각을 해본다.

카카오톡이 2012년 부터 손실율을 기록해 놓고 오픈해놓은 이유는 mVoIP 업체 입장에서 충분히 이해가 가나, 이 손실율이 어떤 방식으로 계산되는지 metric에 대한 부가 설명과 메타 데이터가 없다는 건 다소 실망스럽다. 물론 일반 사용자들에게 가장 쉽게 다가갈 수 있는 정보표현이라는데는 이견이 없으나 어짜피 모집단에 대한 샘플링 결과일 것이기 때문에 추정 평균값과 신뢰구간, 샘플링 방법과 같은 흡사 여론조사를 공표하는 정도의 신중함이 필요하지 않을까 하는 생각을 해본다. 왜냐면 이런 단편적인 정보는 사용자가 오해를 할 수 있는 부분이 충분히 있고, 이런 오해가 특정 회사 서비스 전체에 대한 오해/불신으로 이어질 수 있기 때문이다. 예를 들어 SKT의 손실율의 $\mu \pm 2\sigma$구간이 0을 포함하고 있었다면 과연 위에서 보고 있는 손실율이 의미가 있을까? 그리고 이런 결과를 기반으로 타사들과 절대 비교 및 기상도처럼 표현하는게 맞을까 하는건 생각해볼 문제이다.

그러나 그럼에도 불구하고 이런 좋은 포맷으로 오픈된 데이터는 필자와 같은 데이터 저널리즘을 신봉하는 입장에서는 재밋는 장난감과 같은 존재여서 여간 반가운 일이 아닐 수 없다. 사실 위 시각화보다 더 많은 분석을 해보았으나 제한된 데이터로 과도한 해석을 하지 않기 위해 이곳에 올리지는 않겠다.

Google Analytics 데이터로 블로그 방문자 분석하기

블로그 방문자 추이데이터를 2011년 가량부터 수집해오고 있어 이를 rga패키지를 이용해서 분석해 보았다. 자세한 사용법은 R에서 Google Analytics 데이터 사용하기Analysing your e-commerce funnel with R을 참고하길 바란다.

suppressPackageStartupMessages({
library(rga)
library(lubridate)
library(xts)
library(magrittr)
library(dplyr)
library(ggplot2)
library(forecast)
})
rga.open(instance="ga", where="ga.rga")


daily_visits_src <- ga$getData(
  key,
  start.date = "2011-01-01",
  end.date = "2014-08-27",
  metrics = "ga:users",
  dimensions = "ga:date",
  sort = "ga:date",
  batch = TRUE
)

daily_visits <- daily_visits_src 

daily_visits <- daily_visits %>% mutate(weekdays=factor(weekdays(date), 
                               levels = c("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday","Friday", "Saturday")))


daily_visits %>% data.frame %>% group_by(weekdays) %>% summarise(wdsums=sum(users)) %>% 
  ggplot(aes(weekdays, wdsums)) + geom_bar(aes(fill=weekdays),stat="identity")

plot of chunk unnamed-chunk-1

주간 방문자 방문 빈도수의 분포인데, 예상대로 주중에 방문자가 많다.

daily_xts <- xts(daily_visits$users, order.by = daily_visits$date,frequency = 7)

daily_ts <- ts(daily_visits$users, start = c(2012, yday("2012-01-01")),frequency = 365)

plot(decompose(daily_ts))

plot of chunk times

방문자 추이를 장기 추세와 계절패턴으로 나눠서 보았다. 장기 추세는 흡사 빅데이터 거품이 생기고 사그라 드는 그런 모습과도 같다.ㅋ

2013후반부터 2014초중반까지는 개인적으로나 업무적으로 매우 바빳던 기간으로 그 영향이 블로그 포스팅 빈도로 나타나고 그리고 방문자수의 감소로 나타난것이라 본다. 그리고 이번 여름방학때 여유를 틈타 블로그 포스팅을 빈번하게 한게 최근 상승추세로 나타나지 않았나 싶다.

방문자 예측은 별 의미 없어 하지 않았다.

src_from <- ga$getData(
  key,
  start.date = "2011-01-01",
  end.date = "2014-08-27",
  metrics = "ga:users",
  dimensions = "ga:source,ga:date",
  sort = "ga:date",
  batch = TRUE
)

referers <- src_from 

referers %>% group_by(source) %>% summarise(usercnt=sum(users))  %>% mutate(ratio=usercnt/sum(usercnt) * 100) %>% arrange(-ratio)
## Source: local data frame [488 x 3]
## 
##            source usercnt   ratio
## 1          google   56087 49.7534
## 2           naver   29792 26.4277
## 3        (direct)    9712  8.6153
## 4      feedburner    5230  4.6394
## 5            daum    2004  1.7777
## 6    facebook.com    1457  1.2925
## 7    google.co.kr     714  0.6334
## 8           yahoo     610  0.5411
## 9  m.facebook.com     564  0.5003
## 10 blog.naver.com     489  0.4338
## ..            ...     ...     ...

Referer를 분석한 결과 URL 정규화는 하지 않았지만 구글에서 50%정도의 트래픽이 발생하고 다음으로 네이버에서 26% 발생한다.

개인적으로 더 해보고 싶은 분석은 내가 그동안 주로 언제 포스팅을 했는지와 이 시계열을 기반으로 방문자 증감 효과를 검증해 보는 것이다. 포스팅 날짜는 워드프레스DB에 접근해야 되는 일이라서 미루고 있는 일이긴 한데, 역시 분석에서 데이터 획득이 가장 귀찮으나 중요한 일이란 생각을 해본다.

R에서 Pipe 연산으로 분석하기

magrittr 패키지가 최근에 많은 화제를 불러 일으키고 있다. 사실 이 패키지는 패키지 자체로 유명세를 탔다고 하기 보다는 다른 유명 패키지가 이 패키지를 사용하게 됨으로써 유명세를 탓고 필자도 현재 이 패키지 때문에 dplyr과 같은 패키지를 자연스럽게 사용하게 되었다.

이 패키지는 유닉스에 있는 파이프(|, >) 연산자와 같은 기능을 아래와 같이 R에서 쓸 수 있게 해주는 연산자이다.

library(magrittr)
library(dplyr)
library(lubridate)

#head(iris)
iris %>% head  
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1          5.1         3.5          1.4         0.2  setosa
## 2          4.9         3.0          1.4         0.2  setosa
## 3          4.7         3.2          1.3         0.2  setosa
## 4          4.6         3.1          1.5         0.2  setosa
## 5          5.0         3.6          1.4         0.2  setosa
## 6          5.4         3.9          1.7         0.4  setosa
#iris 데이터에 대해서 group by연산을 수행하고 이 결과를 기반으로 Sepal.Length의 평균과 Petal.Length의 평균을 구한다. 
iris %>% group_by(Species) %>% 
  summarise(mofSL=mean(Sepal.Length), mofPL=mean(Petal.Length)) 
## Source: local data frame [3 x 3]
## 
##      Species mofSL mofPL
## 1     setosa 5.006 1.462
## 2 versicolor 5.936 4.260
## 3  virginica 6.588 5.552
#iris 데이터에서 Species 필드를 제외한 필드들 각각의 max, min 값을 구한다. 
iris %>% select(-Species) %>% 
  apply(2, lambda( x ~ c(max(x), min(x))))
##      Sepal.Length Sepal.Width Petal.Length Petal.Width
## [1,]          7.9         4.4          6.9         2.5
## [2,]          4.3         2.0          1.0         0.1
#특정 기간의 날짜에서 일요일만 제거한 날짜의 첫 부분을 출력한다. 
seq(ymd("2014-01-01"), ymd('2014-08-01'), by="1 day")  %>% 
  Filter(lambda(x ~  (x %>% weekdays) != "Sunday"),.) %>% head %>% print
## [1] "2014-01-01 UTC" "2014-01-02 UTC" "2014-01-03 UTC" "2014-01-04 UTC"
## [5] "2014-01-06 UTC" "2014-01-07 UTC"

그런데 단순한 Syntactic Sugar의 역할을 할것만 같았던 이 패키지가 필자의 마음을 흔들어 놓았는지 설명할 필요가 있을 것이다.

위 방식의 장점은 아래와 같다.

  • 코드의 복잡도가 늘어가면 유지 보수나 추후 코드 리딩에 문제가 생길 수 있어 적절한 중간 변수를 만들어두는데 위와 같은 방식은 코드의 복잡도가 리딩에 끼치는 악영향이 기존보다 획기적으로 줄어들기 때문에 빈번한 중간 변수 할당에 따른 메모리 소모를 줄일 수 있다.
  • 코드 작성 방향과 데이터 흐름의 방향 및 생각의 방향이 일치화 되어 코드를 이해하기 쉽다.
  • 결국 dplyr을 쓰게되는데, 이덕분에 data.table문법을 따로 배울 필요가 없어지는데 이는 dplyr에서 알아서 처리해 주기 때문이다.

단점은 아래와 같다.

  • 내가 아닌 이 방식을 이해하지 못하는 다른 사람들이 이 코드를 보게 된다면 당황해 할 수 있다.
  • 위 방식만 고수하기 보다는 기존 방식과 위 방식을 겸용하게 됨으로써 자칫 스파게티 코드가 될 위험이 있다.
  • 결국 dplyr을 쓰게 된다. 나는 data.table 사용자인데 말이다(물론 dplyr는 잘 지원해준다).

앞으로 magrittr 패키지는 폭넓게 사용될 것으로 예상이 되는데 이는 인기 패키지인 dplyr과 앞으로 많은 각광을 받게될 ggvis가 이 방식을 사용하면서 많은 사용자 경험을 이끌 것이기 때문이다. 장점이 있는 패키지임에는 분명하기 때문에 사용자는 점점 많아질 것이다.