Introduce window function

Tidyverse

“데이터 분석의 중요성이 대두되면서 1990년대 말부터 많은 DBMS는 행과 행간의 관계를 정의하거나 행과 행간을 비교하고 연산하는 함수의 기능을 SQL에 추가하기 시작했다. 그리고 이들을 window function이라 불렀다.”

유충현
2020-03-22

windows function

Tidyverse가 조작하는, 특히 dplyr가 조작하는 데이터 객체인 data.frame이나 tbl_df 객체는 DBMS(Data Base Management System)의 table과 유사하다. 이들은 컬럼(R에서는 변수, Variable)과 컬럼간의 연산이나 비교가 비교적 쉽다. 또한 집합에 대한 집계도 쉽게 처리한다. 그러나, 행(R에서는 관측치, Observation)과 행간의 관계를 정의하거나 행과 행간을 비교하고 연산하는 것은 쉽지 않다.

DBMS에서는 SQL(Structured Query Language)을 이용해서 데이터를 조작한다. 그리고 Tidyverse에서는 dplyr 패키지가 마치 SQL과 유사한 방법으로 데이터를 조작한다.

데이터 분석의 중요성이 대두되면서 1990년대 말부터 많은 DBMS는 행과 행간의 관계를 정의하거나 행과 행간을 비교하고 연산하는 함수의 기능을 SQL에 추가하기 시작했다. 그리고 이들을 window function이라 불렀다.
window function을 활용하면 복잡한 프로그램을 통해 행과 행간의 연산을 수행하던 것을 간단한 SQL 표현으로 쉽게 해결할 수 있다.

dplyr에서 windows function 구현

dplyr 패키지를 사용하여 SQL의 windows function에 해당하는 기능을 수행해 보자.

데이터 생성

예제를 위한 데이터를 생성하기 위해서 quantmod 패키지로 KOSPI 지수와 KOSDAQ 지수를 가져온다. 2019-12-01부터 2020-03-20의 주가 지수를 수집했다.

library(quantmod)

KOSPI <- getSymbols('^KS11', from = '2019-12-01', to = '2020-03-20', auto.assign = FALSE)
KOSPI <- data.frame(market = "KOSPI",
                    date = as.Date(row.names(as.matrix(KOSPI))), 
                    open = as.vector(KOSPI[, 1]), 
                    high = as.vector(KOSPI[, 2]), 
                    low = as.vector(KOSPI[, 3]), 
                    close = as.vector(KOSPI[, 4]), 
                    volume = as.vector(KOSPI[, 5]), 
                    adjusted = as.vector(KOSPI[, 6]))

KOSDAQ <- getSymbols('^KQ11', from = '2019-12-01', to = '2020-03-20', auto.assign = FALSE)
KOSDAQ <- data.frame(market = "KOSDAQ",
                     date = as.Date(row.names(as.matrix(KOSDAQ))), 
                     open = as.vector(KOSDAQ[, 1]), 
                     high = as.vector(KOSDAQ[, 2]), 
                     low = as.vector(KOSDAQ[, 3]), 
                     close = as.vector(KOSDAQ[, 4]), 
                     volume = as.vector(KOSDAQ[, 5]), 
                     adjusted = as.vector(KOSDAQ[, 6]))

문제정의

다음의 문제는 SQL에서 windows function을 사용해서 구할 수 있는 문제들이다.

KOSPI 데이터에서의 집계

zoo 패키지는 시계열 분석을 지원하는 패키지다. 그리고 windows functiontime windows function을 내포하는 것인만큼 이 패키지가 아주 유용하게 사용된다.

cumsum는 누적합을 구하는데 이용하는 함수고, rank는 순위를 구하는 함수다. 이미 여러번 사용해 보았을 것이다.

vol <- KOSPI$volume[1:5]
vol
[1] 385300 450000 347000 472200 440200
# 누적합
cumsum(vol)
[1]  385300  835300 1182300 1654500 2094700
# 순위
rank(vol)
[1] 2 4 1 5 3

