🏫 배움터 💻 정보 🗄️ Ⅱ. 데이터

🗄️ Ⅱ. 데이터

디지털 데이터를 압축·암호화해 안전하게 관리하는 법부터, 빅데이터를 수집·전처리·시각화해 문제를 해결하는 법까지.

2022 개정 교육과정 『정보』 · 2단원 데이터
📄
Ⅱ. 데이터 — 교과서 PDF 원본
전체 42페이지 · 다운로드하여 인쇄하거나 오프라인으로 볼 수 있어요
⬇ 다운로드
📌 참고 · 이 단원은 2022 개정 정보 교과서의 Ⅱ. 데이터를 재구성했습니다. 1단원(컴퓨팅 시스템)도 함께 올라와 있으며, 다른 단원은 순서대로 곧 추가될 예정입니다.
2단원 · 1절

1. 디지털 데이터의 압축Data Compression

사진·영상·통화 기록처럼 우리가 매일 만들어 내는 데이터는 폭발적으로 늘고 있다. 이 많은 데이터를 더 작게, 더 빠르게 주고받기 위한 기술이 데이터 압축이다.

1) 왜 압축이 필요할까?

데이터를 저장·전송하는 데는 공간시간, 그리고 전기가 든다. 전 세계 데이터 센터가 배출하는 이산화탄소량은 2002년 약 7,600만 톤에서 2020년 약 2억 5,900만 톤으로 급증했다 — 우리가 무심코 쓰는 데이터가 실제로 기후 변화에 영향을 준다는 뜻이다. 압축은 이 문제를 줄이는 가장 직접적인 방법이다.

  • 저장 공간을 줄인다 — 같은 서버에 더 많은 정보를 담을 수 있다.
  • 전송 시간을 줄인다 — 스트리밍·업로드가 빨라진다.
  • 보안에도 도움이 된다 — 압축된 데이터는 그대로 봐서는 해독하기 어렵다.
  • 비용과 전력을 줄여 환경적으로도 이득이다.
원본 데이터 압축 압축 데이터 전 세계 데이터센터 CO₂ 배출량 2002년 약 7,600만 톤 2020년 약 2억 5,900만 톤 ≈ 18년 사이 3.4배 증가 압축은 이 부담을 줄이는 직접적 방법
데이터를 압축하면 저장·전송에 드는 자원과 에너지, 즉 탄소 배출까지 함께 줄어든다.
주요 용어 · 데이터 압축, 압축비, 무손실 압축, 손실 압축

💡 잠깐, 그 전에 — 데이터는 결국 2진수다

압축이든 암호화든, 컴퓨터가 다루는 모든 데이터는 결국 0과 1(2진수)로 저장돼 있다. 다만 2진수는 자릿수가 너무 길어서 사람이 읽기 불편하니, 같은 비트를 3개씩 묶어 8진수로, 4개씩 묶어 16진수로 줄여 쓰기도 한다. 묶는 방법만 다를 뿐 같은 데이터이므로, 10진수로 환산한 값은 셋 다 똑같다 — 아래에서 버튼을 눌러 직접 확인해 보자.

🔢 진법의 변환 체험하기 SIMULATION

맨 앞 빨간 버튼은 부호(0=양수, 1=음수)이고, 나머지 15개는 2진수 각 자리다. 버튼을 눌러 0↔1을 바꾸면 아래 8진수·16진수 묶음과 최종 10진수 값이 실시간으로 함께 바뀐다.

2진수 그대로 계산 (자리마다 2의 거듭제곱)
3개씩 묶어 8진수로 계산 — 아래 칸 하나가 위 비트 3칸을 묶은 것
4개씩 묶어 16진수로 계산 (맨 앞은 3비트만 남아 0~7만 가능) — 아래 칸 하나가 위 비트 3~4칸을 묶은 것

2) 무손실 압축 — 원본을 그대로 복원

0과 1의 나열을 손실 없이 더 짧게 표현했다가, 필요할 때 원본과 똑같이 되돌리는 방식이다. 텍스트 파일, 은행 기록처럼 한 글자도 틀리면 안 되는 데이터에 적합하다.

① 런 길이 부호화 (Run Length Encoding)

같은 문자가 몇 번 반복되는지를 기록해 데이터를 짧게 만드는 방식. 단색이 많은 아이콘·로고(BMP 파일 등)에는 효율적이지만, 색이 다양한 사진에는 잘 맞지 않는다.

AAADDBOOOO 원본 (10 bytes) 압축 A3D2B1O4 압축 (8 bytes) 압축비 = 10 / 8 = 1.25 (80%)
▲ 런 길이 부호화: 문자 + 반복 횟수 순서로 표현 (A가 3번 → A3)
🧪 런 길이 부호화 체험하기 SIMULATION

문자열을 입력하면 결과가 바로 갱신돼요. ▶ 압축하기를 누르면 그 순간의 입력값으로 다시 계산해 아래 결과가 잠깐 반짝여요(다시 압축했다는 표시).

위 버튼을 눌러 보세요.

② 사전 압축과 허프만 부호화

압축할 데이터: 문자열 AAADDBOOOO(총 10글자). 컴퓨터에서 한 글자를 8비트(1바이트)로 저장한다고 하면, 압축하기 전 원본 크기는 10글자 × 8비트 = 80비트(10바이트)다. 이 80비트를 어디까지 줄일 수 있는지, 쉬운 방법부터 차근차근 따라가 보자.

1️⃣ 가장 단순한 방법 — 사전 압축(등장 순서대로 번호 붙이기)

문자열에 등장하는 문자를 나온 순서대로 A→D→B→O로 정리하고, 모두 똑같은 길이(3비트)의 번호를 매긴다. (3비트면 000~111, 즉 최대 8종류의 문자를 구분할 수 있다.)

문자등장 순서부호
A1번째로 등장001
D2번째로 등장010
B3번째로 등장011
O4번째로 등장100
A   A   A   D   D   B   O   O   O   O
001 001 001 010 010 011 100 100 100 100
001001001010010011100100100100 (30비트)

문자 10개를 전부 3비트로 바꾸면 10 × 3 = 30비트. 압축비는 80 ÷ 30 ≒ 2.67 (37.5%)이다.

💡 이 방법의 한계 · 4번이나 등장하는 O도, 딱 1번 등장하는 B도 똑같이 3비트씩 차지한다. 자주 나오는 문자 입장에선 손해인 셈이다 — 바로 여기서 허프만 부호화의 아이디어가 시작된다.

2️⃣ 더 똑똑한 방법 — 허프만 부호화(자주 나올수록 짧게)

허프만 부호화(Huffman coding)의 핵심 아이디어는 단 하나다 — 자주 등장하는 문자에는 짧은 부호를, 드물게 등장하는 문자에는 긴 부호를 준다. 5단계를 하나씩 직접 따라가며 트리를 완성해 보자.

[1단계] 문자별 등장 빈도 세기

문자등장 횟수
O4번
A3번
D2번
B1번

빈도가 높은 순서(내림차순)로 미리 정렬해 두었다.

[2단계] 가장 작은 두 값을 하나로 묶는다

지금 가장 작은 두 값은 D(2)와 B(1)이다. 둘을 묶어 합계 3짜리 새 노드를 만들고, 먼저 나열된 쪽(D)에 0, 나중 쪽(B)에 1을 붙인다.

[3단계] 다시 가장 작은 두 값을 찾아 묶는다

이제 후보는 O(4), A(3), 방금 만든 노드(3) — 세 개다. 가장 작은 두 값은 A(3)와 노드(3)인데, 값이 같으면 정렬 순서가 앞선 쪽을 먼저 처리하므로 A와 [D,B]노드를 묶는다. 합계는 6이고, A에 0, [D,B]노드에 1을 붙인다.

[4단계] 마지막 두 값을 묶어 트리를 완성한다

이제 남은 건 O(4)와 방금 만든 노드(6)뿐이다. 이 둘을 묶으면 전체 합계 10인 맨 위(뿌리, root)가 완성된다. 노드(6)에 0, O에 1을 붙인다.

A:3 D:2 B:1 O:4 내림차순 정렬 O:4 A:3 D:2 B:1 0 1 10 0 1 6 O:4 O → 1 A:3 A → 00 0 1 3 D:2 D → 010 B:1 B → 011 📱 QR로 문제 풀기
▲ [그림Ⅱ-2] 허프만 부호화 방식 — 등장 순서(위)를 내림차순으로 정렬한 뒤(가운데), 아래(D,B)에서 위(뿌리 10)로 트리를 쌓아 올라간다. 왼쪽 가지=0, 오른쪽 가지=1. 오른쪽 QR코드는 교과서에 실린 확인 문제 링크다.
📌 참고 — '트리(tree)'란?
컴퓨터 과학에서 데이터를 계층적으로 정리할 때 쓰는 자료구조다. 나뭇가지가 뿌리에서 위로 뻗어나가는 모양을 닮아서 이런 이름이 붙었다(다만 그림은 보통 뿌리를 맨 위에, 가지 끝을 아래에 그린다). 방금 완성한 허프만 트리에서 쓰는 용어는 다음과 같다.
  • 뿌리(root) — 트리의 맨 위 시작점. 위 그림에서는 값이 10인 노드.
  • 노드(node) — 가지가 갈라지는 지점. 그 아래에 속한 문자들의 빈도수 합을 나타낸다.
  • 리프(leaf) — 더 이상 갈라지지 않는 가지 끝. 실제 문자(A, D, B, O)는 항상 리프에만 있다.
  • 가지(branch/edge) — 노드와 노드(또는 노드와 리프)를 잇는 선. 왼쪽 가지엔 0, 오른쪽 가지엔 1을 붙인다.

[5단계] 뿌리부터 각 문자까지 경로를 이어붙여 부호 완성

뿌리에서 출발해 그 문자에 도착할 때까지 지나온 0과 1을 순서대로 이어붙이면, 그것이 그 문자의 부호가 된다.

문자빈도경로(뿌리→문자)부호
O4뿌리 →(1) O1
A3뿌리 →(0) 노드6 →(0) A00
D2뿌리 →(0) 노드6 →(1) 노드3 →(0) D010
B1뿌리 →(0) 노드6 →(1) 노드3 →(1) B011
❓ 왜 길이가 다른데도 헷갈리지 않고 풀릴까? — 접두어 규칙
허프만 부호는 어떤 문자의 부호도 다른 문자 부호의 맨 앞부분(접두어)이 될 수 없도록 만들어진다. 1(O)로 시작하는 부호는 O 하나뿐이고, 00(A)으로 시작하는 부호도 A뿐이며, 010(D)과 011(B)도 앞 두 자리(01)까지는 같지만 마지막 한 자리에서 서로 갈린다. 그래서 비트를 앞에서부터 읽어 나가다가 이미 정해진 부호와 정확히 맞아떨어지는 순간 거기서 끊어 읽으면, 다음에 어디서부터 새 글자가 시작하는지 헷갈릴 일이 없다. (트리에서 모든 문자가 가지 끝(리프)에만 있고, 가지가 갈라지는 중간 지점엔 문자가 없기 때문에 이 규칙이 자동으로 지켜진다.)

