AICE (에이스)

AICE (에이스) 시험 노트개념 정리14 MIN

seaborn으로 분포와 상관 보기

AICE Associate의 시각화 문항이 요구하는 그림을 정리합니다. countplot과 histplot이 갈리는 자리, boxplot·barplot·scatterplot의 쓰임, corr과 heatmap으로 상관을 읽는 법, 한글이 깨질 때의 폰트 설정을 연습 문제 7개로 짚습니다.

표의 상태를 숫자로 재는 것까지 됐으면 다음은 눈으로 보는 자리입니다. 숫자만으로는 안 보이는 것이 있기 때문입니다 — 평균이 같아도 분포는 한 덩어리일 수도 있고 두 무리로 갈려 있을 수도 있고, 상관계수가 0인데 곡선 관계가 또렷할 수도 있습니다.

seaborn은 matplotlib 위에 얹힌 시각화 라이브러리로, DataFrame과 열 이름을 그대로 받아 그림을 그립니다. AICE Associate는 「지역별 고객 수를 막대그래프로 나타내시오」·「수치형 변수 간 상관관계를 히트맵으로 시각화하시오」처럼 어느 그림을 쓸지를 고르게 하는 문항을 여기서 냅니다. 관례상 sns로 줄여 부릅니다.

import seaborn as sns
import matplotlib.pyplot as plt

어느 그림을 쓸까

변수의 개수와 종류

그림을 고르는 기준은 둘입니다 — 변수가 몇 개인지, 그리고 그 변수가 갈래인지 수치인지입니다. 갈래형은 지역·등급처럼 값이 정해진 몇 가지 중 하나인 열이고, 수치형은 나이·소득처럼 크기를 재는 열입니다.

보려는 것 그림
갈래 하나의 빈도 countplot
수치 하나의 분포 histplot
갈래별 수치의 분포 boxplot
갈래별 수치의 평균 barplot
수치 둘의 관계 scatterplot
수치 여럿의 상관 heatmap

이 표를 외우는 것보다 세로축에 무엇이 오는가를 묻는 편이 빠릅니다. 세로축이 개수면 countplot이고, 다른 열의 값이면 barplot이나 boxplot입니다.

공통 인자

seaborn의 함수들은 인자 이름이 거의 같습니다. data에 표를, x와 y에 열 이름을 문자열로 주고, hue에 색으로 더 가를 열을 줍니다.

sns.countplot(data=df, x='City')
plt.show()

plt.show()를 붙이는 이유는 그림을 실제로 띄우기 위해서입니다. 주피터 노트북에서는 마지막 줄이면 없어도 나오지만, 붙여 두면 함수가 돌려준 객체 정보가 같이 찍히는 것을 막아 줍니다.

하나짜리 그림

countplot

countplot은 갈래마다 행이 몇 개인지를 셉니다. 세로축이 개수라서 y를 줄 필요가 없습니다.

sns.countplot(data=df, x='City')
sns.countplot(data=df, x='Churn', hue='Gender')
sns.countplot(data=df, y='City')     # 가로 막대

갈래 이름이 길어 겹칠 때는 x 대신 y에 주어 가로로 눕힙니다. 타깃의 갈래별 개수를 세어 한쪽으로 심하게 치우쳤는지 보는 데도 씁니다 — 그것이 뒤에서 다룰 불균형 문제의 첫 신호입니다.

histplot

histplot은 수치형 열의 값을 구간으로 나누고 구간마다 개수를 셉니다. 구간을 빈이라고 부르고 bins로 개수를 정합니다.

sns.histplot(data=df, x='Age', bins=20)
sns.histplot(data=df, x='Age', kde=True)

kde=True를 주면 막대 위에 매끄러운 곡선이 함께 그려져 분포의 모양을 보기 좋습니다.

countplot과 histplot이 헷갈리는 짝인데, 가르는 선은 세는 대상이 이미 갈래로 나뉘어 있는가입니다. 지역은 Seoul·Busan처럼 값이 이미 갈라져 있으니 countplot이고, 나이는 연속된 수라서 먼저 구간으로 잘라야 하니 histplot입니다.

boxplot

