디지털 글쓰기

수학능력평가

데이터 과학자 학부모입장에서 챗GPT시대 수학능력평가에 대해서 알아보자.

Author
Affiliation

1 수능이란?

수능은 대한민국에서 실시하는 대학입학 자격시험으로 국어, 수학, 영어, 한국사, 사회탐구, 과학탐구 총 6개 과목으로 구성되어 있다. 수능은 대한민국의 모든 고등학교 졸업생과 검정고시 합격자가 응시할 수 있다. 수능은 대학 입학 전형에 있어 가장 중요한 요소 중 하나로, 수능 성적은 대학 입학 정원의 50% 이상을 차지하는 경우가 많다.

한국교육과정평가원에서 밝히 대한수학능력시험의 성격과 목적은 다음과 같다.

  • 대학 교육에 필요한 수학 능력 측정으로 선발의 공정성과 객관성 확보
  • 고등학교 교육과정의 내용과 수준에 맞는 출제로 고등학교 학교교육의 정상화 기여
  • 개별 교과의 특성을 바탕으로 신뢰도와 타당도를 갖춘 시험으로서 공정성과 객관성 높은 대입 전형자료 제공

2 ’23년 일정

대한수학능력시험 주요업무추진 일정은 다음과 같다.

Code
library(datapasta)
library(tidyverse)
library(gtExtras)
library(gt)

schedule_raw <- tibble::tribble(
            ~"주요.업무",                   ~"추진일정",            ~"비고",
       "시행기본계획 발표",              "3.28.(화)",             NA,
       "시행세부계획 공고",               "7.3.(월)",       "중앙 일간지",
  "원서 교부, 접수 및 변경",    "8.24.(목) ~ 9.8.(금)", "토요일 및 공휴일 제외",
           "시험 실시",             "11.16.(목)",             NA,
    "문제 및 정답 이의신청", "11.16.(목) ~ 11.20.(월)",          "5일간",
           "정답 확정",             "11.28.(화)",             NA,
              "채점",  "11.17.(금) ~ 12.8.(금)",         "22일간",
           "성적 통지",              "12.8.(금)",             NA
  )

schedule <- schedule_raw %>%
  janitor::clean_names(ascii=FALSE) %>% 
  separate(추진일정, into = c("시작일", "완료일"), sep = "~") %>% 
  mutate(시작일 = sub("\\(.*\\)", "", 시작일),
         완료일 = sub("\\(.*\\)", "", 완료일)) %>% 
  mutate(시작일 = glue::glue("2023.{시작일}") %>% as.character(.) %>% as.Date(format = "%Y.%m.%d"),
         완료일 = glue::glue("2023.{완료일}") %>% as.character(.) %>% as.Date(format = "%Y.%m.%d"))

schedule %>% 
  gt::gt() %>% 
  gt_theme_nytimes() %>% 
    cols_align(align = "center", columns = everything()) %>% 
    fmt_missing(
      columns = everything(),
      missing_text = "-" # replace NA with "-"
    )
주요_업무 시작일 완료일 비고
시행기본계획 발표 2023-03-28 - -
시행세부계획 공고 2023-07-03 - 중앙 일간지
원서 교부, 접수 및 변경 2023-08-24 2023-09-08 토요일 및 공휴일 제외
시험 실시 2023-11-16 - -
문제 및 정답 이의신청 2023-11-16 2023-11-20 5일간
정답 확정 2023-11-28 - -
채점 2023-11-17 2023-12-08 22일간
성적 통지 2023-12-08 - -
Code
library(vistime)
library(timevis)

schedule_tv <- schedule %>% 
  mutate(id = row_number()) %>% 
  mutate(group = c("시행계획", "시행계획", "원서교부", "시험", rep("채점 성적통지", 4)) ) %>%
  set_names(c("content", "start", "end", "remark", "id", "group"))

schedule_grp <- schedule_tv %>% 
  select(id = group, content = group) %>% 
  distinct()

timevis(data = schedule_tv, groups = schedule_grp)

3 시험시간과 문항수

4 수능통계

4.1 연도별 응시현황

응시자가 2000년 전후 정점을 찍은 이후 수능지원자와 응시자수가 꾸준히 줄고 있으며 응시자와 지원자수 사이 간극이 커지고 있다.

Code
# !pip install tabula-py

