데이터의 의미와 구조

먼저 읽을 장 1.3 문제를 수학적으로 정의하기

데이터 파일을 열면 숫자가 담긴 표가 보인다. 그러나 표만으로는 그 숫자가 무엇인지 알 수 없다. 한 행이 한 번의 측정인지, 한 개체의 요약인지, 같은 개체를 반복해서 잰 것인지 표는 말해 주지 않는다. temp_c라는 열 이름이 붙어 있어도 실제로 섭씨인지, -999가 매우 추운 날인지 측정하지 못한 날인지는 데이터를 만든 사람만 안다. 이 정보를 모른 채 학습을 시작하면 모델은 잘 맞는데 결론은 틀리는 일이 생긴다.

1.3장에서는 연구 문제를 입력·출력·일반화 대상으로 정의했다. 이 장은 그 정의를 데이터 쪽에서 받친다. 데이터의 단위(무엇이 하나의 독립된 관측인가), label의 생성 과정(정답이 어디서 왔는가), schema와 metadata(각 열이 무엇을 뜻하는가), 원본 보존(무엇을 바꾸지 않을 것인가)을 차례로 다루고, 이를 다른 연구자가 읽고 그대로 쓸 수 있는 데이터 사전으로 묶는다. 이 장의 결정은 15.4장의 분할 설계와 15.5장의 데이터셋 문서로 그대로 이어진다.

학습 목표

  • sample·entity·group·trajectory를 정의한다
  • label의 생성 과정을 추적한다
  • 데이터 사전을 작성한다

데이터의 단위

왜 필요한가. 데이터셋의 크기를 물으면 대부분 행 수를 답한다. 그러나 행 수가 곧 정보의 양은 아니다. 배터리 셀 8개를 각각 30사이클 측정하면 240행이 생기지만, "새로 만든 셀에서도 통하는가"라는 질문에 답할 독립된 증거는 셀 8개뿐이다. 같은 셀의 사이클들은 같은 제조 편차를 공유하므로 서로 독립이 아니다. 이 구분을 놓치면 통계적 불확실성을 과소평가하고(12장), 분할에서 누수가 생긴다(15.4장).

정의. 이 책은 다음 용어를 구분해서 쓴다.

용어 뜻 배터리 예 단백질 예 기후 예
sample 모델이 입력 하나로 받는 단위. 보통 표의 한 행 한 사이클의 측정 요약 한 서열 한 격자점·한 시각의 값
entity 측정 대상인 물리적·생물학적 개체 셀 하나 단백질 하나 관측소 하나
group 공통 원인을 공유하는 entity의 묶음 같은 제조 lot, 같은 실험 장비 같은 단백질 family 같은 기후대
trajectory 한 entity를 시간 순서로 따라간 sample의 열 셀 하나의 전체 수명 곡선 분자동역학 궤적 하나 관측소 하나의 시계열
timestamp sample이 관측된 시점 사이클 번호·측정 시각 (해당 없음) 관측 시각

무엇이 독립인가. 핵심 질문은 하나다. 결론을 일반화하려는 대상이 무엇이고, 그 대상의 서로 다른 사례가 데이터에 몇 개 있는가. 새 사이클에 대해 말하고 싶다면 사이클이 단위이고, 새 셀에 대해 말하고 싶다면 셀이 단위다. 이 단위를 독립성의 단위(unit of independence)라 부른다. 실험 설계에서는 처리(treatment)가 독립적으로 반복된 단위를 실험 단위(experimental unit)라 부르는데, 같은 생각이다. 실험 단위가 아닌 반복 측정을 독립 반복처럼 다루는 오류를 pseudoreplication이라 한다(Hurlbert, 1984).

직관과 수식. 행이 모두 독립이라면 평균의 표준오차는 s/Ns/\sqrt{N}이다. 여기서 ss는 표본 표준편차, NN은 행 수다. 그러나 같은 셀 안의 행들이 셀 효과 ugu_g를 공유하는 모형

ygi=μ+ug+εgi,ug∼N(0,σg2),εgi∼N(0,σε2)y_{gi} = \mu + u_g + \varepsilon_{gi}, \qquad u_g \sim \mathcal{N}(0, \sigma_g^2), \quad \varepsilon_{gi} \sim \mathcal{N}(0, \sigma_\varepsilon^2)

에서(gg는 셀, ii는 사이클), 전체 평균의 분산은 GG개 셀, 셀당 mm개 사이클일 때

