데이터 인사이트

데이터 전문가 칼럼
데이터 전문가가 전하는 데이터 노하우

[빅데이터 분석] 나성호의 R 부동산 데이터 분석 특강 (3회) : 깨끗한 데이터는 없다, 전처리 타임을 줄여라!

작성자
관리자
작성일
2020-10-26 13:56
조회
420

나성호의 R 부동산 데이터 분석 특강 (3회)

깨끗한 데이터는 없다, 전처리 타임을 줄여라!

 
나성호는 금융회사에서 데이터 분석을 직접 수행하는 마케터로 17년 동안 근무했다. 지금은 데이터 마이닝 박사 과정에 재학중이며, 머신러닝을 강의하고 있다.
안녕하세요? 나성호입니다. 지난 연재에서 ‘아파트 실거래가 데이터를 수집’할 때 필요한 R 패키지 소개와 HTTP Request(요청) 실행 및 Response(응답) 결과를 확인하는 과정에 대해 설명했습니다. 다만 아쉬웠던 점이라면 조회 조건인 지역코드와 거래년월을 하나씩만 설정했기 때문에 다양한 지역에 대해 다양한 기간으로 수집해보고 싶다는 생각이 들었을 겁니다.

이번 글에서는 지역코드는 고정한 채로 반복문을 이용해 여러 기간에 걸쳐 데이터를 수집하고, 수집한 데이터를 전처리하는 방법에 대해 알아보겠습니다. 참고로 지역코드를 조회하는 방법에 대해서는 지난 연재에서 부록으로 추가해 드렸으므로 참고하시기 바랍니다.

• 화려한 데이터 분석, 인내력을 필요로 하는 전처리

데이터 기반의 머신러닝은 첨단의 영역으로 보입니다. 하지만 머신러닝의 기초가 되는 원천 데이터는 바로 분석할 수 없을 정도로 어수선합니다. 그래서 머신러닝 전체 과정의 70~80%를 데이터 전처리에 쏟아야 한다고 말하기도 합니다. 최신의 기술도 어찌 보면 ‘촌스러운 작업’이라 할 수 있는 데이터 전처리가 없으면 제대로 수행할 수 없다는 말이기도 합니다.

R에서 반복문을 사용하고자 할 때 for() 함수를 이용합니다. 반복하여 처리할 코드를 for() 함수로 감싸주면 됩니다. R 프로그래밍에 익숙한 분들이라면 어렵지 않게 실행할 수 있을 것입니다. 우리의 목표는 반복문 안에서 HTTP Request(요청)를 실행할 때 거래년월을 바꿔가면서 실행하는 것입니다. 먼저 복습한다는 생각으로 HTTP Request 코드를 다시 살펴보겠습니다.



[그림 1] 2019년 7월에 서울특별시 강남구에서 거래된 아파트 실거래가 수집 코드


지난 연재에서 소개해드린 코드와 달라진 점은 [그림 1]에서는 지역코드를 ‘서울특별시 종로구(11110)’에서 ‘서울특별시 강남구(11680)’로, 거래년월은 ‘201512’에서 ‘201907’로 변경했다는 것입니다.



[그림 2] HTTP Response를 확인하고 JSON 형태의 데이터만 추출하는 코드


[그림 1]과 [그림 2]에서 반복문으로 처리해야 할 코드는 무엇일까요? 저라면 18~21번 행과 27~29번 행 및 35번 행만 사용할 것 같습니다. 그리고 35번 행으로 생성한 ‘df’ 객체를 전체 데이터 저장 객체에 추가하는 부분을 새로 만들어 넣어야 할 것입니다.

• 반복문으로 여러 기간에 걸친 데이터 수집하기

R에서는 for() 함수를 사용해 반복문을 실행한다고 앞에서 소개했습니다. for() 함수 안에는 ‘var in seq’ 구문이 포함됩니다. ‘var’는 반복문 안에서 변하는 값(변수)을 지정하는 것이고 ‘seq’에는 변수가 참조할 값이 들어 있는 벡터를 의미합니다(R에서 벡터란 스칼라 값들의 1차원 집합을 의미합니다.).

