행사 전에 알려진 정보 정의하기

PythonIntermediate
지금 연습하기

소개

지역 스튜디오는 각 워크숍 7일 전에 재료를 준비합니다. 행사가 끝난 뒤의 입장 스캔으로 참석 인원을 예측하면 결과가 훌륭해 보일 수 있지만, 그 마감 시점에는 쓸모가 없습니다. 먼저 간단한 기준선을 만든 다음, 실제 사전 예측에서 사용할 수 있는 행과 열을 판단합니다.

Python 함수, 기본 터미널 입력, pandas 선택과 결합, 회귀 및 학습/테스트 분할의 기초 개념이 필요합니다. Python과 과학 계산 라이브러리는 준비되어 있습니다. 모든 표는 직접 만든 합성 교육 데이터입니다. 작은 표본으로 작업 흐름을 보여 주며 운영 환경의 예측을 보장하지 않습니다. 55분은 읽기와 일반적인 오류 수정을 포함한 저자의 추정 시간입니다.

간단한 기준선을 인원 단위로 측정하기

이 단계에서는 초기 중앙값으로 이후 두 워크숍을 예측하고 평균 절대 오차를 해석합니다.

WebIDE에서 Terminal → New Terminal을 엽니다. 편집기와 터미널은 파일을 공유합니다. cd는 이 터미널의 작업 디렉터리를 변경합니다. 새 터미널에는 별도의 cd가 필요합니다.

cd /home/labex/project/workshop-attendance

준비된 ./python 실행기는 이 과정의 Python 환경을 사용합니다. ./는 “현재 디렉터리의 실행 파일”을 뜻합니다. 과정 파일을 실행할 때는 시스템 Python 대신 이것을 사용하세요. cat은 텍스트 파일을 수정하지 않고 표시합니다.

cat warmup-early.csv

이 작은 연습의 초기 실제 참석 인원은 12, 18, 24입니다. 정렬했을 때 가운데 값인 중앙값은 18명입니다. 기준선은 행사 특성을 사용하지 않고 모든 이후 행사에 18명을 예측합니다.

cat warmup-later.csv

이후 실제 인원은 L-7이 20명, L-2가 27명입니다. 절대 오차는 abs(20 - 18) = 2와 abs(27 - 18) = 9입니다. **평균 절대 오차(MAE)**는 두 값의 평균인 (2 + 9) / 2 = 5.5명입니다. “평균 오차 5.5명”은 의미가 명확하지만, 단위 없는 “정확도”는 무엇을 측정했는지 숨깁니다. 이 연습은 다음에 사용할 전체 시즌 데이터와 별개입니다.

재사용 가능한 기준선 함수를 만듭니다. 셸 리디렉션 >는 baseline.py를 덮어씁니다. 따옴표로 감싼 here-document <<'PY'는 PY만 있는 줄까지의 내용을 셸 확장 없이 파일로 보냅니다. 마지막 PY를 포함하여 전체 블록을 붙여 넣으세요. WebIDE에서 같은 파일을 편집한 다음 File → Save로 저장해도 됩니다.

실행 프로그램은 pandas 표 두 개를 summarize에 전달합니다. itertuples()는 이름 있는 필드로 행을 순회하며, 딕셔너리는 각 이후 워크숍 ID를 그 오차와 연결합니다. float는 JSON 출력을 위해 pandas 수치를 일반 Python 숫자로 변환합니다.

cat > baseline.py <<'PY'
def summarize(early, later):
    baseline = float(early.actual_attendance.median())
    errors = {
        row.session_id: float(abs(row.actual_attendance - baseline))
        for row in later.itertuples()
    }
    return {
        "median_attendance": baseline,
        "absolute_errors": errors,
        "mae": sum(errors.values()) / len(errors),
    }
PY

준비된 파일 쓰기 보조 프로그램을 실행합니다. 인수 baseline이 이 단계를 선택하고 함수 결과를 output/baseline.json에 저장합니다. JSON은 CSV 행이 아니라 이름 있는 값과 객체를 저장합니다.

./python run.py baseline

표시 값에는 중앙값 18.0, 오차 2.0과 9.0, MAE 5.5가 있어야 합니다. 준비된 읽기 프로그램으로 저장한 산출물을 읽으세요. 이 프로그램은 누락된 결과를 다시 만들지 않습니다.

./python show_results.py baseline

이제 계획 문제와 같은 단위를 쓰는 오차 기준이 있습니다. 이후 모델도 같은 평가 행사에서 비교해야 합니다. 다른 표본에서 이 연습의 수치보다 나은 결과를 얻어도 공정한 비교는 아닙니다.

