데이터분석 전문가(ADP)

데이터분석 전문가(ADP) 시험 노트개념 정리17 MIN

데이터 마트 구축과 변수 요약·파생

ADP 4과목 데이터 처리의 첫 자리입니다. 데이터 마트와 데이터웨어하우스의 차이, 요약변수와 파생변수, reshape의 melt와 cast, plyr의 ddply, sqldf, data.table, merge·rbind·cbind를 R 코드와 함께 정리합니다.

앞 노트가 R의 그릇을 다뤘다면 이 노트는 그 그릇에 분석할 데이터를 채우는 일입니다. 모델을 돌리기 전에 원천 데이터를 고객 한 명당 한 줄 같은 분석 단위로 다시 짜야 하고, 그 과정에서 새 변수를 만들어 붙입니다. 시험은 두 갈래로 묻습니다 — 요약변수와 파생변수를 가르는 개념 문항, 그리고 melt·cast·ddply·merge 코드의 결과를 맞히는 코드 문항입니다.

데이터 마트

데이터웨어하우스와의 차이

데이터웨어하우스는 여러 운영 시스템의 데이터를 주제별로 통합해 전사가 함께 쓰도록 쌓아 둔 저장소이고, 데이터 마트는 그 가운데 특정 부서나 분석 목적에 필요한 부분만 떼어 만든 작은 저장소입니다. 관계로 말하면 데이터 마트는 데이터웨어하우스에서 나온 부분집합이고, 분석가가 모델에 바로 넣을 수 있도록 다시 짠 것입니다.

데이터웨어하우스 데이터 마트
범위 전사 부서·분석 주제 하나
크기 크다 작다
쓰는 사람 여러 부서가 공유 그 부서·그 분석가
구축 기간 길다 짧다
데이터 모양 주제별로 통합한 상세 데이터 분석 단위로 요약·가공한 데이터

분석 마트 구성

분석용 데이터 마트는 대개 분석 단위 하나당 한 줄로 짭니다. 고객 이탈을 예측한다면 고객 한 명이 한 줄이고, 그 줄에 구매 이력·접촉 이력·인구 정보를 요약한 열이 붙습니다. 거래 한 건이 한 줄인 원천 데이터를 고객 한 명이 한 줄인 마트로 바꾸는 일이 곧 다음 절의 요약변수를 만드는 일입니다. 마트를 잘 짜 두면 모델을 바꿔도 같은 마트를 다시 쓸 수 있어 분석의 대부분이 여기에 걸립니다.

요약변수와 파생변수

요약변수

요약변수는 수집된 정보를 분석 단위에 맞게 종합한 변수입니다. 기간별 구매 금액과 횟수, 상품별 구매 순서, 유통 채널별 구매 금액, 단어 빈도처럼 원천 데이터를 합치고 세고 평균 낸 것이 여기에 듭니다. 연속형 변수를 구간으로 나눈 것과 초기 행동·추세를 요약한 변수도 요약변수로 봅니다.

누가 만들어도 같은 값이 나오고 여러 모델에 두루 쓰이므로 재활용성이 높습니다. 그래서 요약변수는 데이터 마트에 미리 만들어 두고 공유합니다.

파생변수

파생변수는 사용자가 특정 조건이나 함수로 의미를 부여해 만든 변수입니다. 근무 시간 구매 지수, 주 구매 매장, 주 활동 지역, 행사 민감도, 휴면 가능성, 라이프 스테이지, 고객 생애 가치 같은 것입니다. 만드는 사람의 판단이 들어가 주관적일 수 있으므로 논리적 타당성을 갖추는 것이 조건입니다.

요약변수 파생변수
만드는 법 합치고 세고 평균 낸다 조건과 함수로 의미를 붙인다
객관성 누가 만들어도 같다 만드는 사람의 판단이 든다
재활용 여러 모델에 두루 쓴다 특정 목적에 맞춘다
예 월별 구매 금액, 구매 횟수 행사 민감도, 주 구매 매장