데이터 변환하기 — 최종 압축 결과

문자열 AAADDBOOOO를 완성된 부호표(O=1, A=00, D=010, B=011)에 따라 한 글자씩 바꿔서 이어붙이면 다음과 같다.

A  A  A  D   D   B   O O O O
00 00 00 010 010 011 1 1 1 1
0000000100100111111 (19비트)

원본 80비트가 19비트로 줄었다. 압축비는 80 ÷ 19 ≒ 4.21 (23.8%)이다.

3️⃣ 두 방법 비교 — 왜 허프만이 더 효율적일까?

구분사전 압축(고정 길이)허프만 부호화(가변 길이)
부호 길이모든 문자 3비트로 동일문자마다 다름 (1~3비트)
압축 후 크기30비트19비트
압축비80 / 30 ≒ 2.67 (37.5%)80 / 19 ≒ 4.21 (23.8%)
핵심 원리등장 순서로만 번호 부여자주 나올수록 짧은 부호 부여
Tip · 값이 같으면 정렬 순서가 앞선 것에 0을 배정한다. 사전 자체도 공간을 차지하므로, 중복되는 문자가 많을수록 사전/허프만 압축이 더 유리해진다.
클로드 섀넌 (Claude Shannon, 1916~2001)
📌 참고 — 허프만 부호화 뒤에는 클로드 섀넌이 있다
방금 "자주 나오는 문자에는 짧은 부호를, 드물게 나오는 문자에는 긴 부호를 주면 유리하다"는 걸 직접 확인했다. 이 아이디어를 수학적으로 처음 증명한 사람이 클로드 섀넌(Claude Shannon, 1916~2001)이다. '정보이론(Information Theory)의 아버지'로 불리는 미국의 공학자·수학자로, 비트(bit)라는 단위 자체를 세상에 알린 사람이기도 하다.
  • 1948년 논문에서 정보량(엔트로피)을 확률로 정의했다 — 예측하기 쉬운(자주 등장하는) 것일수록 정보량이 작다. 허프만 부호화가 자주 나오는 문자에 짧은 부호를 주는 것은 바로 이 원리를 그대로 따른 것이다.
  • 1937년 석사 논문에서는 불 대수(참/거짓 논리)를 전기 스위치 회로로 구현할 수 있음을 증명했다 — 오늘날 모든 디지털 회로·컴퓨터가 0과 1로 동작하는 이론적 뿌리다.
  • 1949년에는 OTP(일회용 암호)가 이론적으로 절대 뚫리지 않는 "완전 비밀성(perfect secrecy)"을 만족함을 증명했다 — 뒤에 나올 대칭 암호화 단원의 OTP와 연결된다.
  • 요즘 많이 쓰는 AI 챗봇 Claude(클로드)도 바로 이 섀넌에게 헌정하는 의미로 붙인 이름이다 — AI 역시 결국 '정보'를 다루는 기술이기 때문이다.
사진: Tekniska museet(스웨덴 국립기술박물관), Wikimedia Commons · CC BY 2.0

3) 손실 압축 — 사람이 눈치채기 힘든 부분만 버린다

이미지·영상·소리처럼 사람의 눈과 귀가 완벽히 구분하지 못하는 영역의 정보는 과감히 버려서 용량을 획기적으로 줄이는 방식이다. 압축 해제한 데이터는 원본과 정확히 같지 않지만, 체감 품질 차이는 크지 않다.

무손실 압축과 손실 압축
구분
무손실 압축
손실 압축
데이터
손실
손실 없음.
손실 있음.
원본과
비교
거의 같음.
다를 수
있음.
압축률
낮음.
높음.
용량
큰 편
작은 편
파일
형식
ZIP, GIF,
PNG 등
JPEG, MPEG,
MP3 등
💡 궁금해요 — PNG는 무손실인데 왜 용량이 작을 때가 많을까?
위 표의 '용량 큰 편/작은 편'은 같은 이미지를 무손실·손실로 각각 압축했을 때의 비교다. PNG가 실제로 작아 보이는 이유는 두 가지다.
  • PNG는 우리가 배운 사전 압축(반복 패턴 찾기)허프만 부호화(자주 나오는 값에 짧은 부호)를 함께 쓰는 DEFLATE 방식을 쓴다. 게다가 압축 전에 각 픽셀을 바로 옆 픽셀과의 차이값으로 바꿔 저장하는 '필터링'을 거치는데, 색이 단순한 이미지는 이 차이값이 대부분 0에 가까워 허프만 부호화가 아주 짧은 부호를 붙일 수 있다.
  • 그래서 압축률은 무손실이냐 손실이냐보다 이미지 종류가 더 크게 좌우한다. 아이콘·로고·스크린샷·텍스트처럼 색이 단순하고 반복이 많은 이미지는 PNG(무손실)가 오히려 JPEG보다 작을 수도 있다 — JPEG의 8×8 블록 DCT 방식은 이런 날카로운 경계에서 오히려 비효율적이기 때문이다. 반대로 사진처럼 색이 미세하게 계속 바뀌는 이미지는 반복이 거의 없어 무손실 압축의 효과가 작고, 이때는 손실 압축(JPEG)이 압도적으로 작아진다.

① JPEG — 이미지 압축

사람 눈은 색보다 밝기 차이를 더 잘 인식한다는 성질을 이용해, R·G·B 값을 그대로 저장하지 않고 휘도(밝기)와 색 차이 정보 위주로 압축한다. 이미지를 8×8 픽셀 블록으로 나눠 처리한다.

② MPEG — 영상 압축

영상은 JPEG로 압축한 이미지(프레임)가 여러 장 이어진 것이다. MPEG는 연속된 프레임의 유사성을 이용해, 이전 프레임과 달라진 부분만 저장한다 — 배경이 그대로인 장면일수록 압축 효율이 크게 오른다.

③ 소리의 표본화 · 양자화 · 부호화

아날로그 소리를 표본화 → 양자화 → 부호화 순서로 바꿔 디지털 데이터로 만드는 이 과정 전체를 PCM 변환(Pulse Code Modulation)이라 부른다.

  • 표본화: 아날로그 신호를 일정한 시간 간격으로 측정한다.
  • 양자화: 측정값을 정해진 단계 중 가장 가까운 값으로 반올림한다 — 이때 실젯값과 차이(양자화 오차)가 생긴다.
  • 부호화: 변환된 값을 0과 1의 비트로 표현한다.
① 표본화 (Sampling) 일정한 시간 간격으로 신호를 측정 ② 양자화 (Quantization) 7 6 5 4 3 2 1 0 가장 가까운 단계로 반올림 (주황 점=실젯값, 보라 점=양자화값 · 빨간 선=양자화 오차) ③ 부호화 (Encoding) 100 110 111 110 100 010 001 010 단계를 이진수(3비트)로 표현 → 최종 저장되는 값 100 110 111 110 100 010 001 010
▲ 표본화·양자화·부호화(PCM 변환) — 아날로그 소리를 일정 간격으로 측정(①)하고, 가장 가까운 단계로 반올림(②)한 뒤, 이진수로 바꿔(③) 디지털 데이터로 저장한다

예시 — 사람 목소리를 PCM으로 바꾸면?

전화 통화를 예로, 실제 숫자를 따라가며 목소리가 디지털 데이터가 되는 과정을 확인해 보자.

  1. 목소리의 주파수 범위 확인 — 사람 음성이 알아듣기에 충분한 핵심 대역은 약 300Hz~3,400Hz(전화 음성 대역)다. 최고 주파수를 대략 4kHz로 본다.
  2. 표본화 — 나이퀴스트율 적용 — 4kHz까지 왜곡(앨리어싱) 없이 담으려면 나이퀴스트율(2 × 4kHz = 8kHz) 이상으로 표본화해야 한다. 그래서 유선전화·인터넷전화(VoIP)는 실제로 초당 8,000번(8kHz) 목소리를 측정한다.
  3. 양자화 — 측정한 값을 256단계(8비트)로 반올림한다. (국제표준 통화 코덱 G.711의 방식)
  4. 부호화 — 표본 하나하나를 8비트 이진수로 표현한다.
🎙️ 목소리 표본화8,000회/초 양자화256단계(8비트) 부호화8비트 이진수 8,000회/초 × 8비트 = 64,000비트/초 = 64kbps (전화 통화 표준 속도)
▲ 목소리 → 표본화(8kHz) → 양자화(8비트) → 부호화 → 64kbps 디지털 신호. 실제 유선전화 통화의 표준 전송 속도와 정확히 일치한다
Tip · 반면 CD 음질 음악은 44.1kHz · 16비트를 쓴다. 목소리(4kHz)보다 훨씬 넓은 주파수 대역(사람의 가청 범위 최대 20kHz)까지 담아야 하고, 8단계가 아니라 훨씬 세밀한 65,536단계(16비트)로 표현해야 자연스러운 음질이 나오기 때문이다.

④ MP3 — 사람이 못 듣는 소리는 지운다

MP3(MPEG-1 Audio Layer III)는 표본화·양자화·부호화를 거쳐 만들어진 디지털 음원(PCM)을 한 번 더 압축하는 손실 압축 방식이다. 핵심 아이디어는 "사람 귀가 애초에 듣지 못하는 소리는 저장할 필요가 없다"는 심리음향(psychoacoustic) 모델이다.

  • 가청 주파수 한계: 사람은 대략 20Hz~20,000Hz의 소리만 들을 수 있다 — 이 범위 밖 정보는 처음부터 버린다.
  • 주파수 마스킹(동시 마스킹): 큰 소리 옆에 있는 작은 소리는 묻혀서 안 들린다. 예를 들어 시끄러운 콘서트장에서 옆 사람의 속삭임이 안 들리는 것과 같은 원리다.
  • 시간 마스킹: 아주 큰 소리가 난 직후 아주 짧은 순간에는, 원래 들렸을 작은 소리도 잠깐 안 들린다.
주파수 → 크기 마스킹 임계선(이 선 아래 소리는 안 들림) 큰 소리 묻힘 묻힘 들림 → 저장
▲ 큰 소리 근처의 작은 소리(빨강)는 마스킹 임계선 아래로 묻혀 들리지 않으므로 데이터에서 지운다. 임계선 위의 소리(초록)만 남겨 저장한다.

MP3는 이렇게 ① 소리를 여러 주파수 대역으로 나누고 ② 심리음향 모델로 어느 대역이 들리는지/안 들리는지 분석해 ③ 잘 들리는 대역엔 비트를 많이, 안 들리는 대역엔 비트를 적게(또는 아예 0을) 할당한 뒤 ④ 남은 값을 양자화·부호화한다. 이 과정을 거치면 원본 CD 음원(약 1,411kbps)을 1/10 수준(128kbps)까지 줄이면서도 사람 귀에는 거의 같은 소리로 들린다.

