Devin.KR

평균·중앙값과 표본 한계

130분 안팎

학습 목표

분포와 적은 표본의 해석 범위를 설명합니다.

개념

빠른 성공만 보여 주면 실패가 사라집니다

성공 시간은 과업을 독립적으로 완료한 시도에만 정의했습니다. before에서 성공 1건이 빠르고 나머지가 실패한 경우 평균 시간만 보면 제품이 괜찮아 보일 수 있습니다. 제품 담당은 완료율과 시간의 표본 수를 먼저 같이 읽습니다. 이번 레슨은 성공 시간 목록의 중심과 범위를 계산하며 앞 SQL의 전체 시도 수와 다른 분모라는 사실을 설명합니다. 시간 요약은 얼마나 빨랐는지 말하고 완료율은 얼마나 자주 목적을 달성했는지 말하므로 한 숫자로 서로를 대신하지 않습니다.

평균은 총합을 개수로 나눕니다

독립 성공 시간 20·30·40·150초의 합은 240초이고 건수는 4건이므로 평균은 60초입니다. 평균은 모든 시간을 반영하므로 긴 시도의 영향이 큽니다. 관찰에서 오래 걸린 이유를 찾을 때 유용하지만 평균 60초를 모두가 60초에 끝냈다는 뜻으로 읽으면 틀립니다. 이 숫자들은 통계 원리를 익히는 별도 예시이며 90초 완료 계약을 적용한 미션 자료는 아닙니다. 실제 미션 성공 시간은 90초 이하인지 검사하므로 예시의 150초를 그대로 넣지 않습니다.

중앙값은 정렬 뒤 가운데를 읽습니다

20·30·40·150을 정렬하면 짝수 개이므로 가운데 30과 40의 평균 35초가 중앙값입니다. 홀수 개이면 정렬한 목록에서 가운데 한 값을 사용합니다. 입력 순서의 가운데를 고르면 시도 수집 순서에 따라 결과가 바뀝니다. 정렬은 중앙값 계산의 일부이며 원자료 기록을 지워 재배열하는 것이 아닙니다. Python의 statistics.median을 사용하면 짝수·홀수 규칙을 직접 분기하지 않아도 됩니다. 어떤 통계량을 골랐는지 보고서에 이름과 단위를 남깁니다.

최솟값과 최댓값으로 꼬리를 확인합니다

같은 평균이라도 60·60·60·60과 20·30·40·150의 분포는 다릅니다. 최솟값과 최댓값을 함께 보면 긴 사례가 있다는 사실을 전달할 수 있습니다. 범위만으로 중간 값들의 모양을 모두 알 수는 없으므로 표본이 작다면 시간 목록도 같이 읽습니다. 미션처럼 성공이 2건과 3건이면 복잡한 그래프보다 각 값과 건수를 보여 주는 편이 원자료 확인에 좋습니다. 요약은 자료를 줄이는 도구이지 관찰과 기록을 대체하는 진실 하나가 아닙니다.

큰 값을 임의로 버리지 않습니다

150초가 평균을 끌어올렸다고 삭제하면 느린 사용자의 경험이 보고에서 사라집니다. 측정기가 잘못 기록했는지, 중단 뒤 재개했는지, 실제로 길게 걸렸는지 원자료를 확인합니다. 미리 정한 제외 조건에 맞는 오류라면 제외 이유와 ID를 남깁니다. 그렇지 않으면 큰 값도 포함하고 평균·중앙값 차이를 설명합니다. 원하는 개선 결론에 맞추어 after의 긴 값만 제거하면 비교 조건이 달라집니다. 이상값 후보를 찾는 일과 삭제 결정을 하는 일을 구별합니다.

빈 목록과 한 건을 명세로 처리합니다

시간이 하나도 없으면 평균·중앙값·최소·최대를 계산할 수 없습니다. 출력은 0 NA NA NA NA로 정해 산출 불가를 표시합니다. 이 0은 표본 수이며 0초 평균이 아닙니다. 한 건 28초가 있으면 네 시간 요약은 모두 28.0입니다. 한 건에서 요약이 같은 것은 계산 규칙의 결과이고 안정된 성능의 증거는 아닙니다. 통계 함수를 호출하기 전에 목록이 비었는지 확인하면 빈 자료에서 StatisticsError가 발생하는 문제를 피할 수 있습니다.