import pandas as pd
import tabula
file = "data/suneung/수능 연도별 응시현황 (2022.12.게시).pdf"
taker = tabula.read_pdf(file, pages='all')

taker_pd = taker[0]
Code
library(reticulate)

taker_raw <- py$taker_pd %>% 
  as_tibble()

taker_tbl <- taker_raw %>% 
  janitor::clean_names(ascii = FALSE) %>% 
  slice(3:n()) %>% 
  select(-비고, -unnamed_0) %>% 
  set_names(c("학년도", "시험일", "지원자", "응시자", "응시율")) %>% 
  mutate(시험일 = lubridate::ymd(시험일)) %>% 
  pivot_longer(cols = 지원자:응시자, names_to = "구분", values_to = "사람수") %>% 
  mutate(사람수 = parse_number(사람수)) %>% 
  select(시험일, 구분, 사람수)

taker_tbl %>% 
  pivot_wider(names_from = 구분, values_from = 사람수) %>% 
  arrange(desc(시험일)) %>% 
  mutate(응시율 = 응시자 / 지원자) %>% 
  gt::gt() %>% 
  gt_theme_nytimes() %>% 
    cols_align(align = "center", columns = everything()) %>% 
    fmt_missing(
      columns = everything(),
      missing_text = "-" # replace NA with "-"
    ) %>% 
  fmt_integer( columns = c(지원자, 응시자)) %>% 
  fmt_percent(응시율, decimals = 1) %>% 
  tab_header(
    title = md("수능 지원자, 응시자, 응시율표"),
    subtitle = md("1994년 제외")
  )
수능 지원자, 응시자, 응시율표
1994년 제외
시험일 지원자 응시자 응시율
2022-11-17 508,030 447,669 88.1%
2021-11-18 509,821 448,138 87.9%
2020-12-03 493,434 421,034 85.3%
2019-11-14 548,734 484,737 88.3%
2018-11-15 594,924 530,220 89.1%
2017-11-23 593,527 531,327 89.5%
2016-11-17 605,987 552,297 91.1%
2015-11-12 631,187 585,332 92.7%
2014-11-13 640,621 594,835 92.9%
2013-11-07 650,747 606,813 93.2%
2012-11-08 668,522 621,336 92.9%
2011-11-10 693,631 648,946 93.6%
2010-11-18 712,227 668,991 93.9%
2009-11-12 677,834 638,216 94.2%
2008-11-13 588,839 559,475 95.0%
2007-11-15 584,934 550,588 94.1%
2006-11-16 588,899 551,884 93.7%
2005-11-23 593,806 554,345 93.4%
2004-11-17 610,257 574,218 94.1%
2003-11-05 674,154 642,583 95.3%
2002-11-06 675,922 655,384 97.0%
2001-11-07 739,129 718,441 97.2%
2000-11-15 872,297 850,305 97.5%
1999-11-17 896,122 868,366 96.9%
1998-11-18 868,643 839,837 96.7%
1997-11-19 885,321 854,272 96.5%
1996-11-13 824,374 795,338 96.5%
1995-11-22 840,661 809,867 96.3%
1994-11-23 781,749 757,509 96.9%
Code
taker_tbl %>% 
  mutate( 사람수 = 사람수 / 10^4) %>% 
  ggplot(aes(x = 시험일, y = 사람수, color = 구분)) +
    geom_line() +
    geom_point() +
    labs(title = "수능 지원자와 응시자수 추세",
         x = "",
         y = "사람수(만명)") +
    theme(legend.position = "top") +
    scale_color_manual(values = c("gray35", "blue")) +
    expand_limits(y = 0)

4.2 수능 접수현황

Code
import pandas as pd
import tabula
application_file = "data/suneung/수능 연도별 접수현황 (2022.12.게시).pdf"
application = tabula.read_pdf(application_file, pages='all')

applicatin_1_pd = application[0]
applicatin_2_pd = application[1]
Code
## 1 페이지 
application_1_raw <- py$applicatin_1_pd %>% 
  as_tibble() 

application_1 <- application_1_raw %>% 
  mutate(학년도 = map_chr(학년도, 1) %>% as.integer) %>% 
  filter(학년도 >= 2005) %>% 
  janitor::clean_names(ascii=FALSE) %>% 
  set_names(c("학년도", "기간", "총계", "성별_남", "성별_여", "계열별", "출신별_재학", "출신별_졸업", "출신별_검정", "응시료", "가", "나", "다")) %>% 
  select(학년도, contains("_"))