가장 간단한 형태의 반복문을 소개해드리겠습니다. 아래 코드를 실행하면 중괄호 안에 있는 명령문에서 ‘i’의 값을 1부터 10까지 바꿔가며 실행합니다. 즉 ‘var’에는 ‘i’를, ‘seq’에는 ‘1:10’을 할당한 것입니다. ‘1:10’를 실행해보면 1부터 10까지 연속된 정수를 벡터 형태로 반환합니다. 아래 코드를 실행해보면 R에서의 반복문 구조를 금세 이해할 수 있을 것입니다.



[그림 3] 간단한 반복문 코드


이번 연재에서는 거래년월을 변경해가며 아파트 실거래가 데이터를 수집해보기로 했는데요. 반복문의 ‘seq’ 부분에 넣을 벡터를 어떻게 생성하면 좋을까요? 아마도 c() 함수를 가장 먼저 떠올렸을지도 모릅니다. 물론 벡터를 생성하는 가장 쉬운 방법이 될 것입니다. 예를 들어 아래와 같은 코드를 생성하는 것이죠.



[그림 4] c() 함수를 이용해 벡터 생성하기


그런데 위와 같은 방법을 사용하면 두 가지 문제가 발생할 수 있습니다. 첫째, 사람이 직접 작성하는 것이므로 실수가 따를 수 있습니다. 둘째, 기간을 늘리면 시간이 많이 소요될 수 있다는 점입니다. 특히 거래년월과 같은 벡터를 생성할 때는 매년 12월 다음에는 연도를 바꾸고 1월로 변경해주어야 하는데 그걸 사람이 직접 한다면 비효율적인 방법이라는 것에 동의할 수 있을 것입니다.

따라서 저는 ‘날짜(Date)’ 벡터를 생성하는 방법에 대해 소개해드리겠습니다. 기본적으로 알아야 할 내용은 R에서 날짜 데이터 기본 포맷은 ‘yyyy-mm-dd’라는 점입니다. 문자열(string)을 날짜 데이터로 변경할 때 as.Date() 함수를 사용하는데요. as.Date() 함수의 ‘x’ 인자에는 날짜 데이터로 변경할 문자열을 할당하고, ‘format’ 인자에는 문자열에서 ‘년월일'을 추출할 수 있도록 문자열의 포맷을 지정해줍니다.

예를 들어, ‘x’ 인자에 할당되는 문자열이 ‘2019-01-01’이라면 앞에서 언급한 바와 같이 날짜 데이터 기본 포맷이므로 ‘format’ 인자에 별도의 문자열 포맷을 지정하지 않아도 됩니다. 하지만 만약 ‘20190101’과 같은 형태의 문자열을 할당했다면, 반드시 ‘format’ 인자에 ‘%Y%m%d’와 같은 문자열 포맷을 추가해주어야 합니다.

위에서 ‘%Y’는 연도 4자리, ‘%m’은 월 2자리, ‘%d’는 일 2자리를 의미합니다. 이정도 설명으로 이해하기 어려웠을 수도 있으므로 한 가지 예제를 더 추가해보겠습니다. 만약 as.Date() 함수의 ‘x’ 인자에 ‘2019/01/01’과 같은 문자열을 할당했다면 ‘format’ 인자에는 ‘%Y/%m/%d’와 같은 문자열 포맷을 추가해 주어야 합니다.



[그림 5] 문자열을 날짜 데이터로 변환하기


이제 날짜 데이터를 이용해 연속된 값을 생성하는 법을 알아보겠습니다. 아마도 seq() 함수를 잘 알고 있을 것이라 생각됩니다. 일반적으로 seq() 함수는 ‘from’ 인자에 시작할 숫자, ‘to’ 인자에 종료할 숫자, 그리고 ‘by’ 인자에 간격을 추가해 연속된 숫자를 갖는 벡터를 생성할 때 사용하는데요. ‘from’과 ‘to’ 인자에 숫자 대신 날짜 데이터를 할당하면 연속된 날짜 벡터? 지정하면 한 달 단위의 날짜 데이터가 생성됩니다.