학습 시점과 예측 시점 분리하기

이 단계에서는 워크숍 ID로 결과를 연결하고 이후 날짜를 평가용으로 남깁니다.

같은 WebIDE 터미널의 /home/labex/project/workshop-attendance에서 계속합니다. 시즌 경계와 주요 표 세 개를 읽습니다.

cat season.json
cat history.csv
cat labels.csv
cat future.csv

데이터 사전은 다음과 같습니다.

필드 의미와 이용 가능한 시점 용도
session_id 고정된 워크숍 식별자. 파일 순서는 임의 행 연결용이며 모델 특성이 아님
event_date 예정된 행사 날짜, ISO YYYY-MM-DD 시간 분할 결정
booked_7d 행사 정확히 7일 전에 저장한 예약 스냅숏 허용된 수치 특성
duration_minutes 7일 전 마감에 이미 공지된 진행 시간 허용된 수치 특성
final_bookings 행사 시점의 최종 예약 수 예측 마감에 이용 불가
scanned_entries 행사 때 수집한 입장 스캔 예측 마감에 이용 불가
actual_attendance 행사 당일 종료까지 알려진 실제 결과 학습 목표 또는 홀드아웃 평가 레이블

CSV 수치 셀이 비어 있으면 예약 0이 아니라 정보 누락입니다. 허용된 특성마다 관측된 학습 값이 적어도 하나 있습니다. 각 표에서 ID는 고유하며, labels.csv는 과거 ID마다 정확히 하나의 결과를 의도적으로 다른 순서에 담습니다. future.csv에는 결과나 행사 이후 열이 없습니다. 잘못된 키, 학습 특성 열 전체의 결측, 순차적인 모델 갱신은 이 과정의 입력 범위 밖입니다.

모델은 한 번 학습하며 as_of_date, 즉 2026년 4월 1일 00:00 UTC에 고정합니다. 그 날짜 이전 행사만 학습 레이블을 제공할 수 있습니다. 이 시즌의 마지막 학습 행사는 3월 8일이므로 레이블이 이미 알려져 있습니다. 홀드아웃은 holdout_start인 4월 12일부터 시작합니다. 첫 7일 전 예측 마감은 학습 고정 이후인 4월 5일입니다. 제공된 과거 행사 중 이 간격에 있는 행사는 없습니다.

두 시계는 서로 다릅니다. 4월 1일은 학습된 모델을 고정하며, 각 이후 행사는 자신의 예측일에 자신의 booked_7d 스냅숏을 제공합니다. 4월 12일 행사의 4월 5일 스냅숏은 4월 5일에 사용하는 것이지 4월 1일에 알려졌다고 주장하지 않습니다. 이 파일 기반 실습은 저장한 스냅숏을 재현합니다. 이후 실제 레이블은 과거 성능을 평가하기 위해서만 공개합니다. future.csv의 5월 워크숍은 계속 레이블 없는 예측입니다.

미완성인 위치 기반 분할을 바꿉니다. 일대일 merge는 session_id로 레이블을 연결하며 첫 행끼리 대응한다고 가정하지 않습니다. pd.to_datetime은 날짜를, pd.Timestamp는 설정 경계를 해석하고, 불리언 마스크가 해당 행을 선택합니다. 날짜 다음 ID로 정렬하면 읽기 쉽지만 ID 철자가 기간을 결정하지는 않습니다. .copy()는 선택한 표의 독립 사본을 만듭니다.

cat > split.py <<'PY'
import pandas as pd


def split_events(history, labels, season):
    events = history.merge(labels, on="session_id", validate="one_to_one")
    dates = pd.to_datetime(events["event_date"])
    train = events.loc[dates < pd.Timestamp(season["as_of_date"])].copy()
    holdout = events.loc[dates >= pd.Timestamp(season["holdout_start"])].copy()
    return (
        train.sort_values(["event_date", "session_id"]),
        holdout.sort_values(["event_date", "session_id"]),
    )
PY
./python run.py split

학습 순서는 W-91, W-04, W-77, W-12, W-63, W-02, 홀드아웃 ID는 W-88, W-09여야 합니다. 불규칙한 ID는 알파벳순이나 파일의 마지막 두 행으로 분할을 정할 수 없는 이유를 보여 줍니다.

./python show_results.py split