Var⁡(yˉ)=σg2G+σε2Gm\operatorname{Var}(\bar{y}) = \frac{\sigma_g^2}{G} + \frac{\sigma_\varepsilon^2}{Gm}

이다. 사이클 수 mm을 아무리 늘려도 첫째 항 σg2/G\sigma_g^2/G는 줄지 않는다. 셀 효과가 크면 정보의 양은 행 수 GmGm이 아니라 셀 수 GG로 정해진다.

import numpy as np
import pandas as pd

rng = np.random.default_rng(0)
n_cells, n_cycles = 8, 30
cell_effect_ah = rng.normal(0.0, 0.05, size=n_cells)        # (8,) 셀마다 다른 제조 편차
df = pd.DataFrame({
    "cell_id": np.repeat([f"C{i:02d}" for i in range(n_cells)], n_cycles),
    "cycle": np.tile(np.arange(1, n_cycles + 1), n_cells),
})                                                           # (240, 2)
df["capacity_ah"] = (2.0 + np.repeat(cell_effect_ah, n_cycles)
                     - 0.001 * df["cycle"] + rng.normal(0.0, 0.005, size=len(df)))

print("행 수:", len(df), " 셀 수:", df["cell_id"].nunique())
naive_se = df["capacity_ah"].std() / np.sqrt(len(df))
cell_means = df.groupby("cell_id")["capacity_ah"].mean()     # (8,)
cell_se = cell_means.std() / np.sqrt(len(cell_means))
print(f"행을 독립으로 본 평균의 표준오차 = {naive_se:.4f} Ah")
print(f"셀을 단위로 본 평균의 표준오차   = {cell_se:.4f} Ah")
행 수: 240  셀 수: 8
행을 독립으로 본 평균의 표준오차 = 0.0019 Ah
셀을 단위로 본 평균의 표준오차   = 0.0104 Ah

240행을 독립으로 보면 평균 용량을 0.0019 Ah 정도로 안다고 착각한다. 셀을 단위로 보면 불확실성은 다섯 배 넘게 크다. 셀 사이의 편차(표준편차 0.05 Ah)가 사이클 사이의 잡음(0.005 Ah)보다 훨씬 크기 때문이다.

연구에서의 의미. 데이터 문서의 첫 줄에는 행 수가 아니라 단위별 개수를 쓴다. "240행"이 아니라 "셀 8개 × 사이클 30개, 제조 lot 2개"처럼 쓴다. 이 표기가 15.4장에서 어떤 열로 그룹 분할을 할지, 12.5장에서 무엇을 반복 단위로 비교할지를 정한다.

label의 생성 과정

왜 필요한가. 지도학습은 label을 정답으로 믿는다. 그러나 과학 데이터의 label은 대부분 누군가가 어떤 절차로 만든 값이다. 그 절차가 label에 잡음과 편향을 넣는다. 모델은 그 잡음과 편향까지 배우고, 평가도 같은 label로 하므로 문제가 드러나지 않는다.

세 가지 출처. label은 생성 과정에 따라 크게 세 종류다.

종류 예 주된 오차
직접 측정 저울로 잰 질량, 열전대로 잰 온도 센서 잡음, 보정(calibration) 오차
계산된 값 전류를 적분한 용량, DFT로 계산한 에너지, 다른 모델의 추정값 계산 절차의 가정과 근사, 입력 측정의 오차 전파
사람의 판단 병리 영상의 판독, 은하 형태 분류 판독자 사이의 불일치, 기준의 변화

잡음과 편향. label y~\tilde{y}를 참값 yy에 오차를 더한 것으로 쓰면

y~=y+b+η,E[η]=0,Var⁡(η)=ση2\tilde{y} = y + b + \eta, \qquad \mathbb{E}[\eta] = 0, \quad \operatorname{Var}(\eta) = \sigma_\eta^2

이다. bb는 모든 label에 같은 방향으로 들어가는 편향(bias), η\eta는 label마다 독립인 잡음(noise)이다. 두 오차는 성질이 다르다.

  • 잡음 η\eta는 참 함수를 완벽히 아는 모델조차 넘을 수 없는 오차의 바닥을 만든다. 측정 label에 대한 그 모델의 RMSE는 ση\sigma_\eta다. 시험 데이터가 충분히 큰데도 테스트 RMSE가 이 값보다 뚜렷이 작다면 모델이 뛰어난 것이 아니라 무언가 새고 있다는 신호다.
  • 편향 bb는 평가에서 보이지 않는다. 학습과 평가가 같은 편향된 label을 쓰기 때문이다. 다른 방법으로 만든 label과 비교해야만 드러난다.