비트레이트체감 음질파일 크기
128kbps보통 (음원 스트리밍 최소 수준)작음
192kbps좋음중간
320kbps매우 좋음 (원음과 구분 어려움)
Tip · 비트레이트(bitrate)는 1초 분량 소리를 표현하는 데 쓰는 비트 수다. 마스킹으로 버리는 정보가 적을수록(=비트레이트가 높을수록) 음질은 좋아지지만 압축 효율은 떨어진다.
💡 궁금해요 · 소리도 무손실 압축이 가능할까? 가능하다! 심리음향 마스킹 없이, 손실 압축보다 압축률은 낮지만 고품질 음원을 원본 그대로 저장할 수 있다. 대표 포맷은 FLAC, ALAC.
✅ 성취기준 자가 점검

디지털 데이터 압축의 개념과 필요성, 무손실·손실 압축의 차이를 설명할 수 있나요? 런 길이 부호화·허프만 부호화의 원리를 손으로 계산할 수 있나요? 나이퀴스트율에 따라 표본화 주파수를 정하고, PCM 변환 과정을 예로 들어 설명할 수 있나요? MP3가 청각 마스킹을 이용해 데이터를 줄이는 원리를 설명할 수 있나요?

🐍 실습 — 이미지 압축 비교 (BMP · PNG · GIF · JPG)

구글 코랩(Colab)에서 바로 돌려볼 수 있다. 같은 사진을 네 가지 형식으로 저장해 보고, 해상도는 그대로인데 파일 크기만 달라지는 것을 확인해 보자. 마지막에는 무손실과 손실을 눈이 아니라 코드로 판별해 본다.

📥 실습 이미지 내려받기 · 아래 파일을 내려받아 코랩 왼쪽 파일 탭 → 업로드로 올린 뒤 실습을 시작하자.
⬇ flower_BMP.bmp (1.4 MB) 800 × 600 · 약 7만 가지 색
1라이브러리 불러오기

내려받은 BMP 이미지(.bmp)를 코랩에 업로드한 뒤, 이미지 변환에 필요한 라이브러리를 불러온다.

# os: 파일 크기 확인용 / PIL(Pillow): 이미지 열기·저장용
import os
from PIL import Image
2원본 BMP의 해상도와 파일 크기 확인
im = Image.open('flower_BMP.bmp')
print('이미지 확장자', im.format)
print('이미지 해상도', im.size)
print('파일 크기', os.path.getsize('flower_BMP.bmp'), 'Bytes')
실행 결과 · BMP / (800, 600) / 1440054 Bytes
BMP는 압축을 거의 하지 않아 가로 × 세로 × 3바이트(RGB)에 가까운 크기가 나온다.
3같은 이미지를 PNG · GIF · JPG로 변환
# PNG로 저장 후 다시 열어 확인
im.save('flower_PNG.png', 'png')
im2 = Image.open('flower_PNG.png')
print('이미지 확장자', im2.format)
print('이미지 해상도', im2.size)
print('파일 크기', os.path.getsize('flower_PNG.png'), 'Bytes')

# GIF로 저장 후 다시 열어 확인
im.save('flower_GIF.gif', 'gif')
im3 = Image.open('flower_GIF.gif')
print('이미지 확장자', im3.format)
print('이미지 해상도', im3.size)
print('파일 크기', os.path.getsize('flower_GIF.gif'), 'Bytes')

# JPG로 저장 후 다시 열어 확인
im.save('flower_JPG.jpg', 'jpeg')
im4 = Image.open('flower_JPG.jpg')
print('이미지 확장자', im4.format)
print('이미지 해상도', im4.size)
print('파일 크기', os.path.getsize('flower_JPG.jpg'), 'Bytes')
실행 결과
PNG (800, 600) 940822 Bytes — 원본의 65.3% (압축비 1.5)
GIF (800, 600) 418151 Bytes — 원본의 29.0% (압축비 3.4)
JPEG (800, 600) 66469 Bytes — 원본의 4.6% (압축비 21.7)
해상도는 넷 다 똑같다. 달라지는 것은 파일 크기뿐인데, JPG는 1/20 이하로 줄었다. 압도적이다.
4네 이미지를 눈으로 비교
# 코랩에서는 변수 이름만 적어도 이미지가 출력된다
im
im2
im3
im4
확인해 보기 · BMP와 PNG는 원본과 똑같아 보인다(무손실). GIF는 꽃 한가운데 노란 꽃술을 눈여겨보자 — 선명한 노랑이 탁한 베이지색으로 뭉개져 있다. 쓸 수 있는 색이 256가지뿐이라, 사진에서 드물게 쓰인 색부터 버려지기 때문이다.
그런데 JPG는 20배 넘게 줄었는데도 원본과 거의 구별되지 않는다. 어떻게 이럴 수 있을까? 다음 단계에서 확인해 보자.
5무손실일까, 손실일까? — 눈이 아니라 숫자로 판별하기

"똑같아 보인다"와 "똑같다"는 다르다. 두 이미지의 픽셀을 하나하나 빼서 차이가 0인지 확인하면, 정보를 잃었는지 정확히 알 수 있다.

# ImageChops: 두 이미지를 픽셀 단위로 계산해 주는 도구
from PIL import ImageChops

origin = im.convert('RGB')

for name, file in [('PNG', 'flower_PNG.png'),
                   ('GIF', 'flower_GIF.gif'),
                   ('JPG', 'flower_JPG.jpg')]:
    other = Image.open(file).convert('RGB')
    diff = ImageChops.difference(origin, other)   # 픽셀끼리 뺀 결과
    if diff.getbbox() is None:               # 차이가 전부 0이면 None
        print(name, ': 원본과 완전히 동일 → 무손실')
    else:
        print(name, ': 원본과 다름 → 정보가 사라짐')
실행 결과
PNG : 원본과 완전히 동일 → 무손실
GIF : 원본과 다름 → 정보가 사라짐
JPG : 원본과 다름 → 정보가 사라짐
눈으로는 PNG와 JPG가 똑같아 보였지만, 숫자는 정직하다. PNG만 원본과 한 픽셀도 다르지 않다.
🤔 어? GIF는 무손실 압축이라고 했는데 왜 원본과 다르지? · 아주 중요한 질문이다. GIF는 저장할 때 두 단계를 거치는데, 정보를 잃는 곳은 압축이 아니라 그 앞 단계다.
원본 (69,835색) —① 색을 256개로 줄임 —▶ 256색 이미지 —② LZW 압축 —▶ GIF 파일 여기서 색을 버린다 (손실) 여기는 되돌릴 수 있다 (무손실)
"GIF의 압축은 무손실""GIF로 저장한 사진은 원본으로 못 되돌린다"둘 다 맞는 말이다. 버려진 것은 ①에서 버린 색이지, 압축이 잃어버린 것이 아니다.
6정말 압축은 무손실인지 확인하기

말로만 넘어가지 말고 직접 확인해 보자. 색을 미리 256개로 줄여 둔 이미지를 GIF로 저장하면, ①단계에서 버릴 색이 이미 없다. 남는 것은 ②압축뿐이다. 이때도 원본과 달라질까?

# 색을 미리 256개로 줄인 이미지를 만든다 (여기서 색을 버림)
small = im.convert('RGB').quantize(colors=256).convert('RGB')
small.save('small.bmp')          # 이것을 '원본'으로 삼는다

# 이미 256색이므로, GIF로 저장해도 더 버릴 색이 없다
small.save('small.gif', 'gif')

print('BMP', os.path.getsize('small.bmp'), '->  GIF', os.path.getsize('small.gif'), 'Bytes')

diff = ImageChops.difference(small, Image.open('small.gif').convert('RGB'))
print('원본과 동일한가:', diff.getbbox() is None)
실행 결과
BMP 1440054 -> GIF 418151 Bytes
원본과 동일한가: True
크기는 3.4배로 줄었는데 픽셀은 하나도 달라지지 않았다. 이것이 바로 무손실 압축이다. 아까 사진이 뭉개졌던 것은 압축 탓이 아니라 색을 256개로 줄인 탓이었음이 증명된 셈이다.
그래서 GIF는 원래부터 색이 적은 아이콘·로고·단순한 애니메이션에 알맞다. 버릴 색이 없으니 화질 손해 없이 크기만 줄어든다.
7여러 번 저장하면 어떻게 될까?

사진을 고치고 저장하기를 반복하는 일은 흔하다. 형식마다 결과가 다른데, 이 차이가 실제로는 가장 중요하다.

# GIF로 두 번 더 저장해 본다
g1 = Image.open('flower_GIF.gif').convert('RGB')
g1.save('g2.gif', 'gif')
g2 = Image.open('g2.gif').convert('RGB')
g2.save('g3.gif', 'gif')
g3 = Image.open('g3.gif').convert('RGB')
print('GIF 2번 더 저장 후 그대로인가:', ImageChops.difference(g1, g3).getbbox() is None)

# JPG로 두 번 더 저장해 본다
j1 = Image.open('flower_JPG.jpg').convert('RGB')
j1.save('j2.jpg', 'jpeg')
j2 = Image.open('j2.jpg').convert('RGB')
j2.save('j3.jpg', 'jpeg')
j3 = Image.open('j3.jpg').convert('RGB')
print('JPG 2번 더 저장 후 그대로인가:', ImageChops.difference(j1, j3).getbbox() is None)
실행 결과
GIF 2번 더 저장 후 그대로인가: True — 더 나빠지지 않는다
JPG 2번 더 저장 후 그대로인가: False또 나빠졌다
GIF는 이미 256색이라 더 버릴 색이 없어 그대로 멈춘다. 하지만 JPG는 저장할 때마다 새로 버린다. 사진을 열어 조금 고치고 저장하기를 반복하면 화질이 계속 조금씩 나빠진다 — 복사본을 복사하고 또 복사하는 것과 같다.
8결과 정리
형식저장 전에
정보를 버리나?
압축 자체는?파일 크기색 표현사진을 원본대로
되살릴 수 있나?
BMP안 버림거의 압축 안 함1,440,054 B
(100%)
1,600만 색원본 그대로
PNG안 버림무손실940,822 B
(65.3%)
1,600만 색완벽히 가능
GIF버림
(색을 256개로)
무손실418,151 B
(29.0%)
256색만불가능
(버린 은 못 되찾음)
JPG안 버림손실66,469 B
(4.6%)
1,600만 색불가능
(압축하며 버림)
▲ GIF와 JPG는 정보를 잃는 지점이 서로 다르다. GIF는 압축하기 전에 색을 버리고, JPG는 압축하면서 버린다.
💡 JPG는 어떻게 20배나 줄이면서 멀쩡해 보일까? · 사람의 눈은 밝기 변화에는 예민하지만 색의 미세한 변화에는 둔하다. JPG는 이 약점을 이용한다. 이미지를 8×8 칸으로 잘라, 각 칸에서 사람이 알아채기 어려운 미세한 무늬부터 버린다. 버려도 눈치채지 못할 정보를 골라 버리는 것이라, 파일은 확 줄어드는데 보기에는 멀쩡하다.
대신 버린 정보는 영원히 사라진다. ❼단계에서 확인했듯, 저장을 반복할수록 화질이 계속 나빠지는 이유가 이것이다.
왜 PNG가 BMP보다 작을까? · PNG는 앞에서 배운 런 길이 부호화허프만 부호화를 함께 쓰는 방식(Deflate)으로 압축한다. 같은 색이 이어지는 부분을 A3D2B1O4처럼 줄이고, 자주 나오는 값에 짧은 코드를 배정하는 것이다 — 원본을 그대로 복원할 수 있으므로 무손실이다.
직접 바꿔 보기 — 화질을 내가 정한다 · JPG는 얼마나 버릴지quality 값(1~95)으로 고를 수 있다. 아래를 실행해 크기와 화질을 함께 비교해 보자.
for q in [95, 75, 50, 10]:
    im.save(f'q{q}.jpg', 'jpeg', quality=q)
    print(q, os.path.getsize(f'q{q}.jpg'), 'Bytes')

