대푯값

단원 01 자료와 그래프 · 중학 통계 복습 · 1차시(45분)

개념

자료 전체를 하나의 수로 요약한 값이 대푯값입니다. 가장 많이 쓰는 세 가지는 모든 값을 더해 개수로 나눈 평균, 크기순으로 늘어놓았을 때 한가운데 값인 중앙값, 가장 자주 나타나는 값인 최빈값입니다.

\[\text{평균} = \frac{x_1 + x_2 + \cdots + x_n}{n}\]

“직장인 평균 연봉”이 체감보다 높게 느껴지는 이유가 여기 있습니다. 소수의 고액 연봉이 평균을 끌어올리기 때문에, 뉴스에서는 중앙값을 함께 보여 주기도 합니다.

세 값은 성격이 다릅니다. 평균은 모든 값을 반영하는 대신 극단적으로 크거나 작은 값(이상값)에 민감하게 끌려가고, 중앙값과 최빈값은 이상값의 영향을 거의 받지 않습니다. 아래 앱에서 전학 온 친구 한 명의 독서 권수를 극단적으로 키워 보며 세 대푯값이 어떻게 움직이는지 비교해 보세요.

만지며 배우기

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 640

# matplotlib·numpy 없이 stdlib(statistics) + 인라인 SVG 로만 그린다
import statistics
from collections import Counter
from shiny import App, render, ui

ACCENT = "#2563eb"
INK = "#111827"
MUTED = "#6b7280"

# 학생 15명의 한 달 독서 권수
READS = [0, 1, 2, 2, 3, 3, 3, 3, 3, 4, 4, 5, 5, 6, 8]

def stats(data):
    mean = statistics.mean(data)
    median = statistics.median(data)
    mode = Counter(data).most_common(1)[0][0]
    return mean, median, mode

def dots_svg(data, mean, median, mode):
    W, H, ml, mr, mt, mb = 480, 320, 24, 12, 14, 34
    pw, ph = W - ml - mr, H - mt - mb
    xmax = max(10, max(data))
    def px(v):
        return ml + (v / xmax) * pw
    seen = {}
    stacks = []
    for v in sorted(data):
        seen[v] = seen.get(v, 0) + 1
        stacks.append((v, seen[v]))
    ymax = max(seen.values())
    step = min(20, ph / (ymax + 1))
    baseline = mt + ph
    dots = ""
    for v, k in stacks:
        cy = baseline - (k - 0.5) * step
        dots += (f'<circle cx="{px(v):.1f}" cy="{cy:.1f}" r="6" '
                 f'fill="{ACCENT}" opacity="0.8"/>')
    # 범례를 겹치지 않게 세 줄로
    lx = W - 96
    leg = (f'<rect x="{lx}" y="{mt+2}" width="11" height="11" fill="{ACCENT}"/>'
           f'<text x="{lx+15}" y="{mt+11}" font-size="11" fill="{MUTED}">평균</text>'
           f'<rect x="{lx}" y="{mt+18}" width="11" height="11" fill="{INK}"/>'
           f'<text x="{lx+15}" y="{mt+27}" font-size="11" fill="{MUTED}">중앙값</text>'
           f'<rect x="{lx}" y="{mt+34}" width="11" height="11" fill="{MUTED}"/>'
           f'<text x="{lx+15}" y="{mt+43}" font-size="11" fill="{MUTED}">최빈값</text>')
    lines = (
        f'<line x1="{px(mean):.1f}" y1="{mt}" x2="{px(mean):.1f}" y2="{baseline}" '
        f'stroke="{ACCENT}" stroke-width="1.8"/>'
        f'<line x1="{px(median):.1f}" y1="{mt}" x2="{px(median):.1f}" y2="{baseline}" '
        f'stroke="{INK}" stroke-width="1.5" stroke-dasharray="5 3"/>'
        f'<line x1="{px(mode):.1f}" y1="{mt}" x2="{px(mode):.1f}" y2="{baseline}" '
        f'stroke="{MUTED}" stroke-width="1.5" stroke-dasharray="2 3"/>'
    )
    axis = ""
    for t in range(0, xmax + 1, max(1, xmax // 10)):
        axis += (f'<text x="{px(t):.1f}" y="{baseline+16:.1f}" font-size="9" '
                 f'fill="{MUTED}" text-anchor="middle">{t}</text>')
    return (
        f'<svg viewBox="0 0 {W} {H}" style="width:100%;height:auto;'
        'font-family:-apple-system,sans-serif;">'
        f'<line x1="{ml}" y1="{baseline}" x2="{W-mr}" y2="{baseline}" stroke="#9ca3af"/>'
        f'{lines}{dots}{axis}{leg}'
        f'<text x="{ml+pw/2:.0f}" y="{H-4}" font-size="10" fill="{MUTED}" '
        'text-anchor="middle">독서 권수(권)</text>'
        '</svg>'
    )

app_ui = ui.page_sidebar(
    ui.sidebar(
        ui.input_slider("newbie", "전학 온 친구의 독서 권수", min=0, max=100, value=3, step=1),
        width=240,
    ),
    ui.card(
        ui.card_header("점그래프 — 세로선이 평균·중앙값·최빈값입니다"),
        ui.output_ui("dots"),
    ),
    ui.card(ui.output_ui("readout")),
    fillable=True,
)

def server(input, output, session):
    @render.ui
    def dots():
        data = READS + [input.newbie()]
        mean, median, mode = stats(data)
        return ui.HTML(dots_svg(data, mean, median, mode))

    @render.ui
    def readout():
        data = READS + [input.newbie()]
        mean, median, mode = stats(data)
        return ui.HTML(
            '<p class="hs-readline">'
            f"평균 = {mean:.2f}권 · 중앙값 = {median:.1f}권 · 최빈값 = {mode}권. "
            "슬라이더로 전학 온 친구의 독서 권수를 크게 키우면 평균만 크게 끌려가고, "
            "중앙값과 최빈값은 거의 움직이지 않습니다. "
            "이상값에 대한 민감도가 세 대푯값의 가장 큰 차이입니다.</p>"
        )

app = App(app_ui, server)

세 대푯값은 표준 라이브러리 statistics로 이렇게 구합니다(numpy 불필요).

import statistics
from collections import Counter

reads = [0, 1, 2, 2, 3, 3, 3, 3, 3, 4, 4, 5, 5, 6, 8]
data = reads + [100]                     # 극단값 하나 추가

print(statistics.mean(data))             # 평균 — 크게 끌려감
print(statistics.median(data))           # 중앙값 — 거의 그대로
print(Counter(data).most_common(1)[0][0])  # 최빈값 — 그대로 3

스스로 확인

평균은 자료 16개의 합이 97만큼 늘어 약 6권 넘게 커지지만, 중앙값은 크기순 배열의 한가운데 위치가 그대로라 거의 변하지 않고, 최빈값 3권은 전혀 변하지 않습니다. 값 하나가 평균을 얼마나 좌우할 수 있는지 눈으로 확인해 보세요.

연봉·집값·SNS 팔로워 수처럼 한쪽으로 길게 꼬리가 늘어진 자료입니다. 극단적으로 큰 값 몇 개가 평균을 끌어올려 “보통”과 멀어지게 하므로, 이런 자료의 대푯값으로는 중앙값이 더 적절합니다.