독서 요약과 통계 해석
80분 안팎
학습 목표
평균과 중앙값의 차이 및 표본의 한계를 설명합니다.
개념
왜 합계만으로는 충분하지 않은가
독서 쪽수 0·10·80의 합계는 90입니다. 그러나 한 기록에서 90쪽을 읽은 경우와 세 기록의 합계가 같아 분포를 알 수 없습니다. 평균은 전체 합계를 기록 건수로 나눈 수량이고 중앙값은 정렬한 값의 가운데입니다. 이번에는 두 통계를 나란히 보여 주며 어떤 질문에 답하는지 설명합니다. 통계 이름보다 먼저 관측 단위를 정합니다. 여기서 한 관측은 입력된 독서 기록 한 건이며 사람 한 명이나 책 한 권 전체와 같다고 가정하지 않습니다.
평균은 0을 포함한 전체 건수로 나눈다
[0, 10, 80]의 평균은 90 / 3인 30입니다. 0쪽도 관측값이므로 제외하면 분모가 2가 되어 다른 질문에 답합니다. / 연산은 실수 결과를 만들고 //는 정수 나눗셈이므로 소수 부분을 버릴 수 있습니다. [1, 2]의 평균은 1.5입니다. 실습 출력은 소수 첫째 자리로 통일하며 f-string의 .1f를 사용합니다. 이는 표시 자리수의 규칙이며 계산 중 관측값을 반올림해 바꾸는 정책이 아닙니다.
중앙값은 원본 대신 정렬한 복사본에서 구한다
sorted(values)는 작은 값부터 정렬한 새 목록을 만들며 입력 순서는 유지합니다. n이 홀수이면 인덱스 n // 2에 있는 가운데 값을 선택합니다. Python 리스트 인덱스는 0부터 시작합니다. n이 짝수이면 가운데 두 값 ordered[n // 2 - 1]과 ordered[n // 2]의 평균을 구합니다. [0, 10, 80, 100]의 중앙 두 값은 10과 80입니다. values.sort()로 원본을 바꾸면 뒤에서 입력 순서에 맞춘 보고서를 만들 때 문제가 될 수 있습니다.
빈 목록의 값 없음은 0과 다르다
합계는 빈 목록에서 0으로 정의하지만 평균은 분모가 0이고 중앙값은 가운데 값이 없습니다. 따라서 평균과 중앙값 함수는 None을 반환하고 화면에서는 NA를 출력합니다. None은 숫자 0과 다른 상태입니다. [0]은 실제 관측 한 건이므로 평균과 중앙값 모두 0.0입니다. 빈 목록에도 0.0을 넣으면 실제로 읽은 쪽수가 0인 기록과 관측 자체가 없는 상황을 혼동합니다. 분모를 사용하거나 가운데 인덱스를 읽기 전에 빈 경우를 처리합니다.
값의 분포가 설명을 바꾼다
[0, 10, 80]의 평균은 30이고 중앙값은 10입니다. 큰 관측값 80이 합계와 평균을 끌어올리지만 가운데 순서는 10입니다. 어느 값이 더 정답인지 고르는 문제가 아니라 평균은 전체 수량을 건수로 나눈 값, 중앙값은 순서의 중앙이라는 차이를 보고합니다. [0, 10, 800]으로 바꾸면 평균은 크게 달라지고 중앙값은 그대로입니다. 높은 값이 있다는 이유만으로 오류라고 단정하거나 지우지 않습니다. 기록 방식과 실제 읽은 수량부터 확인합니다.
개인 기록의 해석 범위를 쓴다
이 입력은 한 학습자가 남긴 기록이며 전체 독자를 무작위로 뽑은 표본이 아닙니다. 기록을 빼먹었거나 특정 날에만 적었으면 개인의 일상 전체를 대표한다고 말하기도 어렵습니다. 요약에는 관측 건수, 기록 단위, 기록 범위, 빠진 값의 정책을 함께 적습니다. 0은 읽은 수량으로 포함하고 누락 값은 0으로 바꾸지 않습니다. 미션의 docs/summary.md에는 해당 입력 목록의 기록만 요약하며 전체 독자의 평균이나 인과관계를 주장하지 않는다고 적습니다. 그래야 숫자가 정확해도 설명이 과장되는 일을 막습니다.
홀수와 짝수의 가운데 찾기
정렬된 [0, 10, 80]에서 길이는 3이고 middle은 3 // 2인 1입니다. ordered[1]은 10이므로 중앙값은 10입니다. [0, 10, 80, 100]에서는 middle이 2이고 ordered[1]과 ordered[2]를 더해 2로 나눈 45가 중앙값입니다. 길이 하나의 목록은 그 값 자체가 중앙값이며 길이 두 개에서는 두 값의 평균입니다. 인덱스는 값의 크기가 아니라 목록 안의 위치를 뜻합니다. 원래 목록의 가운데 원소를 바로 고르면 정렬되지 않은 입력에서 중앙값을 구하지 못합니다.
반환값과 표시값을 구분하기
첫 따라하기는 함수가 반환한 값을 그대로 print하므로 30.0 10이 나옵니다. 홀수 중앙값은 원래 정수 원소를 반환하고 평균은 나눗셈 결과이기 때문입니다. 빈 목록 예제에서도 반환값을 관찰하므로 None None이 보입니다. 최종 실습은 표시부에서 값 없음을 NA로 바꾸고 숫자는 .1f로 맞춥니다. 따라서 세 기록의 제출 출력은 30.0 10.0이고 빈 목록은 NA NA입니다. 함수에서 문자열 NA를 반환하면 수치 계산과 표시가 섞입니다. 계산 결과를 유지한 채 마지막 출력 경계에서만 형식을 적용합니다.
작은 자료로 통계를 검증하기
[1, 2]는 평균과 중앙값이 모두 1.5여야 합니다. 정수 나눗셈을 사용하면 1이 되어 소수 결과를 잃으므로 이 자료가 유용합니다. [5, 5, 5]는 둘 다 5이지만 관측이 세 건이라는 사실은 그대로입니다. 같은 값을 하나로 줄이면 건수와 분포의 의미가 달라집니다. [100, 0, 80, 10]은 평균 47.5, 중앙값 45이며 함수 호출 뒤에도 원본 순서는 같습니다. 기대 결과뿐 아니라 계산에 사용한 자료를 다시 살펴 입력 변경이 없었는지 확인합니다. 빈 경우에는 나눗셈과 인덱스 접근이 모두 생략되어야 합니다.
대표값만으로 알 수 없는 것
평균과 중앙값이 같다고 모든 기록의 쪽수가 같은 것은 아닙니다. [0, 10, 20]은 둘 다 10이지만 관측값은 서로 다릅니다. 대표값 두 개만으로 가장 큰 값이나 가장 작은 값, 기록별 시간 흐름을 복원할 수 없습니다. 보고서에는 어떤 자료를 요약했는지 함께 적어야 합니다. 예제의 큰 값은 평균에 영향을 주지만 그것만으로 독서 습관이 좋아졌다는 결론은 나오지 않습니다. 날짜나 기록 기준이 없으면 변화 방향도 알 수 없습니다. 계산 가능한 수량과 자료가 뒷받침하지 않는 해석을 구별합니다.
미션 보고서에 남길 설명
미션 출력은 SUMMARY 뒤에 건수, 총쪽수, 총금액, 완료건수, 평균쪽수, 중앙쪽수를 순서대로 붙입니다. 통계 함수는 제공된 기록의 pages 필드만 읽으며 금액을 평균 쪽수 계산에 넣지 않습니다. docs/summary.md에는 입력된 독서 기록 한 건이 관측 단위라고 쓰고 0쪽을 포함한다는 기준을 밝힙니다. 관측이 없을 때 NA라는 표시는 읽은 쪽수가 0이라는 뜻이 아닙니다. 개인이 선택해 남긴 목록이므로 전체 독자의 대표 표본이라고 주장할 근거도 없습니다. 숫자 출력과 해석 문서를 함께 확인해 계산의 정확성과 설명의 범위를 모두 점검합니다.
따라하기
평균과 중앙값
코드를 실행해 반환값을 확인합니다.
def mean_pages(values):
if not values:
return None
return sum(values) / len(values)
def median_pages(values):
if not values:
return None
ordered = sorted(values)
middle = len(ordered) // 2
if len(ordered) % 2 == 1:
return ordered[middle]
return (ordered[middle - 1] + ordered[middle]) / 2
values = [0, 10, 80]
print(mean_pages(values), median_pages(values))
실행 결과
30.0 10
짝수 건수와 원본 보존
코드를 실행해 반환값을 확인합니다.
def mean_pages(values):
if not values:
return None
return sum(values) / len(values)
def median_pages(values):
if not values:
return None
ordered = sorted(values)
middle = len(ordered) // 2
if len(ordered) % 2 == 1:
return ordered[middle]
return (ordered[middle - 1] + ordered[middle]) / 2
values = [100, 0, 80, 10]
print(mean_pages(values), median_pages(values))
print(values)
실행 결과
47.5 45.0 [100, 0, 80, 10]
빈 목록과 0 한 건
코드를 실행해 반환값을 확인합니다.
def mean_pages(values):
if not values:
return None
return sum(values) / len(values)
def median_pages(values):
if not values:
return None
ordered = sorted(values)
middle = len(ordered) // 2
if len(ordered) % 2 == 1:
return ordered[middle]
return (ordered[middle - 1] + ordered[middle]) / 2
print(mean_pages([]), median_pages([]))
print(mean_pages([0]), median_pages([0]))
실행 결과
None None 0.0 0
표시 형식
개수 3과 0, 10, 80을 각각 한 줄씩 입력합니다.
def mean_pages(values):
if not values:
return None
return sum(values) / len(values)
def median_pages(values):
if not values:
return None
ordered = sorted(values)
middle = len(ordered) // 2
if len(ordered) % 2 == 1:
return ordered[middle]
return (ordered[middle - 1] + ordered[middle]) / 2
n = int(input())
values = []
for index in range(n):
values.append(int(input()))
average = mean_pages(values)
median = median_pages(values)
if average is None:
print("NA NA")
else:
print(f"{average:.1f} {median:.1f}")
실행 결과
30.0 10.0
큰 값의 영향
코드를 실행해 반환값을 확인합니다.
def mean_pages(values):
if not values:
return None
return sum(values) / len(values)
def median_pages(values):
if not values:
return None
ordered = sorted(values)
middle = len(ordered) // 2
if len(ordered) % 2 == 1:
return ordered[middle]
return (ordered[middle - 1] + ordered[middle]) / 2
for values in [[0, 10, 80], [0, 10, 800]]:
print(mean_pages(values), median_pages(values))
실행 결과
30.0 10 270.0 10
확인 문제
실습
첫 줄 n 다음 n줄에 0 이상 정수 쪽수가 옵니다. mean_pages와 median_pages를 완성해 평균과 중앙값을 각각 소수 첫째 자리, 공백 구분으로 출력합니다. 빈 목록은 NA NA입니다. 입력 순서를 보존합니다. 제출 전 0·10·80의 평균과 중앙값이 다른 이유와 이 기록을 전체 독자에게 일반화할 수 없는 이유를 두 문장으로 적습니다. 이 설명은 사람이 검토하며 자동 채점은 수치 출력을 검사합니다.
모범 답안
def mean_pages(values):
if not values:
return None
return sum(values) / len(values)
def median_pages(values):
if not values:
return None
ordered = sorted(values)
middle = len(ordered) // 2
if len(ordered) % 2 == 1:
return ordered[middle]
return (ordered[middle - 1] + ordered[middle]) / 2
n = int(input())
values = []
for index in range(n):
values.append(int(input()))
average = mean_pages(values)
median = median_pages(values)
if average is None:
print("NA NA")
else:
print(f"{average:.1f} {median:.1f}")
더 읽기
면접 질문
- 평균과 중앙값은 큰 관측값의 영향을 어떻게 다르게 받나요?
- 빈 목록과 0쪽 기록 한 건을 왜 구분해야 하나요?
- 개인 독서 기록의 요약을 전체 독자에게 일반화하기 어려운 이유는 무엇인가요?