복잡한 확률 통계를 손쉽게 – R

시스템 인프라 관련 개발을 진행하다 보니 수많은 성능 측정 데이터들을 어떻게 하면 보기 좋게 정리할 수 있을지 고민이 많다. 일반적으로 이런 일에 가장 많이 사용하는 도구는 당연히 스프레드시트다. 스프레드시트의 대명사 엑셀은 응용범위가 넓고, 간단한 수식과 함수를 이용해 다양한 결과물을 도출해 낼 수 있는 강력한 데이터 처리 도구이다. 많은 회사에서 사용하고 있어 손쉽게 접할 수 있기도 하다. 그러나 범용이기 때문에 원하는 결과를 만들기 위해서는 품이 많이 들 수밖에 없다.

 

최근 진행한 디스크의 성능을 측정하는 업무를 복기해 보았다. 결과의 정확도를 높이기 위해 반복 실험을 진행하여 1,500개의 결과를 얻었다. 결과 파일에는 필요한 메트릭만 있는 것이 아니므로 원하는 값을 선택해 엑셀 시트에 입력해야 했다. 1,500개의 결과 파일을 일일이 열어 원하는 메트릭을 뽑아 시트에 입력하는 것은 말도 안 되는 중노동이었기 때문에, bash script를 이용해 필요한 메트릭만을 추출해 tsv 파일을 만들어냈다. 이 파일을 엑셀에서 읽어 들여 시트에 입력하고, 각 메트릭의 통계를 위한 수식들을 만들었다. 그리고 별도로 그래프를 생성했다. 어렵지 않은 작업이지만 반복되는 일들이 있어 생각보다 많은 시간이 걸렸다. 필요한 통계치를 도출하기 위해 알고 있어야 하는 함수와 옵션들도 많고, 확률분포 그래프라도 그리려면 더 많은 작업을 해야 한다.

 

이런 일을 쉽고 빠르게 하는 방법이 없을까? SAS나 SPSS와 같은 통계 전용 프로그램을 사용하면 어떨까? 아니면 수치 해석용 프로그램인 MATLAB은? 모두 진입장벽이 높은 도구들이다. 게다가 비싸기까지 하다.

 
 

하지만 R이 출동하면 어떨까? r_logo_300

R은 통계와 그래프를 위한 오픈소스 프로그래밍 언어이다. 다양한 통계 기법과 수치 해석 기법을 제공하며 사용자 제작 패키지를 추가하여 기능 확장도 가능하다.

