FacetGrid
"요일별로, 흡연 여부별로 따로따로 그리지 말고, 한 번에 격자로 보여주면 어때?"
강주원 선배의 제안에 민서는 눈을 반짝였습니다. 슬라이드를 여러 장 넘기는 대신, 하나의 그림에서 조건별 차이를 바로 비교할 수 있다면 훨씬 설득력 있는 발표가 됩니다.
그 도구가 바로 FacetGrid입니다.
FacetGrid란 무엇인가
FacetGrid는 데이터의 특정 열을 기준으로 차트를 행(row)과 열(col)로 분할해 격자 형태로 배치하는 도구입니다.
핵심 아이디어는 간단합니다. 데이터를 조건별로 쪼개고, 각 조건에 같은 유형의 차트를 그립니다. 예를 들어 "시간대(Lunch/Dinner)" 기준으로 열을 나누고, "흡연 여부(Yes/No)" 기준으로 행을 나누면 2×2 격자가 만들어집니다.
중요한 점은, relplot, catplot, displot 같은 Figure-level 함수들이 내부적으로 FacetGrid를 사용합니다. 즉, 지금까지 col= 파라미터를 써서 차트를 나눴던 것이 바로 FacetGrid를 활용한 것입니다. FacetGrid를 직접 다루면 그보다 세밀하게 제어할 수 있습니다.
기본 사용법
FacetGrid는 두 단계로 사용합니다. 먼저 격자 구조를 만들고, 그 다음 각 칸에 차트를 그립니다.
# 파일: facetgrid_basic.pyimport seaborn as snsimport matplotlib.pyplot as plttips = sns.load_dataset("tips")# 1단계: 격자 구조 만들기g = sns.FacetGrid(tips, col="time", row="smoker")# 2단계: 각 칸에 차트 그리기g.map_dataframe(sns.scatterplot, x="total_bill", y="tip")plt.show()
col="time"은 time 열의 고유값(Lunch, Dinner)으로 열을 나눕니다. row="smoker"는 smoker 열의 고유값(Yes, No)으로 행을 나눕니다. 결과는 2행 2열, 총 4개의 산점도입니다.
col과 row, hue 조합하기
세 가지 파라미터를 조합하면 데이터를 3차원으로 쪼갤 수 있습니다.
# 파일: facetgrid_hue.pyimport seaborn as snsimport matplotlib.pyplot as plttips = sns.load_dataset("tips")# col로 열 분할, hue로 색상 분류g = sns.FacetGrid(tips, col="time", hue="sex")g.map_dataframe(sns.scatterplot, x="total_bill", y="tip")g.add_legend()plt.show()
hue="sex"는 격자를 추가로 나누지 않고, 각 칸 안에서 성별에 따라 색상을 구분합니다. 같은 칸 안에서 두 그룹을 겹쳐서 비교할 수 있습니다.
col_wrap으로 열 수 제한하기
범주가 많을 때 col을 사용하면 차트가 가로로 너무 넓어집니다. col_wrap으로 한 행에 표시할 최대 열 수를 제한합니다.
# 파일: facetgrid_colwrap.pyimport seaborn as snsimport matplotlib.pyplot as plttips = sns.load_dataset("tips")# day 열은 4개 값: Thur, Fri, Sat, Sun# col_wrap=2이면 2열씩 배치해서 2행 2열 격자가 됩니다g = sns.FacetGrid(tips, col="day", col_wrap=2, height=4)g.map_dataframe(sns.histplot, x="total_bill")# 각 칸의 제목을 더 명확하게g.set_titles(col_template="{col_name}요일")g.set_axis_labels("총 금액", "빈도")plt.show()
height는 각 칸의 높이(인치)를 설정합니다. col_wrap을 사용할 때는 row를 함께 사용할 수 없습니다.
map과 map_dataframe의 차이
FacetGrid에서 차트를 그리는 방법은 두 가지입니다.
map()은 각 칸에 해당하는 데이터를 배열 형태로 넘깁니다. 오래된 스타일이며, Seaborn 구버전과 호환됩니다.
map_dataframe()은 데이터프레임을 통째로 넘기고, 그 안에서 x, y 컬럼명을 지정합니다. 더 직관적이고 Pandas 스타일과 일치합니다.
# 파일: facetgrid_map_compare.pyimport seaborn as snsimport matplotlib.pyplot as plttips = sns.load_dataset("tips")# map_dataframe 방식 (권장)g = sns.FacetGrid(tips, col="time")g.map_dataframe(sns.boxplot, x="day", y="total_bill", order=["Thur", "Fri", "Sat", "Sun"])g.set_axis_labels("요일", "총 금액 ($)")g.set_titles("{col_name}")plt.tight_layout()plt.show()
set_titles()의 {col_name} 플레이스홀더는 각 칸의 분류값으로 자동 치환됩니다.
격자 크기와 비율 조정
# 파일: facetgrid_size.pyimport seaborn as snsimport matplotlib.pyplot as plttips = sns.load_dataset("tips")# height: 각 칸의 높이(인치), aspect: 가로/세로 비율g = sns.FacetGrid(tips, col="time", row="smoker", height=3, aspect=1.2)g.map_dataframe(sns.scatterplot, x="total_bill", y="tip", alpha=0.6)g.set_axis_labels("총 금액 ($)", "팁 ($)")plt.show()
aspect=1.2이면 각 칸의 가로 길이가 높이의 1.2배가 됩니다.
relplot, catplot, displot과의 관계
지금까지 배운 Figure-level 함수들은 내부적으로 FacetGrid를 생성하고 반환합니다.
# 파일: facetgrid_figure_level.pyimport seaborn as snsimport matplotlib.pyplot as plttips = sns.load_dataset("tips")# 이 두 코드는 동일한 결과를 만듭니다# 방법 1: relplot 사용 (내부에서 FacetGrid 생성)g1 = sns.relplot(data=tips, x="total_bill", y="tip", col="time", kind="scatter")# 방법 2: FacetGrid 직접 사용g2 = sns.FacetGrid(tips, col="time")g2.map_dataframe(sns.scatterplot, x="total_bill", y="tip")plt.show()
Figure-level 함수가 더 간편하지만, FacetGrid를 직접 사용하면 map_dataframe에 임의의 Matplotlib 함수도 전달할 수 있어 유연합니다.
민서의 정리
"결국 FacetGrid는 데이터를 조건에 따라 쪼개서 같은 모양의 차트를 격자로 늘어놓는 거구나. col로 열 나누고, row로 행 나누고, hue로 색 나누고. 발표 자료에 딱이다."
강주원 선배가 고개를 끄덕였습니다. "다음엔 더 강력한 게 있어. 변수가 여러 개일 때, 모든 쌍의 관계를 한꺼번에 보여주는 방법."