## 2 페이지 
application_2_raw <- py$applicatin_2_pd %>% 
  as_tibble() 

application_2 <- application_2_raw %>% 
  slice(2:n()) %>% 
  janitor::clean_names(ascii=FALSE) %>% 
  set_names(c("학년도", "기간", "총계", "성별_남", "성별_여", "계열별", "출신별_재학", "출신별_졸업", "출신별_검정", "응시료")) %>% 
  select(학년도, contains("_"))
  
## 1~2 페이지
application_tbl <- bind_rows(application_1, application_2) %>% 
  pivot_longer(-학년도) %>% 
  separate(name, into =c("대구분", "중구분"), sep = "_") %>% 
  mutate(value = str_remove(value, '\\r\\(.*\\)') %>% parse_number(.))

## 표 제작
library(scales)

### 성별
application_tbl %>% 
  filter(대구분 == "성별") %>% 
  pivot_wider(names_from = 중구분, values_from = value) %>% 
  mutate(총계 =+ 여) %>% 
  mutate(남여비율 = glue::glue("{scales::percent(남/(남+여), accuracy=0.1)} / {scales::percent(여/(남+여), accuracy=0.1)}")) %>% 
  arrange(desc(학년도)) %>% 
  gt::gt() %>% 
  gtExtras::gt_theme_nytimes() %>% 
  fmt_integer(columns = c(남, 여, 총계)) %>% 
  cols_align(align = "center")
학년도 대구분 총계 남여비율
2023 성별 260,126 247,904 508,030 51.2% / 48.8%
2022 성별 261,350 248,471 509,821 51.3% / 48.7%
2021 성별 254,027 239,407 493,434 51.5% / 48.5%
2020 성별 282,036 266,698 548,734 51.4% / 48.6%
2019 성별 306,142 288,782 594,924 51.5% / 48.5%
2018 성별 303,620 289,907 593,527 51.2% / 48.8%
2017 성별 310,451 295,536 605,987 51.2% / 48.8%
2016 성별 323,783 307,404 631,187 51.3% / 48.7%
2015 성별 333,204 307,417 640,621 52.0% / 48.0%
2014 성별 342,776 307,971 650,747 52.7% / 47.3%
2013 성별 356,924 311,598 668,522 53.4% / 46.6%
2012 성별 371,771 321,860 693,631 53.6% / 46.4%
2011 성별 379,385 332,842 712,227 53.3% / 46.7%
2010 성별 358,142 319,692 677,834 52.8% / 47.2%
2009 성별 313,002 275,837 588,839 53.2% / 46.8%
2008 성별 312,064 272,870 584,934 53.4% / 46.6%
2007 성별 313,715 275,184 588,899 53.3% / 46.7%
2006 성별 314,323 279,483 593,806 52.9% / 47.1%
2005 성별 324,700 285,557 610,257 53.2% / 46.8%
Code

### 출신별
application_tbl %>% 
  filter(대구분 == "출신별") %>% 
  pivot_wider(names_from = 중구분, values_from = value) %>% 
  mutate(총계 = 재학 + 졸업 + 검정) %>% 
  mutate(출신비율 = glue::glue("{scales::percent(재학/총계, accuracy=0.1)} / {scales::percent(졸업/총계, accuracy=0.1)} / {scales::percent(검정/총계, accuracy=0.1)}")) %>% 
  arrange(desc(학년도)) %>% 
  select(-대구분) %>% 
  gt::gt() %>% 
  gtExtras::gt_theme_nytimes() %>% 
  fmt_integer(columns = c(재학, 졸업, 검정, 총계)) %>% 
  cols_align(align = "center") %>% 
  cols_label(출신비율 ~ md("비율 (%)<br> 졸업 / 검정 / 총계")) %>% 
  tab_spanner(
    label = md("**출신별**"),
    columns = c(
      재학, 졸업, 검정
    )
  )