일단 R의 프로젝트 사이트(https://cran.r-project.org)에서 제공하는 문서를 찾아보았다. 문서를 열자 영문 스크립트가 방대하게 펼쳐진다. 당장 필요한 것은 최대/최솟값과 중간값, 평균값 그리고 테스트 조건별 확률분포 그래프 정도인데 전체 문서를 정독하며 학습할 필요는 없을 것 같다. 필요한 내용만 체리피킹 해보자.

 

설치

R은 Windows, Mac, Linux를 모두 지원한다. 웹페이지를 참조하면 쉽게 설치할 수 있다.

  • Windows
    • 웹페이지에서 제공하는 인스톨러를 다운로드하여 설치한다.
  • Mac
    • 역시 웹페이지에서 인스톨러를 제공한다.
    • 그래프를 그리기 위해 XQuartz가 필요하니 설치되어 있지 않다면 먼저 설치하고 진행하자.
    • brew를 이용해 설치할 수도 있다.
    $ brew tap homebrew/science
    $ brew install Caskroom/cask/xquartz
    $ brew install r
    
  • Linux (Ubuntu)
    • apt를 이용해 쉽게 설치할 수 있다.
    $ sudo apt-get install r-base
    

 

실행

Unix 계열에서는 터미널을 열고 R을 입력하면 인터프리터가 실행된다. 윈도에서는 설치 후 생성된 R 아이콘을 클릭하면 GUI 콘솔이 실행된다. Mac에서도 GUI 콘솔을 사용할 수 있다.

$ R

R version 3.3.2 (2016-10-31) -- "Sincere Pumpkin Patch"
Copyright (C) 2016 The R Foundation for Statistical Computing
Platform: x86_64-apple-darwin13.4.0 (64-bit)

R은 자유 소프트웨어이며, 어떠한 형태의 보증없이 배포됩니다.
또한, 일정한 조건하에서 이것을 재배포 할 수 있습니다.
배포와 관련된 상세한 내용은 'license()' 또는 'licence()'을 통하여 확인할 수 있습니다.

R은 많은 기여자들이 참여하는 공동프로젝트입니다.
'contributors()'라고 입력하시면 이에 대한 더 많은 정보를 확인하실 수 있습니다.
그리고, R 또는 R 패키지들을 출판물에 인용하는 방법에 대해서는 'citation()'을 통해 확인하시길 부탁드립니다.

'demo()'를 입력하신다면 몇가지 데모를 보실 수 있으며, 'help()'를 입력하시면 온라인 도움말을 이용하실 수 있습니다.
또한, 'help.start()'의 입력을 통하여 HTML 브라우저에 의한 도움말을 사용하실수 있습니다
R의 종료를 원하시면 'q()'을 입력해주세요.

> 

 

데이터 읽어들이기

사용할 데이터는 worker의 개수를 10에서 250까지 변경하며 각 케이스의 bandwidth를 30회 반복 측정한 결과이다. bash script를 이용해 다음과 같은 포맷의 tsv 파일을 만들었다.

$ cat result.tsv
worker  bandwidth
010     370.4
020     356.4
030     291.2
040     162.4
050     166.9
060     160.0
070     156.2
080     243.3
090     275.0
100     258.6
...

이 파일을 R에서 읽기 위해서는 read.table() 함수를 사용하면 된다.

> result <- read.table('./result.tsv', header=TRUE, sep='\t')
> result
    worker bandwidth
1       10     370.4
2       20     356.4
3       30     291.2
4       40     162.4
5       50     166.9
6       60     160.0
7       70     156.2
8       80     243.3
9       90     275.0
10     100     258.6
...

read.table() 함수에는 수 많은 옵션이 있지만, 예제에서 사용한 정도만 알면 csv, tsv 파일은 쉽게 읽을 수 있다.

  • header=TRUE
    • TRUE이면 파일의 첫 줄을 header로 사용한다.
    • FALSE이면 자동으로 V1, V2… 형태의 컬럼 이름이 붙는다.
    • default 값은 FALSE이다.
  • sep='\t'
    • 컬럼 구분할 구분자를 설정한다.
    • csv의 경우 tab 문자 대신 comma를 지정해주면 된다.
    • default 값은 하나 이상의 white space이다. space와 tab의 경우 구분자를 지정하지 않아도 된다.

이제 result라는 데이터셋 객체에 tsv 파일의 데이터가 로드되었다. read.table() 함수 외에도 읽을 파일 포맷에 따라 read.csv(), read.delim() 등의 함수를 사용할 수도 있다. read.csv()는 header=TRUE, sep=',', read.delim()는 header=TRUE, sep='\t'이 default 옵션이다.

 

통계치 얻기

전술했지만 필요한 통계치는 최대/최솟값과 중간값, 평균값이다. 엑셀에서는 각각 수식을 작성하고 범위를 지정하면 얻을 수 있는 값들이다. R을 이용하면 더 간단하고 빠르게 모든 값을 얻을 수 있다. 요약 통계를 출력하는 summary() 함수를 제공하기 때문이다.

> bw <- result$bandwidth  # result 데이터셋의 bandwidth 컬럼 데이터를 bw 객체에 할당

> summary(bw)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.
  156.2   263.2   280.3   284.3   297.3   383.3 

예제는 result 데이터셋의 bandwidth 컬럼이 가진 모든 값을 대상으로 최대/최솟값, 1사/3사분위값, 중간값, 평균값을 계산해 출력해준다. 엑셀처럼 각각의 함수를 이용해 계산할 수도 있다. min(), max(), median(), mean() 등의 함수를 이용하면 된다.

 

summary()와 유사한 fivenum(), quantile() 함수도 제공되는데, 평균값을 제외한 나머지 값들을 출력해준다. 다만 quantile() 함수는 확률적으로 균등하게 자른 값을 출력해주기 때문에 summary()나 fivenum()의 출력값과 결과가 다를 수 있다.

> fivenum(bw)
[1] 156.20 263.15 280.30 297.35 383.30

> quantile(bw)
     0%     25%     50%     75%    100%
156.200 263.225 280.300 297.325 383.300

 

이 밖에도 통계하면 떠오르는 분산, 표준편차를 계산해주는 함수도 제공한다. 각각 var()sd() 함수다.

> var(bw)
[1] 1451.567

> sd(bw)
[1] 38.09944

이처럼 R이 기본 제공하는 함수들을 이용하여 간단하게 필요한 통계치들을 모두 얻을 수 있다.

 

그래프 그리기

통계치를 얻었으니 이제 확률분포 그래프를 그릴 차례다. 확률분포 그래프는 셀을 잘게 잘게 쪼갠 히스토그램을 이용해 그린다고 배웠던 것 같은 느낌적 느낌이 기억 저편에 흐릿하다. 일단 그려보자. 히스토그램은 hist() 함수를 이용하여 그릴 수 있다.

> hist(bw, breaks=100, probability=TRUE)
  • breaks=100
    • 히스토그램 셀의 개수
    • 숫자가 커질수록 선형에 가까워진다.
    • default 값은 10
  • probability=TRUE
    • Y축을 출현 확률로 지정
    • FALSE인 경우, Y축은 출현 빈도를 나타낸다.
    • default 값은 FALSE

histogram

가운데가 불룩한 정규분포도에 정확히 부합하지는 않지만 비슷한 결과가 나오기는 했다. 정규분포도와 다른 이유는 데이터셋의 문제다. worker의 개수를 10에서 250까지 변경하며 bandwidth를 반복 측정했던 데이터이기 때문에 worker 개수 별 분포가 섞여 있는 결과가 나온 것이다. 정확한 그래프를 얻기 위해서는 worker의 개수를 각각의 집단으로 하는 집단별 히스토그램을 그려야 한다.

25개의 히스토그램을 그리자니 몹시 귀찮다. 한꺼번에 그릴 수는 없을까? 물론 방법은 있다.

 

박스플롯(boxplot)

박스플롯은 fivenum() 또는 quantile() 함수로 얻을 수 있는 5가지 통계치(최솟값, 1사분위값, 중간값, 3사분위값, 최댓값)을 이용해 그리는 상자 형태의 그래프다. 히스토그램과는 다르게 집단이 여러 개인 경우에도 하나의 그래프에 표현할 수 있다. R에서는 박스플롯 그래프를 그리기 위해 boxplot() 함수를 제공한다.

> worker <- report$worker  # result 데이터셋의 worker 컬럼 데이터를 worker 객체에 할당
>
> boxplot(bw~worker, ylim=c(0, 400), xlab=~Workers, ylab=Bandwidth~MB/s)
>
> grid()  # 그래프에 grid 표시
  • bw~worker
    • y~grp, 그룹별 numeric vector를 나타내는 수식
    • worker의 개수 별 그룹을 만들고 각각의 bandwidth를 입력하겠다는 의미
  • ylim=c(0, 400)
    • y축의 범위를 0에서 400으로 설정
  • xlab, ylab
    • x축과 y축의 label 설정

boxplot

이상한 그래프가 그려졌다. 그래프만 봐서는 무슨 의미인지 잘 모르겠다. 좀 더 자세히 알아보자.

boxplot_singlebox

박스는 1사분위값과 3사분위값을 경계로 그려지며 박스 안의 선은 중간값이다. 박스는 전체 분포의 50%에 해당한다. LIF와 UIF는 각각 하한값 상한값을 나타내며 두 값의 사이는 전체 분포의 약 99%에 해당한다. LIF와 UIF 밖의 값들은 이상 수치로 간주하고 통계에서는 제외한다.

이를 정규분포도와 비교해보면 각각의 박스플롯은 그룹별 정규분포를 나타낸다는 것을 알 수 있다.

boxplot_vs_norm_dist

 

이런 그래프를 그려야 하는 이유가 무엇일까? 앞서 설명한 바와 같이, 이 그림은 worker 갯수 별 bandwidth의 정규분포를 박스플롯으로 나타낸 것이다. 즉, multi process 환경에서 디스크의 대략적인 bandwidth를 한눈에 쉽게 알아보기 위함이다.

앞의 박스플롯 그래프를 다시 보면 대부분의 박스가 250~300MB/s 사이에 분포한다는 것을 쉽게 알 수 있다. 따라서 이 디스크의 bandwidth는 대략 250~300 MB/s라 할 수 있다.

 
 

마치며…

… 확실히 확률과 통계는 어렵다. 배운지 20년이 다 되어가니 다시 공부해도 잘 모르겠다. 손으로 계산하는 것은 상상도 못 할 것 같다. 하물며 엑셀을 이용한다 하더라도 쉽지 않은 작업일 것이다. R은 이런 복잡한 계산을 순식간에 처리해준다. 필요한 내용만 빠르게 학습한 수준이지만, 앞으로 있을 여러 가지 성능측정 시험을 정리하는 데 유용하게 사용할 수 있을 것 같다.

 
 

글로 다이어그램을 그려요 – PlantUML

소프트웨어 설계를 하다 보면 UML 다이어그램으로 표현해야 할 일이 종종 있다. 객체지향 설계이거나, 여러 가지 모듈이 연동되어 동작하는 시스템이거나 또는 상태 변화를 설명해야 할 때 UML 다이어그램은 아주 좋은 표현 방법이다.

다이어그램은 그림이다. 컴퓨터를 이용해 그림을 그리려면 그림판과 같은 도구가 필요하다. (물론 그림판으로 다이어그램을 그리는 장인은 게으른 개발자 중엔 없으리라 생각한다) 당연하게도 오래전부터 UML 다이어그램을 그리기 위한 많은 도구가 개발되어 사용되고 있다. 역사와 전통을 가진 IBM의 Rational 시리즈, Boland의 Together Architect, MS의 Visio 그리고 국산 UML 도구의 대명사 Plastic을 오픈소스화한 StarUML 등이 대표적이라 할 수 있다. UML 도구는 아니지만, PowerPoint나 Keynote 같은 프레젠테이션 도구를 이용하는 ppt 장인들도 있다. 모두 강력한 도구임은 분명하다. 그런데 모두 GUI를 가진 도구이다. 마우스를 이용하기 위해 키보드에서 손이 떨어지면 생산성도 떨어질 것 같아 자괴감이 드는 개발자들에게는 굉장히 귀찮은 도구가 아닐 수 없다.

 

키보드만으로 다이어그램을 그릴 수는 없을까?

이 화려한 그래픽을 보라!

MUD 게임의 이 화려한 그래픽을 보라!

우리는 이미 오래전 키보드만으로 진행하는 텍스트 기반의 온라인 RPG 게임인 MUD를 경험했다. 커맨드를 입력하고, 뇌내(腦內) 그래픽카드를 풀가동해 머릿속에 화려한 맵을 그리며 게임을 했던 바로 그 경험이다. 연식 인증인가? 그 경험처럼 텍스트만으로 다이어그램을 표현해보자.

 

Alice -> Bob: Authentication Request
Alice <- Bob: Authentication Response

 

plantuml_logo예제는 Alice가 Bob에게 인증을 요청하고 Bob이 요청에 대한 응답을 주는 절차를 표현한 간단한 문장이다. 이름과 화살표, 그리고 동작을 직관적으로 설명하고 있다. 그대로 다이어그램으로 바꿀 수 있으면 좋을 것 같다. 그리고 그런 일이 실제로 가능하다. 오픈소스 UML 도구인 PlantUML을 이용하면 된다.

PlantUML을 이용해 예제를 시퀀스 다이어그램으로 변환하면 다음 그림과 같다.

auth

Awesome!! GUI 도구를 이용해 같은 다이어그램을 그리려면 인스턴스 둘을 끌어다 놓고, 라벨링을 두 번 하고, 화살표를 두 번 연결하고, 또 라벨링을 두 번 해야 한다. 우리의 손은 마우스와 키보드를 넘나들며 바쁜 시간을 보낼 수밖에 없다. 게다가 칼각을 원하는 강박증 환자라면 어긋난 1 ~ 2 픽셀에 스트레스를 받으며 저주받은 손을 원망하고 있을 것이 뻔하다.

 

 

설치 방법

PlantUML은 Java로 구현되어 있으므로 JVM만 돌아갈 수 있으면 어떠한 머신에서도 동작한다. 가장 쉬운 설치방법은 웹사이트의 다운로드 페이지에서 미리 컴파일된 jar 파일을 받아 사용하는 것이다. 이클립스나 IntelliJ를 이용한다면 플러그인으로 설치할 수도 있다. 역시 웹사이트를 참조하자.

키보드에서 손을 떼기 싫어서 PlantUML을 사용하려는데, 웹사이트에서 내려받기 위해 마우스를 잡아야 한다는 것이 모순으로 느껴질 수도 있다. 그런 키보드 성애자분들에게는 cli를 이용해 설치하는 방법을 추천한다.

  • Devian 계열 Linux
$ sudo apt-get install plantuml
  • Redhat 계열 Linux
$ sudo yum install plantuml
  • macOS
$ brew install plantuml

 

 

사용 방법

시퀀스 다이어그램을 표현했던 앞의 예제를 PlantUML 문법으로 표현하면 다음과 같다.

@startuml 
Alice -> Bob: Authentication Request 
Alice <- Bob: Authentication Response 
@enduml

그대로 txt 파일로 저장하고 plantuml을 실행하면 위와 같은 다이어그램 이미지 파일이 생성된다.

$ plantuml filename.txt

 

클래스 다이어그램은 다음과 같이 표현할 수 있다.

@startuml
class Class01 {
  String data
  void methods()
}
Class01 <|-- Class02
@enduml

거의 코드 수준이다. 개발자에게 이보다 더 좋은 문법이 있을 수 없다. 역시 txt 파일로 저장하고 plantuml을 실행하면 다음과 같이 클래스 다이어그램이 생성된다.

class

 

그렇다면 스테이트 다이어그램은 어떨까?

@startuml
[*] --> Running: Start
Running --> Pause: Pause
Pause --> Running: Resume
Running --> [*]: Stop
@enduml

state

시퀀스 다이어그램의 문법과 거의 흡사한 문법으로 스테이트 다이어그램을 얻을 수 있다.

이 밖에도 유즈케이스, 액티비티, 컴포넌트 다이어그램 등 UML 다이어그램 대부분을 간단하고 직관적인 문법으로 표현할 수 있다. 다이어그램에 따라 문법이 조금씩 다르지만, 전체적으로 볼 때 직관적이다 하는 그런 기운이 느껴진다. 자세한 문법은 역시 웹사이트를 참조하자. 한글 매뉴얼을 제공하고 있어 쉽게 학습할 수 있다.

 

약을 한번 팔아보자.

다이어그램을 그려야 하는데 다음과 같은 문제가 있어 어려운 분에게 이 약 PlantUML을 강력하게 추천한다.

  • 키보드에서 손을 떼면 생산성이 떨어진다고 생각하는 키보드 성애자
  • 마우스를 사용하면 생산성이 떨어진다고 생각하는 마우스 혐오자
  • 1 ~ 2 픽셀이 어긋나 있는 것을 두고 볼 수 없는 라인 강박증 환자
  • 회사에서 고가의 UML 도구를 사주지 않아 파워포인트로 한 땀 한 땀 그리고 있는 PPT 장인
  • 마우스 때문에 손목이 아픈 손목 터널 증후군 환자
  • bash와 vi가 없으면 아무것도 할 수 없는 리눅스 덕후
  • MUD 게임의 향수에 젖어 있는 아재 개발자

 

PlantUML로 글 쓰듯이 다이어그램을 그려보자.