파생변수가 요약변수 위에서 만들어지는 경우가 많습니다. 행사 기간 구매 금액과 전체 구매 금액은 요약변수이고, 그 둘의 비율로 정의한 행사 민감도는 파생변수입니다. 고객 한 명의 전체 구매가 48만 원이고 그중 행사 기간 구매가 31만 2천 원이면 행사 민감도는 312,000÷480,000=0.65312{,}000 \div 480{,}000 = 0.65 입니다.

reshape

melt

reshape 패키지는 데이터의 모양을 넓은 꼴과 긴 꼴 사이에서 바꿉니다. melt()는 넓은 꼴을 녹여 긴 꼴로 만드는 함수입니다. 식별 변수(id)로 지정한 열은 그대로 두고, 나머지 열을 variable(원래 열 이름)과 value(그 값) 두 열로 쌓습니다.

library(reshape)
df <- data.frame(id = c(1, 2), q1 = c(10, 30), q2 = c(20, 40), q3 = c(15, 25))
md <- melt(df, id = "id")
nrow(md)   # 6 — 2행 × 측정 열 3개
#>   id variable value
#> 1  1       q1    10
#> 2  2       q1    30
#> 3  1       q2    20 ...

결과의 행 수는 원래 행 수에 녹인 열 수를 곱한 것입니다. 이 곱셈이 코드 문항의 단골입니다.

cast

cast()는 녹인 데이터를 다시 원하는 모양으로 굳힙니다. 수식의 왼쪽이 행, 오른쪽이 열이 되고, 한 칸에 값이 여럿 들어가면 fun.aggregate로 요약합니다.

cast(md, id ~ variable)                 # 원래 넓은 꼴로 되돌린다
cast(md, id ~ ., mean)                  # id별 전체 평균: 1은 15, 2는 약 31.67
cast(md, variable ~ ., sum)             # 문항별 합: q1 40, q2 60, q3 40

melt로 녹이고 cast로 굳히는 두 걸음이면 요약변수 대부분을 만들 수 있습니다. 뒤를 이은 reshape2 패키지에서는 cast가 결과를 데이터프레임으로 주는 dcast()와 배열로 주는 acast()로 갈라졌습니다.

plyr

ddply와 이름 규칙

plyr은 데이터를 쪼개고, 조각마다 함수를 적용하고, 결과를 다시 합치는 일을 한 함수로 합니다. 함수 이름의 첫 글자가 받는 자료 구조, 둘째 글자가 돌려주는 자료 구조입니다.

글자 자료 구조
d 데이터프레임
l 리스트
a 배열
_ 돌려주지 않음

그래서 ddply는 데이터프레임을 받아 데이터프레임을 돌려주고, ldply는 리스트를 받아 데이터프레임을, dlply는 데이터프레임을 받아 리스트를 돌려줍니다.

summarise와 transform

ddply의 셋째 인자로 무엇을 주느냐에 따라 결과의 행 수가 달라집니다. summarise는 그룹마다 한 줄로 줄이고, transform은 원래 행을 그대로 두고 그룹 계산값을 열로 덧붙입니다.

library(plyr)
sales <- data.frame(store = c("A", "A", "B", "B", "B"), amt = c(10, 30, 20, 40, 60))
ddply(sales, .(store), summarise, total = sum(amt), avg = mean(amt))
#>   store total avg
#> 1     A    40  20
#> 2     B   120  40
ddply(sales, .(store), transform, share = amt / sum(amt))   # 5행 그대로, 매장 안 비율

summarise의 결과는 그룹 수만큼 2행이고 transform의 결과는 원래대로 5행입니다. B 매장 첫 줄의 share는 20÷120≈0.16720 \div 120 \approx 0.167 입니다.

sqldf와 data.table

sqldf