학년도 출신별 총계 비율 (%)
졸업 / 검정 / 총계
재학 졸업 검정
2023 350,239 142,303 15,488 508,030 68.9% / 28.0% / 3.0%
2022 360,710 134,834 14,277 509,821 70.8% / 26.4% / 2.8%
2021 346,673 133,070 13,691 493,434 70.3% / 27.0% / 2.8%
2020 394,024 142,271 12,439 548,734 71.8% / 25.9% / 2.3%
2019 448,111 135,482 11,331 594,924 75.3% / 22.8% / 1.9%
2018 444,873 137,533 11,121 593,527 75.0% / 23.2% / 1.9%
2017 459,342 135,120 11,525 605,987 75.8% / 22.3% / 1.9%
2016 482,054 136,090 13,043 631,187 76.4% / 21.6% / 2.1%
2015 495,027 131,539 14,055 640,621 77.3% / 20.5% / 2.2%
2014 509,081 127,634 14,032 650,747 78.2% / 19.6% / 2.2%
2013 510,972 142,561 14,989 668,522 76.4% / 21.3% / 2.2%
2012 526,418 151,887 15,326 693,631 75.9% / 21.9% / 2.2%
2011 541,880 154,661 15,686 712,227 76.1% / 21.7% / 2.2%
2010 532,436 130,658 14,740 677,834 78.5% / 19.3% / 2.2%
2009 448,472 127,586 12,781 588,839 76.2% / 21.7% / 2.2%
2008 446,597 126,729 11,608 584,934 76.3% / 21.7% / 2.0%
2007 425,396 151,697 11,806 588,899 72.2% / 25.8% / 2.0%
2006 422,310 159,190 12,306 593,806 71.1% / 26.8% / 2.1%
2005 435,538 161,524 13,195 610,257 71.4% / 26.5% / 2.2%
Code
application_tbl  %>% 
  mutate(value = value/ 10^4)  %>% 
  filter( 대구분 == "출신별") %>% 
  ggplot(aes(x = 학년도, y = value, color = 중구분)) +
    geom_line() +
    geom_point() +
    labs(title = "출신별 수능 접수현황",
         x ="",
         y = "접수자수(만명)",
         color = "접수구분: ") +
    theme(legend.position = "top") +
    scale_color_manual(values = c("gray30", "blue", "red"))

4.3 채점: 등급구분

공공데이터포털에서 한국교육과정평가원_대학수학능력시험 등급구분-표준점수 데이터를 가져온다.

Code
library(tidyverse)

## 연도별 등급 데이터
grade_files <- fs::dir_ls("data/suneung/", glob="*.csv")

grade_raw <- map(grade_files, read_csv, locale = locale(encoding = "EUC-KR"))

## 연도
grade_year <- map_chr(grade_files, str_extract, pattern = "\\d{4}")

## 연도별 데이터 정제함수
clean_grade <- function(df, year) {
  grade_tbl <- df %>% 
    janitor::clean_names(ascii = FALSE) %>% 
    select(!starts_with("x")) %>% 
    mutate(연도 = year) %>% 
    set_names(c("등급", "과목", "구분점수", "도수_명", "비율", "연도")) %>% 
    mutate_all(as.character)
  return(grade_tbl)
}

## 데이터 결합

grade_list <- map2(grade_raw, grade_year, clean_grade)

grade_tbl <- map_df(grade_list, bind_rows)
Code
grade_df <- grade_tbl %>% 
  mutate(구분점수 = ifelse(str_detect(구분점수, "미만"), parse_number(구분점수) - 3, 구분점수)) %>% 
  mutate(등급 = as.integer(등급),
         연도 = as.integer(연도),
         도수_명 = parse_number(도수_명),
         구분점수 = parse_number(구분점수),
         비율 = parse_number(비율))

grade_df  %>% 
  filter(str_detect(과목, "^국어")) %>% 
  filter(!str_detect(과목, "[A|B|C|가|나|다]형")) %>% 
  ggplot(aes(x = 등급, y = 비율, color = as.factor(연도))) +
  # ggplot(aes(x = 등급, y = 구분점수, color = as.factor(연도))) +
  # ggplot(aes(x = 구분점수, y = 등급, color = as.factor(연도))) +
    geom_line() +
    geom_point()

Code
grade_df %>% 
  filter(연도 == max(연도),
         str_detect(과목, "국어|(영어)|수학")) %>% 
  pivot_longer(cols = c(구분점수, 비율)) %>% 
  ggplot(aes(x = 등급, y = value, color = 과목)) +
    geom_line() +
    geom_point() +
    labs(title = "2023년 수능 등급분포",
         y = "비율(%)") +
    facet_wrap(~name, nrow = 2, scales = "free_y") +
    theme(legend.position = "top")

5 배점