diff는 차분을 구하는 함수로, 이전 거래일 대비 주가지수의 차이를 쉽게 구할 수는 있지만, 관측치의 개수보다 1개 적은 값을 반환하는 문제가 있다. 그리고 이 함수가 dplyr 내에서 사용되면 1개 모자란 값때문에 에러가 발생한다. 그래서 맨 앞에 NA를 추가하는 것이 필요하다.

library(dplyr)

close <- KOSPI$close[1:5]
close
[1] 2091.92 2084.07 2068.89 2060.74 2081.85
# 차분구하기
diff(close)
[1]  -7.849854 -15.180175  -8.149903  21.110108
# dplyr 내에서 사용
KOSPI %>% 
  head(n = 5) %>% 
  select(date, close) %>% 
  mutate(diff_close = c(NA, diff(close)))
        date   close diff_close
1 2019-12-02 2091.92         NA
2 2019-12-03 2084.07  -7.849854
3 2019-12-04 2068.89 -15.180175
4 2019-12-05 2060.74  -8.149903
5 2019-12-06 2081.85  21.110108

이동평균은 zoo 패키지의 rollapply 함수를 사용한다. 이 함수는 차수만큰 롤링라면서 특정 함수를 적용하는 기능을 수행한다. 이동평균이므로 mean 함수를 적용했다.

이상을 정리하여, 첫번제 문제인 KOSPI 지수 데이터의 집계를 다음과 같이 수행했다. 관측치가 많아서 마지막 10개 관측치만 출력해 본다.

KOSPI_summary <- KOSPI %>% 
  select(-(open:low), -adjusted) %>% 
  arrange(date) %>% 
  mutate(total_volume = cumsum(volume),
         rank_close = rank(close),
         diff_prev = round(c(NA, diff(close)), 2),
         avg_close_5 = round(zoo::rollapply(close, 5, mean, fill = TRUE, 
                                        align = "right", partial = TRUE)))

KOSPI_summary %>% 
  select(-market) %>% 
  tail(n = 10)
         date   close  volume total_volume rank_close diff_prev
66 2020-03-09 1954.77  666900     41200500          9    -85.45
67 2020-03-10 1962.93  638600     41839100         10      8.16
68 2020-03-11 1908.27  679700     42518800          8    -54.66
69 2020-03-12 1834.33  850300     43369100          7    -73.94
70 2020-03-13 1771.44 1035300     44404400          6    -62.89
71 2020-03-16 1714.86  682000     45086400          5    -56.58
72 2020-03-17 1672.44  649800     45736200          4    -42.42
73 2020-03-18 1591.20  728000     46464200          3    -81.24
74 2020-03-19 1457.64  977700     47441900          1   -133.56
75 2020-03-20 1566.15  817000     48258900          2    108.51
   avg_close_5
66        2031
67        2021
68        1990
69        1940
70        1886
71        1838
72        1780
73        1717
74        1642
75        1600

거래소별로 데이터의 집계

KOSPI와 KOSDAQ를 합쳐서 stock라는 객체를 생성한다.

stock <- KOSPI %>% 
  union(KOSDAQ)

stock %>% 
  arrange(date, market) %>% 
  tail(n = 10)
    market       date    open    high     low   close volume adjusted
141 KOSDAQ 2020-03-16  538.68  541.87  504.51  504.51   1200   504.51
142  KOSPI 2020-03-16 1805.43 1805.43 1714.38 1714.86 682000  1714.86
143 KOSDAQ 2020-03-17  488.02  518.72  483.51  514.73   1400   514.73
144  KOSPI 2020-03-17 1640.84 1722.97 1637.88 1672.44 649800  1672.44
145 KOSDAQ 2020-03-18  520.79  526.10  485.14  485.14   1300   485.14
146  KOSPI 2020-03-18 1686.12 1693.95 1591.12 1591.20 728000  1591.20
147 KOSDAQ 2020-03-19  501.59  502.12  419.55  428.35   1600   428.35
148  KOSPI 2020-03-19 1626.09 1626.09 1439.43 1457.64 977700  1457.64
149 KOSDAQ 2020-03-20  443.88  467.75  435.11  467.75   1200   467.75
150  KOSPI 2020-03-20 1498.49 1566.82 1466.48 1566.15 817000  1566.15