[그림 6] 한 달 단위의 날짜 데이터 생성하기


위 코드를 실행하면 2015년 1월 1일부터 2018년 12월 1일까지 한 달 단위의 날짜 데이터를 포함하는 벡터를 생성할 수 있습니다. 그런데 반복문에는 연도 4자리와 월 2자리가 문자열 형태로 할당돼야 하므로 날짜 벡터를 문자 벡터로 변환해야 합니다. 이 때 사용하면 좋을 함수는 format() 함수입니다.

format() 함수의 ‘x’ 인자에 날짜 벡터를 할당하고, ‘format’ 인자에는 날짜 데이터를 텍스트로 변환할 포맷을 지정합니다. 만약 연도 4자리와 월 2자리만 붙여서 6자리 숫자만 남기고 싶다면 아래 코드와 같이 실행하면 됩니다.



[그림 7] 날짜 벡터를 문자 벡터로 변환하기


이제까지 반복문에 포함할 연도 4자리 + 월 2자리 형태의 문자 벡터를 만드는 방법에 대해 알아보았습니다. 이번 예제에서는 2019년 1월부터 2019년 7월까지의 7개월 간 거래된 아파트 실거래가를 수집해보도록 하겠습니다. 앞에서 배운 코드를 응용해 다음과 같이 ‘months’ 객체를 생성합니다.




[그림 8] 반복문을 실행할 날짜 형태의 문자 벡터 생성하기


반복문을 실행하기에는 다루어야 할 것이 아직 세 가지가 더 남았습니다. 첫째, 전체 데이터를 저장할 객체를 빈 데이터프레임으로 미리 생성하는 것입니다. 우리는 이번 연재를 지난 코드의 복습으로 시작했고 그 결과로 ‘df’라는 데이터프레임 객체를 생성할 수 있었습니다. 반복문을 실행하면 해당 거래기간의 데이터가 매번 ‘df’로 생성되는데요. 반복문 안에는 생성된 ‘df’를 rbind() 함수를 이용해 하나의 결과 객체에 추가하는 부분이 포함돼야 합니다.

둘째, 사용자 본인을 위한 현재 진행 상황을 알려주는 부분을 추가하는 것입니다. 이번 예제 코드는 7개의 거래년월을 반복하는 것이므로 금세 끝날 것입니다. 하지만 우리가 장기간 거래 데이터를 수집한다고 했을 때, 반복문 실행이 오래 걸리면 현재 어디까지 진행됐는지 궁금할 수 있습니다. 이럴 때 반복문이 매번 실행될 때마다 현재 실행 중인 변수 값이 얼마인지를 알게 되면 답답한 마음을 조금은 덜 수 있을 겁니다. R에서는 cat() 함수를 이용해 문자열과 객체를 조립한 결과를 출력할 수 있습니다. print() 함수로는 실행할 수 없는 기능이므로 이번에 cat() 함수를 알아가는 기회가 되길 바랍니다.

마지막으로, 반복문 마지막 부분에 코드 실행을 일정 시간 동안 잠시 멈추는 기능을 추가해주어야 합니다. 특히 웹 크롤링을 할 때 잠시 멈춤(pause)을 추가하는 것은 기본 예의에 해당하는 사항입니다. HTTP Request를 잠시 멈춤으로써 웹 서버에 부하를 낮추는 것이죠. 웹 사이트는 해당 기업 에서 온라인 비즈니스를 위해 만든 것이지, 애써 모은 데이터를 가져가라고 만들어 놓은 것이 아니겠지요. 따라서 최소한의 예의를 지켜야 할 것입니다. 물론 오픈 API는 데이터를 주고 받기 위해 내놓은 것이므로 수집 코드에 잠시 멈춤 기능을 추가하지 않아도 될 것 같지만, 그래도 여러 명의 사용자가 한 번에 몰리게 되면 그 역시도 상당한 부하가 될 수 있습니다. R에서 잠시 멈춤 기능은 Sys.sleep() 함수를 이용합니다.