결과는 95 → 212,094 · 75 → 66,469 · 50 → 37,791 · 10 → 9,384 Bytes다. quality=10으로 저장한 이미지를 열어 보면 8×8 네모 칸 자국이 눈에 보일 만큼 뭉개져 있다 — JPG가 무엇을 버려서 크기를 줄이는지 직접 확인할 수 있다.
🤔 함께 이야기해 보기

네 가지 형식을 비교해 보고, 어떤 상황에 어떤 형식이 가장 알맞을지 이야기 나누어 보자.

① 졸업 사진 원본을 오래 보관할 때  ② 홈페이지에 로고를 올릴 때  ③ SNS에 사진 수십 장을 올릴 때  ④ 사진을 여러 번 고쳐 가며 작업할 때 — 각각 무엇을 고르겠는가? 특히 ④번은 왜 JPG를 피해야 할지 생각해 보자.

🐍 실습 — 허프만 부호화 직접 만들기

앞에서 손으로 만든 허프만 트리를 이번엔 파이썬으로 만들어 본다. 구글 코랩(Colab)에서 그대로 붙여 넣어 실행하면 된다. 설치할 라이브러리는 없다.

1문자별 등장 횟수 세기

압축할 문자열은 앞에서 쓴 AAADDBOOOO 그대로다.

# heapq: 가장 작은 값을 빠르게 꺼내는 도구 (트리를 아래에서부터 쌓을 때 사용)
import heapq
from collections import Counter

data = 'AAADDBOOOO'

# 문자별 등장 횟수
freq = Counter(data)
print('빈도:', dict(freq))
실행 결과 · 빈도: {'A': 3, 'D': 2, 'B': 1, 'O': 4}
2허프만 트리 쌓아 올리기

가장 드문 문자 두 개를 꺼내 하나로 묶는 일을, 노드가 하나만 남을 때까지 반복한다. 손으로 D와 B를 먼저 묶었던 것과 같은 과정이다.

# [빈도, 순번, 문자, 왼쪽자식, 오른쪽자식]  — 순번은 빈도가 같을 때 순서를 정해 준다
heap = [[w, i, ch, None, None] for i, (ch, w) in enumerate(sorted(freq.items()))]
heapq.heapify(heap)

counter = len(heap)
while len(heap) > 1:
    left = heapq.heappop(heap)      # 가장 드문 것
    right = heapq.heappop(heap)     # 그 다음으로 드문 것
    # 둘을 묶어 부모 노드를 만든다 (빈도는 두 개의 합)
    heapq.heappush(heap, [left[0] + right[0], counter, None, left, right])
    counter += 1

root = heap[0]   # 마지막에 남은 하나가 뿌리(root)
3부호 배정 — 왼쪽 가지 0, 오른쪽 가지 1

뿌리에서 각 문자까지 내려가며 지나온 가지의 0·1을 이어 붙이면 그 문자의 부호가 된다.

def walk(node, prefix=''):
    if node[2] is not None:      # 문자가 들어 있으면 = 가지 끝(리프)
        codes[node[2]] = prefix or '0'
        return
    walk(node[3], prefix + '0')   # 왼쪽으로 가면 0
    walk(node[4], prefix + '1')   # 오른쪽으로 가면 1

codes = {}
walk(root)

for ch in sorted(codes, key=lambda c: (len(codes[c]), c)):
    print(f'{ch} ({freq[ch]}회) -> {codes[ch]} ({len(codes[ch])}비트)')
실행 결과
O (4회) -> 0 (1비트)
A (3회) -> 10 (2비트)
B (1회) -> 110 (3비트)
D (2회) -> 111 (3비트)
자주 나오는 O가 가장 짧고(1비트), 한 번뿐인 B는 가장 길다(3비트).
4압축하고 크기 비교하기
encoded = ''.join(codes[c] for c in data)

print('인코딩:', encoded)
print('원본 :', len(data) * 8, '비트')   # 한 글자 8비트로 저장했다면
print('압축 :', len(encoded), '비트')
print('압축비: %.2f' % (len(data) * 8 / len(encoded)))
실행 결과
인코딩: 1010101111111100000
원본 : 80 비트압축 : 19 비트압축비: 4.21
80비트가 19비트로 줄었다. 손으로 계산한 결과와 같은 19비트다.
5되돌려 보기 — 정말 무손실일까?

압축한 비트열을 다시 원래 문자열로 되돌려, 한 글자도 잃지 않았는지 확인한다.

out = []
node = root
for b in encoded:
    node = node[3] if b == '0' else node[4]   # 0이면 왼쪽, 1이면 오른쪽
    if node[2] is not None:                    # 가지 끝에 닿으면 한 글자 완성
        out.append(node[2])
        node = root                              # 다시 뿌리로 돌아가 다음 글자

print('복호화:', ''.join(out))
print('원본과 일치:', ''.join(out) == data)
실행 결과 · 복호화: AAADDBOOOO / 원본과 일치: True
한 글자도 잃지 않고 완벽히 복원됐다 — 무손실 압축이 확인된 것이다.
💡 교과서 그림과 부호가 다른데, 틀린 걸까? · 아니다. 위 그림에서는 O=1, A=00, D=010, B=011이었는데 코드는 O=0, A=10, B=110, D=111을 내놓았다. 하지만 부호의 길이는 1, 2, 3, 3으로 똑같고 전체도 똑같이 19비트다. 갈림길에서 어느 쪽을 0으로 부를지, 그리고 빈도가 같을 때 누구를 먼저 묶을지는 정하기 나름이라서, 허프만 부호는 여러 가지 정답이 나올 수 있다. 길이와 총 비트 수가 같으면 모두 똑같이 최적이다.
직접 바꿔 보기 · data를 바꿔 가며 실행해 보자. 'AAAAAAAAAB'처럼 한 글자가 유난히 많으면 압축비가 크게 오르고, 'ABCDEFGHIJ'처럼 모두 한 번씩만 나오면 거의 줄지 않는다. 이것이 앞에서 배운 "중복이 많을수록 압축이 잘 된다"는 원리다.
2단원 · 1절

2. 디지털 데이터의 암호화Encryption

온라인 쇼핑, 민원 서류 발급처럼 인터넷으로 처리하는 일이 많아질수록 비밀번호와 개인정보를 안전하게 지키는 기술이 중요해진다. 그 핵심이 암호화다.

1) 암호화의 기본 개념

평문plaintext 암호화(키) 암호문ciphertext 복호화(키) 평문plaintext
▲ 암호화의 기본 흐름 — 평문 → 암호문 → 평문
  • 암호화(encryption): 원본 데이터(평문)를 읽기 어려운 형태(암호문)로 바꾸는 과정.
  • 복호화(decryption): 암호문을 다시 평문으로 되돌리는 과정.
  • 암호키(encryption key): 암호화·복호화에 필요한 규칙.
암호화의 세 가지 기능 · 기밀성(권한 있는 사람만 볼 수 있음) · 무결성(내용이 임의로 바뀌지 않았는지 확인) · 인증(진짜 사용자인지 식별)
주요 용어 · 암호화, 복호화, 치환형 암호화, 전치형 암호화, 단방향 암호화, 대칭 암호화, 비대칭 암호화

2) 치환형 암호화 — 카이사르 암호

평문의 각 문자를 약속된 다른 문자로 치환하는 방법이다. 로마 황제 율리우스 카이사르가 전쟁 중 군사 정보를 전달하기 위해 고안했다고 전해져 카이사르 암호라 불린다. 알파벳을 정해진 칸수(키)만큼 밀어서 치환하고, 복호화할 때는 반대 방향으로 밀어 되돌린다.

키 3으로 밀어서 치환 — 각 평문 글자를 알파벳에서 3칸 뒤 글자로 바꾼다 평문 암호문 A B C D E F G H I J D E F G H I J K +3
A는 3칸 뒤인 D로, B는 E로 … 항상 같은 규칙으로 밀어서 치환한다.
🔐 카이사르 암호 체험하기 SIMULATION

영문자를 입력하고 키(이동 칸수)를 조절해 보세요. 숫자·공백·한글은 그대로 둡니다.
🔒 암호화를 누르면 입력칸의 글자가 암호문으로 바뀐다. 이어서 🔓 복호화를 누르면 원문으로 돌아온다.

3
🔒 암호화를 눌러 보자. 입력칸의 글자가 암호문으로 바뀐다.

고대에는 문자의 순서를 재배치하는 전치형 암호화도 쓰였다. 그리스 군사들이 사용한 스키테일(scytale)이 대표적인 예로, 정해진 지름의 막대에 종이를 감아 글을 쓰면 풀었을 때 문자가 뒤섞여 보인다 — 같은 지름의 막대를 가진 사람만 다시 감아 해독할 수 있다.

실제 스키테일 복제품 — 막대에 가죽 끈을 나선으로 감아 글자를 새긴 모습
실제 스키테일 복제품 — 막대에 가죽 끈을 나선으로 감고 글자를 새겨 넣었다. 풀면 글자 순서가 뒤섞이고, 같은 굵기의 막대에 다시 감아야만 원래 순서로 읽힌다.
① 막대에 감긴 순서 그대로 1 → 10을 적는다 1 2 3 4 5 6 7 8 9 10 풀면 ② 종이를 풀면 순서가 뒤섞여 보인다 1 3 5 7 9 2 4 6 8 10 막대 없이는 원래 순서(1→10)를 알 수 없다 ③ 같은 굵기의 막대에 다시 감아야만 1→10의 원래 순서로 되돌아온다 막대의 굵기(지름)가 곧 비밀 키다
스키테일 암호 — 문자를 바꾸지 않고 순서만 재배치하는 전치형 암호화
Tip · 예를 들어 COMPUTER를 키 35164827의 순서로 재배치하면 MUCTPROE가 된다. 그러나 전치형 암호화는 컴퓨팅 기술이 발전하며 쉽게 뚫리게 되어, 더 안전한 방법이 필요해졌다.
✅ 성취기준 자가 점검

