포스트

Pandas 기초 (2) - Series와 DataFrame

pandas의 두 자료구조인 Series와 DataFrame의 구조, 열 선택 문법, 연산마다 따라붙는 인덱스 정렬이라는 숨은 동작, 그리고 인덱스를 올리고 내리는 set_index와 reset_index를 정리했습니다.

Pandas 기초 (2) - Series와 DataFrame

Pandas를 기초부터 정리하는 시리즈의 두 번째 글입니다. 시리즈 소개와 진행 순서는 0편을, pandas를 쓰는 이유는 1편을 참고하세요. 앞선 NumPy 기초 시리즈의 배열 개념 위에서 pandas를 시작합니다.

pandas는 NumPy 배열에 라벨을 붙인 것

pandas의 두 축은 Series(1차원)와 DataFrame(2차원)입니다. 본질은 단순합니다.

  • Series: NumPy 배열 + 인덱스(라벨)
  • DataFrame: 같은 인덱스를 공유하는 Series들의 묶음. 열마다 dtype이 달라도 됩니다.
1
2
3
4
5
6
7
8
9
import pandas as pd

s = pd.Series([92, 85, 77], index=["김철수", "이영희", "박민수"])
s["이영희"]     # 85, 위치가 아니라 이름으로 접근

df = pd.DataFrame({
    "student": ["김철수", "이영희", "박민수"],
    "score": [92, 85, 77],
})

NumPy가 “위치”로만 데이터를 다룬다면, pandas는 “이름”으로 다룰 수 있게 해줍니다. 2번째 열이 아니라 "score" 열, 1024번째 행이 아니라 "이영희" 행으로 말할 수 있게 되는 것, 이것이 pandas의 존재 이유입니다.

Series를 열어 보면 값과 라벨 두 부분이 그대로 보입니다.

1
2
3
s.to_numpy()   # array([92, 85, 77]), 값 부분은 NumPy 배열 그대로
s.index        # Index(['김철수', '이영희', '박민수'], dtype='object')
s.dtype        # int64, 값 부분의 dtype

1편에서 본 “column 하나마다 하나의 ndarray”가 바로 이 값 부분입니다. pandas가 마법을 부리는 것이 아니라, NumPy 배열 옆에 라벨을 붙여 관리해 주는 것뿐입니다.

열 선택: 한 겹이면 Series, 두 겹이면 DataFrame

DataFrame에서 열을 꺼내는 기본 문법은 대괄호이고, 무엇을 넣는지에 따라 결과 타입이 달라집니다.

1
2
3
4
df["score"]                # Series: 열 하나
df[["student", "score"]]   # DataFrame: 열 이름의 목록을 넣으면 표가 나온다
df[["score"]]              # 열 하나짜리 DataFrame: Series와는 다른 물건
df["score"].to_numpy()     # 무거운 수치 계산은 NumPy로 꺼내서

대괄호 안에 열 이름 하나를 넣으면 Series가 나오고, 열 이름의 목록을 넣으면(그래서 대괄호가 두 겹으로 보입니다) DataFrame이 나옵니다. 열 하나짜리 DataFrame과 Series는 타입이 달라서, 함수가 어느 쪽을 요구하는지에 따라 구분해서 넘겨야 합니다.

숨은 동작: 인덱스 정렬(alignment)

라벨의 대가로 pandas에는 NumPy에 없는 동작이 항상 따라붙습니다. 두 Series를 연산하면 위치가 아니라 인덱스 라벨이 같은 것끼리 계산됩니다.

1
2
3
4
5
6
7
8
midterm = pd.Series([88, 92, 75], index=["김철수", "이영희", "박민수"])
final   = pd.Series([90, 80, 85], index=["이영희", "박민수", "정수진"])

midterm + final
# 김철수      NaN     <- 기말 명단에 없음 (수강 철회)
# 박민수    155.0     <- 75 + 80
# 이영희    182.0     <- 92 + 90
# 정수진      NaN     <- 중간 명단에 없음 (수강 정정으로 추가)

중간과 기말 명단의 순서가 달라도 이름만 맞으면 올바르게 합산된다는 점에서 강력한 기능이지만, 모르고 있으면 “왜 갑자기 NaN이 쏟아지지?”의 원인이 됩니다. 서로 다른 곳에서 온 두 데이터를 연산했는데 NaN이 보인다면 십중팔구 인덱스가 어긋난 것입니다. 위처럼 수강 철회나 정정으로 명단이 달라진 경우가 전형적입니다.

결과에서 눈여겨볼 점이 하나 더 있습니다. 정수끼리 더했는데 합계가 155.0처럼 소수점을 달고 있습니다. NaN이 부동소수점 전용 값이라서, NaN이 하나라도 생기면 정수 컬럼 전체가 float로 승격되기 때문입니다. 지금은 현상만 기억해 두면 됩니다. 이 승격이 일으키는 사고와 대처법은 6편에서 정리합니다.

인덱스는 올릴 수도 내릴 수도 있다: set_index, reset_index

인덱스가 DataFrame을 만들 때 자동으로 붙는 0, 1, 2…뿐이라면 라벨의 힘을 쓰지 못하는 것입니다. 의미 있는 컬럼을 set_index로 인덱스로 승격시키면 라벨 접근이 강력해집니다. 특히 datetime 인덱스는 부분 문자열 슬라이싱이 열립니다.

1
2
3
4
ts = df.set_index("submitted_at")     # 과제 제출 로그의 제출 시각을 인덱스로

ts.loc["2024-06-15"]                  # 그 날 하루치 행 전부
ts.loc["2024-06-15":"2024-06-21"]     # 한 주치 슬라이스

조건식으로 범위를 표현할 필요 없이 날짜 문자열만으로 자를 수 있습니다. 시계열 데이터를 다룰 때 pandas가 진가를 발휘하는 지점입니다.

반대 방향이 reset_index()로, 인덱스를 일반 컬럼으로 되돌립니다. 5편에서 보게 될 groupby 결과는 그룹 키가 인덱스로 오기 때문에, 평평한 표로 저장하거나 이어서 가공하려면 reset_index가 마무리 관용구처럼 붙습니다.

오가는 기준은 이렇게 잡으면 됩니다. 라벨로 접근하고 슬라이스할 때는 인덱스로 올리고, 저장하거나 merge할 때는 컬럼으로 내린다.

정리

개념한 줄 요약
SeriesNumPy 배열 + 인덱스(라벨). .to_numpy()로 값만 꺼낸다
DataFrame같은 인덱스를 공유하는 Series들의 묶음
열 선택대괄호에 이름 하나면 Series, 목록(두 겹)이면 DataFrame
인덱스 정렬연산은 위치가 아니라 라벨끼리. 갑작스러운 NaN의 주범
NaN과 dtypeNaN이 생기면 정수 컬럼이 float로 승격된다 (6편에서 상세히)
set_index / reset_index라벨로 다룰 때는 올리고, 저장하거나 merge할 때는 내린다

구조를 이해했으니 다음은 실전의 시작입니다. 다음 편에서는 파일에서 데이터를 읽는 법(CSV, Parquet)과 읽자마자 실행해야 할 첫 점검 루틴을 다룹니다.

다음 글: Pandas 기초 (3) - 데이터 읽기와 첫 점검

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.