지금까지 언급한 내용을 바탕으로 작성한 반복문 코드를 공개하겠습니다.



[그림 9] 아파트 실거래가를 수집하는 반복문 코드


[그림 9]에서 48번 행은 전체 데이터를 저장할 최종 객체를 빈 데이터프레임으로 생성한 것입니다. ‘result’ 객체는 71번 행과 같이 반복문 안에서 생성된 ‘df’ 객체를 저장하는 용도로 사용됩니다. 그리고 54번 행에서 현재 반복문이 진행되고 있는 상황을 출력하는 코드를 추가했습니다. cat() 함수 마지막 부분에 추가된 ‘\n’은 강제 줄 바꿈 기능을 합니다. ‘\n’을 추가하지 않으면 cat() 함수에 저장된 내용이 한 줄로 길게 출력되기 때문에 보기에 좋지 않습니다. 그리고 74번 행에 1초간 코드 실행을 잠시 멈추는 기능을 추가했습니다. Sys.sleep() 함수의 ‘time’ 인자에는 잠시 멈출 시간을 초 단위 정수로 지정하면 됩니다.

위 코드를 실행하면 ‘result’ 객체는 1823행, 11열을 갖는 데이터프레임이 됩니다. 그러니까 2019년 1월부터 7월까지 서울특별시 강남구에 거래된 아파트 실거래가 데이터가 총 1823건이라는 의미가 됩니다.

• 수집한 데이터 전처리하기

반복문을 이용해 2019년 1월부터 7월까지 서울특별시 강남구에서 거래된 아파트 실거래가 데이터를 수집했다면 이제 전처리 과정을 거쳐야 합니다. 이번 예제로 수집한 데이터에는 전처리 과정으로 소개할 내용이 많지 않습니다만, 관련 내용에 대해서 가능한 자세하게 소개하도록 하겠습니다.

사족입니다만, 데이터 전처리를 빠르게 수행할 수 있으면 전체 데이터 분석 과정에 소요되는 기간을 크게 단축할 수 있으므로 유능한 데이터 분석가가 되려면 데이터 전처리 역량을 반드시 익혀야 합니다. 다음에 좋은 기회가 된다면 데이터 전처리에 관한 일반적인 내용에 대해 다루었으면 합니다.

다시 수집한 데이터로 돌아와서, ‘result’ 객체의 구조를 확인하고 전처리해야 할 내용에 대해 정리해보도록 하겠습니다.



[그림 10] result 객체의 구조 파악


먼저 첫 번째 컬럼인 ‘거래금액'이 문자 벡터로 돼 있으므로 이 데이터를 숫자 벡터로 변환해야 합니다. 왜냐하면 탐색적 데이터 분석 과정에서 거래금액의 대푯값인 평균이나 중앙값을 생성하거나 히스토그램을 그리기 위해서는 거래금액이 숫자 벡터여야 하고, 나중에 거래금액을 추정하는 회귀모형을 적합할 때에도 목표변수는 숫자 벡터여야 하기 때문입니다.

문자 벡터를 숫자 벡터로 변환하려면 as.numeric() 함수를 사용하면 된다는 것은 R 초보자들도 잘 알고 있을 것입니다. 그런데 이번 예제의 거래금액은 as.numeric() 함수를 바로 사용할 수 없습니다. 왜냐하면 문자열 중간에 포함돼 있는 콤마(,) 때문입니다. 숫자 사이에 콤마가 있으면 숫자 벡터로 변환되지 않습니다.