치환형·전치형 암호화 방법을 설명하고, 그 한계(문자 빈도 분석 등으로 해독 가능)를 말할 수 있나요?

3) 지금 우리가 쓰는 암호화 — 단방향 / 대칭 / 비대칭

① 단방향 암호화

암호화는 되지만 복호화는 불가능한 방식. 같은 평문은 항상 같은 암호문(해시값)을 만들고, 한 글자만 달라져도 완전히 다른 결과가 나온다. 그래서 원문을 노출하지 않고도 일치 여부만 비교할 수 있어 비밀번호 저장, 파일 위·변조 확인, QR코드 신원 인증 등에 쓰인다. 대표 알고리즘은 SHA-256.

이 글은 원본입니다. 이 글은 원본입니다 (마침표 하나 차이) 해시 함수 SHA-256 a1b2...9f3d (고정 64자) 7e4c...02a8 (고정 64자) → 완전히 다른 해시값! (되돌려서 원문을 알아낼 수 없다)
입력이 아무리 길어도 출력은 항상 같은 길이 — 한 글자만 달라져도 해시값 전체가 바뀐다 (눈사태 효과)
💡 궁금해요 · "이 글은 원본입니다."와 "이 글은 원본입니다"(마침표 하나 차이)를 SHA-256으로 변환하면 완전히 다른 해시값이 나온다. 이 성질 덕분에 해시값만 비교해도 데이터가 조금이라도 변조됐는지 알 수 있다.
📌 참고 — 해시 함수와 SHA 계열
해시 함수(hash function)는 길이가 제각각인 데이터를 정해진 길이의 값(해시값)으로 요약하는 함수다. 원문이 한 글자만 달라져도 해시값은 완전히 달라지고(눈사태 효과), 반대로 해시값만 보고 원문으로 되돌릴 수는 없다 — 그래서 '단방향'이라 부른다.

SHA(Secure Hash Algorithm)는 이 해시 함수의 대표 표준 계열이고, 이름 뒤의 숫자는 바로 결과 해시값의 비트(bit) 길이다. (SHA-64·SHA-128 같은 버전은 없고, 실제로는 아래처럼 나뉜다.)
이름해시값 길이비고
SHA-1160비트2017년 실제 충돌(같은 해시값을 갖는 서로 다른 파일) 사례가 발견돼 더 이상 안전하지 않음 — 사용 비권장
SHA-256256비트현재 가장 널리 쓰이는 표준. 비트코인, 인증서, 비밀번호 저장 등에 사용
SHA-512512비트SHA-256보다 해시값이 더 길어 안전성이 높고, 64비트 컴퓨터에서는 오히려 더 빠르게 동작하기도 함
💡 "256비트"인데 왜 화면엔 "64자"로 보일까? · 해시값을 사람이 읽기 편하게 적을 땐 보통 16진수(0~9, a~f)로 바꿔 쓴다. 16진수는 한 자리에 16가지(2⁴)를 표현하므로 한 자리 = 4비트다 — 이건 일반 텍스트 한 글자가 8비트(1바이트)인 것과는 다르다. 그래서 256비트 ÷ 4비트 = 64자가 되는 것이고, 8비트로 착각해 나누면 32자가 나와 계산이 안 맞는다.
숫자가 클수록 가능한 해시값의 경우의 수가 기하급수적으로 늘어나 무작위로 맞히거나 같은 해시값을 갖는 다른 원문(충돌)을 찾기가 훨씬 어려워진다 — 다만 계산에 드는 시간·자원은 조금 더 필요하다.
🔐 단방향 암호화(해시 함수) 체험하기 SIMULATION

61쪽 스스로 해결하기 — 문장을 입력하면 진짜 SHA-256 알고리즘으로 해시값을 계산해요. 아래 두 칸에 원문과, 마침표 하나·띄어쓰기 하나만 바꾼 문장을 각각 넣고 해시값이 어떻게 달라지는지 직접 확인해 보자.

계산 중…
계산 중…

② 대칭 암호화 (비밀키)

송신자와 수신자가 같은 키로 암호화·복호화하는 양방향 방식. 속도가 빠르지만, 사용자마다 키를 개별적으로 안전하게 전달해야 하는 키 배송 문제가 있다 — 사용자가 10만 명이면 키도 10만 개를 관리해야 한다. 인터넷·모바일 뱅킹의 OTP보안카드가 대표 사례다.

동일한 키 사용 (대칭키 · 비밀키 방식) 비밀키 비밀키 (같음) 평문 암호화 암호문 복호화 평문
▲ 대칭 암호화 — 암호화할 때와 풀 때 똑같은 하나의 키를 쓴다
은행에서 쓰는 대칭 암호화 — 같은 비밀값을 양쪽이 나눠 갖는다 OTP 384921 OTP 생성기 1분마다 새 번호 보안카드 ○○은행 01 5382 02 9174 03 2065 04 7413 05 8629 06 3057 07 ▩▩▩▩ 08 6248 09 1793 10 ▩▩▩▩ 11 4506 12 9021 보안카드 은행이 지정한 번호를 찾아 입력 (▩ = 이미 사용)
▲ OTP 생성기와 보안카드 — 둘 다 고객과 은행이 완전히 같은 비밀값을 나눠 갖는 대칭 방식이다
왜 이게 대칭 암호화일까? · OTP는 기기 안에 심어둔 씨앗값과 현재 시각으로 번호를 만든다. 은행 서버도 똑같은 씨앗값을 갖고 있어서 같은 시각에 같은 번호를 계산해 낸다 — 두 번호가 맞으면 본인이다. 보안카드는 카드에 인쇄된 번호표를 은행이 똑같이 한 벌 보관한다 — "7번 알려주세요"라고 물으면 그 카드를 가진 사람만 답할 수 있다. 양쪽이 동일한 비밀값을 나눠 갖는다는 점에서 둘 다 대칭 암호화다.
OTP·보안카드를 왜 은행에 직접 가서 받을까? · 바로 키 배송 문제 때문이다. 사람마다 값이 달라야 하니 10만 명이면 10만 벌을 만들어야 하고, 그 값이 전달 과정에서 새어 나가면 그대로 뚫린다. 인터넷으로 보내면 가로채일 수 있으니 창구에서 직접 건네주는 방법을 쓰는 것이다. 이 불편함을 없애려고 등장한 것이 바로 다음에 배울 비대칭 암호화다.

③ 비대칭 암호화 (공개키 · 개인키)

대칭 암호화의 키 배송 문제를 해결하기 위해 등장했다. 송수신자가 서로 다른 키 쌍(공개키·개인키)을 가지며, 한쪽 키로 암호화한 것은 반드시 짝을 이루는 다른 키로만 풀 수 있다. 처리 속도는 느리지만 키를 안전하게 교환할 필요가 없다. 전자 서명, 공동(공인) 인증서가 대표 사례다.

서로 다른 키 사용 (비대칭키 방식 = 공개키 + 개인키) 공개키 개인키 (다름) 평문 암호화 암호문 복호화 평문
▲ 비대칭 암호화 — 잠근 키와 다른 키로 푼다. 한쪽 키로 잠근 것은 반드시 짝이 되는 다른 키로만 열린다

그럼 이 두 개의 키를 실제로 누가, 어떻게 나눠 갖는 걸까? 우리가 매일 쓰는 은행 거래로 살펴보자.

🏦 은행 거래 — 고객과 은행이 각자 키 쌍을 가진다 🙍 고객 🔑 개인키 (나만 보관) 🔓 공개키 (누구나 가짐) 🏦 은행 🔑 개인키 (은행만 보관) 🔓 공개키 (누구나 가짐) Ⓐ 내용을 비밀로 — 받는 쪽(은행) 키 쌍을 쓴다 이체 내용 "김철수 100만원" 🔐 암호문 은행만 읽음 내용 확인 🔓 은행 공개키로 암호화 🔑 은행 개인키로 복호화 Ⓑ 본인임을 증명 — 보내는 쪽(고객) 키 쌍을 쓴다 이체 내용 + 전자 서명 🔏 🔏 서명 첨부 본인 확인 ✅ 위·변조도 확인 🔑 고객 개인키로 서명 🔓 고객 공개키로 검증
▲ 목적에 따라 어느 쪽 키 쌍을 쓰는지가 달라진다 — 비밀로 하려면 받는 쪽, 본인 증명은 보내는 쪽
🔓 공개키 = 열린 자물쇠, 🔑 개인키 = 그 자물쇠를 여는 유일한 열쇠 · 자물쇠(공개키)는 복사해서 누구에게나 나눠 줘도 된다. 누구나 그 자물쇠로 잠글 수는 있지만, 한 번 잠기면 짝이 되는 열쇠(개인키)를 가진 주인만 열 수 있기 때문이다.
Ⓐ 이체 내용을 비밀로 보내기 — 기밀성

누가 중간에 가로채도 내용을 못 읽게 하는 것이 목적. 받는 쪽(은행)의 키 쌍을 쓴다.

  1. 고객이 은행이 공개해 둔 은행 공개키를 받아온다. (공개된 것이라 누구나 가질 수 있다)
  2. 은행 공개키로 이체 내용을 암호화한다 → 알아볼 수 없는 암호문이 된다.
  3. 암호문을 은행에 전송한다. 중간에 누가 훔쳐봐도 읽지 못한다.
  4. 은행은 자기만 가진 은행 개인키로 복호화해 내용을 읽는다.
왜 은행만 읽을 수 있나? · 은행 공개키로 잠근 것은 은행 개인키로만 열리는데, 그 개인키는 은행만 갖고 있기 때문이다. 심지어 암호화한 고객 본인도 다시 열 수 없다.
Ⓑ 내가 보낸 게 맞음을 증명하기 — 인증·전자 서명

"진짜 본인이 보냈는지"를 증명하는 것이 목적. 보내는 쪽(고객)의 키 쌍을 쓴다.

  1. 고객이 인증서 비밀번호를 입력해, 내 기기에 잠겨 있던 고객 개인키를 꺼낸다.
  2. 고객 개인키로 이체 내용에 전자 서명을 만든다. 개인키 자체는 기기 밖으로 나가지 않는다.
  3. 이체 내용과 전자 서명을 함께 은행에 전송한다.
  4. 은행은 인증서에 담긴 고객 공개키로 서명을 검증한다.
  5. 정상적으로 검증되면 인증(개인키를 가진 본인이 맞다)과 무결성(금액·계좌가 바뀌지 않았다)이 동시에 증명된다 → 이체 실행.
