summary

아래 분석과정과 내용은 개인의 데이터 분석과정에 대한 연습 수준입니다. 즉, 아마추어 수준이며, 분석 절차나 결과에 대한 내용은 믿을만한 것이 되지 못합니다. 따라서, 제가 잘못 알고 있거나 개선에 대한 의견은 언제든지 환영합니다.

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: 데이터 프레임의 컬럼명을 확인하거나 수정할 수 있다.
  • gsub: regular expression 정규식을 활용해서 점을 ’’로 치환한다.
# 변경 전
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차 데이터이기 때문에 데이터 분석 시에는 큰 의미가 없다. 따라서 해당 정보는 삭제하도록 한다.

  • tbl_df: 대용량 데이터프레임을 효율적으로 다루기 쉽게 만든다.
  • filter: 특정 행을 선택하거나 생략할 수 있는 조건을 지정한다.
  • select: 특정 컬럼을 선택하거나 생략할 수 있는 조건을 지정한다.
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
  • tidyr: gather 함수를 사용할 수 있게 해주는 라이브러리
  • gather: 넓은 형태의 데이터 프레임을 좁은 구조로 변환하는 함수
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…)