W-88을 확인하세요. 같은 행 위치의 레이블이 아니라 일치하는 레이블의 실제 인원 46을 유지해야 합니다. 저장한 training.csv와 holdout.csv에는 검토를 위해 행사 이후 열도 남아 있습니다. 다음 단계에서 허용된 두 특성만 선택합니다. 숫자로 저장되어 있다고 예측에 쓸 수 있는 열이 되는 것은 아닙니다.

실수로 터미널을 닫았다면 Terminal → New Terminal을 열고 위 cd를 반복합니다. “No such file or directory”는 보통 잘못된 디렉터리나 파일명 오타를 뜻합니다. pwd로 현재 경로, ls로 파일 목록을 보고 작업 공간으로 돌아와 올바른 명령을 다시 실행하세요. 일반적인 경로 오류 때문에 랩을 재시작하지 마세요.

결측값 평균을 학습 데이터에서만 배우기

이 단계에서는 초기 워크숍에서 대체기 하나를 학습하고 이후 스냅숏에서 새로 학습하지 않고 재사용합니다.

WebIDE의 /home/labex/project/workshop-attendance에서 계속합니다. baseline.py, split.py, prepare.py와 자신의 output/ 파일을 편집할 수 있습니다. 제공된 모든 CSV/JSON 입력, python, run.py, coursekit.py, show_results.py, 모든 starter-*.py를 보존하세요. 초기 사본으로 모듈을 복구하거나 배운 완성본 전체를 다시 붙여 넣을 수 있습니다. 날짜, 행 순서, 수치가 바뀌어도 주요 함수는 재사용 가능해야 합니다.

결측값 대체는 누락된 특성을 학습한 값으로 바꿉니다. SimpleImputer(strategy="mean")은 각 열의 산술평균을 학습합니다. 여기서 booked_7d의 학습 관측값은 10, 20, 40, 25, 35로 평균은 26입니다. 알려진 여섯 진행 시간의 평균은 80분입니다. 모든 기간을 함께 사용하면 결과 레이블이 없어도 이후 워크숍이 학습 선택에 영향을 줍니다. 먼저 분할하고 나서 학습하세요.

coursekit이 제공하는 FEATURES는 정확히 ['booked_7d', 'duration_minutes']입니다. 이중 대괄호는 두 열을 이 순서로 선택합니다. fit_transform은 평균을 배우고 학습 행렬을 채우며, transform은 같은 평균을 홀드아웃과 미래 행렬에 재사용합니다. statistics_는 특성 순서대로 학습 평균을 보여 줍니다. 반환 배열은 입력 표의 행 순서를 유지하여 보조 프로그램이 ID를 보존하도록 합니다.

cat > prepare.py <<'PY'
from sklearn.impute import SimpleImputer
from coursekit import FEATURES


def prepare_features(train, holdout, future):
    imputer = SimpleImputer(strategy="mean")
    training_values = imputer.fit_transform(train[FEATURES])
    holdout_values = imputer.transform(holdout[FEATURES])
    future_values = imputer.transform(future[FEATURES])
    return training_values, holdout_values, future_values, imputer.statistics_
PY
./python run.py prepare

학습한 평균은 booked_7d: 26.0과 duration_minutes: 80.0이어야 합니다.

./python show_results.py prepare

holdout-features.csv에서 W-88은 예약 26과 관측된 150분을 쓰고, W-09는 예약 90을 유지하면서 80분을 씁니다. future-features.csv의 F-101에도 같은 학습 예약 평균을 사용합니다. 최종 예약, 스캔, 날짜, ID, 결과는 이 수치 행렬에 들어가지 않습니다. 원본 입력의 빈 셀은 그대로입니다.

초기에서 후기로 이어지는 실제 평가 경계를 준비했습니다. 미래가 과거처럼 행동한다는 증거는 아닙니다. 다음 독립 랩에서는 작은 모델을 학습하고 같은 레이블 있는 홀드아웃에서 연속 예측 오차와 학습 중앙값 기준선을 비교합니다. ID 연결과 수치 의미가 같다면 동등한 계산과 다른 출력 행 순서도 허용합니다. 수치 비교는 1e-8의 절대·상대 허용 오차를 사용합니다.

요약

재사용 가능한 중앙값 기준선을 만들고, MAE를 인원 단위로 측정하고, ID로 결과를 결합하여 이른 학습 날짜와 이후 평가 날짜를 분리했습니다. 7일 전 마감에 이용할 수 있는 정보만 선택하고, 결측값 대체 평균은 학습 행에서만 학습했습니다. 저장한 표는 원래 식별 관계를 유지하며, 입력 데이터도 다음 분석 단계를 위해 그대로 보존됩니다.