sqldf는 데이터프레임을 테이블로 보고 SQL 문장을 그대로 실행합니다. SQL에 익숙한 사람이 R 문법을 몰라도 요약과 조인을 할 수 있다는 것이 쓰는 이유입니다.

library(sqldf)
sqldf("SELECT store, SUM(amt) AS total FROM sales GROUP BY store")
sqldf("SELECT * FROM sales WHERE amt >= 30")   # 3행

data.table

data.table은 데이터프레임을 넓힌 구조로, 큰 데이터를 훨씬 빠르게 다룹니다. 문법이 DT[i, j, by] 한 틀입니다 — i로 행을 거르고, j로 계산하고, by로 그룹을 나눕니다. setkey()로 키를 지정하면 이진 탐색으로 찾아 행이 많을수록 속도 차이가 커집니다.

library(data.table)
DT <- as.data.table(sales)
DT[amt >= 20, .(total = sum(amt)), by = store]   # A 30, B 120
setkey(DT, store)
DT["B"]            # 키로 B 매장 3행을 찾는다

같은 요약을 세 방식으로 할 수 있다는 것이 요점이고, 고르는 기준은 속도(data.table), SQL 문법(sqldf), 쪼개고 합치는 흐름의 가독성(plyr)입니다.

데이터 결합

merge

두 데이터프레임을 공통 열로 옆으로 잇는 것이 merge()입니다. 기본은 양쪽에 다 있는 키만 남기는 내부 조인이고, 인자로 조인의 종류를 바꿉니다.

인자 조인 남는 키
없음 내부 조인 양쪽에 다 있는 것
all.x = TRUE 왼쪽 외부 조인 왼쪽에 있는 것 전부
all.y = TRUE 오른쪽 외부 조인 오른쪽에 있는 것 전부
all = TRUE 완전 외부 조인 어느 한쪽에라도 있는 것
x <- data.frame(id = 1:4, age = c(23, 35, 41, 29))
y <- data.frame(id = c(3, 4, 5), grade = c("S", "A", "B"))
nrow(merge(x, y, by = "id"))                # 2 — id 3, 4
nrow(merge(x, y, by = "id", all.x = TRUE))  # 4 — id 1, 2의 grade는 NA
nrow(merge(x, y, by = "id", all = TRUE))    # 5

rbind와 cbind

rbind()는 행을 아래로 쌓고 cbind()는 열을 옆으로 붙입니다. rbind()는 열 이름과 개수가 같아야 하고, cbind()는 행 수가 같아야 합니다. cbind()는 키를 보지 않고 자리대로 붙이므로 두 데이터의 행 순서가 다르면 다른 고객의 값이 한 줄에 섞입니다. 키가 있으면 merge()를 쓰는 이유가 이것이고, 지문은 이 차이를 상황으로 줍니다.

