12장 공정 데이터 다루기
이번 주의 실전: 시험2(90분 현장 실기 리허설)
다음 상황을 생각해 보자. 학부연구생으로 들어간 실험실에서 첫 임무를 받았다. 파일럿 설비의 차압식 유량계를 보정한 열두 번의 측정 기록이 담긴 CSV 파일을 건네받으며 "보정 상수 구해서 내일까지 보고해 주세요"라는 말을 들었다. 스프레드시트를 열어 평균부터 내 보니 어딘가 이상하다. 오후 측정의 평균 유량이 오전의 다섯 배다. 설비가 오후에 다섯 배 힘차게 돌았을 리는 없다.
데이터 어딘가에 거짓말이 숨어 있고, 그것을 찾아내는 것은 감이 아니라 절차다. 이 장은 그 절차를 만든다. 표를 읽는 pandas, 선을 긋는 최소자승 회귀, 거짓말을 찾는 이상치 탐지다. 그리고 이번 주 실습은 이 셋을 90분 안에 조립하는 시험2다.
이 장에서 다루는 것
- 표 형태의 공정 데이터를 pandas로 읽고 요약하는 기본기를 익힌다
- 최소자승 회귀의 직관을 잔차로 세우고
np.polyfit코드와 잇는다 - 평균과 기울기를 무너뜨리는 이상치를 탐지하고 조사하는 절차를 세운다
- 시험2(20점, AI 필수, 90분 현장 실기)의 규정과 시간 배분 전략을 정리한다
학습목표
이 장을 마치면 다음을 할 수 있다.
- CSV 공정 데이터를 pandas로 읽고
head·info·describe세 명령으로 상태를 점검할 수 있다. - 조건 선택과 그룹 집계로 데이터에서 원하는 부분만 추릴 수 있다.
- 최소자승 회귀가 무엇을 최소화하는지 잔차로 설명하고,
np.polyfit으로 직선을 피팅할 수 있다. - 요약 통계와 잔차를 근거로 이상치 후보를 찾아내고, 지우기 전에 조사·기록하는 절차를 적용할 수 있다.
- 시험2 규정(빈 repo 첫 커밋, 90분 내 커밋 4회 이상)에 맞는 시간 배분 계획을 세울 수 있다.
이번 주 실습 2시간은 도구 제작이 아니라 시험2(20점, AI 필수, 90분 현장 실기) 그 자체다. 형식·인프라·규정이 최종 시험과 같은 3/4 스케일 리허설이므로, 이 장의 12.1~12.3절이 시험 범위의 마지막 조각이고 실습 12가 시험장 매뉴얼이다.
12.1 표를 다루는 근육: pandas
5장부터 지금까지 만든 도구들은 입력이 숫자 몇 개였다. 온도 하나, 압력 하나, 조성 두어 개. 그러나 도입부의 보정 기록처럼 현장에서 만나는 데이터는 행 단위로 쌓이는 표다. 표를 다루는 파이썬의 표준 도구가 pandas 라이브러리다.
12.1.1 읽기: 파일에서 표로
도입부의 측정 기록을 지면과 동일하게 재현하기 위해, 데이터 파일부터 코드로 만들어 두자. 실제 현장에서는 계측 시스템이 이런 파일을 내보낸다. (이 데이터는 교재용으로 만든 가상 측정값이다.)
from pathlib import Path
csv_text = """run,session,dP_kPa,Q_m3h
1,am,2,1.19
2,am,4,1.72
3,am,6,2.06
4,am,8,2.43
5,am,10,2.67
6,am,14,3.19
7,pm,18,3.58
8,pm,22,4.01
9,pm,26,43.3
10,pm,30,4.64
11,pm,36,
12,pm,40,5.40
"""
Path("flow_calib.csv").write_text(csv_text, encoding="utf-8")
열 이름 dP_kPa(차압, kPa)와 Q_m3h(유량, m³/h)에 단위를 박아 넣었다. 여섯 달 뒤 파일만 열어 본 사람도 단위를 알 수 있다. 단위 체크 루틴의 데이터 버전이다. 이제 읽어 보자.
import pandas as pd
df = pd.read_csv("flow_calib.csv") # CSV → 표
print(df.head()) # 앞 5행만 표시
실행 결과는 다음과 같다.
run session dP_kPa Q_m3h
0 1 am 2 1.19
1 2 am 4 1.72
2 3 am 6 2.06
3 4 am 8 2.43
4 5 am 10 2.67
지금 화면에 뜬 것처럼 행과 열로 이루어진 표를 담는 pandas의 자료구조를 데이터프레임(DataFrame)이라 한다. 행 하나가 측정 한 번, 열 하나가 변수 하나다. 리스트가 값의 나열이라면 데이터프레임은 이름표가 달린 열들의 묶음이고, 그 이름표로 열을 꺼내 쓴다.
12.1.2 점검 3연타: head·info·describe
head()가 보여 준 다섯 행이 전부가 아니다. 파일을 받으면 세 가지를 연달아 확인한다.
df.info() # 열별 자료형과 채워진 칸 수
print(df["Q_m3h"].describe()) # 유량 열의 통계 요약
print(df.isna().sum()) # 열별 결측 칸 개수
info()의 출력에서 자료형과 채워진 칸 수를 읽을 수 있다.
RangeIndex: 12 entries, 0 to 11
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 run 12 non-null int64
1 session 12 non-null str
2 dP_kPa 12 non-null int64
3 Q_m3h 11 non-null float64
…
Q_m3h 열만 11 non-null이다. 열두 행 중 한 칸이 비어 있다는 뜻이고, 이렇게 기록되지 않은 칸을 결측값(missing value)이라 한다. pandas는 결측을 NaN으로 표시하며, df.isna().sum()으로 열별 개수를 셀 수 있다.
이어서 describe()의 출력을 보자.
count 11.000000
mean 6.744545
std 12.189858
min 1.190000
25% 2.245000
50% 3.190000
75% 4.325000
max 43.300000
max 43.3이 눈에 걸린다. 75% 지점이 4.33인데 최댓값은 그 열 배다. 아직 아무것도 지우지 않는다. 지금은 표시만 해 두고, 처분은 12.3절에서 절차대로 결정한다.
12.1.3 선택과 집계
표 전체가 아니라 일부만 필요할 때가 더 많다. 대괄호에 열 이름을 넣으면 열 하나를 얻고, 조건식을 넣으면 조건을 만족하는 행만 남는다.
q = df["Q_m3h"] # 열 하나 선택 (m³/h)
high = df[df["dP_kPa"] > 20] # 차압 20 kPa 초과 행만
print(len(high)) # 몇 행이 남았는가
print(df.groupby("session")["Q_m3h"].mean()) # 세션별 평균 유량
df["dP_kPa"] > 20은 행마다 참/거짓을 매기고, 그 결과를 다시 대괄호에 넣으면 참인 행 다섯 개만 걸러진다. 마지막 줄처럼 데이터를 그룹으로 나눠 그룹별 요약 통계를 내는 조작을 집계(aggregation)라 한다. 출력은 다음과 같다.
session
am 2.210
pm 12.186
Name: Q_m3h, dtype: float64
도입부의 위화감이 숫자로 나타났다. 오전 평균 2.21, 오후 평균 12.19다. 설비가 오후에 다섯 배 힘차게 돈 것이 아니라, 43.3 한 점이 오후 평균을 끌어올렸다. 평균은 이상치 한 점에 이렇게 약하다.
이 정도 점검은 AI에게 통째로 맡겨도 된다. 다만 결과를 그대로 믿는 대신, 눈으로 센 값과 대조한다.
flow_calib.csv를 pandas로 읽어서 크기, 열별 자료형, 결측값 개수, 숫자 열의 기초 통계를 한 번에 요약해 주는 파이썬 함수를 짜 줘. 주석은 한국어로 달아 줘.
import pandas as pd
def summarize(path):
"""CSV 파일을 읽어 상태 요약을 출력하고 표를 반환한다."""
df = pd.read_csv(path)
print("크기:", df.shape) # (행 수, 열 수)
print(df.dtypes) # 열별 자료형
print(df.isna().sum()) # 열별 결측 개수
print(df.describe()) # 숫자 열 통계 요약
return df
df = summarize("flow_calib.csv")
✔ 실행 결과 크기 (12, 4), 결측 Q_m3h 1건, max 43.3으로, 파일을 직접 열어 센 값과 일치한다. describe()가 문자열 열 session을 빼고 숫자 열만 요약하는 것도 확인했다. 반환값을 df로 받아 두어 이후 분석에 재사용한다.
스스로 점검
df[df["dP_kPa"] > 20]이 반환하는 것은 행의 부분집합인가, 열의 부분집합인가?info()와describe()중 결측값 개수를 알 수 있는 것은 어느 쪽인가?- 출력 예측:
df.groupby("session")["dP_kPa"].max()는 무엇을 출력하는가?
12.2 최소자승 회귀: 가장 덜 틀리는 직선
12.2.1 잔차와 최소자승의 원리
측정점이 두 개면 자를 대고 이으면 된다. 그러나 열한 개의 점이 정확히 한 직선 위에 놓이는 일은 없다. 그렇다면 "가장 덜 틀리는" 직선은 무엇인가.
직선 y = ax + b를 그었을 때, i번째 측정값 yi와 직선이 예측한 값의 차이를 잔차(residual)라 한다: ri = yi − (axi + b). 잔차를 그냥 더하면 양수와 음수가 상쇄되어 엉터리 직선도 합이 0에 가까울 수 있다. 그래서 잔차를 제곱해 더한 값을 기준으로 삼는다.
S(a, b) = Σi [yi − (axi + b)]² (12.1)
식 (12.1)의 S를 최소로 만드는 a와 b를 택하는 방법을 최소자승법(method of least squares)이라 한다. 제곱은 두 가지 일을 한다. 부호를 없애고, 크게 벗어난 점에 제곱으로 커지는 벌점을 준다. 잔차 2의 벌점은 4지만 잔차 10의 벌점은 100이다. 이 두 번째 성질이 어떤 부작용을 일으키는지 12.3절에서 다시 만난다.
S를 a와 b로 각각 미분해 0으로 놓으면 닫힌 형태의 해가 나온다. 그 계산은 np.polyfit이 대신하므로, 사람이 할 일은 결과가 물리적으로 말이 되는지 검증하는 것이다.
12.2.2 np.polyfit으로 피팅하기
보정 대상 유량계의 사양서는 유량과 차압 사이에 다음 관계를 명시한다고 하자.
Q = k √(ΔP) (12.2)
√(ΔP)를 새 변수 x로 놓으면 식 (12.2)는 Q = kx가 되고, 원점을 지나는 직선이다. 곡선 관계를 직선으로 펴는 이런 변수 변환은 회귀의 기본 기술이다. 사양서의 설계값은 k = 0.85 m³ h−1 kPa−1/2이고, 우리 일은 측정 데이터로 이 값을 확인하는 것이다.
예제 12.2-1 보정 상수 피팅: 검증이 먼저 경고한다
flow_calib.csv의 유효한 11개 측정으로 식 (12.2)의 보정 상수 k와 절편을 구하고, 결정계수와 함께 보고하라. 12.1절에서 표시해 둔 43.3은 아직 건드리지 않는다. 모든 데이터를 넣었을 때 무슨 일이 생기는지 보기 위해서다.
결측 행의 처리와 √ 변환은 판단이 필요하므로 프롬프트에 직접 명시하고, 피팅 코드는 AI에게 맡긴다. 단위와 반환값 형식까지 지정한다(사이클 ①~③).
flow_calib.csv의 dP_kPa와 Q_m3h로 Q = k·√ΔP의 보정 상수를 구하고 싶어. √ΔP를 x로 놓고 1차 다항식을 피팅해서 기울기, 절편, R²를 반환하는 함수를 만들어 줘. 결측 행은 빼고, 주석은 한국어로, 단위도 주석에 적어 줘.
import numpy as np
import pandas as pd
def fit_calibration(df):
"""√ΔP에 대한 Q의 1차 피팅. (기울기, 절편, R²)를 반환한다."""
d = df.dropna(subset=["Q_m3h"]) # 결측 행 제외
x = np.sqrt(d["dP_kPa"].to_numpy()) # kPa^0.5
y = d["Q_m3h"].to_numpy() # m³/h
k, b = np.polyfit(x, y, 1) # 1차 피팅: [기울기, 절편]
y_fit = np.polyval((k, b), x) # 피팅 직선의 예측값
ss_res = np.sum((y - y_fit) ** 2) # 잔차 제곱합
ss_tot = np.sum((y - np.mean(y)) ** 2)
return k, b, 1 - ss_res / ss_tot
✔ dropna가 Q_m3h 열 기준으로만 행을 빼는 것, polyfit의 반환 순서가 [기울기, 절편]인 것을 확인했다. R² 계산식은 12.2.3절의 식 (12.3)과 대조했다.
실행해 보자.
df = pd.read_csv("flow_calib.csv")
k, b, r2 = fit_calibration(df)
print(f"k = {k:.3f}, b = {b:.3f}, R² = {r2:.3f}")
k = 3.019, b = -4.628, R² = 0.147
답: k = 3.019 m³ h−1 kPa−1/2, 절편 −4.628 m³/h. 코드는 에러 없이 돌았다. 이제 이 답이 말이 되는지가 문제다.
검증 루틴 3종
- 단위: x가 kPa1/2, y가 m³/h이므로 기울기 k의 단위는 m³ h−1 kPa−1/2로, 사양서와 같은 단위. 통과.
- 대조값: 사양서 설계값 0.85 대비 계산값 3.019로, 3.5배 차이. 실패.
- 극한값: ΔP = 0에서 Q = b = −4.6 m³/h. 차압이 없는데 음수 유량이다. 물리적으로 불가능. 실패.
분석 코드에는 버그가 없다. polyfit은 시킨 일(제곱합 최소화)을 정확히 했고, 잔차 제곱의 벌점 규칙 때문에 43.3 한 점이 직선을 자기 쪽으로 끌어당겼을 뿐이다. 검증 루틴이 잡아낸 것은 코드 결함이 아니라 데이터 결함이다. 이 구분이 이 예제의 교훈이다. What-if: 43.3을 4.33으로 바꿔 재실행하면 세 검증이 어떻게 달라지는지 미리 예측해 보고 12.3절에서 확인하라.
12.2.3 결정계수와 잔차 플롯
피팅이 데이터의 산포를 얼마나 설명하는지 요약하는 지표가 결정계수(coefficient of determination) R²다.
R² = 1 − SSres / SStot (12.3)
SSres는 잔차 제곱합, SStot은 평균 둘레의 총 제곱합이다. 직선이 산포를 전부 설명하면 SSres = 0이 되어 R² = 1이고, 평균보다 나을 게 없으면 0에 가깝다. 예제 12.2-1의 0.147은 "이 직선은 데이터의 15%도 설명하지 못한다"는 뜻이다.
거꾸로 R²가 높다고 모델이 옳다는 보장도 없다. 잔차를 x에 대해 그렸을 때 아무 패턴이 없어야 하며, 잔차가 곡선을 그리면 직선 모델 자체가 틀렸다는 신호다. 숫자만으로는 감이 오지 않으니 그림을 그려 보자.
import matplotlib.pyplot as plt
d = df.dropna(subset=["Q_m3h"])
x = np.sqrt(d["dP_kPa"].to_numpy()) # kPa^0.5
y = d["Q_m3h"].to_numpy() # m³/h
plt.plot(x, y, "o", label="측정값")
xs = np.linspace(0, 6.5) # kPa^0.5
plt.plot(xs, np.polyval((k, b), xs), "-", label="피팅 직선")
plt.xlabel("√ΔP [kPa^0.5]")
plt.ylabel("Q [m³/h]")
plt.legend()
plt.show()
열 점이 한 직선 부근에 가지런히 모여 있고, 한 점만 하늘에 떠 있다. 피팅 직선은 그 점에 끌려 올라가 나머지 점 대부분의 위를 지나간다. 그림 한 장이 R² = 0.147의 사정을 전부 설명한다.
생각해보기
어떤 피팅에서 R² = 0.99가 나왔는데, 잔차를 x에 대해 그렸더니 완만한 곡선 패턴이 보인다. 데이터와 모델 중 무엇을 먼저 의심해야 하는가? 확인하는 방법을 두 가지 제안하라.
스스로 점검
- 식 (12.1)에서 잔차를 제곱하는 두 가지 이유는 무엇인가?
np.polyfit(x, y, 1)의 반환값 순서는 무엇인가?- R² = 1이 되려면 잔차들이 어떤 조건을 만족해야 하는가?
12.3 이상치: 지우기 전에 조사한다
12.3.1 세 가지 근원
나머지 데이터가 이루는 패턴에서 크게 벗어난 점을 이상치(outlier)라 한다. 이상치를 방치하면 예제 12.2-1처럼 결과 전체가 왜곡된다. 그런데 조사 없이 지우는 것도 같은 크기의 실수다. 어느 쪽인지 판단하려면 이상치가 어디서 오는지부터 알아야 한다.
첫째 근원은 기록과 입력의 실수다. 현장 기록지의 4.33이 전산 입력에서 43.3이 되는 식으로, 소수점 한 칸이 값을 열 배로 만든다. 둘째는 센서와 계측의 고장이다. 특정 시간대나 특정 장비의 값만 몰려서 튀면 이쪽을 의심한다.
셋째 근원인 진짜 물리 현상이 문제다. 예상 밖의 조건에서 실제로 일어난 일이 이상치로 보일 수 있고, 이것을 기계적으로 지우는 습관은 새로운 현상의 발견을 지우는 습관이다. 그래서 원칙은 하나로 고정한다. 지우기 전에 조사한다.
12.3.2 잔차로 후보 찾기
조사하려면 먼저 후보를 골라야 한다. describe()의 min/max 훑기와 그림(코드 12-8)이 1차 검사이고, 정량적 기준은 피팅의 잔차에서 나온다.
res = y - np.polyval((k, b), x) # 잔차 (m³/h)
print(np.round(res, 2))
sigma = np.std(res) # 잔차 표준편차
print("잔차 표준편차:", round(sigma, 2))
suspect = d[np.abs(res) > 3 * sigma] # 3σ 초과 후보
print(suspect)
[ 1.55 0.31 -0.71 -1.48 -2.25 -3.48 -4.6 -5.52 32.53 -7.27 -9.07]
잔차 표준편차: 10.73
run session dP_kPa Q_m3h
8 9 pm 26 43.3
잔차 크기가 잔차 표준편차의 3배를 넘는 점을 의심 후보로 본다. 이 책의 작업 규칙이다. run 9가 걸렸다. 그런데 숫자를 자세히 보면 아슬아슬하다. 후보의 잔차 32.53에 대해 경계값 3σ = 32.20으로, 겨우 넘었다.
왜 이렇게 아슬아슬한가. 이상치 자신이 표준편차 계산에 들어가 σ를 부풀렸기 때문이다. 극단적인 이상치일수록 자신을 가려 주는 경계를 스스로 키우므로, 기계적 규칙은 후보 선별까지만 쓰고 최종 판단은 조사에 맡긴다.
12.3.3 조사, 그리고 수정
이 장의 시나리오에서 run 9의 현장 기록지에는 4.33이 적혀 있었다고 하자. 전산 입력에서 소수점이 밀린 것으로, 근원 ①(입력 실수)로 확정되었다. 원인이 확정되었으므로 원본값으로 수정하는 것이 정당하다. 원인을 밝히지 못했다면 수정은 불가능하고, 제거하되 무엇을 왜 몇 건 지웠는지 README에 남긴다(시험의 검증 증빙과 같은 형식이다).
예제 12.3-1 이상치 수정과 재피팅
run 9의 Q_m3h를 기록지 원본값 4.33 m³/h로 수정하고 재피팅하여 보정 상수 k를 확정하라.
어느 행을 어떤 근거로 고치는가는 판단의 영역이므로 두 줄짜리 수정 코드는 직접 쓴다. 재피팅은 예제 12.2-1의 fit_calibration을 그대로 재사용한다.
df_fix = df.copy() # 원본은 남겨 둔다
df_fix.loc[df_fix["run"] == 9, "Q_m3h"] = 4.33 # 기록지 원본값 (m³/h)
k2, b2, r2 = fit_calibration(df_fix)
print(f"k = {k2:.3f}, b = {b2:.4f}, R² = {r2:.4f}")
k = 0.852, b = -0.0069, R² = 0.9998
답: k = 0.852 m³ h−1 kPa−1/2.
검증 루틴 3종
- 단위: 변환·피팅 구조가 그대로이므로 k의 단위 역시 m³ h−1 kPa−1/2. 통과.
- 대조값: 사양서 설계값 0.85 대비 0.852로, 0.3% 이내. 통과.
- 극한값: ΔP = 0에서 Q = −0.007 m³/h ≈ 0. 차압이 없으면 흐름도 없다는 물리와 부합. 통과.
분석 한 점을 고쳤을 뿐인데 k는 3.019에서 0.852로, R²는 0.147에서 0.9998로 바뀌었다. 열한 점 중 한 점이 결과를 지배했다는 뜻이고, 수정 전후를 판정해 준 것은 같은 검증 루틴 3종이었다. 원본 df를 남기고 copy()로 고친 것도 관례다. 무엇을 바꿨는지 추적할 수 있어야 증빙이 된다. What-if: 코드 12-9의 경계를 3σ에서 2σ로 바꿔 정상 점이 후보로 걸려 나오는지 확인해 보라.
생각해보기
열두 번의 측정 중 두 번이 같은 방향으로 튀었고, 센서 점검 결과는 정상이었다. 이 두 점을 지워도 되는가? 판단을 내리기 위해 추가로 필요한 정보 두 가지를 제안하라.
스스로 점검
- 이상치의 세 근원 중 삭제가 가장 위험한 것은 어느 것이며, 왜 그런가?
- 3σ 규칙이 극단적인 이상치를 오히려 놓칠 수 있는 이유를 한 문장으로 설명할 수 있는가?
12.4 데이터 검증 습관: 루틴 3종의 데이터 버전
3장에서 도입한 검증 루틴 3종은 계산 결과만이 아니라 데이터 작업의 모든 단계에 적용된다. 단위 체크는 열 이름에 단위를 넣는 것(dP_kPa, Q_m3h)과 읽은 직후의 자료형 확인으로 나타난다. 단위 행 오염은 dtypes에서 걸렸다. 대조값 체크는 describe()의 min/max를 설비의 조업 상식과 대조하고, 피팅 계수를 사양서·문헌값과 대조하는 일이다. 극한값 체크는 절편의 물리적 의미(ΔP = 0이면 Q = 0)를 캐묻는 일이었다.
회귀식에도 유효범위가 있다. 이 장의 보정식은 ΔP = 2~40 kPa의 데이터로 만들어졌으므로 그 밖은 외삽이다. 8장에서 Antoine 상수를 유효 온도범위 밖으로 외삽했을 때 생긴 오차를 보았다. 같은 규칙이 데이터 피팅에 그대로 적용된다. 회귀식의 유효범위는 데이터의 범위다.
여기에 데이터 특유의 습관 하나를 더한다. 행을 없애거나 고치는 모든 처리의 앞뒤에서 행 수를 찍는 것이다.
n0 = len(df)
df2 = df.dropna(subset=["Q_m3h"]) # 결측 행 제외
print(f"결측 제외: {n0}행 → {len(df2)}행") # 12행 → 11행
한 줄짜리 확인이지만, 12.3절의 AI 경고 사례에서 조용한 2행 손실을 잡아낸 것이 바로 이 습관이다. 정리하면 이 장의 검증 습관은 네 가지다. 읽은 직후 3연타, 열 이름에 단위, 처리 전후 행 수 비교, 피팅 후 검증 3종이다.
- 문제 정의
- 분해
- 프롬프트
- 생성 코드 읽기
- 테스트
- 화공 검증
시험2는 이 사이클 전체를 90분 안에 도는 시험이다. 이제 그 규정과 전략으로 넘어가자.
실습 12: 시험2 (90분 현장 실기, 20점)
이번 주 실습 시간에는 만들 도구가 없다. 실습 그 자체가 시험2이고, 시험2는 최종 시험과 인프라·규정이 같은 3/4 스케일 리허설이다. 여기서 미리 실수를 겪어 두면 최종 시험에서 같은 실수를 줄일 수 있다.
준비물
- 본인 노트북(전원 어댑터 포함). 시험은 유선랜 전산실습실에서 치른다
- Claude Code·게이트웨이 가상 키·GitHub 로그인이 살아 있는 환경 (전날 점검)
- 본인 위키. 시험2와 최종 시험에서 본인 위키 참조는 허용이며 활용도 가점 대상이다
- 재사용할 이전 도구: 도구④~⑧ repo. 특히 이 장의 회귀·이상치 코드는 시험 주제 풀과 직결된다
규정
| 항목 | 내용 |
|---|---|
| 배점·형식 | 20점, AI 필수, 90분 현장 실기 |
| 주제 | 당일 공개 + 학번별 파라미터 |
| 과제 구성 | 소형 도구 제작 + 고장 코드 디버깅 |
| 커밋 | 빈 repo 첫 커밋, 90분 내 커밋 ≥4 |
| 채점 | 자동 스모크 테스트 12점 + 과정 8점(커밋 타임라인·세션 로그 정합성) |
| 선언문 | AI 사용내역 선언문 1쪽 첨부(미첨부 시 부정행위 처리) |
| 위상 | 형식·인프라·규정이 최종 시험과 완전 동일한 3/4 스케일 리허설 |
배점이 시간 배분을 정한다. 자동 스모크 테스트가 12점이므로 "README대로 돌아가는 것"이 최우선이고, 과정 8점은 커밋 타임라인과 세션 로그로 채점된다. 종료 직전에 몰아서 커밋 4개를 만드는 것은 타임라인에 그대로 드러나므로, 커밋은 작업이 진행되는 흐름에 맞춰 남기도록 하자.
| 구간 | 할 일 | 커밋 |
|---|---|---|
| 0~10분 | 문제 읽기, 함수 단위 분해, repo 생성 | ① 첫 커밋(빈 repo) + README 계획 |
| 10~35분 | 핵심 계산 기능(대표 입력 1개가 끝까지 돈다) | ② |
| 35~55분 | 고장 코드 디버깅(재현 → 원인 → 수정) | ③ |
| 55~75분 | 검증 3종 수행 + README 증빙 | ④ |
| 75~90분 | 클론 직후 실행 관점 최종 점검, 선언문, 업로드 | ⑤ (여유분) |
최종 시험에서는 커밋 주기와 제출 규정이 한 단계 확장되며, 전문은 부록 E에 있다. 시험2에서 커밋 리듬을 미리 몸에 붙여 두면 최종 시험의 규정에도 익숙해질 것이다.
과제: 시험 수행 절차
- (전날) 새 터미널에서 환경을 점검하라. (완료 기준:
git --version과 Claude Code 실행이 정상 응답한다) - (0~10분) 주제 공개 즉시 빈 repo를 만들고 첫 커밋을 하라. (완료 기준: GitHub 원격에 커밋 1개가 보인다)
- (0~10분) 문제를 입력→계산→출력의 함수 3~4개로 분해해 README에 계획을 적어라. (완료 기준: README에 함수 이름 목록이 있다)
- (10~35분) 핵심 계산 기능을 만들고 커밋하라. (완료 기준: 학번 파라미터의 대표 입력 1개로 실행이 끝까지 돈다)
- (35~55분) 고장 코드의 에러를 재현하고, 원인을 한 문장으로 적은 뒤 수정하고 커밋하라. (완료 기준: 에러 전문과 원인 한 줄이 README에 있다)
- (55~75분) 검증 3종을 수행하고 README에 기록한 뒤 커밋하라. (완료 기준: 단위·대조값·극한값 각 1줄 이상)
- (75~90분) repo를 새로 클론한 심정으로 README 순서대로 실행해 본 뒤 최종 커밋·업로드하라. (완료 기준: 커밋 ≥4, 선언문 1쪽 포함)
완성 기준
- 빈 repo 첫 커밋을 포함해 90분 내 커밋 4회 이상
- README대로 클론 직후 실행이 성공한다 (자동 스모크 테스트의 관점)
- README에 검증 3종 증빙이 각 1줄 이상 기록되어 있다
- AI 사용내역 선언문 1쪽이 첨부되어 있다
- 종료 시각 전에 최종 업로드가 완료되었다
막혔는가?
정답 코드가 아니라 프롬프트를 준다. 막힌 상황에 맞는 것을 골라 써라.
- 에러가 났을 때: "다음 에러 전문을 보고 원인 후보 3개와 각각의 확인 방법을 알려 줘: (에러 전문 붙여넣기)"
- 어디서 시작할지 모를 때: "이 문제를 입력·계산·출력 함수 3개로 분해하고, 함수 시그니처만 먼저 제안해 줘. 코드는 아직 쓰지 마."
- 결과가 의심될 때: "이 회귀 결과를 검증할 극한값 테스트 2개를 제안해 줘. 각 테스트가 어떤 실수를 잡는지도 설명해 줘."
시험 직후 10분: 위키 기록 긴장이 풀리기 전에 기록하라. 어디서 막혔고, 어떤 프롬프트가 살렸는지(또는 실패했는지) 1건을 위키에 커밋하라. 같은 형식의 최종 시험을 준비할 때 좋은 원자료가 된다.
요약
- S 12-1 데이터 점검 3연타:
head()→info()→describe(). 읽은 직후, 그리고 처리 전후에. - S 12-2 최소자승법은 식 (12.1)의 S(a, b) = Σ [yi − (axi + b)]² 를 최소화한다. 제곱 벌점은 이상치에 민감하다.
- S 12-3
k, b = np.polyfit(x, y, 1)의 반환 순서는 [기울기, 절편]이다. 품질은 식 (12.3)의 R²와 잔차 플롯으로 함께 본다. - S 12-4 이상치 원칙: 후보는 기계(3σ)로 찾고 판단은 조사로 한다. 수정·삭제는 반드시 README에 기록한다.
| 근원 | 데이터 속 신호 | 올바른 처리 |
|---|---|---|
| 기록·입력 실수 | 자릿수·소수점이 튄다, 원본 기록과 불일치 | 원본 대조 후 수정, 수정 내역 기록 |
| 센서·계측 고장 | 특정 시점·장비에 몰려서 튄다, 물리적으로 불가능한 값 | 해당 구간 제외 + 제외 근거 기록 |
| 진짜 물리 현상 | 재현된다, 조업 조건과 상관을 보인다 | 지우지 않는다(조사 대상으로 승격) |
도구 사다리는 이 장으로 이론 구간을 마쳤다. 단위환산(도구④)에서 시작해 증기압(⑤), T-xy(⑥), 물질수지(⑦), 압력강하(⑧)를 지나며 사이클의 ⑤ 테스트와 ⑥ 화공 검증 근육이 매주 자랐고, 시험2는 그 근육의 총연습이다. 13장부터는 만든 것을 화면에 올려 보여 주는 기술로 넘어간다.
용어 정리
- 데이터프레임(DataFrame)
- 행과 열로 이루어진 표를 담는 pandas의 자료구조. 행은 관측 한 건, 열은 변수 하나다.
- 결측값(missing value)
- 기록되지 않아 비어 있는 칸. pandas에서는 NaN으로 표시된다.
- 집계(aggregation)
- 데이터를 그룹으로 나눠 그룹별 요약 통계를 구하는 조작.
- 잔차(residual)
- 측정값에서 모델 예측값을 뺀 차이.
- 최소자승법(method of least squares)
- 잔차 제곱합을 최소화하는 모델 파라미터를 택하는 피팅 방법.
- 결정계수(coefficient of determination)
- 피팅이 데이터 산포를 설명하는 비율. R² = 1 − SSres/SStot.
- 이상치(outlier)
- 나머지 데이터의 패턴에서 크게 벗어난 점. 근원 조사 없이 지우지 않는다.
연습문제
Q군: 개념·읽기 (AI 없이 풀 수 있어야 한다)
- Q12.1 코드 12-1의 데이터에 대해
df.groupby("session")["dP_kPa"].max()의 출력을 코드 실행 없이 예측하라. - Q12.2 코드 12-6의
fit_calibration함수가 하는 일을 한국어 2~3문장으로 설명하라. 첫 줄의dropna를 지우면 어떤 문제가 생기는지도 밝혀라. - Q12.3 식 (12.1)에서 잔차를 제곱하는 이유 두 가지를 쓰고, 그중 하나가 이상치 민감성으로 이어지는 논리를 설명하라.
P군: 제작·계산 (AI 사용 전제)
- P12.1
find_outliers(df, x_col, y_col, n_sigma)함수를 만들어라. 행을 삭제하지 말고, 의심 행과 그 잔차를 담은 데이터프레임을 반환해야 한다. 코드 12-1의 데이터로 n_sigma = 3과 2의 결과 차이를 보고하라. - P12.2 이 장의 탐지 절차(describe → 그림 → 3σ)를 통과하도록 이상치를 "숨기는" 데이터 조작 방법을 두 가지 제시하라. 각 방법이 검증 루틴 3종 중 무엇에는 여전히 걸리는지 밝혀라.
- P12.3 최종 시험 주제 예시 "학번별 실험 데이터 회귀 + 이상치 자동 탐지 리포트 생성기"와 동형의 도구를 만들어라: CSV를 받아 회귀 계수·R²·이상치 후보 목록·검증 3종 결과를 담은 마크다운 리포트를 생성한다. 빈 repo 첫 커밋부터 시작해 90분 타이머로 수행하고 커밋 4회 이상을 남겨라.
위키 기록 과제
이번 주 의미 커밋 1회 이상이 채점 지표다. 다음 3항을 기록하라.
- 이번 장 핵심 1페이지 정리(#seed): 점검 3연타와 최소자승·이상치 절차를 본인의 말로.
- "내가 틀렸던 것" 1건: 시험2에서 만난 에러 전문 스크린샷과 원인 한 줄.
- 타 과목 연결 1건: 실험 과목 보고서의 데이터 처리에 이 장의 절차를 적용해 본 기록.