거래소별로 집계하는 방법은 매우 쉽다. 앞서 구한 스크립트에 그룹핑해서 집계할 변수를 group_by 함수에 적용하면 된다.

stock_summary <- stock %>% 
  select(-(open:low), -adjusted) %>% 
  group_by(market) %>% 
  arrange(date) %>% 
  mutate(total_volume = cumsum(volume),
         rank_close = rank(close),
         diff_prev = round(c(NA, diff(close)), 2),
         avg_close_5 = round(zoo::rollapply(close, 5, mean, fill = TRUE, 
                                        align = "right", partial = TRUE))) 

집계된 KOSPI 결과를 보면, 앞서 개별적으로 구한 결과와 동일하다.

stock_summary %>%   
  filter(market == "KOSPI") %>% 
  tail(n = 10) 
# A tibble: 10 x 8
# Groups:   market [1]
   market date       close  volume total_volume rank_close diff_prev
   <chr>  <date>     <dbl>   <dbl>        <dbl>      <dbl>     <dbl>
 1 KOSPI  2020-03-09 1955.  666900     41200500          9    -85.4 
 2 KOSPI  2020-03-10 1963.  638600     41839100         10      8.16
 3 KOSPI  2020-03-11 1908.  679700     42518800          8    -54.7 
 4 KOSPI  2020-03-12 1834.  850300     43369100          7    -73.9 
 5 KOSPI  2020-03-13 1771. 1035300     44404400          6    -62.9 
 6 KOSPI  2020-03-16 1715.  682000     45086400          5    -56.6 
 7 KOSPI  2020-03-17 1672.  649800     45736200          4    -42.4 
 8 KOSPI  2020-03-18 1591.  728000     46464200          3    -81.2 
 9 KOSPI  2020-03-19 1458.  977700     47441900          1   -134.  
10 KOSPI  2020-03-20 1566.  817000     48258900          2    109.  
# … with 1 more variable: avg_close_5 <dbl>

집계된 KOSDAQ 결과를 보면, KOSPI의 시황과 유사하다.

stock_summary %>%   
  filter(market == "KOSDAQ") %>% 
  tail(n = 10) 
# A tibble: 10 x 8
# Groups:   market [1]
   market date       close volume total_volume rank_close diff_prev
   <chr>  <date>     <dbl>  <dbl>        <dbl>      <dbl>     <dbl>
 1 KOSDAQ 2020-03-09  615.   1300        62400         10    -28.1 
 2 KOSDAQ 2020-03-10  620.   1400        63800         12      5.37
 3 KOSDAQ 2020-03-11  596.   1600        65400          8    -24.4 
 4 KOSDAQ 2020-03-12  563.   1600        67000          7    -32.1 
 5 KOSDAQ 2020-03-13  524    1500        68500          6    -39.5 
 6 KOSDAQ 2020-03-16  505.   1200        69700          4    -19.5 
 7 KOSDAQ 2020-03-17  515.   1400        71100          5     10.2 
 8 KOSDAQ 2020-03-18  485.   1300        72400          3    -29.6 
 9 KOSDAQ 2020-03-19  428.   1600        74000          1    -56.8 
10 KOSDAQ 2020-03-20  468.   1200        75200          2     39.4 
# … with 1 more variable: avg_close_5 <dbl>

Citation

For attribution, please cite this work as

유충현 (2020, March 22). Dataholic: Introduce window function. Retrieved from https://choonghyunryu.github.io/posts/2020-03-22-window_function/

BibTeX citation

@misc{유충현2020introduce,
  author = {유충현, },
  title = {Dataholic: Introduce window function},
  url = {https://choonghyunryu.github.io/posts/2020-03-22-window_function/},
  year = {2020}
}