아래 분석과정과 내용은 개인의 데이터 분석과정에 대한 연습 수준입니다. 즉, 아마추어 수준이며, 분석 절차나 결과에 대한 내용은 믿을만한 것이 되지 못합니다. 따라서, 제가 잘못 알고 있거나 개선에 대한 의견은 언제든지 환영합니다.
2012년도 범죄자 국적 데이터를 이용해서 탐험적 데이터 분석 연습을 한다. 데이터는 범죄 유형, 세부유형과 국적별 건수를 나타내고 있다.
여기서는 카테고리 변수인 범죄 유형과 역시 카테고리 변수인 국적 변수의 관계를 파악하는 교차분석을 시도해 볼 수도 있고, 수량 변수인 범죄 건수를 outcome으로 두고, 카테고리 변수인 범죄유형과 국적을 설명변수로 두는 선형회귀 분석도 가능할 것으로 생각된다.
data source: 공공데이터포털 - 2012년 범죄자 국적
위의 데이터를 xls 파일로 다운로드 받을 수 있다. R 에서는 여러가지 방식으로 데이터 로딩이 가능하다. 여기서는 osx 상에서 클립보드에 있는 내용을 붙여넣기 하는 식으로 진행해 본다.
먼저, 다운로드 받은 엑셀 파일을 오픈한다. 그리고 영역을 선택한 후 pipe(“pbpaste”) 를 이용하면 클립보드 내용을 R 변수에 저장할 수 있다.
a <- read.table(pipe("pbpaste"), sep="\t", header=T) # clipboard 내용을 읽는다.
write.csv(x = a, file="../data/151003-criminal.csv") # 파일로 저장한다.
df <- read.csv(file="../data/151003-criminal.csv")
데이터 파일을 읽고 적절한 포맷으로 구성되어 있는지 확인한다. 컬럼의 이름은 적절한지? 컬럼의 특성에 맞는 자료인지? 수치형과 카테고리형이 같이 혼합되어 있는지? NA 값이 존재하는지 등을 확인한다.
head(df) # 자료의 앞부분을 조회한다.
## X 구분 상세구분 총계 한국인 외국인 미.국 중.국 일.본
## 1 1 총계 총계 1,723,815 1,700,901 22,914 1,668 13,152 171
## 2 2 강력범죄 소계 23,789 23,199 590 37 223 5
## 3 3 강력범죄 살인(기수) 446 423 23 - 6 1
## 4 4 강력범죄 살인(미수등) 610 549 61 - 35 -
## 5 5 강력범죄 강도 3,322 3,181 141 10 50 -
## 6 6 강력범죄 강간·강제추행 18,012 17,664 348 24 120 4
## 몽.골 베트남 태.국 대.만 러시아 방글라데시 스리랑카 우즈베키스탄
## 1 1,381 1,819 576 427 270 115 354 710
## 2 45 69 13 1 13 15 29 42
## 3 - 7 4 - 2 - - -
## 4 3 1- - - - 1 6 2
## 5 12 28 7 - 5 - 6 10
## 6 30 24 2 1 6 14 17 29
## 인도네시아 캐나다 키르키스스탄 필리핀 파키스탄 기타
## 1 189 323 84 270 198 1,207
## 2 11 10 3 7 13 54
## 3 - 1 - 1 - 1
## 4 - - - 1 - 3
## 5 5 - - - - 8
## 6 6 9 2 5 13 42
tail(df) # 자료의 끝부분을 조회한다.
## X 구분 상세구분 총계 한국인 외국인 미.국 중.국 일.본 몽.골
## 35 35 교통범죄 소계 512,076 507,403 4,673 534 1,869 30 397
## 36 36 노동범죄 소계 2,378 2,340 38 - 31 - -
## 37 37 안보범죄 소계 126 123 3 - 1 - -
## 38 38 선거범죄 소계 1,855 1,853 2 - 1 - -
## 39 39 병역범죄 소계 18,359 18,358 1 1 - - -
## 40 40 기타범죄 소계 208,432 205,436 2,996 133 2,114 40 94
## 베트남 태.국 대.만 러시아 방글라데시 스리랑카 우즈베키스탄 인도네시아
## 35 453 200 73 51 24 142 291 78
## 36 - - - - - - 1 -
## 37 - - - - - - - -
## 38 1 - - - - - - -
## 39 - - - - - - - -
## 40 110 50 58 33 15 20 51 52
## 캐나다 키르키스스탄 필리핀 파키스탄 기타
## 35 79 29 79 46 298
## 36 4 - 1 1 -
## 37 1 - - - 1
## 38 - - - - -
## 39 - - - - -
## 40 37 23 34 29 103
먼저 컬럼 이름부터 조정한다. 컬럼 이름에 .(dot) 을 없앤다. ex) 미.국 -> 미국
# 변경 전
names(df)
## [1] "X" "구분" "상세구분" "총계"
## [5] "한국인" "외국인" "미.국" "중.국"
## [9] "일.본" "몽.골" "베트남" "태.국"
## [13] "대.만" "러시아" "방글라데시" "스리랑카"
## [17] "우즈베키스탄" "인도네시아" "캐나다" "키르키스스탄"
## [21] "필리핀" "파키스탄" "기타"
# 변경 후
names(df) <- gsub("\\.", "", names(df))
names(df)
## [1] "X" "구분" "상세구분" "총계"
## [5] "한국인" "외국인" "미국" "중국"
## [9] "일본" "몽골" "베트남" "태국"
## [13] "대만" "러시아" "방글라데시" "스리랑카"
## [17] "우즈베키스탄" "인도네시아" "캐나다" "키르키스스탄"
## [21] "필리핀" "파키스탄" "기타"
이후 진행에서는 데이터 편집을 쉽게 도와주는 dplyr 패키지를 이용한다.
데이터를 보면 범죄구분과 상세구분에 따라 총계, 소계 행이 있고, 총계, 외국인 열이 존재한다. 해당 컬럼은 다른 컬럼이나 행의 데이터를 합해서 만든 2차 데이터이기 때문에 데이터 분석 시에는 큰 의미가 없다. 따라서 해당 정보는 삭제하도록 한다.
library(dplyr)
df_tbl <- tbl_df(df)
df_tbl <- df_tbl %>% filter(상세구분 != "소계", 상세구분 != "총계") %>%
select(-X, -총계, -외국인)
데이터의 포맷을 보면, 넓은 형태의 테이블 구조이다. 즉, 컬럼명이 국적을 나타내는데 이와 같은 자료 구조는 데이터 분석이나 그래프를 통한 가시화 작업에 불편한 구조이다. 이 구조는 좁은 형태의 데이터 프레임으로 변환한다.
ex)
| 구분 | 상세구분 | nation | count |
|---|---|---|---|
| 강력범죄 | 살인(기수) | 한국인 | 423 |
| 강력범죄 | 살인(미수등) | 한국인 | 549 |
| 강력범죄 | 강도 | 한국인 | 3,181 |
| 강력범죄 | 강간·강제추행 | 한국인 | 17,664 |
| 강력범죄 | 방화 | 한국인 | 1,382 |
library(tidyr)
df_tbl <- gather(df_tbl, nation, count, -구분, - 상세구분)
## Warning: attributes are not identical across measure variables; they will
## be dropped
head(df_tbl)
## Source: local data frame [6 x 4]
##
## 구분 상세구분 nation count
## 1 강력범죄 살인(기수) 한국인 423
## 2 강력범죄 살인(미수등) 한국인 549
## 3 강력범죄 강도 한국인 3,181
## 4 강력범죄 강간·강제추행 한국인 17,664
## 5 강력범죄 방화 한국인 1,382
## 6 폭력범죄 상해 한국인 83,453
자료를 보면 count 속성이 문자 타입이다. 이를 숫자로 변환하고, 구분 및 상세구분 컬럼을 영문으로 변환한다. 그리고 - 로 표시된 데이터는 NA 처리한다.
원본 데이터 파일을 읽을 시에 NA 처리가 가능하며, NA로 변환하는 방법은 다양하게 있다.
df_tbl <- rename(df_tbl, type = 구분, detail_type = 상세구분) # 컬럼명 변경
df_tbl[df_tbl$count == "-",]$count <- NA # 결측치 처리
df_tbl$count <- as.integer(gsub(",", "", df_tbl$count)) # 숫자 형태로 변환
## Warning: 강제형변환에 의해 생성된 NA 입니다
str(df_tbl)
## Classes 'tbl_df', 'tbl' and 'data.frame': 432 obs. of 4 variables:
## $ type : Factor w/ 16 levels "강력범죄","교통범죄",..: 1 1 1 1 1 14 14 14 14 14 ...
## $ detail_type: Factor w/ 26 levels "강간·강제추행",..: 9 10 2 1 6 11 24 20 25 15 ...
## $ nation : Factor w/ 18 levels "한국인","미국",..: 1 1 1 1 1 1 1 1 1 1 ...
## $ count : int 423 549 3181 17664 1382 83453 165400 1102 4667 266 ...
먼저 범죄 유형별 전체 건수를 국적 구분으로 막대그래프를 그려본다.
library(ggplot2)
g <- ggplot(df_tbl, aes(x = type, y = count, fill=nation)) + geom_bar(stat = "identity")
g <- g + labs(title = "범죄유형별 건수", x = "범죄유형", y = "건수")
g <- g + theme(text = element_text(family = "나눔명조"))
g
## Warning: Removed 191 rows containing missing values (position_stack).
# 범죄 유형별 그룹핑하고 전체 건수를 카운팅한다.
a <- df_tbl %>% group_by(type) %>% summarize(cnt = sum(count, na.rm=TRUE))
폭력범죄가 전체 유형에서 가장 많이 발생하고 있고, 한국인의 비중이 절대적으로 크다.
폭력범죄를 조금 자세히 살펴 보자.
폭력범죄에서 국적별 차이가 유의미할까? 한국인에 대한 수치가 절대적으로 크기 때문에 국적별 전체 건수에 대한 비율로 변환해서 비율의 차이를 비교해 본다.
먼저, 국적별 전체 건수를 알아보자.
# 국적별 전체 건수를 카운팅
nation_count <- aggregate(count ~ nation, df_tbl, FUN = "sum", na.action = na.omit)
# 전체 건수 기준으로 정렬
nation_count[order(nation_count$count, decreasing = TRUE), ]
## nation count
## 1 한국인 743281
## 3 중국 7576
## 6 베트남 936
## 2 미국 738
## 5 몽골 635
## 18 기타 567
## 12 우즈베키스탄 241
## 8 대만 221
## 7 태국 184
## 11 스리랑카 161
## 14 캐나다 160
## 17 파키스탄 105
## 16 필리핀 100
## 9 러시아 94
## 4 일본 70
## 10 방글라데시 68
## 13 인도네시아 46
## 15 키르키스스탄 26
이젠 기존의 df_tbl 데이터 프레임에 각 국적에 해당하는 총 인원수를 신규 컬럼으로 추가하고 비율을 계산한다.
df_tbl$n_total <- 0 # 중요! 신규 컬럼을 먼저 0으로 추가한다.
for(m in nation_count$nation) {
cnt <- nation_count[nation_count$nation == m,]$count
df_tbl[df_tbl$nation == m,]$n_total <- cnt
}
df_tbl <- mutate(df_tbl, rate = count / n_total)
head(df_tbl)
## Source: local data frame [6 x 6]
##
## type detail_type nation count n_total rate
## 1 강력범죄 살인(기수) 한국인 423 743281 0.0005690984
## 2 강력범죄 살인(미수등) 한국인 549 743281 0.0007386170
## 3 강력범죄 강도 한국인 3181 743281 0.0042796735
## 4 강력범죄 강간·강제추행 한국인 17664 743281 0.0237649018
## 5 강력범죄 방화 한국인 1382 743281 0.0018593237
## 6 폭력범죄 상해 한국인 83453 743281 0.1122765145
폭력범죄 데이터만 따로 추출하고, 국적별로 그룹핑해서 비중에 대한 합을 요약한다.
df_tbl_violent <- filter(df_tbl, type == "폭력범죄") %>%
group_by(nation) %>%
summarize(p_sum = sum(rate, na.rm=TRUE))
summary(df_tbl_violent)
## nation p_sum
## 한국인 : 1 Min. :0.4571
## 미국 : 1 1st Qu.:0.5616
## 중국 : 1 Median :0.6437
## 일본 : 1 Mean :0.6311
## 몽골 : 1 3rd Qu.:0.6750
## 베트남 : 1 Max. :0.8441
## (Other):12
# hist(df_tbl_violent$p_sum, breaks=5) # 아래 그래프와 동일함
g <- ggplot(df_tbl_violent, aes(x = p_sum))
g <- g + geom_histogram(binwidth=0.1) + xlim(c(0, 1))
g <- g + geom_density()
g <- g + labs(title = "국적별 비중에 대한 분포", x = "비중", y = "건수")
g <- g + theme(text = element_text(family = "나눔명조"))
g
g <- ggplot(df_tbl_violent, aes(x = 1, y = p_sum)) + geom_boxplot()
g
그래프를 보면, 일종의 분포 경향을 가지는 것으로 보인다. 다만, 국적에 대한 전체 건수가 18개(기타 포함) 이므로 사례 수가 충분하다고 볼 수는 없다.
국적별 비중에 대한 18개의 수치 정보에 대한 평균과 표준편차를 본다.
m = mean(df_tbl_violent$p_sum)
s = sd(df_tbl_violent$p_sum)
평균은 0.6311462 이고 표준편차는 0.0914856 가 된다. 이것은 국적별로 전체 범죄수의 평균 약 63%가 폭력범죄에 연루되고 있으며 표준편차는 9%로 생각된다.
모집단을 범죄가 발각되지 않은 건수를 포함한 전체 범죄 현황이라고 하자.
만약 63%를 표본의 비율이라고 한다면, 모비율에 대한 신뢰구간은 95% 신뢰수준으로 구할 경우 t test를 통해 구할 수도 있을 것이다.
t.test(df_tbl_violent$p_sum)
##
## One Sample t-test
##
## data: df_tbl_violent$p_sum
## t = 29.2694, df = 17, p-value = 5.513e-16
## alternative hypothesis: true mean is not equal to 0
## 95 percent confidence interval:
## 0.5856515 0.6766409
## sample estimates:
## mean of x
## 0.6311462
모비율에 대한 신뢰구간은 58.6 ~ 67.7% 로 생각할 수도 있다.
(to be continued…)