입력은 초 단위 숫자 배열입니다

브라우저 입력은 JSON 배열이며 0 이상의 유한한 정수·실수를 허용합니다. 0초는 이 통계 연습의 경계값이고 측정 해상도 해석은 별도로 남깁니다. 문자열 숫자, 음수, null, true·false, 배열 아닌 객체는 ERROR 한 줄입니다. Python에서 bool은 숫자와 비슷하게 동작하므로 type(v)가 int 또는 float인지 검사합니다. NaN과 Infinity도 유한 값이 아니므로 거절합니다. 입력 오류는 과업 실패를 뜻하지 않으며 계산 가능한 자료를 준비하지 못했다는 신호입니다.

계산과 출력 형식을 분리합니다

처음 값에서 소수를 버리지 않고 원래 숫자로 평균과 중앙값을 계산합니다. 출력할 때만 소수 첫째 자리로 맞춥니다. 출력 순서는 N MEAN MEDIAN MIN MAX이며 공백으로 구분한 한 행입니다. 건수는 정수이고 시간 네 값은 0.0 같은 한 자리 형식입니다. 예를 들어 10·11초는 평균과 중앙값 10.5초입니다. 중간 단계에서 정수로 반올림하면 최종 결과가 달라질 수 있으므로 보고용 표시와 계산용 값의 역할을 나눕니다.

표본 수가 작으면 숫자의 변화도 큽니다

성공이 두 건뿐인 자료에서 한 건이 추가되면 평균과 중앙값이 크게 달라질 수 있습니다. 작은 표본은 특정 사례를 자세히 보는 데 유용하지만 전체 사용자 경험의 안정된 추정으로 넓히기는 어렵습니다. 이번 미션 이벤트는 before 성공 2건과 after 성공 3건을 흉내 냅니다. 연습에서 평균 차이를 계산하더라도 실제 제품 성과나 통계적 유의성을 검증한 것은 아닙니다. 표본 수와 원자료 범위를 적어 동료가 숫자의 정밀한 표시를 근거의 강도로 오해하지 않게 합니다.

성공한 집단이 달라질 수 있습니다

화면 개선 뒤 이전에는 실패하던 사람이 성공 집단에 들어오면 성공 시간 평균이 늘 수 있습니다. 이때 시간 상승만 보고 개선 실패라고 결론 내리면 더 많은 사람이 목적을 달성한 변화를 놓칩니다. 반대로 어려운 사용자가 빠지고 익숙한 사람만 남아 평균이 내려갈 수도 있습니다. 전후 완료율·성공 시간 표본 수·참여 조건을 함께 읽습니다. 같은 이름의 평균도 성공한 사람의 구성 변화에 영향을 받을 수 있다는 점을 보고서의 한계로 남깁니다.

관찰 시각은 완료 시간이 아닙니다

m07의 O03은 18초에 내역을 읽고 상태를 설명한 관찰이고 O05는 도움 뒤 62초 사건입니다. 기록 목적은 행동 순서이며 별도 시작·종료 측정 계약으로 얻은 duration_seconds가 아닙니다. 일부 사건을 성공 시간으로 선택하면 다른 세션에 대응 사건이 없는 문제가 생깁니다. 앞 자료는 완료·도움·발화 근거로 보존하고 시간 평균을 새로 만들어 넣지 않습니다. 미션 시간 통계는 events.sql의 독립 성공 행만 사용합니다. 서로 다른 필드가 숫자라는 이유로 같은 단위의 측정이라고 가정하지 않습니다.

오류를 읽고 해석 범위를 말합니다