boxplot은 앞 글에서 계산한 사분위수와 IQR 경계를 그대로 그린 그림입니다. x에 갈래 열, y에 수치 열을 주면 갈래마다 상자가 하나씩 섭니다.

sns.boxplot(data=df, x='Grade', y='Income')

등급별로 소득의 중앙값이 어떻게 다른지, 어느 등급에 이상치 후보가 몰려 있는지를 한 번에 봅니다.

관계를 보는 그림

barplot

barplot은 갈래마다 수치 열의 평균을 막대 높이로 그립니다. countplot이 개수를 세는 데 반해 이쪽은 다른 열의 값을 집계합니다.

sns.barplot(data=df, x='City', y='Income')

막대 위에 가느다란 선이 함께 그려지는데, 그 선은 평균이 흔들릴 수 있는 범위를 뜻합니다. 지워도 되면 errorbar=None을 줍니다.

scatterplot

scatterplot은 수치형 두 열을 가로·세로축에 놓고 행마다 점 하나를 찍습니다. 두 값이 함께 커지는지, 반대로 움직이는지, 아무 관계가 없는지가 점의 모양으로 보입니다.

sns.scatterplot(data=df, x='Age', y='Income')
sns.scatterplot(data=df, x='Age', y='Income', hue='Churn')

hue에 타깃을 주면 점이 갈래마다 다른 색으로 찍혀서, 두 무리가 갈리는 자리가 있는지 눈으로 보입니다. 분류 문제에서 피처를 고를 때 쓰는 방법입니다.

상관

corr

상관계수는 두 수치형 변수가 함께 움직이는 정도를 −1-1과 11 사이의 수로 나타낸 값입니다. 1에 가까우면 한쪽이 커질 때 다른 쪽도 커지고, −1-1에 가까우면 반대로 움직이며, 0에 가까우면 직선 관계가 없습니다.

df.corr(numeric_only=True)
df[['Age', 'Income', 'Tenure']].corr()

numeric_only=True를 주는 이유는 문자 열이 섞인 표에서 오류가 나는 것을 막기 위해서입니다. 결과는 열 이름이 행에도 열에도 똑같이 붙은 정사각형 표이고, 대각선은 자기 자신과의 상관이라 언제나 1입니다.

주의할 것이 둘입니다. 상관계수는 직선 관계만 재므로 뚜렷한 곡선 관계가 있어도 0에 가까울 수 있고, 값이 크다고 해서 한쪽이 다른 쪽의 원인이라는 뜻은 아닙니다.

heatmap

상관표는 열이 열 개만 되어도 100칸이라 숫자로는 못 읽습니다. heatmap이 그 표의 값을 색의 진하기로 바꿔 줍니다.

corr = df.corr(numeric_only=True)
sns.heatmap(corr, annot=True, cmap='coolwarm', fmt='.2f')
plt.show()

annot=True가 칸마다 숫자를 함께 적고, fmt='.2f'가 소수점 둘째 자리까지 자릅니다. cmap='coolwarm'은 음수를 파랑, 양수를 빨강으로 칠해 방향까지 색으로 보이게 합니다.

heatmap에 넘기는 것은 원래 표가 아니라 상관표입니다. sns.heatmap(df)라고 적으면 표의 값을 그대로 색칠해 버려 아무 뜻이 없는 그림이 나옵니다.

한글 폰트

깨짐의 원인

그림 안의 한글이 네모로 나오는 것은 데이터 문제가 아니라 matplotlib이 쓰는 기본 폰트에 한글 글자가 없기 때문입니다. 쓸 폰트를 지정하면 해결됩니다.

plt.rc('font', family='Malgun Gothic')    # 윈도우
plt.rc('font', family='AppleGothic')      # 맥

코랩처럼 한글 폰트가 아예 없는 환경에서는 나눔 폰트를 먼저 설치하고 family='NanumBarunGothic'으로 지정합니다.

마이너스 부호

폰트를 바꾸면 이번에는 음수의 빼기 기호가 네모로 나옵니다. 한글 폰트에 그 기호가 없는 경우가 있어서인데, 아스키 빼기로 대신 그리게 하면 됩니다.