왜 본인 증명이 되나? · 고객 공개키로 풀리는 서명은 고객 개인키로 만든 것뿐인데, 그 개인키는 본인만 갖고 있기 때문이다. 그래서 나중에 "나는 그런 이체 한 적 없다"고 발뺌할 수 없다(부인 방지).
목적암호화·서명에 쓰는 키복호화·검증에 쓰는 키얻는 것
Ⓐ 비밀로 보내기받는 사람(은행)의 공개키받는 사람(은행)의 개인키기밀성
Ⓑ 본인 증명보내는 사람(고객)의 개인키보내는 사람(고객)의 공개키인증 · 무결성 · 부인 방지
실제 인터넷뱅킹은 Ⓐ와 Ⓑ를 함께 쓴다 · 내용은 은행 공개키로 감추고(기밀성), 서명은 고객 개인키로 붙여서(인증·무결성) 한 번에 보낸다. 그래서 암호화의 세 가지 기능인 기밀성·무결성·인증이 모두 충족된다.
한 줄 정리 · 비밀로 하고 싶으면 받는 사람의 키 쌍, 내가 보냈다고 증명하고 싶으면 보내는 사람의 키 쌍을 쓴다. 어느 쪽이든 한쪽 키로 잠근 것은 반드시 짝이 되는 다른 키로만 풀린다.

공동(공인) 인증서는 어떻게 발급될까?

"은행에서 인증서를 받는다"고 흔히 말하지만, 실제로는 은행이 내 키를 만들어 주는 것이 아니다. 키 쌍은 내 기기 안에서 만들어지고, 인증기관은 그중 공개키에만 도장을 찍어 준다.

인증서 발급 — 개인키는 기기 밖으로 나가지 않는다 🖥️ 내 기기 (브라우저·앱) 🔑 개인키 여기서 생성 · 여기에만 보관 🔓 공개키 인증기관으로 전송 🏛️ 인증기관 (금융결제원 등) 신원 확인 후 CA 개인키로 인증서에 서명 ② 공개키 + 신원정보 ④ 인증서 발급 🔑 개인키는 점선을 넘지 않는다 — 은행도 인증기관도 내 개인키를 모른다
▲ 인증서는 공개키의 신분증이다. 인증기관은 공개키에 서명해 줄 뿐, 개인키는 만지지도 보지도 못한다
단계실제로 일어나는 일키 관점
① 발급 신청내 PC·휴대폰 안에서 키 쌍이 생성된다개인키·공개키가 내 기기에서 태어남
② 본인 확인창구 방문 또는 계좌번호 + 보안카드·OTP 입력"이 공개키의 주인이 누구인지" 확인
③ 신청서 전송공개키 + 신원정보를 인증기관에 보냄. 이때 내 개인키로 서명해 "짝 개인키를 갖고 있음"을 증명공개키만 나가고 개인키는 안 나감
④ 인증서 발급인증기관이 확인 후 자기 개인키로 서명한 인증서를 준다인증서 = 내 신원 + 내 공개키 + CA 서명
⑤ 저장개인키는 인증서 비밀번호로 암호화되어 기기에 저장개인키는 끝까지 내 기기 안에만
인증서 비밀번호는 개인키가 아니다 · 개인키를 잠가 둔 자물쇠를 여는 암호일 뿐이다. 그래서 비밀번호를 잊어버리면 은행도 알려 줄 방법이 없고 재발급밖에 답이 없다 — 이것이 은행조차 내 개인키를 갖고 있지 않다는 가장 확실한 증거다.
②번에서 보안카드·OTP를 왜 쓸까? · 공개키는 그냥 숫자 덩어리라서 "이게 홍길동 것"이라는 보증이 없다. 그래서 앞 단원에서 배운 대칭 방식(보안카드·OTP)으로 먼저 본인임을 확인한 뒤, 그 사람의 공개키로 인증서를 만들어 주는 것이다. 대칭과 비대칭이 함께 쓰이는 자리다.

공개키와 개인키 사이에 수학적 관계가 있을까?

있다. 두 키는 남남이 아니라 한 쌍으로 태어난 짝꿍이다. 그런데 이상하다 — 관계가 있다면 공개키를 보고 개인키를 알아낼 수 있어야 하지 않을까? 바로 그 지점을 막아 둔 것이 비대칭 암호의 핵심이다. 세 단계로 나눠 보자.

1단계 · 한쪽으로만 쉬운 계산이 있다

다음 두 문제를 직접 풀어 보자. 계산기 없이 걸리는 시간이 완전히 다르다.

문제 A.  61 × 53 = ?            → 몇 초면 된다 (3233)
문제 B.  3233 = ? × ?           → 2, 3, 5, 7, 11 … 하나씩 나눠 봐야 한다

둘은 같은 사실을 묻는데 난이도가 다르다. 곱하는 방향은 쉽고, 되돌리는 방향은 어렵다. 이렇게 한쪽으로만 쉬운 계산일방향 함수라고 부른다. 공개키는 이미 곱해 놓은 결과(3233)를, 개인키는 그 재료(61과 53)를 아는 사람만 만들 수 있다.

3233쯤이야 금방 풀 수 있지 않나? · 그렇다. 그래서 실제 RSA는 300자리가 넘는 소수 두 개를 쓴다. 곱하는 건 컴퓨터가 눈 깜짝할 사이에 하지만, 그 결과를 되돌려 소수 두 개를 찾는 데는 지금 지구의 모든 컴퓨터를 동원해도 우주의 나이보다 긴 시간이 걸린다. 어려워서 못 푸는 게 아니라 시간이 모자라서 못 푸는 것이다.

2단계 · 암호는 '시계 산수'를 쓴다

시계를 떠올려 보자. 지금이 10시인데 5시간이 지나면 15시가 아니라 3시다. 12를 넘으면 다시 처음으로 돌아오기 때문이다. 이런 계산을 나머지 연산이라 하고 mod(모드)라고 쓴다.

10 + 5 = 15  →  15 mod 12 = 3        (12시간짜리 시계)

암호에서는 3233짜리 시계를 쓴다고 생각하면 된다. 숫자를 아무리 크게 곱해도 결과는 항상 0~3232 사이 어딘가로 돌아온다. 밖에서 보면 값이 마구 튀어서 원래 숫자를 짐작할 수 없다.

3단계 · 두 키는 서로를 되돌리는 짝이다

이제 키를 실제로 만들어 보자. 순서대로 따라가면 숫자가 어디서 나오는지 보인다.

① 소수 두 개를 고른다        p = 61,  q = 53     ← 나만 아는 재료
② 시계 크기를 만든다          n = 61 × 53 = 3233   ← 공개해도 되는 값
③ 시계의 '한 바퀴'를 구한다    (61-1) × (53-1) = 60 × 52 = 3120
④ 잠금 횟수를 고른다          e = 17               ← 내가 정하는 값
⑤ 풀림 횟수를 계산한다        17 × d 를 3120으로 나눠 나머지가 1이 되는 d
                             d = 2753             (17 × 2753 = 46801 = 3120 × 15 + 1)

17은 우리가 고른 숫자고, 2753은 계산해서 나온 숫자다. ④에서 e를 19나 23으로 골랐다면 d도 다른 값이 됐을 것이다. 그러니 공개키 e는 "17번 곱해서 잠그기", 개인키 d는 "2753번 곱해서 풀기"인 셈이고, 17번 잠그고 2753번 풀면 정확히 제자리로 돌아온다. ⑤를 그렇게 되도록 맞춰 놓았기 때문이다.

③의 3120은 왜 필요할까? · 3233짜리 시계에서 숫자를 계속 곱하면 3120번마다 제자리로 돌아오는 성질이 있다. 그래서 "몇 번 잠그고 몇 번 풀면 원래대로인지"를 정하려면 이 주기를 알아야 한다. 이 값을 오일러 파이 함수 φ(n)이라 부른다.

여기가 핵심이다. ⑤에서 d를 구하려면 ③의 3120이 필요하고, 3120을 구하려면 ①의 61과 53을 알아야 한다. 그런데 밖으로 공개되는 건 3233과 17뿐이다. 공개키만 아는 사람은 3233을 61과 53으로 되돌릴 수 없으니 3120도, d도 구할 수 없다 — 관계는 분명히 있지만 따라갈 수가 없는 것이다.

읽는 법 · φ는 그리스 문자로 "파이"라고 읽는다. 원주율 π도 우리말로 똑같이 "파이"라서 헷갈릴 때는 "피"라고 구분해 읽기도 한다. φ(n)오일러 파이 함수라 부르며, "n보다 작으면서 n과 서로소인 수의 개수"를 뜻한다. "합동", mod"모드"라고 읽고 나머지를 구한다는 뜻이다 — e × d ≡ 1 (mod φ(n))은 "e와 d를 곱해 φ(n)으로 나누면 나머지가 1"이라는 말이다.
n = p × q                     (아주 큰 소수 두 개를 곱한다)
φ(n) = (p-1) × (q-1)
e × d ≡ 1  (mod φ(n))         ← 개인키 d는 공개키 e의 "역수"

  공개키 = (n, e)              암호화:  c = me mod n
  개인키 = (n, d)              복호화:  m = cd mod n

앞에서 쓴 61과 53으로 실제로 맞는지 확인해 보자. 잠근 뒤 풀면 원래 숫자로 정확히 돌아온다.

p = 61,  q = 53   →   n = 3233,   φ(n) = 60 × 52 = 3120
e = 17            →   d = 2753     (17 × 2753 = 46801 = 1 + 15 × 3120 ✓)

  평문 65  --암호화-->  6517 mod 3233   = 2790
  암호문 2790 --복호화-->  27902753 mod 3233 = 65   ✓ 원문 복원
🔐 공개키 암호 체험하기 SIMULATION

위 숫자를 그대로 쓴다. 공개키 (n=3233, e=17)는 누구나 알고, 개인키 d=2753은 나만 안다.
잠근 키로는 열리지 않는다는 것을 직접 확인해 보자.

