R | Journal Entry Test | Fast track
Feb 11, 2024
코딩이 필수인 시대이다.
컴퓨터 활용 방식을 운전에 비유해 보자.
운전 | 컴퓨터 |
---|---|
운전대 핸들을 목적지에 가기 위하여 열심히 운전을 해서 목적지에 도착한다는 목적을 달성하고 주차 실력을 발휘하여 차를 안전한 장소에 주차한다. 다행히 길은 네비게이션이 알려주고 기어 변속은 자동으로 된다. | 솜씨 좋은 실력으로 컴퓨터를 열심히 타자를 치고 마우스 드래그를하고 클릭을 하면서 원하는 형태의 결과물을 만들어내고자 애쓴다. 오른손은 수 없이 마우스와 키보드를 오간다. 다행히 단축키를 열심히 배워서 오른손이 마우스와 키보드로 오가는 횟수를 줄였다. |
핵심적인 공통점은 사람이 계속해서 움직이면서 작동을 해야 한다는 것이다. 그리 생산적인 것 같지 않은 일을 계속해서 반복하는 것은 너무 따분하다.
Code는 컴퓨터에게 일을 시키는 명령어이다. 만약에 이 명령어가 제대로 써져 있다고 가정한다면 누군가 잘 작동하게 만들어 놓은 이 명령어를 복사해서 사용한다고 할 때 사용자가 그 명령어의 의미를 알고 있던 모르던 상관 없이 컴퓨터는 Code에 따라서 일을 한다. 몰라도 작동이 되기는 된다. 제대로 작동하는 Code라면 그게 대충 추상적으로 어떤 일을 하겠구나 하는 정도만 생각해도 그대로 움직인다. 일련의 절차를 반복해서 수행하기에 최고이다.
물론 무슨 말인지도 모르는데 컴퓨터가 마구 돌아가면 초보자의 입장에서는 초보운전자가 가속페달을 팍 밟았다가 급발진에 깜짝 놀라는 것처럼 컴퓨터가 움직이는 자체를 보고 공포에 질릴지도 모른다. 본인이 페달을 밟으면 차가 앞으로 쎄게 나갈 것이라는 것을 몰랐기 때문이다. 처음에는 놀라지만 페달을 자꾸 밟아보면 이것을 밟으면 차가 힘을 낸다는 것에 대하여 점차 익숙해지게 된다. Code가 처음에는 무섭지만 일단 자꾸 써보다 보면 점차 내 말을 듣는 기특한 글자로 받아들이게 된다. 누구든 처음은 어색하기 마련이다.
JE Tester by Joy 사용 설명서에서 설명했던 Journal Entry Test를 R로 쉽게 해결할 수가 있다.
몇 줄 안되는 Code만으로 Journal Entry Test를 쉽게 할 수 있다. 여기서 다루는 Code를 그대로 복사해서 사용하면서 동작하는 것을 보고 그 명령이 무엇을 의미하는지 알고 나면 추가적으로 궁금한 사항들은 Google 의 도움을 받아서 해결하거나 그 때 가서 R의 기능 원리에 대하여 차분하게 공부해 보는 것도 좋은 접근 방법인 것 같다.
우리 사람들도 아기로 태어나서 처음 말을 배울 때 ‘가나다라…’ 부터 배우거나 기초를 튼튼히 한다는 방식으로 배우지 않았다. 일단 들어보고 저러면 되나 하고 말을 뱉어 보니 동작을 하니까 그 말을 받아들이기 시작한 것이 모든 사람의 어린 시절에 언어를 배우는 모습이었을 것이다.
컴퓨터에게 R이라는 언어로 저렇게 말을 할 수도 있구나 하고 관찰하고 그게 무슨 말인지 이해해 보고 그 다음에 나의 말을 하는 것이 순리이다.
R은 Journal Entry Test를 하라는 프로그램은 아니지만 그 외에 엄청나게 많은 것을 할 수 있는 방대한 능력을 소유한 프로그램이다. Journal Entry Test 수행 정도는 R에게 있어 특별한 일도 아니다. 하지만 이것을 해 보는 것이 더 어려운 Data 분석이나 상상도 못했던 놀라운 기능을 실현하기 전에 연습하는 좋은 기초 연습이 될 수가 있다.
JE Tester by Joy 사용 설명서에서와 같이 기본 시나리오로 전표 Data의 완전성을 검증하고 선택 시나리오 한 가지 상대계정분석(Corresponding Accounts Test)을 해보고자 한다. 실습에 사용하는 Data는 JE Tester by Joy Tutorial 에서 사용한 전표 데이터를 사용하였다.1
Data 준비하기
Rstudio에는 작업 환경을 분할해서 관리할 수 있는 훌륭한 기능이 있다. New Project로 Project를 생성하고 Project를 열어서 작업을 하면 작업 환경이 지정 되고 폴더도 생성되어 그 안에서 독립된 환경을 갖추어 작업 할 수 있다. Alt + f, p 를 눌러서 Project를 생성한다.
다운로드 링크에서 우측 상단의 ↓ Code 을 클릭하여 Download ZIP 을 통해서 파일을 다운로드 받는다.
다운로드 받은 파일은 압축을 풀어 je.csv
, CYTB.xlsx
, PYTB.xlsx
세 개의 파일을 생성한 프로젝트 폴더에 옮겨 담는다.
파일 불러오기 (Data 읽기)
Excel은 파일을 열면 열심히 읽는다. 대체로 큰 Data는 열리는데 오래 걸린다. 1% ~ 100%의 진행률을 나타내 주기도 한다. R은 파일을 열기 위한 명령어를 써주어야 읽어온다. R은 파일을 읽어서 메모리에 한꺼번에 다 올려놓고 쓴다. 메모리의 한계에 봉착할 수 있다는 단점이 있으나 일단 읽어 오는데 성공하고 나면 그 다음은 상당한 속도로 일이 해결 된다. Excel이 큰 Data를 읽어서 열었어도 가공을 시작하면 버벅거리는 것과 대조적이다. R은 메모리에 읽어온 내용으로 작업을 하므로 원본 파일은 그대로 있다. 이것도 Journal Entry Test에 있어 장점이다. JE test를 하면서 원본 파일을 보존해야 한다는 것은 기본이다. R이나 다른 언어 입장에서는 너무나 당연하지만 그것이 Excel에서는 중요한 고려사항이었다. Excel은 읽기 전용으로 읽지 않은 한 잘못 누르면 오타가 기록되기도 하고 물 위에 떠다니는 듯 셀들이 출렁거려 불안하다. 그렇다고 해서 Excel이 나쁘다는 것은 아니다. 용도가 다르다. 적당한 크기의 내용을 눈으로 쓰고 연산하고 봐가면서 무엇인가 할 때는 Excel이 최고이다.
Prerequisite
R | 프로그래밍 언어 R 설치하기 에서와 같이 설치하고 tidyverse 패키지가 정상적으로 설치되었다는 것을 전제하여 다음을 스크립트 창에 조금씩 써보거나 복사해서 붙여 넣는다. 모든 명령에는 우리말로 번역을 하고자 하였다. 명령어는 대부분 tidyverse이고 약간의 base R 명령어가 포함되었다.
코드 블럭을 설정하거나 프롬프트가 위치한 행에서 ‘Ctrl + Enter’를 누르면 선택 부분이 Console로 들어가서 실행된다.
library(tidyverse)
je <- read_csv('je.csv', locale=locale('ko',encoding='euc-kr'))
print(je)
- tidyverse 패키지를 불러온다. 이렇게 명시해 주면 tidyverse의 기능을 R에 붙인다고 생각하면 된다.
- 프로젝트 폴더 경로에 전표 파일을 두고 읽는 명령으로 읽어와서 je라고 이름 붙인다.(이름 붙인 것을 객체라고 한다. je 객체) 윈도우즈 기준의 파일의 경우 파일명 뒤의 encoding 명시를 해주면 글자가 깨지지 않고 읽어온다. 윈도우 기준의 한글 형식에 맞출 수 있다. read_csv 명령 함수는 utf-8 encoding을 기본으로 하기 때문에 한글이 깨질 수 있다. 2기가 정도 되는 data file이 100초 정도 걸린다. (컴퓨터 성능에 따라서 다르다.) 몇십메가 정도의 data file은 몇초가 되기 전에 읽어버린다.2
- 이름 붙인 je 객체를 Console에 나타내 확인한다.
A01 Data Integrity Test
colnames(je)
str(je)
table(is.na(je))
sapply(je, function(x) sum(is.na(x)))
max(je$JEDATE)
min(je$JEDATE)
range(je$JEDATE)
- 읽어온 je의 변수명(열 이름, column명)을 확인한다.
- je의 구조를 파악한다.
- je에 있는 Null 값을 집계한다.
- je의 변수명에 따른 Null값을 각각 집계하여 보여준다.
- 전표 일자의 가장 큰 값을 나타낸다.
- 전표 일자의 가장 작은 값을 나타낸다.
- 전표 일자의 가장 큰값과 작은 값을 나타낸다.
A02 전표번호 별 차대변 일치검증
A02 <- je |>
select(JENO, DR, CR) |>
mutate_all(~replace(., is.na(.), 0)) |>
group_by(JENO) |>
summarise(DR_sum=sum(DR),
CR_sum=sum(CR)) |>
mutate(Differ= DR_sum - CR_sum)
A02_Differ <- count(A02[A02$Differ > 0, ])
print(A02_Differ)
- 전표번호, 차변금액, 대변금액 열을 선택(select)한다. 선택한 세 개의 열에 Null 값 (NA)이 있으면 0으로 모두 바꾼다.(mutate_all) Null 값(NA)은 계산의 결과가 무조건 NA가 되면서 원하지 않은 불편한 결론을 내기 때문이다. 전표번호별로 묶어서(group_by) 전표번호별 차변의 합계를 DR_sum의 열 이름으로 전표번호별 대변의 합계를 CR_sum의 열 이름으로 정리한다.(summarise) 전표번호별 차변 금액과 대변 금액 합계를 각각 차이를 계산하여 Differ라는 열 이름으로 추가(mutate)한다.
- 전표번호별 차변 금액 합계와 대변 금액 합계의 차이 Differ열의 전체 합계를 계산한다.
- 계산한 차이의 합계를 확인한다.
A03 시산표 Reconciliation 검증(Trial Balance Rollforward Test)
시산표 검증에는 추가로 시산표 파일에서 Data를 읽어와야 한다.
시산표는 엑셀로 되어 있기 쉬우므로 엑셀 파일에서 바로 읽어온다. 전표 파일은 엑셀로 읽으면 csv 파일로 읽어 오는 것보다 훨씬 오래 걸린다. 대용량 Data는 엑셀이 담을 수 있는 행을 넘어버릴 것이므로 애초에 엑셀일 수가 없다. 시산표 정도는 프린터로 종이에 프린트 해도 두세장이면 담아 낼 수 있으므로 엑셀 파일로 바로 읽어도 상관 없다. 명령을 ‘Ctrl + Enter’로 누르는 즉시 읽어들인다.
if(!require(readxl)){install.packages("readxl");library(readxl)}
CYTB <- read_excel('CYTB.xlsx')
PYTB <- read_excel('PYTB.xlsx')
table(is.na(CYTB))
sapply(CYTB, function(x) sum(is.na(x)))
table(is.na(PYTB))
sapply(PYTB, function(x) sum(is.na(x)))
- 엑셀을 읽기 위한 기능을 R에 부착한다. 생김새가 복잡한 이유는 readxl패키지가 설치되어 있지 않은 경우에는 설치하고 이미 설치되어 있는 경우에는 설치하지 않고 불러오라는 의미를 내포하였기 때문이다. 아직 readxl 패키지를 설치하지 않았으므로 바로 library 명령을 썼다가 오류가 나서 놀라서 충격 받지 않기를 바라는 배려이다.
- 엑셀 파일 형태의 당기 시산표를 읽어들인다. (R이 칼질하기 좋게 R의 도마에 올린다.)
- 엑셀 파일 형태의 전기 시산표를 읽어들인다.
- 당기 시산표의 Null 값을 확인한다.
- 당기 시산표의 변수별(열 이름)로 NA 갯수를 확인한다.3
- 전기 시산표의 Null 값을 확인한다.
- 전기 시산표의 변수별(열 이름)로 NA 갯수를 확인한다.
Rstudio의 Powerful한 기능 View로 시산표를 볼 수 있다. 이 기능 때문이라도 Rstudio를 애정한다.
View(CYTB)
아주 자연스럽게 느껴지는 엑셀 같은 화면을 만날 수 있다. 물론 읽기 전용이다. 각 변수(열)에 필터 값으로 함축해 볼 수도 있다. 다른 언어에서 찾아 볼 수 없는 독보적인 기능이다. Rstudio는 시원하게 작업중인 중간에 들여다 볼 수가 있다. 무조건 말로만 해야 하는 것이 아니다. 보면서 해야 한다. 말로 하고 듣기도 하지만 볼수도 있어야 당연하다. 우리는 엑셀에 익숙하므로 즉각적으로 표를 보고 싶다. View(je) 로 전표 데이터를 볼 수도 있고 환경 창에 있는 변수를 마우스 클릭해도 View() 기능이 작동한다.
CYTB <- drop_na(CYTB, ACCTCD)
PYTB <- drop_na(PYTB, ACCTCD)
CYTB_FP <- CYTB[1:99,]
CYTB_PL <- CYTB[100:length(CYTB$ACCTCD),]
PYTB_FP <- PYTB[1:103,]
tail(CYTB_FP)
head(CYTB_PL)
tail(PYTB_FP)
CYTB_FP <- full_join(CYTB_FP, PYTB_FP, by='ACCTCD') |>
mutate_all(~replace(., is.na(.), 0)) |>
mutate(balance = (DRSUM.x - CRSUM.x) -(DRSUM.y - CRSUM.y)) |>
select(ACCTCD, balance)
CYTB_PL <- CYTB_PL |>
mutate(balance = (DRSUM - CRSUM)) |>
select(ACCTCD, balance)
CYTB_balance <- bind_rows(CYTB_FP, CYTB_PL)
- 결측값(Null, NA)이 ACCTCD 열에 있었으며 결측값 때문에 어려움이 발생할 수 있으므로 결측을 없애고 시작한다.
- 전기 시산표에서도 결측을 없앤다.
- 결측을 없애고 나서 View(CYTB) 기능으로 읽어들인 시산표를 보니 99행이 시산표 중 재무상태표의 마지막 행이다. 시산표의 재무상태표 부분을 나누기 위하여 1행 부터 99행까지를 썰어낸다.(slicing)
- 시산표의 손익계산서 부분을 뜯어내기 위하여 100행부터 끝까지 썰어낸다.
- 시산표에서 썰어낸 당기재무상태표 하단을 확인한다. 재무상태표 끝단이 썰렸는지 확인.
- 시산표에서 썰어낸 당기손익계산서 상단을 확인한다. 손익계산서 시작점부터 썰렸는지 확인.
- 전기시산표에서 썰어낸 전기재무상태표 하단을 확인한다. 재무상태표 끝단이 썰렸는지 확인.
- 전기 시산표의 재무상태표 부분을 썰어낸다. 전기 시산표가 필요한 이유는 시산표의 재무상태표 부분에는 기초잔액이 포함되어 있다. 전표에는 기초잔액이 포함되어 있지 않고 당기의 변동분만을 포함하고 있다. 만약 전표가 기초 잔액을 포함하고 있다면 이 Code는 수정되어야 할 수도 있다. 일반적으로 전표는 당기 변동을 분개로 포함하고 시산표의 재무상태표 부분은 기초잔액과 당기 변동을 포함하여 잔액이 기말 잔액이 되도록 한다. 기초잔액이 없이는 기말 잔액이 존재할 수 없다.
- 당기시산표에 전기 시산표의 재무상태표 부분을 계정과목별로 맞춰서 붙인다. 이 때 당기시산표에 있는 계정과 전기시산표에 있는 계정이 모두 포함되도록 붙여야 한다. 전기에 변동이 있었으나 당기에 변동이 없었던 항목이 누락되지 않아야 하기 때문이다.
- 시산표의 손익계산서 부분은 당기에 발생한 잔액만 있으면 된다. 재무상태표처럼 기초잔액이라는 개념이 없다.
- 재무상태표의 잔액에서 기초잔액을 제거한 결과와 시산표의 손익계산서 부분의 잔액을 합하여 전표에서 당기에 변동이 있었던 금액을 하나로 붙인다.
A03 <- je |>
select(ACCTCD, DR, CR) |>
mutate_all(~replace(., is.na(.), 0)) |>
group_by(ACCTCD) |>
summarise(DR_sum=sum(DR),
CR_sum=sum(CR))
A03 <- left_join(A03, CYTB_balance, by = 'ACCTCD')
table(is.na(A03))
sapply(A03, function(x) sum(is.na(x)))
A03 <- A03 |> mutate_all(~replace(.,is.na(.), 0)) |>
mutate(Differ = (DR_sum - CR_sum - balance))
A03_Differ <- count(A03[A03$Differ != 0, ])
A03[A03$Differ != 0, ]
- 전표에서 계정과목별 차변금액과 대변금액 합계를 계산하여 준비한다.
- 시산표에서 준비한 당기 변동금액(전표에서 계산한 계정과목별 당기 변동 금액)을 전표에서 계산한 차변금액과 대변 금액의 합계에 붙인다.
- 결측값의 갯수를 확인한다.
- 변수별 결측값의 갯수를 확인한다.
- 결측값을 0으로 바꾸고 전표의 차변합계에서 대변합계를 빼서 당기변동분을 계산하고 시산표에서 계산한 당기변동분을 빼서 차이를 낸다.
- 차이가 0이 아닌 항목 갯수를 세어 A03_Differ 변수로 지정한다.
- A03 변수에서 차이가 0이 아닌 항목을 찾아 나타낸다. 이익잉여금 항목 하나만 나타나면 이상이 없다고 생각할 수 있다.
B01 매출의 상대계정분석
Corr_Acc = '40401'
B09_main <- je |> filter(ACCTCD == Corr_Acc) |>
select(JENO, ACCTCD)
B09_Corr <- je |>
select(JENO, ACCTCD)
B09 <- semi_join(B09_Corr, B09_main, by = 'JENO')
B09 <- B09 |> filter(!is.na(ACCTCD)) |>
count(ACCTCD)
- 상대계정분석에서 기준이 되는 본계정코드를 설정한다.
- 전표 데이터에서 본계정이 포함된 전표번호를 필터링(filter)한다. (행 선택)
- 전표 데이터에서 전표번호와 계정코드 열을 선택(select)한다. (열 선택)
- 선택한 전표번호와 계정코드에서 본계정이 포함된 전표번호만 남겨서 B09 변수로 지정한다.
- 본계정과 상대계정의 전표 갯수를 세어 B09 변수로 지정한다.
View(B09) 명령으로 확인해 보거나 B09를 Console에 쳐서 확인해 볼 수도 있다. 계정코드를 기준으로 본계정과 상대계정들이 모여 있다. 계정과목명이 보이지 않아 보기에 불편하므로 계정과목명을 붙인다.
B09_name <- je |> select(ACCTCD, ACCT_NM) |> distinct()
B09 <- left_join(B09, B09_name, by = 'ACCTCD')
- 계정코드와 계정과목명 열을 선택하고 고유의 값 하나씩만 남긴다.4
- 계정코드에 맞는 계정과목명을 B09 에 붙인다.
write file
R에 파일의 Data를 읽어와서 요리가 되었는데 이 요리의 결과물을 다시 파일로 갖고 싶다. 과정을 거쳐 오면서 결정적 증거물은 메모리에 객체로 꼬리표를 남겨 두었다. 이 객체를 파일로 저장하고 싶다면 다음 명령어들로 할 수 있다.
A02 |> write_csv('A02.csv')
A03 |> write_csv('A03.csv')
B09 |> write_csv('B09.csv')
“나는 csv 싫다. 엑셀로 저장할거다.” 하는 마음이라면 다음과 같이 하면 된다.
if(!require(writexl)){install.packages("writexl");library(writexl)}
A02 |> write_xlsx('A02.xlsx')
A03 |> write_xlsx('A03.xlsx')
B09 |> write_xlsx('B09.xlsx')
Coding의 매력은 강력한 재사용성이다. Coding을 하느라 끙끙 앓을 수도 있지만 한번 앓고 나면 그 과정은 내가 아니라 컴퓨터가 한다. 엄청난 속도로 일처리에 부스터를 달고 날아가는 기분이다.
반드시 한번 시도해 볼만 하다. 개발자가 되고자 하는 것이 아니다. 컴퓨터를 말로 부려먹는 느낌을 가져보고 싶은 것이다. 우리가 코딩을 해야 하는 이유는 재현성으로 인한 자동화로 한번 하고 나면 다음에 또 할 때는 간단하게 컴퓨터가 일련의 과정을 하도록 할 수 있어서라고 생각한다.
프로그래밍 기술이야 말로 데이터를 다루는 모든 사람에게 필수라는 강한 신념을 가지고 있다. 간편하긴 하지만 GUI는 근본적으로 제약이 많다. 왜냐하면 양질의 데이터분석을 위해 반드시 필요한 다음 3가지 요소를 방해하기 때문이다.
- 재현성(Reproducibility)
과거 분석을 그대로 재현할 수 있는지 나타내며 좋은 연구를 위해 결정적이다.
- 자동화(Automation)
데이터가 (늘 그렇듯) 변하더라도 빠르게 다시 분석을 해낼 수 있는 능력을 말한다.
- 정보 전달(Communication)
코드는 그저 텍스트다. 이메일이나 구글, 스택 오버플로 등 많은 곳을 통해 쉽게 주고 받을 수 있으며, 또한 배우면서 도움을 받기도 쉽다.
프로그래밍을 겁내지 마라! 정확한 동기와 의지가 있다면 누구나 배울 수 있다.
Further study
-
‘이 데이터는 충분히 크기가 크지 않은데 이 데이터보다 더 큰 데이터를 다루는데 있어서도 문제가 없을까?’ 하는 의문을 가질 수도 있다. R은 이보다 훨씬 큰 용량의 자료도 다룰 수 있다. code를 다른 기능의 코드(data.table)로 바꾸는 등의 방법을 사용할 수 있으며 2~3기가 바이트를 다루는데는 여기서 사용하는 Code로도 충분하다. 적당한 크기로 실습하고 더 큰 데이터를 사용하다가 한계에 부딪힐 때 더 큰 데이터 분석에 적합한 방법을 연구하면 된다. Journal Entry Test | 필수적 절차 수행 | Bash | Shell Script | Terminal | Large Data | 메모리 용량을 초과하는 전표 데이터 완전성 검증에서와 같이 Shell에서 데이터에 직접 붙어 처리하는 방식도 메모리 용량에 한계가 있을 때 하나의 대안이 될 수 있다. ↩︎
-
원래 utf-8 encoding 형식으로 되어 있는 파일을 , locale=locale(‘ko’,encoding=‘euc-kr’ 없이 읽으면 훨씬 더 빠르게 읽는다. 파일 형식에 대한 전환 과정이 필요 없기 때문이다. 대용량의 전표를 받아서 test 할 때는 utf-8 encoding 형식으로 요청하는 것도 방법이다. ↩︎
-
colSums(is.na(CYTB)) 함수를 사용해도 결과가 같다. ↩︎
-
계정코드와 계정과목명은 각각 일대일 매칭 되므로 변수가 두 개이지만 하나씩 남겨도 된다. ↩︎