계산된 label에서 두 오차를 구분해 본다. 방전 전류를 1시간 동안 적분해 용량을 계산하는데, 전류 센서에 잡음(표준편차 0.05 A)과 고정 offset이 있다고 하자.

t_s = np.arange(0.0, 3600.0, 1.0)                  # (3600,) 1 Hz로 1시간
current_a = np.full_like(t_s, 2.0)                 # 실제 방전 전류 2 A
for offset_a in [0.0, 0.02]:
    measured_a = current_a + offset_a + rng.normal(0.0, 0.05, size=t_s.shape)
    capacity_ah = measured_a.sum() * 1.0 / 3600.0  # 전류 적분(dt = 1 s) → Ah
    print(f"센서 offset {offset_a:.2f} A → 계산된 용량 {capacity_ah:.4f} Ah (참값 2.0000 Ah)")
센서 offset 0.00 A → 계산된 용량 1.9990 Ah (참값 2.0000 Ah)
센서 offset 0.02 A → 계산된 용량 2.0210 Ah (참값 2.0000 Ah)

3600개 측정을 적분하는 동안 잡음은 평균에서 상쇄되어 오차가 0.001 Ah 수준으로 줄었다. offset 0.02 A는 1시간 동안 그대로 쌓여 약 0.02 Ah(1%)의 편향이 된다. 측정을 더 많이 해도 이 편향은 줄지 않는다. 이 장비로 만든 모든 label이 1% 크게 나오고, 그 label로 학습하고 평가한 모델은 1% 크게 예측하면서도, 평가에서는 그 1%가 오차로 전혀 잡히지 않는다.

추적하는 방법. label 열마다 다음을 기록한다.

  • 생성 방법: 측정·계산·판단 중 무엇이고, 어떤 장비·코드·판독 기준을 썼는가
  • 입력: 계산된 label이라면 어떤 측정값에서 계산했는가. 그 측정값이 모델의 입력에도 들어가는가(그렇다면 1.3장의 순환 정의다)
  • 불확실성: 반복 측정이나 판독자 간 일치도로 추정한 잡음의 크기
  • 버전: 계산 절차나 판독 기준이 바뀐 시점

schema와 metadata

왜 필요한가. 같은 이름의 열이 출처마다 다른 뜻을 가질 수 있다. 한 실험실은 온도를 섭씨로, 다른 실험실은 켈빈으로 저장하고 두 파일 모두 열 이름을 temp로 붙일 수 있다. 결측을 빈칸으로 두는 장비도 있고 -999나 9999 같은 숫자 코드(sentinel)로 채우는 장비도 있다. 이런 정보는 숫자 안에 없으므로, 표 바깥에 따로 적어 두어야 한다.

정의. schema는 각 열의 이름·자료형·단위·허용 범위·결측 규칙을 정한 명세다. metadata는 데이터 전체에 대한 정보다. 누가, 언제, 어떤 장비와 조건으로 만들었고, 어떤 처리를 거쳤는지를 담는다. 그중 데이터가 어디서 와서 어떤 변환을 거쳤는지의 기록을 provenance(출처 이력)라 한다. 이 셋을 사람이 읽을 수 있게 모은 문서가 데이터 사전(data dictionary)이다.

열 자료형 단위 허용 범위 결측 규칙 뜻과 출처
cell_id 문자열 — C00–C99 결측 불가 셀 일련번호. 제조사 lot 번호와 대응표 별도
cycle 정수 회 ≥ 1 결측 불가 충방전 사이클 번호. 1부터
temp_c 실수 °C −20–60 -999 = 센서 미연결 셀 표면 온도. 열전대, 셀 중앙 부착
voltage_v 실수 V 2.5–4.25 빈칸 = 기록 누락 단자 전압. 1 Hz 샘플링
capacity_ah 실수 Ah 0–3 결측 불가 계산값: 방전 구간 전류 적분