0 ~ 3232
숫자를 넣고 위 버튼을 눌러 보자.
💡 무엇을 확인했나 · 공개키로 잠근 것은 개인키로만, 개인키로 잠근 것은 공개키로만 열린다. 잠근 키를 그대로 다시 쓰면 엉뚱한 숫자가 나온다 — 카이사르 암호(대칭)에서 같은 키로 잠그고 열던 것과 결정적으로 다른 점이다.
같은 키 쌍인데 어느 쪽으로 잠그느냐에 따라 쓰임이 갈린다. 공개키로 잠그면 Ⓐ 나만 읽게 하는 것(기밀성), 개인키로 잠그면 Ⓑ 내가 썼음을 증명하는 것(전자서명)이다. 위 표에서 본 두 방향이 바로 이것이다.
그럼 공개키로 개인키를 계산하면 되지 않나? · 바로 이 지점이 핵심이다. d를 구하려면 φ(n)이 필요하고, φ(n)을 구하려면 p와 q가 필요하고, p·q를 구하려면 n을 소인수분해해야 한다. 위 예제의 3233은 61×53으로 금방 쪼개지지만, 실제로 쓰는 n은 2048비트(십진수 617자리)다. 곱하는 것은 순식간이지만 되돌려 인수분해하는 것은 현재 기술로 수십억 년이 걸린다.
트랩도어(함정문) 함수 · 들어가기는 쉬운데 되돌아 나오는 길은 사실상 없는 구조라서 이렇게 부른다. 정리하면 — 두 키는 수학적으로 완벽히 짝지어져 있지만, 그 관계를 역으로 푸는 것이 불가능에 가깝다. 비대칭 암호는 "관계가 없어서" 안전한 것이 아니라 "관계는 있는데 되돌릴 수 없어서" 안전한 것이다.
요즘은 타원곡선(ECC)도 많이 쓴다 · 공개키 = 개인키 × G (타원곡선 위의 점 덧셈)로 관계가 더 단순해 보이지만, 역산(이산로그 문제)이 지극히 어렵다는 원리는 똑같다. 같은 안전성을 더 짧은 키로 얻을 수 있어 스마트폰·인증서에 널리 쓰인다.
구분대칭 암호화비대칭 암호화
키 관계암호화 키 = 복호화 키암호화 키 ≠ 복호화 키
속도빠름느림
키 교환필요 (문제 발생 가능)불필요 (쉬움)
대표 사례OTP, 보안카드공동 인증서, 전자 서명
✅ 성취기준 자가 점검

단방향·대칭·비대칭 암호화의 차이와 각각의 실제 사용 사례를 설명할 수 있나요?

2단원 · 2절

3. 빅데이터의 개념과 수집Big Data & Collection

4차 산업혁명 시대, 우리 주변에는 규모를 가늠할 수 없을 만큼 많은 데이터가 매일 쏟아진다. 이 방대한 데이터를 빅데이터라 하고, 이를 목적에 맞게 다루는 것이 데이터 분석이다.

1) 빅데이터의 3V

빅데이터는 기존 방식으로는 처리하기 어려운 막대한 양의 데이터셋(또는 그 처리 기술)을 말하며, 세 가지 특징 3V로 요약된다.

Volume · TB~PB 속도Velocity · 실시간 생성 다양성Variety · 정형+비정형 빅데이터
▲ 빅데이터의 3V — 양(Volume) · 속도(Velocity) · 다양성(Variety)
  • 양(Volume): 데이터 집합의 크기가 테라바이트(TB, 10¹²byte)에서 페타바이트(PB, 10¹⁵byte)에 이른다.
  • 속도(Velocity): 센서·인터넷·모바일 등 다양한 곳에서 빠르게 생성·수집된다.
  • 다양성(Variety): 데이터베이스 같은 정형 데이터부터 이메일·SNS 게시물 같은 비정형 데이터까지 형태가 다양하다.
💡 궁금해요 · 최근에는 가치(Value)정확성(Veracity)을 더해 4V, 5V라고도 표현해요.
주요 용어 · 빅데이터, 공공 데이터, 민간 데이터, 웹크롤링

2) 내게 필요한 데이터는 어떻게 모을까?

공공 데이터
공공기관 개방 · 높은 신뢰성
민간 데이터
기업·개인 수집 · 실생활 특화
웹크롤링
프로그램이 자동으로 수집
직접 수집
설문·촬영·센서 측정
수집 방법특징
공공 데이터공공 기관이 만든 데이터. 개방성이 가장 큰 특징으로, 개인정보·안보를 침해하지 않으면 누구나 자유롭게 쓸 수 있다. 신뢰성이 높다.공공데이터포털(data.go.kr), 기상자료개방포털
민간 데이터기업·개인이 상업적 목적으로 수집. 더 전문적이고 실생활에 도움이 되는 경우가 많다.캐글(Kaggle), 데이콘(DACON)
웹크롤링웹 크롤러(web crawler)라는 프로그램으로 웹사이트 정보를 자동 수집하는 방법.특정 웹사이트의 실시간 통계 자동 수집
직접 수집필요한 데이터가 없을 때 직접 얻는 방법. 목적에 딱 맞는 데이터를 얻을 수 있다.설문 조사, 카메라 촬영, 센서 측정
Tip · 데이터 수집 방법마다 장단점이 있다. 목적에 알맞은 방법을 고르면 문제 해결에 도움이 되고 비용도 아낄 수 있다.
✅ 성취기준 자가 점검

빅데이터의 개념과 3V 특징을 설명하고, 문제 해결에 필요한 데이터를 수집 방법별로 구분해 고를 수 있나요?

2단원 · 2절

4. 데이터 전처리와 시각화Preprocessing & Visualization

모은 데이터를 그대로 분석에 쓸 수는 없다. 다듬어야(전처리) 하고, 눈에 보이게 그려야(시각화) 비로소 의미를 읽을 수 있다.

1) 데이터 전처리 — 분석 전 손질하기

데이터 전처리는 원시 데이터를 원하는 형식으로 다듬는 작업이다. 적절히 전처리하면 분석과 기계학습의 정확도·신뢰도가 올라간다.

문제 상황
이해하기 데이터
수집하기
데이터
탐색하기
데이터 전처리하기
▲ 데이터 처리 과정 — 문제 상황 이해 → 수집 → 탐색 → 전처리
전처리 작업내용
결측치 제거데이터셋에서 값이 빠진(누락된) 결측치를 채우거나 제거한다.
이상치 제거일반적인 경향에서 크게 벗어난 이상치는 분석 결과를 왜곡할 수 있어 제거한다.
데이터 정리중복 항목 제거, 오류·맞춤법 수정 등.
데이터 통합서로 유사하거나 공통 속성(예: ID)을 가진 데이터를 하나로 합친다.
데이터 정규화속성값을 공통 척도나 일정 범위로 맞춰 분석 일관성을 높인다.

결측치는 어떻게 생길까 — 서울 기온이 3년 넘게 비어 있다

기상청 기상자료개방포털에서 서울(지점 108)의 일별 기온을 받아 그려 보면, 그래프 한가운데가 뻥 뚫려 있다. 관측 장비가 고장 난 것도, 자료를 잃어버린 것도 아니다. 6·25 전쟁으로 관측 자체가 멈췄던 기간이다.

-100102030 자료 없음 1,187일 1945195019551960 (℃)
▲ 서울(108) 월평균기온 1945~1960년 · 1950-09-01 ~ 1953-11-30 자료 없음 (출처: 기상청 기상자료개방포털)

결측 구간의 앞뒤 실제 기록은 이렇다. 1950년 8월 31일에 끊기고, 1,187일(약 3년 3개월) 뒤인 1953년 12월 1일에 다시 시작된다.

날짜최저기온평균기온최고기온
1950-08-3018.0 ℃24.6 ℃32.6 ℃
1950-08-3120.1 ℃25.4 ℃32.5 ℃
⋯ 1950-09-01 ~ 1953-11-30 · 1,187일 결측 ⋯
1953-12-017.4 ℃12.2 ℃16.2 ℃
1953-12-021.4 ℃5.6 ℃9.0 ℃

결측치는 한 가지 모습으로 오지 않는다. 같은 1,187일인데도 자료를 열어 보면 두 가지가 섞여 있다.

모습일수실제 자료
행이 아예 없음431일그 날짜가 목록에 등장하지 않는다
행은 있는데 값이 없음756일{"dt":"1950-09-01", "stnNm":"서울(108)"} — 날짜는 있는데 기온 항목이 통째로 빠져 있다

그래서 "빈칸을 찾아라"만으로는 결측치를 다 못 찾는다. 날짜를 1일 간격으로 쭉 만들어 놓고 있어야 할 날과 실제 있는 날을 맞춰 봐야 빠진 날이 드러난다.

그럼 3년치를 평균값으로 채우면 될까? · 안 된다. 결측치를 채우는 대체(imputation)며칠 정도의 짧은 구멍에 쓰는 방법이다. 3년치를 앞뒤 평균으로 메우면 없던 기록을 지어내는 것이 된다. 만들어 낸 값으로 "1950년대 서울은 이랬다"고 말하는 순간 그건 분석이 아니라 창작이다. 이런 경우엔 비어 있다는 사실을 그대로 두고 밝히는 것이 옳다 — 위 그래프에서 선을 잇지 않고 끊어 그린 이유도 같다.
정리 · 결측치를 만나면 순서대로 묻자. ① 비었나(고장·미관측·수집 실패) → ② 얼마나 비었나(며칠 vs 몇 년) → ③ 그래서 채울까, 지울까, 남길까. ①을 건너뛰고 바로 채우는 것이 가장 흔한 실수다.
📌 참고 · 교과서 실습에서는 오렌지3(Orange3)라는 무료 데이터 분석 도구로 태양광 발전량 데이터의 결측치·이상치를 제거하고, 칼로리 데이터를 병합하는 과정을 다룬다. 산점도로 이상치를 눈으로 확인한 뒤 조건에 맞는 행만 남기는 방식이 대표적이다.
주요 용어 · 데이터 전처리, 결측치, 이상치

2) 데이터 시각화 — 숫자를 그림으로

데이터 시각화는 수치·통계 데이터를 그래픽으로 표현해 분석 결과를 직관적으로 이해하기 쉽게 만드는 기술이다. 데이터 형태에 따라 알맞은 시각화 방법이 다르다.

막대그래프
수치를 항목별로 비교
선그래프
시간에 따른 변화 추이
원그래프
전체 대비 비율(%)
산점도
두 변수 사이의 상관관계
히스토그램·분포도
구간별 데이터 분포
박스 플롯
통곗값과 이상치
워드 클라우드
단어 등장 빈도(클수록 빈번)
지도(Geo Map)
위도·경도가 있는 지리 정보

정형 데이터(수치·문자·날짜·지리)는 표·그래프로, 비정형 데이터(이미지·텍스트·소리)는 이미지 뷰어·워드 클라우드처럼 형태에 맞는 방법으로 시각화한다.

📊 산점도 & 회귀선 체험하기 SIMULATION

교과서의 ‘운동 시간과 소모 열량’ 예시처럼, 두 변수의 상관관계 강도를 슬라이더로 바꾸면 산점도와 회귀선이 실시간으로 달라져요.

0.80
Tip · 회귀선의 기울기가 1에 가까울수록, 두 변수가 정비례에 가깝게 함께 움직인다는 뜻이에요. 상관관계 강도를 낮춰 보면 점들이 흩어지며 회귀선이 데이터 흐름을 잘 대표하지 못하게 되는 걸 볼 수 있어요.
✅ 성취기준 자가 점검