JSONDecodeError는 배열 괄호와 쉼표를 먼저 확인하고 ERROR는 허용하지 않은 값이 들어 있는지 읽습니다. solution은 입력 검증 뒤 statistics.mean·median과 min·max를 실행합니다. 테스트가 맞아도 “모든 신청자가 빨라졌습니다”라는 문장은 계산 범위를 넘어섭니다. “제공 성공 시간 3건의 평균은 이 값이며 실패 시간은 포함하지 않았습니다”처럼 대상과 빠진 자료를 말합니다. 평균과 중앙값을 고르는 일반 원리는 더 읽기의 통계 장에서 확장하고 여기서는 소수 표본을 정직하게 보고하는 연습으로 마칩니다.

따라하기

평균과 중앙값의 차이를 봅니다

다음 독립 예제를 실행하고 결과의 대상과 단위를 확인합니다.

import statistics
values = [20, 30, 40, 150]
print(len(values), f'{statistics.mean(values):.1f}', f'{statistics.median(values):.1f}', min(values), max(values))

실행 결과

4 60.0 35.0 20 150

짝수와 홀수의 가운데를 확인합니다

다음 독립 예제를 실행하고 결과의 대상과 단위를 확인합니다.

import statistics
for values in [[11, 10], [40, 20, 30]]:
    print(sorted(values), f'{statistics.median(values):.1f}')

실행 결과

[10, 11] 10.5
[20, 30, 40] 30.0

빈 목록을 계산 전에 처리합니다

다음 독립 예제를 실행하고 결과의 대상과 단위를 확인합니다.

import json
import math
import statistics
import sys

try:
    raw = sys.stdin.read()
    values = json.loads(raw) if raw.strip() else []
    if not isinstance(values, list):
        raise ValueError('array required')
    if any(type(v) not in (int, float) or not math.isfinite(v) or v < 0 for v in values):
        raise ValueError('finite nonnegative numbers required')
    if not values:
        print('0 NA NA NA NA')
    else:
        summaries = (statistics.mean(values), statistics.median(values), min(values), max(values))
        print(len(values), *(f'{v:.1f}' for v in summaries))
except (ValueError, OverflowError):
    print('ERROR')

실행 결과

0 NA NA NA NA

성공 시간 입력을 실행합니다

다음 독립 예제를 실행하고 결과의 대상과 단위를 확인합니다.

import json
import math
import statistics
import sys

try:
    raw = sys.stdin.read()
    values = json.loads(raw) if raw.strip() else []
    if not isinstance(values, list):
        raise ValueError('array required')
    if any(type(v) not in (int, float) or not math.isfinite(v) or v < 0 for v in values):
        raise ValueError('finite nonnegative numbers required')
    if not values:
        print('0 NA NA NA NA')
    else:
        summaries = (statistics.mean(values), statistics.median(values), min(values), max(values))
        print(len(values), *(f'{v:.1f}' for v in summaries))
except (ValueError, OverflowError):
    print('ERROR')

실행 결과

3 30.0 30.0 20.0 40.0

확인 문제

실습

표준 입력 JSON 배열에서 초 단위 시간의 N MEAN MEDIAN MIN MAX를 공백으로 구분해 출력합니다. N은 정수, 네 시간은 소수 첫째 자리입니다. 빈 입력·빈 배열은 0 NA NA NA NA입니다. 0 이상의 유한 숫자만 허용하며 음수·null·불리언·문자열·배열 아닌 값·JSON 문법 오류·NaN·Infinity는 ERROR입니다. 이 함수에는 성공 시간만 전달하며 실패 시간을 임의 생성하지 않습니다.

모범 답안
import json
import math
import statistics
import sys

try:
    raw = sys.stdin.read()
    values = json.loads(raw) if raw.strip() else []
    if not isinstance(values, list):
        raise ValueError('array required')
    if any(type(v) not in (int, float) or not math.isfinite(v) or v < 0 for v in values):
        raise ValueError('finite nonnegative numbers required')
    if not values:
        print('0 NA NA NA NA')
    else:
        summaries = (statistics.mean(values), statistics.median(values), min(values), max(values))
        print(len(values), *(f'{v:.1f}' for v in summaries))
except (ValueError, OverflowError):
    print('ERROR')

더 읽기

면접 질문

  • 기능 개선 여부를 확인할 지표를 정하는 방법을 설명해 주시면 됩니다.