본문 바로가기
  • plotly로 바로쓰는 동적시각화 in R & 파이썬
데이터 전처리

데이터 개수 세기(도수분포)와 구간 나누기

by 아참형인간 2021. 7. 1.
count.knit

데이터 개수(빈도) 세기

R에서 많이 사용하는 tidy한 데이터는 데이터의 특성을 열로 지정하고 관측 데이터는 행으로 저장하는 데이터를 말한다.(https://2stndard.tistory.com/16 참조) 따라서 조건에 적합한 행을 필터링하고 행의 갯수를 세면 조건에 맞는 관측치의 수를 알아낼 수 있다. 조건에 적합한 열을 필터링한 후 결과를 알아내기 위해서는 행의 수를 세야한다. 이 포스트에서는 행의 수를 세어 빈도를 산출하는 방법을 알아 본다.

본 포스트에서 사용하는 샘플 데이터는 교육통계 서비스 홈페이지(https://kess.kedi.re.kr)에서 제공하는 고등교육기관 시도별 기관 신입생 충원률 데이터을 사용하였다. 이 데이터를 로딩하는 코드는 다음과 같다.

library(readxl)
library(tidyverse)

data <- read_xlsx('./시도별 신입생 충원율(2010_2020)_탑재용.xlsx', sheet = 'Sheet1', skip = 7, col_types = c(rep('text', 2), rep('numeric', 12)), col_names = FALSE)

names(data) <- c('연도', '시도', '전체_모집인원', '전체_신입생', '전체_충원률', '대학_모집인원', '대학_신입생', '대학_충원률', '전문대_모집인원', '전문대_신입생', '전문대_충원률', '대학원_모집인원', '대학원_신입생', '대학원_충원률')

data$시도 <- fct_relevel(data$시도, '서울', '부산', '대구', '인천', '광주', '대전', '울산', '세종', '경기', '강원', '충북', '충남', '전북', '전남', '경북', '경남', '제주')

전체 행의 수 산출

전체 행의 수를 산출하기 위해서는 dim(), nrow()를 사용할 수 있다.

dim(data)
## [1] 195  14

dim()은 데이터프레임의 전체 행의 수와 전체 열의 수가 산출된다.

nrow(data)
## [1] 195

nrow()는 데이터프레임의 전체 행의 수를 산출한다.

특정 열의 도수 분포(데이터 빈도) 산출

위의 두 함수는 특정 조건없이 전체 데이터의 개수를 세는 함수이다. 하지만 대부분의 응용은 특정 열에 기록된 구분별로 데이터가 몇개인지를 알아보는 도수 분포를 산출하는 경우가 많다. 이런 경우는 R base에서 제공하는 table()을 사용할 수도 있고 dplyr 패키지에서 제공하는 n(), count()를 사용할 수 있다.

table(data$시도)
## 
## 서울 부산 대구 인천 광주 대전 울산 세종 경기 강원 충북 충남 전북 전남 경북 경남 
##   11   11   11   11   11   11   11    8   11   11   11   11   11   11   11   11 
## 제주 전국 
##   11   11

table()을 사용하면 지정된 열의 값에 따라 해당 값의 개수(도수 분포)를 산출해 준다. 위의 결과와 같이 data 데이터프레임에는 시도별로 11개씩의 데이터를 가지지만 세종만 8개의 데이터가 있다는 것을 알수 있다.

table()prop.table()을 사용하여 다음과 같이 도수 분포의 백분률을 사용하는 경우가 많다.

round(prop.table(table(data$시도)) * 100, 2)
## 
## 서울 부산 대구 인천 광주 대전 울산 세종 경기 강원 충북 충남 전북 전남 경북 경남 
## 5.64 5.64 5.64 5.64 5.64 5.64 5.64 4.10 5.64 5.64 5.64 5.64 5.64 5.64 5.64 5.64 
## 제주 전국 
## 5.64 5.64
data$시도 |>
  table() |>
  prop.table() * 100 |>
  round(2)
## 
##     서울     부산     대구     인천     광주     대전     울산     세종 
## 5.641026 5.641026 5.641026 5.641026 5.641026 5.641026 5.641026 4.102564 
##     경기     강원     충북     충남     전북     전남     경북     경남 
## 5.641026 5.641026 5.641026 5.641026 5.641026 5.641026 5.641026 5.641026 
##     제주     전국 
## 5.641026 5.641026
table(data$전체_충원률)
## 
##  75.5  75.6  75.7  76.8  78.8  79.6  81.1  82.8  83.9    86  86.1  87.6  88.4 
##     2     1     1     1     1     1     1     1     2     1     1     2     1 
##  88.5  88.6  89.9    90  90.5  90.6  90.7  90.9    91  91.1  91.2  91.3  91.5 
##     1     1     2     1     2     1     1     1     2     2     1     1     1 
##  91.7  91.9    92  92.1  92.4  92.5  92.6  92.7  92.8  92.9    93  93.1  93.2 
##     3     3     2     1     1     1     2     1     1     1     1     2     4 
##  93.3  93.4  93.5  93.6  93.7  93.8  93.9    94  94.1  94.2  94.3  94.4  94.5 
##     3     1     1     3     2     2     3     3     3     3     2     4     1 
##  94.6  94.7  94.8  94.9    95  95.1  95.2  95.4  95.5  95.6  95.7  95.8  95.9 
##     4     3     3     3     2     2     1     3     3     2     1     3     2 
##    96  96.1  96.2  96.3  96.5  96.7  96.8  96.9    97  97.1  97.2  97.3  97.4 
##     2     3     3     2     2     2     3     8     2     3     2     3     3 
##  97.5  97.6  97.7  97.8  97.9  98.1  98.2  98.3  98.4  98.5  98.7  98.8  98.9 
##     2     1     1     1     2     2     2     2     1     4     4     2     3 
##    99  99.2  99.3  99.4  99.5 100.8   102 102.3   105 
##     2     2     1     1     1     1     1     1     1

factor의 형태가 아닌 숫자형 열에 대해서도 사용할 수 있는데 위와 같이 충원률에 대한 빈도를 산출할 수 있다. 충원률이 가장 많은 도수는 98.5, 98.7로 각각 4개의 데이터가 존재한다.

dplyrn()count()를 사용하는 코드는 다음과 같다.

data |>
  group_by(시도) |>
  summarise(n = n())
## # A tibble: 18 x 2
##    시도      n
##    <fct> <int>
##  1 서울     11
##  2 부산     11
##  3 대구     11
##  4 인천     11
##  5 광주     11
##  6 대전     11
##  7 울산     11
##  8 세종      8
##  9 경기     11
## 10 강원     11
## 11 충북     11
## 12 충남     11
## 13 전북     11
## 14 전남     11
## 15 경북     11
## 16 경남     11
## 17 제주     11
## 18 전국     11
data |>
  group_by(시도) |>
  count()
## # A tibble: 18 x 2
## # Groups:   시도 [18]
##    시도      n
##    <fct> <int>
##  1 서울     11
##  2 부산     11
##  3 대구     11
##  4 인천     11
##  5 광주     11
##  6 대전     11
##  7 울산     11
##  8 세종      8
##  9 경기     11
## 10 강원     11
## 11 충북     11
## 12 충남     11
## 13 전북     11
## 14 전남     11
## 15 경북     11
## 16 경남     11
## 17 제주     11
## 18 전국     11

위의 코드에서 보듯이 count()는 단독으로 사용이 가능하지만n()summarise()내에서 사용해야 한다.

연속된 값의 범주화

앞선 예에서는 열에 기록된 값을 도수로 하여 분포를 산출하는 방법의 예였다. 열에 기록된 값이 이미 범주화 되어 있는 경우는 위의 예처럼 도수 분포를 간단히 산출할 수 있지만 ‘충원률’과 같이 연속된 수치값인 경우는 일반적으로 수치값을 몇가지 범주로 구분하고 도수 분포를 산출하는 응용이 일반적이다. 예를 들어 나이가 기록된 열의 경우 1세 단위의 데이터 빈도를 살펴볼수도 있지만 ’10대’, ‘20대’, ‘30대’ 등과 같이 나이를 적절히 범주화하여 도수분포를 구하는 경우이다. 이를 위해서는 먼저 연속된 수치열을 적절히 범주화 하는 방법을 살펴본다.

연속된 수치열을 범주화하려면 ifelse()를 사용하거나 cut(), cut_interval(), cut_width(), ntile()등의 함수를 사용할 수 있다.

ifelse 사용

연속된 숫자열을 범주화하는데 가장 직관적인 방법이 ifelse()를 사용하는 방법이다. 각각의 구간을 ifelse()를 사용하여 구분하고 이에 대한 라벨을 지정함으로써 범주화 할 수있다.

data$전체_충원률_그룹 <- ifelse(data$전체_충원률 > 70 &data$전체_충원률 <= 80, '70초과 80이하',
                         ifelse(data$전체_충원률 > 80 &data$전체_충원률 <= 90, '80초과 90이하',
                         ifelse(data$전체_충원률 > 90 &data$전체_충원률 <= 100, '90초과 100이하',
                         '100이상')))
data |>
  count(전체_충원률_그룹)
## # A tibble: 4 x 2
##   전체_충원률_그룹     n
##   <chr>            <int>
## 1 100이상              4
## 2 70초과 80이하        7
## 3 80초과 90이하       14
## 4 90초과 100이하     170

이 방법은 구간의 구분이 명확히 눈에 보인다는 장점이 있지만 구간이 많아지거나 구간을 나누는 함수를 사용해야 하는 경우 사용하기가 어렵다는 단점이 있다.

cut()

cut()은 매개변수롤 전달되는 연속된 숫자 벡터를 n등분으로 나눈 구간을 설정하거나 구분점별로 나눈 구간을 설정할 때 사용하는 함수이다. 이 함수의 매개변수는 다음과 같다.

  • x : 구간을 설정하기 위해 사용하는 연속된 숫자 벡터
  • breaks : 구간을 어떻게 설정할지에 대한 정보를 전달. 단일 숫자가 전달되면 x벡터의 최소와 최대값의 구간을 단일 숫자만큼의 구간으로 분할하고, c()를 사용하여 하나 이상의 숫자로 구성된 벡터가 전달 되면 각 숫자를 구분점으로 한 구간을 설정
  • labels : breaks로 구분된 각 구간을 대표할 수 있는 라벨을 설정
  • right : 각 구간을 이하로 설정할지, 이상으로 설정할지를 결정. 예를 들어 10에서 20까지의 구간에 대해 right = TRUE인 경우 10 < x <= 20으로 설정되고 right = FALSE인 경우 10 <= x < 20으로 설정됨
  • include.lowest : x의 최소값을 구간에 포함할지를 결정. 예를 들어 10이 최소값이고 20까지의 구간이 가장 작은 값들의 구간이라면 include.lowest = TRUE의 경우 10 <= x <= 20이고 include.lowest = FALSE인 경우는 10 < x <= 20의 구간으로 설정되며 최소값은 포함되지 못하기 때문에 NA로 설정됨

샘플 데이터에서 전체 충원률을 구간의 길이가 균등하게 5개의 구간으로 나누는 코드는 다음과 같다.

data |>
  mutate(전체_충원률_그룹 = cut(data$전체_충원률, breaks = 5, labels = paste0(1:5, '그룹'))) |>
  count(전체_충원률_그룹)
## # A tibble: 5 x 2
##   전체_충원률_그룹     n
##   <fct>            <int>
## 1 1그룹                8
## 2 2그룹                5
## 3 3그룹               43
## 4 4그룹              130
## 5 5그룹                9

전체 충원률 구간을 사용자가 직접 설정하기 위해서는 cut()break에 구간점을 지정함으로써 설정할 수 있다.

data |>
  mutate(전체_충원률_그룹 = cut(data$전체_충원률, breaks = c(-Inf, 80, 90, 100, Inf))) |>
  count(전체_충원률_그룹)
## # A tibble: 4 x 2
##   전체_충원률_그룹     n
##   <fct>            <int>
## 1 (-Inf,80]            7
## 2 (80,90]             14
## 3 (90,100]           170
## 4 (100, Inf]           4

위 코드의 결과값을 보면 ‘(’와’]‘가 나오는데’(‘는 경계값을 포함하지 않는다는 의미(초과, 미만)이고’]’는 경계값을 포함(이상, 이하)한다는 의미이다.

만약 값의 범위를 기준으로 구간을 구분하는 것이 아니고 값을 기준으로 분위로 구분되는 구간을 설정하기 위해서는 quantile()을 사용할 수 있다.

data |>
  mutate(전체_충원률_그룹 = cut(전체_충원률, breaks = quantile(전체_충원률, probs = seq(0, 1, 0.25)))) |>
  count(전체_충원률_그룹)
## # A tibble: 5 x 2
##   전체_충원률_그룹     n
##   <fct>            <int>
## 1 (75.5,93]           47
## 2 (93,94.9]           51
## 3 (94.9,97]           46
## 4 (97,105]            49
## 5 <NA>                 2

위의 결과를 보면 구간간의 도수가 비슷하게 나오는 구간으로 설정되었다. 다만 NA로 설정된 2개의 행이 보이는데 이는 가장 작은 구간에 최소값인 75.5가 포함되지 않기 때문이다. 이를 수정하기 위해서는 다음과 같이 코드를 수정한다.

data |>
  mutate(전체_충원률_그룹 = cut(전체_충원률, breaks = quantile(전체_충원률, probs = seq(0, 1, 0.25)), include.lowest = TRUE)) |>
  count(전체_충원률_그룹)
## # A tibble: 4 x 2
##   전체_충원률_그룹     n
##   <fct>            <int>
## 1 [75.5,93]           49
## 2 (93,94.9]           51
## 3 (94.9,97]           46
## 4 (97,105]            49

cut() 파생함수

cut()과 유사한 기능을 제공하는 함수로 cut_interval(), cut_width(), cut_number()ggplot2패키지에서 제공된다.

  • cut_interval()

이 함수는 값의 범위를 같은 간격으로 나누는 함수이다. 이 함수는 cut(x, breaks = n)과 동일한 결과를 산출한다.

data |>
  mutate(전체_충원률_그룹 = cut_interval(전체_충원률, 5, labels = paste0(1:5, '그룹'))) |>
  count(전체_충원률_그룹)
## # A tibble: 5 x 2
##   전체_충원률_그룹     n
##   <fct>            <int>
## 1 1그룹                8
## 2 2그룹                5
## 3 3그룹               43
## 4 4그룹              130
## 5 5그룹                9
  • cut_width()

이 함수는 값의 범위를 지정된 길이만큼의 구간으로 나누는 함수이다. 이 함수는 cut(x, breaks = n)과 동일한 결과를 산출한다. 이 함수에는 center매개변수를 지정할 수있는데 데이터를 나눌때 center값을 중심으로 구간을 나누게 된다.

data |>
  mutate(전체_충원률_그룹 = cut_width(전체_충원률, 5, center = 85)) |>
  count(전체_충원률_그룹)
## # A tibble: 7 x 2
##   전체_충원률_그룹     n
##   <fct>            <int>
## 1 [72.5,77.5]          5
## 2 (77.5,82.5]          3
## 3 (82.5,87.5]          5
## 4 (87.5,92.5]         31
## 5 (92.5,97.5]        115
## 6 (97.5,102]          35
## 7 (102,108]            1
  • cut_number()

이 함수는 quantile()을 사용한 것과 같은 결과를 도출한다. cut()quantile()을 섞어 쓰는 것보다 간결하게 사용할 수 있다는 점에서 장점이 있다.

data |>
  mutate(전체_충원률_그룹 = cut_number(전체_충원률, 4)) |>
  count(전체_충원률_그룹)
## # A tibble: 4 x 2
##   전체_충원률_그룹     n
##   <fct>            <int>
## 1 [75.5,93]           49
## 2 (93,94.9]           51
## 3 (94.9,97]           46
## 4 (97,105]            49

ntile()

ntile()은 매개변수로 전달된 구간의 수만큼 구간을 나누는데 각 구간의 데이터 개수(도수)가 유사한 값을 가지도록 구간을 설정하는 함수이다.

data |>
  mutate(전체_충원률_그룹 = ntile(전체_충원률, 4)) |>
  count(전체_충원률_그룹)
## # A tibble: 4 x 2
##   전체_충원률_그룹     n
##              <int> <int>
## 1                1    49
## 2                2    49
## 3                3    49
## 4                4    48

댓글