Pandas 기초 (1) - Why we use Pandas
Pandas를 왜 사용하는지를 정리합니다.
Pandas를 기초부터 정리하는 시리즈의 첫 글입니다. 시리즈 소개와 실습 환경 준비는 0편을 참고하세요.
Pandas 소개
Pandas는 Python에서 table 형태의 데이터를 다루는 표준 라이브러리입니다. Pandas를 사용하는 이유는 여러 가지가 있지만, 핵심은 아래 세 가지입니다.
- Label: 데이터를 index가 아니라 column name으로 다룰 수 있습니다.
- Mixed type: column마다 data type이 다른 표를 그대로 담을 수 있습니다.
- Data tools: 파일 읽기, missing value 처리, aggregation 등 table 작업에 필요한 기능이 갖추어져 있습니다.
특히 pandas는 1차원과 2차원 데이터를 다루는 데 특화되어 있습니다. 3차원 이상의 데이터는 pandas의 대상이 아닙니다.
NumPy만으로는 뭐가 문제였는가?
NumPy 기초 1편에서 본 것처럼, ndarray가 빠른 이유는 연속된 메모리에 동일한 dtype의 값을 저장하기 때문입니다. 하지만 현실의 데이터는 하나의 data type으로 통일되어 있지 않습니다. 성적표만 보더라도 숫자, 날짜, 글자가 한 표에 섞여 있습니다.
Problem 1: mixed type은 object ndarray가 된다
mixed type 데이터를 ndarray에 담으면 object ndarray가 됩니다.
1
2
3
4
5
import pandas as pd
df = pd.read_csv("https://blog.hoseunggg.com/assets/data/grades.csv")
arr = df.to_numpy()
arr.dtype # dtype('O'), object ndarray라는 뜻입니다.
object ndarray는 element 자리에 Python object를 가리키는 pointer를 담습니다. 이름은 ndarray지만 메모리 구조는 Python list와 같습니다. 그래서 element마다 인터프리터가 개입하게 되고, NumPy 특유의 빠른 속도를 잃습니다.
Problem 2: index로만 데이터를 가리킬 수 있다
ndarray에는 column name이 없습니다. 성적 column을 꺼내려면 “index 6”이라는 위치로 지정해야 합니다.
1
scores = arr[:, 6].astype(float) # 6이 성적 column이라는 정보는 코드에 없다
이 코드의 가장 큰 문제는 유지보수성입니다. column이 하나 추가되면 index가 밀리면서, 같은 코드가 엉뚱한 column을 가리키게 됩니다.
Problem 3: missing value가 계산을 무너뜨린다
missing value가 있으면 NumPy의 기본 연산 결과가 통째로 NaN이 됩니다.
1
2
3
4
5
import numpy as np
scores = df["성적"].to_numpy()
scores.mean() # nan, 결측 2건 때문에 전체가 nan
np.nanmean(scores) # 결측을 제외한 평균
성적 데이터에는 병결로 응시하지 못한 시험 2건이 빈칸으로 들어 있습니다. 빈칸이 NaN으로 읽히기 때문에 평균 전체가 NaN이 됩니다. nan 계열 함수로 바꿔 쓰면 해결되지만, 모든 연산마다 결측을 의식하고 함수를 골라 써야 한다는 뜻이기도 합니다.
pandas는 이 세 문제를 table 하나로 해결한다
같은 데이터를 pandas로 다루면 이렇게 됩니다.
1
df["성적"].mean() # column은 이름으로 부르고, 결측은 알아서 제외
- column을 index가 아니라 column name으로 부릅니다. column 순서가 바뀌어도 코드는 그대로입니다.
- aggregation 연산이 missing value를 기본으로 건너뜁니다.
1
2
3
4
Pandas DataFrame
┌───────────┬───────────┬───────────┐
│ ndarray 1 │ ndarray 2 │ ndarray 3 │
└───────────┴───────────┴───────────┘
속도를 지킬 수 있는 이유는 DataFrame의 구조에 있습니다. DataFrame은 표 전체를 하나의 ndarray에 담지 않고, column 하나마다 하나의 ndarray를 둡니다.
1
2
3
4
5
df.dtypes
# 과목명 object
# 학점 int64
# 성적 float64
# ...
column 하나 안에서는 dtype이 하나이므로, column 단위 연산에는 NumPy의 vectorization이 그대로 적용됩니다. 속도는 NumPy에서 오고, label 관리는 pandas가 맡습니다.
같은 작업을 나란히 비교
과목별 평균 점수를 구해 보겠습니다. NumPy로는 ndarray를 직접 쪼개고 결측도 직접 챙겨야 합니다.
1
2
3
4
5
6
courses = df["과목명"].to_numpy()
scores = df["성적"].to_numpy()
means = {}
for course in np.unique(courses):
means[course] = np.nanmean(scores[courses == course])
pandas로는 한 줄입니다.
1
df.groupby("과목명")["성적"].mean()
결과는 같지만, pandas 쪽은 “과목별로 묶어 성적 평균”이라는 의도가 코드에 그대로 읽히고, 과목이 늘어도 코드가 변하지 않습니다. 이 집계 문법(groupby)은 5편에서 자세히 다룹니다.
파일 읽기부터 이미 차이가 났습니다. 위에서 아무 설명 없이 쓴 pd.read_csv는 column마다 타입을 추론하고, 헤더를 column 이름으로 올리고, 빈칸을 missing value로 바꿔서 표를 만들어 줍니다. NumPy에도 텍스트 파일을 읽는 함수(np.genfromtxt)가 있지만, mixed type 표를 읽는 순간 위의 object 문제로 돌아갑니다.
그래도 NumPy가 맞는 자리가 있다
pandas가 NumPy를 대체하는 것은 아닙니다. 모든 element가 같은 dtype인 수치 데이터, 행렬 연산, 이미지 처리는 NumPy가 맞습니다. label을 관리하는 비용이 없어서 더 빠르고 메모리도 덜 씁니다.
기준은 데이터의 모양입니다. 균질한 수치 ndarray면 NumPy, column마다 성격이 다른 표면 pandas입니다. 실제 분석에서는 표 전체를 pandas로 다루다가, 무거운 수치 계산 부분만 .to_numpy()로 꺼내 NumPy로 처리하는 조합이 일반적입니다.
다음 편에서는 pandas의 두 자료구조인 Series와 DataFrame의 구조를 자세히 들여다봅니다.