빅데이터 분석 도구로 데이터를 시각화하고, 그 결과의 의미와 가치를 해석할 수 있나요? 정형·비정형 데이터에 맞는 시각화 방법을 구분할 수 있나요?

3) 전체 예시 — 기상청 데이터로 ‘내 생일의 기온’ 그리기

지금까지 나눠서 배운 수집 → 전처리 → 시각화를 하나의 프로그램으로 이어 본다. 120년 가까운 서울 기온 기록에서 내 생일 하루만 뽑아, 그날의 기온이 해마다 어떻게 달라졌는지 그려 볼 것이다.

📥 실습 자료 · weather.csv 내려받기 (1.4MB · 서울 1907-10-01 ~ 2026-09-05 일별 기온 43,010일) — 기상청 기상자료개방포털 → 기후통계분석 → 기온분석에서 받은 자료다. 아래 코드가 이 파일을 자동으로 내려받으므로 직접 받지 않아도 실습할 수 있다.

① 한글이 깨지지 않게

matplotlib은 기본 상태에서 한글을 로 그린다. 한글 폰트를 붙여 주는 도구를 먼저 설치한다.

# 한글 시각화 도구 설치 (matplotlib은 기본 상태로는 한글이 □로 깨진다)
!pip install koreanize-matplotlib
import koreanize_matplotlib
import matplotlib.pyplot as plt
import csv

② 분석할 자료 가져오기

# 분석할 자료를 코랩으로 바로 내려받는다 (기상청에서 받은 파일 그대로)
!wget -q -O weather.csv https://edu.sunnylab.co.kr/data/weather.csv
!ls -lh weather.csv
구글 드라이브에 올려 둔 파일을 쓰고 싶다면 · 위 셀 대신 아래처럼 드라이브를 연결하고 작업 폴더를 옮기면 된다. 그 뒤로는 파일 이름만으로 열 수 있다.
from google.colab import drive
drive.mount('/content/drive', force_remount=True)
import os
os.chdir('/content/drive/MyDrive/data/')     # weather.csv 를 올려 둔 폴더로 이동

③ 파일부터 열어 보기

코드를 쓰기 전에 파일이 어떻게 생겼는지 먼저 본다. 첫 줄은 제목 줄이고, 둘째 줄부터 하루에 한 줄씩이다.

날짜,지점,평균기온(℃),최저기온(℃),최고기온(℃)
"1907-10-01",108,13.5,7.9,20.7
"1907-10-02",108,16.2,7.9,22
    ⋮
"1950-08-31",108,25.4,20.1,32.5
"1950-09-01",108,,,          ← 값이 비어 있다 (결측치)
    ⋮
"2026-09-05",108,24,19.1,29.4

→ 는 눈에 보이지 않는 탭 문자를 표시한 것

두 가지를 미리 알아 두면 코드가 쉬워진다.

  • 날짜 앞에 보이지 않는 탭 문자가 붙어 있다 → 쓸 때 .strip()으로 떼어 낸다.
  • 기온 자리가 통째로 비어 있는 날이 759일 있다 → 앞에서 본 그 결측 구간이다.
encoding='cp949'일까? · 기상청 CSV는 윈도우 한글 인코딩으로 저장되어 있다. 코랩(리눅스)의 기본값인 UTF-8로 열면 한글이 깨지거나 오류가 난다. 남이 만든 파일을 열 때 인코딩부터 확인하는 것이 전처리의 첫걸음이다.

④ 가장 더웠던 날 찾기 — 결측치를 바꿔서 처리

def 최고온도():
    f = open('weather.csv', mode='r', encoding='cp949')
    data = csv.reader(f, delimiter=',')
    max_date = ''
    max_temp = -999
    header = next(data)   # 첫 줄은 제목 줄이므로 건너뛴다
    print(header)
    for row in data:
        if row[-1] == '':     # ← 결측치 처리 ①  값이 비었으면
            row[-1] = -999    #    '있을 수 없는 값'으로 바꿔 둔다
        row[-1] = float(row[-1])
        if row[-1] > max_temp:
            max_date = row[0].strip()
            max_temp = row[-1]
    print("최고기온일", max_date)
    print("최고온도", max_temp)
    f.close()

row[-1]맨 뒤 열(최고기온), row[0]날짜다. 뒤에서부터 세면 가운데에 열이 하나 늘어도 코드를 고칠 필요가 없다.

-999일까? 0은 안 되나? · 안 된다. 이 코드는 가장 큰 값을 찾는다. 빈 값을 0으로 바꾸면 영하로 내려간 겨울날보다 0이 더 커서 순위가 뒤틀린다. 그래서 절대 나올 수 없는 값을 넣어 비교에서 자연스럽게 밀려나게 한다. 다만 이 -999평균을 구하는 계산에 섞이면 결과가 망가진다 — 목적에 따라 안전한 값이 달라진다는 뜻이다.

⑤ 내 생일 기온만 모으기 — 결측치를 건너뛰어 처리

def 생일날씨조회():
    f = open('weather.csv', mode='r', encoding='cp949')
    data = csv.reader(f)
    next(data)
    high, low, year = [], [], []
    birth = input('생일을 입력하세요(2000-01-02)')
    if len(birth) < 1:
        birth = "2000-01-03"                 # 그냥 Enter를 누르면 1월 3일로
    [y, m, d] = birth.split("-")
    for row in data:
        if row[-1] != '' and row[-2] != '':  # ← 결측치 처리 ②  최고·최저가 둘 다 있는 날만
            날짜 = row[0].strip()
            if m == 날짜.split("-")[1] and d == 날짜.split("-")[-1]:
                high.append(float(row[-1]))
                low.append(float(row[-2]))
                year.append(날짜.split("-")[0])
    f.close()
    return high, low, year


def show_Plot(high, low, year):
    plt.figure(figsize=(30, 3))
    plt.plot(high, "r", marker='.', label='최고 온도')
    plt.plot(low, 'b', marker='s', label='최저 온도')
    plt.xlabel("년도")
    plt.ylabel("온도")
    x = range(0, len(year), 5)               # 5개마다 눈금 하나
    plt.xticks(x, year[::5], rotation=45, fontsize=14)
    plt.legend()
    plt.title('내 생일의 기온 변화 그래프')
    plt.show()

여기서는 최고·최저가 둘 다 있는 날만 골라 담는다. 하나라도 비면 그 해는 아예 빼는 것이다. ④와 ⑤는 같은 결측치를 서로 다른 방법으로 다룬다.

⑥ 실행

if __name__ == '__main__':
    최고온도()
    high, low, year = 생일날씨조회()
    show_Plot(high, low, year)

실행 결과

['날짜', '지점', '평균기온(℃)', '최저기온(℃)', '최고기온(℃)']
최고기온일 2018-08-01
최고온도 39.6
생일을 입력하세요(2000-01-02)
1월 3일의 최고·최저 기온이 해마다 어떻게 달라졌는지 보여 주는 꺾은선 그래프
▲ 1월 3일의 최고(빨강)·최저(파랑) 기온 — 코랩 실행 결과 · 가로로 밀어서 연도 눈금을 확인해 보자
x축을 자세히 보자 — 1948 다음이 왜 1956일까? · 눈금은 5개마다 하나씩 찍는데, 1948에서 다섯 칸 뒤가 1953이 아니라 1956이다. 세 해가 통째로 사라졌기 때문이다. 앞에서 본 그 결측 구간(1950-09-01 ~ 1953-11-30)에 걸려 1951·1952·1953년 1월 3일 기록이 없고, ⑤에서 건너뛰기로 처리했으니 목록에서 빠진 것이다.

이것이 건너뛰기 방식의 대가다. 그래프의 가로 간격이 실제 시간 간격과 어긋난다. 옆으로 나란히 놓인 두 점이 1년 차이일 수도, 4년 차이일 수도 있다. 결측치를 어떻게 처리했는지 모르면 그래프를 잘못 읽게 된다.
결측치 처리방법좋은 점대가
바꾸기(④)빈 값을 -999로 치환모든 행을 그대로 유지평균·합계에 섞이면 결과가 망가진다
건너뛰기(⑤)빈 값이 있는 행을 제외남은 값은 전부 진짜 값가로축 간격이 실제와 어긋난다
정리 · 결측치 처리에 정답은 없다. 무엇을 구하려는지에 따라 안전한 방법이 달라지고, 어느 쪽을 골라도 대가가 따른다. 중요한 건 내가 무엇을 어떻게 처리했는지 알고 있는 것이다.
🔗 직접 실행해 보기 · 구글 코랩에서 이 프로그램 열기 — 사본을 만들어 자기 생일을 넣어 보자.
✅ 스스로 확인하기

max_temp의 처음 값이 -999인 이유는? ② .strip()을 빼면 출력이 어떻게 달라질까? ③ 내 생일이 2월 29일이면 그래프가 어떻게 달라질까? ④ 건너뛰기 대신 빠진 해를 빈칸으로 남겨 가로축 간격을 실제와 맞추려면 코드를 어떻게 고쳐야 할까?

단원 정리

5. 데이터 단원 정리Wrap-up

🎯 이 단원의 핵심 질문

  • 데이터를 효율적으로 관리하고 보호하는 방법은? → 압축과 암호화
  • 빅데이터를 목적에 맞게 처리하여 분석하는 방법은? → 수집 → 전처리 → 시각화

🧭 진로 이야기 — 데이터 마이너

광산에서 광물을 캐내는 광부처럼, 방대한 데이터에서 의미 있는 정보를 찾아내는 직업이 데이터 마이너(data miner)다. 쇼핑몰 구매 기록, 검색 기록, SNS 데이터 같은 빅데이터를 분석해 미래에 도움이 될 정보를 만든다. 통계·프로그래밍에 대한 이해와, 세상을 바라보는 안목(도메인 지식)이 함께 필요한 직업이다.

📋 한눈에 정리

주제핵심
데이터 압축무손실(RLE, 허프만) vs 손실(JPEG, MPEG) — 압축비 = 압축 전 용량 ÷ 압축 후 용량
데이터 암호화치환형·전치형(고전) / 단방향·대칭·비대칭(현대)
빅데이터3V(양·속도·다양성), 수집(공공·민간·웹크롤링·직접)
전처리·시각화결측치·이상치 제거, 정규화, 통합 → 막대·산점도·박스플롯·워드클라우드로 시각화

✔️ 역량 체크

컴퓨팅 사고력추상화 · 자동화
디지털 문화 소양디지털 의사소통 · 윤리
인공지능 소양데이터 문해력
📌 참고 · 다음 단원(Ⅲ. 알고리즘과 프로그래밍, Ⅴ. 디지털 문화 등)도 순서대로 추가될 예정입니다. ← Ⅰ. 컴퓨팅 시스템 보러 가기