연습 문제

  1. 데이터 마트에 대한 설명으로 옳지 않은 것은?
    ① 데이터웨어하우스에서 특정 목적에 필요한 부분을 떼어 만든다
    ② 데이터웨어하우스보다 구축 기간이 짧다
    ③ 전사가 함께 쓰는 통합 저장소로 주제별 상세 데이터를 쌓는다
    ④ 분석 단위에 맞게 요약·가공한 데이터를 담는다
    ③. 전사가 함께 쓰는 통합 저장소는 데이터웨어하우스입니다. 데이터 마트는 그 부분집합을 특정 부서나 분석 목적에 맞게 다시 짠 것입니다.
  2. 다음 중 파생변수로 보기 가장 알맞은 것은?
    ① 월별 구매 금액
    ② 유통 채널별 구매 횟수
    ③ 주 구매 매장
    ④ 상품별 구매 순서
    ③. 여러 매장 가운데 무엇을 「주」로 볼지 기준을 사람이 정해야 하므로 의미를 부여한 파생변수입니다. 나머지는 원천 데이터를 합치고 센 요약변수입니다.
  3. 5행이고 열이 id, m1, m2, m3, m4인 데이터프레임을 melt(df, id = "id")로 녹인 결과의 행 수와 열 수는?
    ① 5행 5열
    ② 20행 3열
    ③ 20행 5열
    ④ 25행 3열
    ②. 측정 열 넷이 행으로 쌓여 5×4=205 \times 4 = 20 행이 되고, 열은 id·variable·value 셋입니다.
  4. 본문의 sales에 ddply(sales, .(store), summarise, n = length(amt), top = max(amt))를 실행한 결과로 옳은 것은?
    ① A는 n 2, top 30 / B는 n 3, top 60
    ② A는 n 2, top 40 / B는 n 3, top 120
    ③ 5행이 그대로 남고 n과 top 열이 붙는다
    ④ A는 n 3, top 60 / B는 n 2, top 30
    ①. A 매장은 10·30 두 건이라 개수 2, 최댓값 30이고, B 매장은 20·40·60 세 건이라 개수 3, 최댓값 60입니다. summarise는 그룹마다 한 줄로 줄이므로 결과가 2행입니다. 5행이 남는 것은 transform일 때입니다.
  5. id가 16인 고객 표와 id가 49인 주문 표를 merge(a, b, by = "id", all = TRUE)로 합쳤을 때의 행 수는? (두 표 모두 id는 겹치지 않는다)
    ① 3
    ② 6
    ③ 9
    ④ 12
    ③. 완전 외부 조인은 어느 한쪽에라도 있는 키를 모두 남기므로 1부터 9까지 9행입니다. 내부 조인이면 4·5·6의 3행, 왼쪽 외부 조인이면 6행입니다.
  6. 행 수가 수천만인 거래 데이터에서 매장별 합계를 빠르게 구하려 한다. 가장 알맞은 것은?
    ① cbind로 매장 열을 붙인 뒤 for문으로 더한다
    ② data.table로 바꾸고 DT[, sum(amt), by = store]로 구한다
    ③ melt로 녹인 뒤 rbind로 쌓는다
    ④ ldply로 리스트를 받아 데이터프레임을 돌려준다
    ②. data.table은 DT[i, j, by] 한 틀로 그룹 요약을 하고 큰 데이터에서 속도가 가장 빠릅니다. ①의 반복문은 느리고, ③은 모양을 바꿀 뿐 합계를 구하지 않으며, ④는 입력이 리스트인 경우의 함수입니다.
  7. 서술형 연습입니다. 거래 한 건이 한 줄인 원천 데이터(고객번호, 거래일, 금액, 행사여부)로 고객 한 명이 한 줄인 이탈 예측용 마트를 만들려 합니다. 요약변수 둘과 파생변수 하나를 정의하고, 만드는 R 코드의 흐름을 5줄 이내로 쓰시오.
    채점 기준은 셋입니다. (1) 요약변수가 요약변수인가 — 고객별 총 구매 금액, 구매 횟수, 최근 3개월 구매 금액처럼 합치고 센 것이면 됩니다. (2) 파생변수에 정의와 타당성이 있는가 — 행사 기간 구매 금액을 총 구매 금액으로 나눈 행사 민감도, 마지막 거래일부터 기준일까지의 일수로 정한 휴면 가능성처럼 계산식과 이탈과의 관련을 함께 적어야 합니다. (3) 코드 흐름이 맞는가 — ddply(tx, .(cust), summarise, total = sum(amt), n = length(amt), promo = sum(amt[promo == 1]) / sum(amt))처럼 그룹 요약으로 한 줄씩 만들고, 인구 정보 표와는 merge(..., by = "cust", all.x = TRUE)로 잇는 흐름입니다. data.table이나 sqldf로 쓴 답도 같은 점수입니다. 키 없이 cbind로 붙이면 행 순서가 어긋날 수 있어 감점합니다.
데이터분석 전문가(ADP) 시험 노트 전체 보기