이 장에서는
데이터를 처음 받으면 드는 생각은 아마 쉘 도구나 스프레드 시트로 정리를 하려고 먼저 생각할테지만, 실제 분석에 걸리는 시간보다 이런 정제 작업에 걸리는 시간이 훨씬 더 길다는 걸 깨닫는 데에는 별로 긴 시간이 걸리지 않는다. R 의 주요 데이터 타입인 Data Frame 은 구조화된 데이터를 다루는데에 굉장히 적합하며, 많은 일반적인 데이터 포맷을 작업할 수 있는 방법이 R에는 굉장히 많다. 이번 장에서는 파일에 있는 작은 데이터에서부터 시작해서 관계형 데이터 베이스에 적재되어 있는 데이터로 갈 것이다. 이 장을 마칠 때 쯤이면 여러분은 자신감을 갖고 R을 이용해서 분석을 위해 데이터를 추출하고, 변환하고, 불러올 수 있을 것이다.
불러오기 가장 쉬운 데이터 포맷은 행이름이 있는 테이블 구조의 데이터이다. 많은 공공 데이터가 이러한 형태로 되어 있기 때문에 이를 읽어올 수 있다는 것은 우리에게 많은 새로운 기회를 준다.
파일이나 URL 로부터 잘 구조화된 데이터 불러오기
read.table() 함수는 강력하고 유연하다. 다양한 형태의 구분자를 이용할 수 있고, 로컬 파일이나 URL 로부터도 읽어올 수 있다. 파일의 확장자가 .gz 인 경우에는 read.table() 함수가 파일이 gzip 으로 압축된 것으로 생각하고 알아서 압축을 풀고 읽어올 것이다.
uciCar <-read.table(
"http://win-vector.com/dfiles/car.data.csv",
sep = ",",
header = TRUE)
head(uciCar)
## buying maint doors persons lug_boot safety rating
## 1 vhigh vhigh 2 2 small low unacc
## 2 vhigh vhigh 2 2 small med unacc
## 3 vhigh vhigh 2 2 small high unacc
## 4 vhigh vhigh 2 2 med low unacc
## 5 vhigh vhigh 2 2 med med unacc
## 6 vhigh vhigh 2 2 med high unacc
데이터 살펴보기
# uciCar object 의 타입
class(uciCar)
## [1] "data.frame"
# uciCar object 의 요약 통계량
summary(uciCar)
## buying maint doors persons lug_boot safety
## high :432 high :432 2 :432 2 :576 big :576 high:576
## low :432 low :432 3 :432 4 :576 med :576 low :576
## med :432 med :432 4 :432 more:576 small:576 med :576
## vhigh:432 vhigh:432 5more:432
## rating
## acc : 384
## good : 69
## unacc:1210
## vgood: 65
# uciCar object 의 차원 수
dim(uciCar)
## [1] 1728 7
다른 포맷의 데이터로 작업하기
데이터가 항상 준비된 형태로 있는 것은 아니다. 컴퓨터에서 사용할 수 있는 형태가 아닌 채로 있는 데이터도 있고, 1장에서 사용한 독일 은행의 신용도 데이터가 그렇다. 테이블 형태의 데이터이긴 하지만 행이름이 없고, 데이터는 암호화되어 있어서 이를 해독하려면 별도의 파일이 필요하다.
d <- read.table(paste("http://archive.ics.uci.edu/ml/",
"machine-learning-databases/statlog/german/german.data",
sep = ""),
stringsAsFactors = FALSE)
colnames(d) <- c('Status.of.existing.checking.account',
'Duration.in.month', 'Credit.history', 'Purpose',
'Credit.amount', 'Savings account/bonds',
'Present.employment.since',
'Installment.rate.in.percentage.of.disposable.income',
'Personal.status.and.sex', 'Other.debtors/guarantors',
'Present.residence.since', 'Property', 'Age.in.years',
'Other.installment.plans', 'Housing',
'Number.of.existing.credits.at.this.bank', 'Job',
'Number.of.people.being.liable.to.provide.maintenance.for',
'Telephone', 'foreign.worker', 'Good.Loan')
d$Good.Loan <- as.factor(ifelse(d$Good.Loan==1,'GoodLoan','BadLoan'))
head(d)
## Status.of.existing.checking.account Duration.in.month Credit.history
## 1 A11 6 A34
## 2 A12 48 A32
## 3 A14 12 A34
## 4 A11 42 A32
## 5 A11 24 A33
## 6 A14 36 A32
## Purpose Credit.amount Savings account/bonds Present.employment.since
## 1 A43 1169 A65 A75
## 2 A43 5951 A61 A73
## 3 A46 2096 A61 A74
## 4 A42 7882 A61 A74
## 5 A40 4870 A61 A73
## 6 A46 9055 A65 A73
## Installment.rate.in.percentage.of.disposable.income
## 1 4
## 2 2
## 3 2
## 4 2
## 5 3
## 6 2
## Personal.status.and.sex Other.debtors/guarantors Present.residence.since
## 1 A93 A101 4
## 2 A92 A101 2
## 3 A93 A101 3
## 4 A93 A103 4
## 5 A93 A101 4
## 6 A93 A101 4
## Property Age.in.years Other.installment.plans Housing
## 1 A121 67 A143 A152
## 2 A121 22 A143 A152
## 3 A121 49 A143 A152
## 4 A122 45 A143 A153
## 5 A124 53 A143 A153
## 6 A124 35 A143 A153
## Number.of.existing.credits.at.this.bank Job
## 1 2 A173
## 2 1 A173
## 3 1 A172
## 4 1 A173
## 5 2 A173
## 6 1 A172
## Number.of.people.being.liable.to.provide.maintenance.for Telephone
## 1 1 A192
## 2 1 A191
## 3 2 A191
## 4 2 A191
## 5 2 A191
## 6 2 A192
## foreign.worker Good.Loan
## 1 A201 GoodLoan
## 2 A201 BadLoan
## 3 A201 GoodLoan
## 4 A201 GoodLoan
## 5 A201 BadLoan
## 6 A201 GoodLoan
# 실행 안 됨
mapping <- list('A11'='... < 0 DM',
'A12'='0 <= ... < 200 DM',
'A13'='... >= 200 DM / salary assignments for at least 1 year',
'A14'='no checking account',
'A30'='no credits taken/all credits paid back duly',
'A31'='all credits at this bank paid back duly',
'A32'='existing credits paid back duly till now',
'A33'='delay in paying off in the past',
'A34'='critical account/other credits existing (not at this bank)',
'A40'='car (new)',
'A41'='car (used)',
'A42'='furniture/equipment',
'A43'='radio/television',
'A44'='domestic appliances',
'A45'='repairs',
'A46'='education',
'A47'='(vacation - does not exist?)',
'A48'='retraining',
'A49'='business',
'A410'='others',
'A61'='... < 100 DM',
'A62'='100 <= ... < 500 DM',
'A63'='500 <= ... < 1000 DM',
'A64'='.. >= 1000 DM',
'A65'='unknown/ no savings account',
'A71'='unemployed',
'A72'='... < 1 year',
'A73'='1 <= ... < 4 years',
'A74'='4 <= ... < 7 years',
'A75'='.. >= 7 years',
'A91'='male : divorced/separated',
'A92'='female : divorced/separated/married',
'A93'='male : single',
'A94'='male : married/widowed',
'A95'='female : single',
'A101'='none',
'A102'='co-applicant',
'A103'='guarantor',
'A121'='real estate',
'A122'='if not A121 : building society savings agreement/life insurance',
'A123'='if not A121/A122 : car or other, not in attribute 6',
'A124'='unknown / no property',
'A141'='bank',
'A142'='stores',
'A143'='none',
'A151'='rent',
'A152'='own',
'A153'='for free',
'A171'='unemployed/ unskilled - non-resident',
'A172'='unskilled - resident',
'A173'='skilled employee / official',
'A174'='management/ self-employed/highly qualified employee/ officer',
'A191'='none',
'A192'='yes, registered under the customers name',
'A201'='yes',
'A202'='no')
for (i in 1:dim(d)[2]) {
if (class(d[, i]) == "character") {
d[, i] <- as.factor(as.character(mapping[d[, i]]))
}
}
head(d)
## Status.of.existing.checking.account Duration.in.month
## 1 ... < 0 DM 6
## 2 0 <= ... < 200 DM 48
## 3 no checking account 12
## 4 ... < 0 DM 42
## 5 ... < 0 DM 24
## 6 no checking account 36
## Credit.history
## 1 critical account/other credits existing (not at this bank)
## 2 existing credits paid back duly till now
## 3 critical account/other credits existing (not at this bank)
## 4 existing credits paid back duly till now
## 5 delay in paying off in the past
## 6 existing credits paid back duly till now
## Purpose Credit.amount Savings account/bonds
## 1 radio/television 1169 unknown/ no savings account
## 2 radio/television 5951 ... < 100 DM
## 3 education 2096 ... < 100 DM
## 4 furniture/equipment 7882 ... < 100 DM
## 5 car (new) 4870 ... < 100 DM
## 6 education 9055 unknown/ no savings account
## Present.employment.since
## 1 .. >= 7 years
## 2 1 <= ... < 4 years
## 3 4 <= ... < 7 years
## 4 4 <= ... < 7 years
## 5 1 <= ... < 4 years
## 6 1 <= ... < 4 years
## Installment.rate.in.percentage.of.disposable.income
## 1 4
## 2 2
## 3 2
## 4 2
## 5 3
## 6 2
## Personal.status.and.sex Other.debtors/guarantors
## 1 male : single none
## 2 female : divorced/separated/married none
## 3 male : single none
## 4 male : single guarantor
## 5 male : single none
## 6 male : single none
## Present.residence.since
## 1 4
## 2 2
## 3 3
## 4 4
## 5 4
## 6 4
## Property
## 1 real estate
## 2 real estate
## 3 real estate
## 4 if not A121 : building society savings agreement/life insurance
## 5 unknown / no property
## 6 unknown / no property
## Age.in.years Other.installment.plans Housing
## 1 67 none own
## 2 22 none own
## 3 49 none own
## 4 45 none for free
## 5 53 none for free
## 6 35 none for free
## Number.of.existing.credits.at.this.bank Job
## 1 2 skilled employee / official
## 2 1 skilled employee / official
## 3 1 unskilled - resident
## 4 1 skilled employee / official
## 5 2 skilled employee / official
## 6 1 unskilled - resident
## Number.of.people.being.liable.to.provide.maintenance.for
## 1 1
## 2 1
## 3 2
## 4 2
## 5 2
## 6 2
## Telephone foreign.worker Good.Loan
## 1 yes, registered under the customers name yes GoodLoan
## 2 none yes BadLoan
## 3 none yes GoodLoan
## 4 none yes GoodLoan
## 5 none yes BadLoan
## 6 yes, registered under the customers name yes GoodLoan
새로운 데이터 살펴보기
https://github.com/WinVector/zmPDSwR/blob/master/Statlog/GCDSteps.R 여기에 가면 한 번에 정리된 게 있으니 이걸 보면서 따라해보자.
table() 함수를 이용하면 범주형 변수에 대한 빈도표를 쉽게 만들 수 있다.
# Purpose 행에 대한 빈도표
summary(d$Purpose)
## business car (new) car (used)
## 97 234 103
## domestic appliances education furniture/equipment
## 12 50 181
## others radio/television repairs
## 12 280 22
## retraining
## 9
# Purpose 에 따른 Good.Load 의 교차 빈도표
table(d$Purpose, d$Good.Loan)
##
## BadLoan GoodLoan
## business 34 63
## car (new) 89 145
## car (used) 17 86
## domestic appliances 4 8
## education 22 28
## furniture/equipment 58 123
## others 5 7
## radio/television 62 218
## repairs 8 14
## retraining 1 8
많은 운영 환경에서 우리가 원하는 데이터는 파일이 아닌 관계형 또는 SQL 데이터 베이스에 적재되어 있다. 공개해도 되는 데이터의 경우에는 종종 파일로 되어 있지만, 중요한 고객에 대한 데이터 같은 경우에는 데이터 베이스에 저장되어 있기도 한다. 관계형 데이터 베이스의 경우에는 수백만 개의 데이터까지 쉽게 늘릴 수 있고, 병렬화(parallelism), 일관성(consistency), 거래(transaction), 로깅(logging), 감사(audit) 등의 중요한 운영 기능을 제공하기도 한다.
운영 크기의 예제로 2011년 미국 인구조사 데이터를 사용하려고 한다. 이 데이터는 약 300만 명, 150만 가구에 대한 데이터를 가지고 있다. 각 열은 한 개인 또는 가구에 대한 200개의 값을 가지고 있다.
원본 데이터는 아래 링크에서 받을 수 있다.
United States Population Records: csv_pus.zip(http://www2.census.gov/acs2011_1yr/pums/csv_pus.zip)
United States Housing Unit Records: csv_hus.zip(http://www2.census.gov/acs2011_1yr/pums/csv_hus.zip)
데이터 사이언스에서의 엄격한 규칙은 동일한 결과를 재현해야한다는 것이다. 최소한 여러분의 성공적인 작업을 동일한 단계를 거쳐서 중간의 결과를 사용하지 않고 재현할 수 있어야한다. 모든 단계에서 그 결과를 얻을 수 있는 방법을 적어놓거나, 어디서 온 것인지에 대한 명확한 문서가 있어야한다.
기록하기 데이터 사이언티스트의 역할 중 큰 부분을 차지하는 것이 여러분의 결과를 가지고 설득하고, 이를 재현하는 것이다. 항상 기록하는 것을 강력히 추천한다. 뿐만 아니라, 부록 A에서 이야기한 것처럼 스크립트와 코드를 버전 컨트롤로 관리하는 것도 강력히 추천한다. 지난 주에 발표한 결과를 만들어 낸 코드와 데이터가 정확하게 어디서 기인한 것인지 확실하게 대답해야할 지도 모르기 때문이다.
수백만 줄에 달하는 구조화된 데이터는 데이터 베이스에서 다루는 것이 최선이다. 텍스트 프로세싱 툴에서 작업해도 되지만, 데이터 베이스에서 하는 것이 여러분의 데이터가 열과 행에 맞춰서 잘 준비되었다는 것을 보여주기에는 훨씬 더 나을 것이다.
데이터 베이스에서 데이터를 불러오기 위해서는 데이터베이스 커넥터가 필요하다. 그러면 R에서 SQL 쿼리를 직접 보낼 수 있다. SQL 은 선언형(declarative) 언어라고 부르는데, SQL 에서는 우리의 데이터 샘플이 어떤 관계를 가지고 있는지를 특정해주지 어떻게 계산을 할 것인지를 다루지는 않기 때문이다. 샘플링한 데이터는 phsample.RData 파일 안에 있고 이는
load(“./PUMS/loadExample/phsample.RData”)
를 이용해서 불러올 수 있다. dhus 는 hus 데이터셋에서 ORIGRANGROUP <=1 인 값만 가지고 온 것인데, ORIGRANGROUP 은 SQL Screwdriver 가 샘플링을 가능하게 하기 위해 추가하는 0-999 사이의 값이다. 이 중에서 0, 1의 값을 가진 row 만 가지고 오는 것이기 때문에 전체 데이터에서 2/1000 을 샘플링 한 것이다. dpus 는 pus 데이터셋에서 phus 데이터셋에 뽑힌 household ID(hus.SERIALNO) 와 동일한 것만 뽑아온 것이다.
샘플링을 너무 자랑스럽게 여기진 말자 많은 데이터 사이언티스트들이 대량의 데이터에 직접 알고리즘을 적용하느라 많은 시간을 낭비하곤 한다. 종종 이는 쓸데 없이 힘을 빼는 것이고, 적당한 사이즈의 데이터 샘플이면 왠만한 모델은 거의 동일한 결과를 얻을 수 있기 때문이다.
데이터 베이스로부터나, 데이터를 R로 불러오는 것은 모델링과 분석에 이용하기 위한 것임을 명심하자. 데이터 분석가는 항상 “데이터를 다뤄야” 하고, 데이터를 불러온 다음엔 빠르게 살펴봐야 한다. 데이터를 다루지 않을 거면 애초에 R 로 불러올 필요도 없을 것이다.
PUMS 데이터 불러오고 다루기
PUMS 에서 각 행은 익명화된 개인이나 가정을 나타낸다. 사람에 대한 데이터는 직업, 교육 정도, 수입, 그리고 다양한 인구통계 값을 가지고 있다.
load(“C:/Dev/pdsr/PUMS/phsample.RData”)
위의 명령어를 이용해서 데이터를 불러오자. 우리가 다룰 예제는 다음의 변수를 가지고 개인의 수입(USD, 컬럼명 PINCP)을 예측하는 것이다.
이는 데이터 자체에 집중하기 보다는 모델링 절차를 보여주기 위한 것이다. 결과는 데이터 조건(사용할 데이터의 부분집합) 과 데이터 코딩(데이터를 의미있는 문자로 치환하는 방법)에 크게 달려있다. 이런 이유 때문에 실증적 과학 논문에 “자료와 방법” 섹션이 있고, 여기서 데이터가 어떻게 수집됐고, 준비됐는지 설명하는 것이다. 우리는 다음 조건을 이용해서 “전형적인 상시 근로자” 만 선택할 것이다.
# Load the sample data
load("C:/Dev/pdsr/PUMS/phsample.RData")
# Select a subset of the Census data considered as a full-time worker
psub <- subset(dpus, with(dpus, (PINCP > 1000) & ESR == 1) &
(PINCP <= 250000) & (PERNP > 1000) & (PERNP <= 250000) &
(WKHP >= 40) & (AGEP >= 20) & (AGEP <= 50) &
(PWGTP1 > 0) & (COW %in% (1:7)) & SCHL %in% (1:24))
데이터 값 바꾸기(Recoding)
이 데이터를 이용해서 작업에 들어가기 전에 가독성을 위해 몇 가지 변수의 데이터를 바꾸려고 한다. 특히, 열거형 정수로 된 변수들을 가독성과 이런 변수들을 일반적인 수치값으로 다루는 것을 방지하기 위해 의미있는 요인-수준으로 바꾸는 것이다.
# Recoding variables
# 성별
psub$SEX <- as.factor(ifelse(psub$SEX == 1, 'M', 'F'))
psub$SEX <- relevel(psub$SEX, 'M')
# 직장의 종류
cowmap <- c("Employee of a private for-profit",
"Private not-for-profit employee",
"Local government employee",
"State government employee",
"Federal government employee",
"Self-employed not incorporated",
"Self-employed incorporated")
psub$COW <- as.factor(cowmap[psub$COW])
psub$COW <- relevel(psub$COW, cowmap[1])
# 학력
schlmap <- c(rep("no high school diploma", 15),
"Regular high school diploma",
"GED or alternative credential",
"some college credit, no degree",
"some college credit, no degree",
"Associate's degree",
"Bachelor's degree",
"Master's degree",
"Professional degree",
"Doctorate degree")
psub$SCHL <- as.factor(schlmap[psub$SCHL])
psub$SCHL <- relevel(psub$SCHL, schlmap[1])
# 데이터셋 나누기
dtrain <- subset(psub, ORIGRANDGROUP >= 500)
dtest <- subset(psub, ORIGRANDGROUP < 500)