plt.rc('axes', unicode_minus=False)

두 줄을 노트북 맨 위 한 번만 적어 두면 그 뒤의 모든 그림에 적용됩니다.

이 영역의 출제 형태는 대개 셋입니다. 목적을 글로 주고 알맞은 함수를 고르게 하거나, 함수 이름을 주고 빈 인자를 채우게 하거나, 히트맵을 주고 어느 두 변수의 상관이 가장 높은지 읽게 합니다.

연습 문제

  1. 지역별 고객 수를 막대그래프로 그리려 한다. 알맞은 것은?
    ① sns.scatterplot(data=df, x='City')
    ② sns.countplot(data=df, x='City')
    ③ sns.barplot(data=df, x='City')
    ④ sns.heatmap(data=df, x='City')
    ②. 세로축에 오는 것이 개수이고 City가 이미 갈래로 나뉘어 있으므로 countplot입니다. ①은 x와 y를 짝지어 점을 찍는 함수라 y 없이는 찍을 자리가 없고, ③은 y에 준 수치 열을 집계해 막대 높이로 삼으므로 y 없이는 높이가 정해지지 않으며, ④는 표를 통째로 받아 칸마다 색을 칠하는 함수라 x 인자 자체가 없습니다.
  2. x = [1, 2, 3, 4, 5]이고 y = [2, 4, 6, 8, 10]일 때 두 변수의 상관계수는 얼마인가?
    1입니다. 모든 점이 y=2xy = 2x라는 하나의 직선 위에 정확히 놓이고 기울기가 양수이므로 완전한 양의 상관입니다. 기울기의 크기는 상관계수에 영향을 주지 않습니다.
  3. sns.countplot과 sns.barplot은 무엇이 다른가?
    countplot은 갈래마다 행이 몇 개인지를 세어 막대 높이로 그리므로 세로축이 개수입니다. barplot은 y에 준 수치 열을 갈래마다 집계해 그리고 기본 집계가 평균이므로 세로축이 그 열의 평균입니다.
  4. df.corr(numeric_only=True)의 결과에서 대각선 값이 모두 1인 이유는?
    대각선은 각 변수와 자기 자신의 상관계수이기 때문입니다. 자기 자신과는 언제나 완전히 같은 방향으로 같은 비율로 움직이므로 1이 나옵니다.
  5. 그림 안의 한글이 네모로 나온다. 원인과 해결 방법을 쓰시오.
    matplotlib의 기본 폰트에 한글 글자가 없어서입니다. plt.rc('font', family='Malgun Gothic')처럼 한글이 든 폰트를 지정하면 됩니다. 이때 음수 기호가 다시 깨질 수 있으므로 plt.rc('axes', unicode_minus=False)를 함께 적습니다.
  6. 나이와 소득의 상관계수가 0.02로 나왔다. 「두 변수는 아무 관계가 없다」고 결론지어도 되는가?
    안 됩니다. 상관계수는 직선 관계의 세기만 재므로, 중년까지 오르다 이후 내려가는 곡선 관계가 있어도 0에 가까운 값이 나옵니다. scatterplot으로 점을 직접 찍어 모양을 확인해야 합니다.
  7. 수치형 열 다섯 개의 상관을 숫자까지 적힌 히트맵으로 그리시오.
    상관표를 먼저 만들고 그것을 heatmap에 넘깁니다.
    corr = df[['Age', 'Income', 'Tenure', 'Visits', 'Spend']].corr()
    sns.heatmap(corr, annot=True, fmt='.2f', cmap='coolwarm')
    plt.show()

    annot=True가 칸마다 값을 적습니다. 원래 표인 df를 그대로 넘기면 값 자체를 색칠한 뜻 없는 그림이 나옵니다.

여기까지가 데이터 분석 과목입니다. 표를 읽고, 골라 세고, 상태를 재고, 그려 보는 네 걸음이 한 벌입니다. 다음 과목은 데이터 전처리이고, 여기서 찾아 둔 빈 칸과 튀는 값을 실제로 손보는 자리입니다 — 결측치를 지울지 채울지부터 정합니다.

AICE (에이스) 시험 노트 전체 보기