결측의 의미. 결측은 한 종류가 아니다. 센서가 연결되지 않아 값이 없는 것, 값이 측정 범위를 넘어 기록되지 않은 것, 실험 설계상 측정하지 않은 것은 서로 다른 정보다. 특히 결측이 값 자체와 관련 있으면(예: 온도가 너무 높아 센서가 꺼짐) 결측을 그냥 지우는 것만으로 분포가 왜곡된다. 데이터 사전에는 결측 코드와 함께 왜 결측이 생기는지를 쓴다. 결측 코드를 숫자로 믿으면 통계가 무너진다.

raw = pd.DataFrame({
    "temp_c":    [25.1, 24.8, -999.0, 25.3, 25.0, -999.0],
    "voltage_v": [3.71, 3.69, 3.70, np.nan, 3.68, 3.72],
})
print("sentinel 그대로의 temp_c 평균 :", round(raw["temp_c"].mean(), 2))
clean = raw.replace({"temp_c": {-999.0: np.nan}})
print("NaN으로 바꾼 뒤의 temp_c 평균:", round(clean["temp_c"].mean(), 2))
print("열별 결측 수:", clean.isna().sum().to_dict())
sentinel 그대로의 temp_c 평균 : -316.3
NaN으로 바꾼 뒤의 temp_c 평균: 25.05
열별 결측 수: {'temp_c': 2, 'voltage_v': 1}

단위. 단위는 열 이름에 접미사로(temp_c, voltage_v) 붙이고, 데이터 사전에도 쓴다. 그러나 이름은 약속일 뿐 검사가 아니다. 여러 출처를 합칠 때는 사전에 적은 허용 범위로 실제 값을 검사한다. 아래에서 출처 B는 열 이름은 같지만 실제로는 켈빈과 밀리볼트로 저장했다.

dictionary = {
    "temp_c":    {"unit": "°C", "min": -20.0, "max": 60.0},
    "voltage_v": {"unit": "V",  "min": 2.5,   "max": 4.25},
}
site_a = pd.DataFrame({"temp_c": [24.9, 25.2, 26.0], "voltage_v": [3.70, 3.65, 3.61]})
site_b = pd.DataFrame({"temp_c": [298.4, 298.9, 299.3], "voltage_v": [3702, 3651, 3608]})  # 실제로는 K, mV
merged = pd.concat([site_a.assign(site="A"), site_b.assign(site="B")], ignore_index=True)

def validate(df, dictionary):
    for col, spec in dictionary.items():
        bad = df[col].notna() & ~df[col].between(spec["min"], spec["max"])
        sites = sorted(df.loc[bad, "site"].unique())
        print(f"{col:9s} [{spec['unit']}] 범위 밖 {bad.sum()}개, 출처 {sites}")

validate(merged, dictionary)
temp_c    [°C] 범위 밖 3개, 출처 ['B']
voltage_v [V] 범위 밖 3개, 출처 ['B']

범위 검사가 출처 B만 골라냈다. 범위 검사는 켈빈과 섭씨처럼 크기가 크게 다른 단위 혼동은 잡지만, 섭씨와 화씨처럼 범위가 겹치는 혼동은 놓칠 수 있다. 그래서 출처별 분포를 함께 비교한다(15.3장).

연구에서의 의미. 데이터를 공개하거나 공유할 때 FAIR 원칙(Findable·Accessible·Interoperable·Reusable)이 널리 쓰인다(Wilkinson 외, 2016). 그중 재사용 가능성(Reusable)은 풍부한 metadata와 명확한 provenance를 요구한다. 데이터 사전은 그 요구를 실험실 수준에서 지키는 가장 작은 단위다. 표의 모양도 metadata의 일부다. 한 열은 한 변수, 한 행은 한 관측, 한 표는 한 종류의 관측 단위를 담는 "tidy" 구조(Wickham, 2014)로 두면 사전을 쓰기도, 검사하기도 쉽다.

원본 보존

왜 필요한가. 전처리 중에 오류를 발견하면 원본 파일을 열어 고치고 싶어진다. 그러나 원본을 고치면 무엇을 고쳤는지, 왜 고쳤는지, 고치기 전에는 어땠는지가 사라진다. 나중에 그 수정이 틀렸다고 밝혀지거나 다른 연구자가 다른 기준으로 처리하고 싶을 때 되돌릴 방법이 없다.

세 단계. 데이터를 처리 단계에 따라 나누어 저장한다.

