대푯값
개념
자료 전체를 하나의 수로 요약한 값이 대푯값입니다. 가장 많이 쓰는 세 가지는 모든 값을 더해 개수로 나눈 평균, 크기순으로 늘어놓았을 때 한가운데 값인 중앙값, 가장 자주 나타나는 값인 최빈값입니다.
\[\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스스로 확인
힌트문제 1 — 슬라이더를 3에서 100으로 올리면 세 대푯값은 각각 얼마나 변하는가?
평균은 자료 16개의 합이 97만큼 늘어 약 6권 넘게 커지지만, 중앙값은 크기순 배열의 한가운데 위치가 그대로라 거의 변하지 않고, 최빈값 3권은 전혀 변하지 않습니다. 값 하나가 평균을 얼마나 좌우할 수 있는지 눈으로 확인해 보세요.
힌트문제 2 — 어떤 자료에는 평균보다 중앙값이 더 좋은 대푯값인가?
연봉·집값·SNS 팔로워 수처럼 한쪽으로 길게 꼬리가 늘어진 자료입니다. 극단적으로 큰 값 몇 개가 평균을 끌어올려 “보통”과 멀어지게 하므로, 이런 자료의 대푯값으로는 중앙값이 더 적절합니다.