따라서 콤마를 먼저 삭제한 다음에 숫자 벡터로 변환해주어야 합니다. R에서 문자열을 변경할 때 gsub() 함수를 많이 사용하지만 저는 ‘stringr’ 패키지의 함수들을 소개해드리고자 합니다. ‘stringr’ 패키지는 ‘tidyverse’ 패키지를 호출할 때 함께 호출되기 때문에 따로 library() 함수를 사용할 필요가 없습니다.

‘stringr’ 패키지용하는 함수는 str_remove() 함수가 있습니다. ‘stringr’ 패키지가 좋은 점은 모든 함수가 ‘str_’로 시작하고 그 다음에 ‘동사'에 해당하는 단어가 사용된다는 것입니다. 예를 들어 문자열에 특정 패턴이 있는지 확인하는 함수는 str_detect(), 문자열에 특정 패턴을 추출하는 함수는 str_extract(), 문자열에 특정 패턴을 바꿀 때에는 str_replace() 함수를 사용합니다. 아울러 모든 함수들에서 거의 공통적으로 ‘string’과 ‘pattern’ 인자가 사용됩니다.

파이프 연산자(%>%)와 stringr 패키지 함수를 이용해 이번 예제의 거래금액 컬럼을 숫자 벡터로 변환해 보겠습니다. (파이프 연산자는 지난 연재에서 소개했으므로 자세한 내용은 지난 연재를 참조하시기 바랍니다.)



[그림 11] 거래금액 컬럼을 숫자 벡터로 변환하기


[그림 11]을 실행하면 ‘result’ 객체의 ‘거래금액' 컬럼에서 콤마를 지우고 숫자 벡터로 변환해 다시 ‘거래금액' 컬럼으로 재할당(덮어쓰기)한 결과를 얻을 수 있습니다. 이제 평균을 내거나 범위를 확인하는 작업을 할 수 있게 됐습니다.

데이터 전처리 두 번째로는 ‘법정동', ‘아파트', ‘지역코드' 등 문자 벡터를 범주형(factor) 벡터로 변환해주는 것이 좋습니다. R 초심자라면 범주형 벡터에 익숙하지 않을 수도 있는데요. 이번 글에서는 간단한 내용만 언급하도록 하겠습니다.



[그림 12] 문자 벡터와 범주형 벡터의 차이


[그림 12]를 보면 범주형 벡터가 문자 벡터와 다른 점을 쉽게 확인할 수 있습니다. 범주형 벡터를 출력하면 개별 원소에서 따옴표가 사라지고 아래에 ‘Levels’가 추가됩니다. ‘Levels’는 문자 벡터의 원소를 모두 모아 중복을 제거한 다음, (순서를 임의로 정해주지 않으면) 가나다 순으로 정렬한 것입니다. 이번 예제에서 출력된 것은 ‘14 Levels’이니까 서울특별시 강남구에서 2019년 1~7월 동안 거래된 법정동이 모두 14곳이라는 것을 의미합니다.

그런데 따옴표가 없는 문자열은 무엇을 의미일까요? 그것은 바로 출력된 문자열이 문자가 아니라는 것입니다. 실제로 범주형 벡터의 각 원소는 출력되는 문자열 대신에 ‘Levels’의 순서에 따라 1부터 n까지 정수가 할당돼 있습니다. 이번 예제에서는 ‘14 Levels’이니까 ‘개포동’ 대신 정수 1, 논현동 대신 정수 2 등으로 개별 원소가 문자열이 아닌 정숫값을 갖습니다. 그러니까 실제로는 정수형 벡터에 가깝다는 의미입니다. 범주형 벡터를 정수로 변환하면 바로 확인할 수 있습니다.



[그림 13] 범주형 벡터를 숫자 벡터로 변환한 결과 확인