Code
library(datapasta)
library(tidyverse)
library(rvest)
library(gt)
library(gtExtras)

table_url <- "https://www.jbe.go.kr/jinro/index.jbe?menuCd=DOM_000001105004005005"

point_raw <- read_html(table_url) %>% 
  html_elements("table") %>% 
  html_table() %>% 
  .[[1]]

point_tbl <- point_raw %>% 
  janitor::clean_names(ascii = FALSE) %>% 
  slice(2:n()) %>% 
  set_names(c("대구분", "소구분", "문항수", "문항유형", "배점_문항", "배점_전체", "시험시간",
              "출제범위")) %>% 
  mutate_all(.funs = str_remove_all, pattern = "ㆍ?\n\t\t\t|\t\t") %>% 
  mutate(대구분 = str_remove(대구분, "\\(필수\\)"),
         소구분 = str_remove(소구분, "\\(필수\\)")) %>% 
  mutate(과목수 = ifelse(str_detect(출제범위, "지구과학"), 2, 1))  %>% 
  mutate(배점_전체 = parse_number(배점_전체),
         과목점수 = 배점_전체 * 과목수) %>% 
  relocate(과목수, .before=시험시간) %>% 
  relocate(과목점수, .before=시험시간)
  

point_tbl %>% 
  gt(rowname_col = "대구분") %>% 
  gt_theme_538() %>%
  cols_align("center") %>% 
  tab_options(
    table.font.size = px(12L),
    stub.border.width = px(2),
    stub.border.color = "black"
  ) %>% 
  tab_spanner(
    label = md("**배점**"),
    columns = c(
      배점_문항, 배점_전체, 과목수, 과목점수 
    )
  )  %>% 
  cols_label(
    배점_문항 = "문항",
    배점_전체 = "전체"
  ) %>% 
  tab_footnote(
    footnote = md("필수"),
    locations = cells_body(columns = c(대구분, 소구분),
      rows = 대구분 == "한국사")
  ) %>% 
  grand_summary_rows(
    columns = 과목점수,
    fns = list(
      총점 = ~ sum(., na.rm = TRUE)
    )
  )
소구분 문항수 문항유형 배점 시험시간 출제범위
문항 전체 과목수 과목점수
국어 국어 45 5지선다형 2,3 100 1 100 80분 공통과목: 독서, 문학 선택과목(택 1): 화법과 작문, 언어와 매체 공통 75%, 선택 25% 내외
수학 수학 30 5지선다형, 단답형 2,3,4 100 1 100 100분 공통과목: 수학Ⅰ, 수학Ⅱ 선택과목(택 1): 확률과 통계, 미적분, 기하 공통 75%, 선택 25% 내외 단답형 30% 포함
영어 영어 45 5지선다형(듣기17문항) 2,3 100 1 100 70분 영어Ⅰ, 영어Ⅱ를 바탕으로 다양한 소재의 지문과 자료를 활용하여 출제
한국사 한국사1 20 5지선다형 2,3 50 1 50 30분 한국사를 바탕으로 우리 역사에 대한 기본 소양을 평가하기 위한 핵심 내용 중심으로 출제
탐구 사회과학탐구 과목당 20 5지선다형 2,3 50 2 100 과목당 30분 생활과 윤리, 윤리와 사상, 한국지리, 세계지리, 동아시아사, 세계사, 경제, 정치와 법, 사회・문화물리학Ⅰ, 화학Ⅰ, 생명과학Ⅰ, 지구과학Ⅰ, 물리학Ⅱ, 화학Ⅱ, 생명과학Ⅱ, 지구과학Ⅱ 17개 과목 중 최대 택 2
탐구 직업탐구 과목당 20 5지선다형 2,3 50 1 50 과목당 30분 1과목 선택: 농업 기초 기술, 공업 일반, 상업 경제, 수산・해운 산업 기초, 인간 발달 중 택 1 2과목 선택: 성공적인 직업생활+ 위 5개 과목 중 택1
제2외국어/한문 제2외국어/한문 과목당 30 5지선다형 1,2 50 1 50 과목당40분 독일어Ⅰ, 프랑스어Ⅰ, 스페인어Ⅰ, 중국어Ⅰ, 일본어Ⅰ, 러시아어Ⅰ, 아랍어Ⅰ, 베트남어Ⅰ, 한문Ⅰ 9개 과목 중 택 1
총점 550
1 필수