단계 내용 규칙
raw 장비·외부 출처에서 받은 그대로 읽기 전용. 수정하지 않는다. 받은 날짜와 checksum을 기록한다
interim 형식 변환·병합·단위 통일 등 중간 결과 코드로만 만든다. 지워도 raw와 코드로 다시 만들 수 있다
processed 학습·평가에 바로 쓰는 최종 데이터 코드로만 만든다. 만든 코드의 버전을 기록한다
data/
├── raw/          # 받은 그대로, 읽기 전용 (chmod a-w)
├── interim/      # 코드가 만든 중간 결과
└── processed/    # 코드가 만든 최종 데이터

수정은 코드로 한다. 센서 오류를 발견했다면 raw 파일을 고치지 않고, 처리 코드에 규칙을 추가한다. 예를 들어 "셀 C03의 2024-05-02 측정은 열전대 탈락으로 temp_c를 결측 처리한다"를 코드와 그 이유를 적은 주석(또는 별도의 수정 목록 파일)으로 남긴다. 그러면 수정이 리뷰 가능하고, 되돌릴 수 있고, 다른 데이터에도 같은 규칙을 적용할 수 있다. raw의 무결성은 15.5장의 checksum으로 확인한다. 디렉터리 구조 전반은 6.1장(연구 프로젝트 구조)에서 다룬다.

실습

자신의 연구 데이터(없으면 공개 데이터셋 하나)로 두 문서를 작성한다.

  1. 데이터 사전. 열마다 이름·자료형·단위·허용 범위·결측 규칙(코드와 원인)·뜻과 출처를 표로 쓴다. 첫 줄에는 단위별 개수(예: "entity 8개 × sample 30개, group 2개")와 독립성의 단위를 쓴다.
  2. label 생성 과정 문서. label 열마다 생성 방법(측정·계산·판단), 계산에 쓴 입력, 불확실성의 추정치와 그 근거, 알려진 편향의 가능성을 쓴다.

문서를 쓴 뒤 사전의 범위·결측 규칙을 코드로 옮겨 실제 데이터에 적용한다. 아래 연습문제가 그 검사의 핵심 함수다. 검사에 걸린 값이 있으면 데이터 오류인지 사전 오류인지 판단하고, 판단 근거를 사전에 덧붙인다.

마지막으로 동료에게 사전만 주고 "이 데이터로 새 셀의 용량을 예측하는 실험을 설계해 달라"고 부탁한다. 동료가 질문한 내용은 모두 사전에 빠진 정보다.

실패 사례

  • 계산된 label을 측정값처럼 취급하는 것. 다른 모델이나 시뮬레이터의 출력을 정답으로 학습하면, 모델의 성능은 그 계산 절차를 얼마나 잘 흉내 내는가를 잰다. 계산 절차의 편향은 평가에서 보이지 않는다. 논문에 "실험값 예측"이라고 쓰기 전에 label의 출처를 확인한다.
  • 단위가 섞인 데이터를 병합하는 것. 열 이름이 같다고 단위가 같지는 않다. 병합 전에 출처별로 사전의 범위를 검사하고 분포를 비교한다. 범위가 겹치는 단위 혼동(°C와 °F)은 범위 검사로도 놓칠 수 있다.
  • 결측 코드를 숫자로 계산하는 것. -999가 평균·표준화·학습에 그대로 들어가면 통계와 모델이 모두 왜곡된다. 결측 코드는 읽는 즉시 NaN으로 바꾼다.
  • 행 수를 독립 표본 수로 보고하는 것. 반복 측정은 독립 반복이 아니다. 불확실성과 분할은 독립성의 단위로 계산한다.
  • raw를 직접 고치는 것. 수정 내역이 사라져 되돌릴 수도, 검토할 수도 없다.

통과 기준

  • 데이터 사전만 받은 다른 연구자가 추가 질문 없이 독립성의 단위, 각 열의 단위·결측 규칙, label의 출처를 정확히 설명한다.
  • label마다 생성 방법과 불확실성 추정치를 근거와 함께 기록한다.
  • 연습문제 check-column-spec의 테스트를 통과한다.

참고 문헌

  • Hurlbert (1984). Pseudoreplication and the design of ecological field experiments. Ecological Monographs, 54(2), 187–211. — 독립 반복이 아닌 측정을 반복으로 다루는 오류의 정의
  • Wickham (2014). Tidy data. Journal of Statistical Software, 59(10), 1–23. — 변수·관측·관측 단위에 대응하는 표 구조
  • Wilkinson 외 (2016). The FAIR Guiding Principles for scientific data management and stewardship. Scientific Data, 3, 160018. — 재사용 가능한 과학 데이터의 원칙