원래의 ‘법정동' 컬럼은 ‘역삼동'으로 시작하는 문자 벡터였지만 범주형 벡터로 변환한 다음 다시 숫자 벡터로 바꾸니 숫자 10으로 변경됐습니다. 숫자 10은 ‘역삼동'이 가나다 순으로 정렬된 ‘Levels’에서 10번째라는 것을 의미합니다. R 초심자의 경우, 데이터 전처리 과정에서 각별한 주의를 기울여야 합니다. 그렇지 않으면 나도 모르는 사이에 원래 데이터인 ‘역삼동'이 숫자 10이라는 이상한 형태로 바뀐 걸 보고 순간 당황할 수 있기 때문입니다.

문자 벡터를 범주형 벡터로 바꾸면 좋은 점이라면, 일단 저장 용량이 가벼워집니다. 문자열을 그대로 저장하는 것보다 범주형으로 변환하면 저장 용량이 그만큼 감소하기 때문입니다. 특히 각 원소의 길이가 긴 경우라면 범주형으로 변환했을 때 저장용량이 크게 감소합니다. 또 다른 장점으로는 문자열 대신 범주형을 요구하는 함수가 있다는 것입니다. 특히 분류 모형을 적합할 때 목표변수의 속성을 범주형으로 할당해야 될 때가 있습니다. 나중에 기계학습 알고리즘을 다룰 때 다시 언급하도록 하겠습니다.

다시 원래 데이터로 돌아와서, 범주형 벡터로 변환하면 좋을 컬럼이 3개가 있으므로 as.factor() 함수를 세 번 실행해주면 간단하게 해결될 문제입니다. 그런데 말입니다! 만약 우리가 범주형 벡터로 변환해야 할 컬럼이 3개가 아니고 30개라면 어떻게 해야 할까요? 그리고 가능성은 희박하지만 만약 범주형 벡터로 변환해야 할 컬럼이 100개를 초과한다면, 그 모든 컬럼을 한 줄씩 따로 실행해야 할까요? 생각만해도 답답합니다. 우리 이런 일은 컴퓨터에 시키도록 합니다. ‘purrr’ 패키지의 map_df() 함수를 사용하면 간단하게 해결됩니다.



[그림 14] 여러 개의 문자 벡터를 한 번에 범주형 벡터로 변환하기


‘purrr’ 패키지 역시 ‘tidyverse’ 패키지를 호출할 때 동시에 불러올 수 있으므로 따로 library() 함수를 사용할 필요가 없습니다. map_df() 함수의 기본형은 map() 함수인데요. map() 함수는 실행 결과로 리스트 형태의 객체를 반환하므로 데이터프레임을 반환하기 위해서는 map_df() 함수를 사용해야 합니다.

‘result’ 객체에서 범주형을 변환하고자 하는 컬럼은 4, 5, 10번째이므로 [그림 14]와 같이 map_df() 함수의 ‘.x’ 인자에 해당 컬럼만 추출해 할당합니다. 공통적으로 적용할 함수인 as.factor는 ‘.f’ 인자에 할당합니다. 함수 실행 결과를 다시 ‘result’ 객체의 4, 5, 10번째 컬럼에 재할당(덮어쓰기)합니다.



[그림 15] RDS 파일로 저장하기


[그림 15]와 같이 지금까지 작업한 ‘result’ 객체를 저장할 폴더로 작업경로를 설정한 다음 나중에 사용하기 위해 일단 RDS 파일로 저장합니다. RDS 파일은 R 객체 하나만 저장할 수 있는 파일 형태입니다. R 사용자 간 데이터를 주고 받을 때 csv, txt 파일 대신 RDS 파일로 주고 받으면 편리합니다.

이상으로 반복문을 통해 여러 기간에 걸친 거래 데이터를 수집하고 전처리하는 방법에 대해서 소개했습니다. 이번에 소개해드린 데이터 전처리 부분은 아주 일부에 지나지 않으며, 연재글 전반에 걸쳐 기회가 있을 때마다 소개해드릴 것을 약속드립니다. 다음 연재에서는 수집된 데이터로 탐색적 데이터 분석을 하는 과정을 다룰 예정입니다. 감사합니다. (끝)



출처 : 한국데이터산업진흥원

제공 : 데이터 전문가 지식포털 DBguide.net