본문 바로가기
ML & DL/시각화 도구

Seaborn tutorial (2-2) : displot() - kernel density

by 별준 2020. 11. 6.

이전글 : 2020/11/05 - [ML and DL] - Seaborn tutorial (2-1) : displot() - histogram

 

Seaborn tutorial (2-1) : displot() - histogram

* v0.11.0 기준으로 작성되었습니다. 이번에는 seaborn의 데이터 분포 시각화에 대해서 알아보도록 하겠습니다. - Visualizing distributions of data 데이터의 분포를 시각화하기 위한 API는 다양합니다. 이전

junstar92.tistory.com

Kernel Density Estimation(KDE)

이전 게시글에 이어서, 데이터 분포 시각화를 위한 API인 displot()에서 kernel density를 나타낼 수 있는 기능에 대해서 알아보도록 하겠습니다. 이전에 알아봤던 histogram은 관측치를 막대그래프(bin)를 이산형(discrete)로 나타내는 반면, KDE는 카우스 커널을 사용해서 그래프를 연속적으로 부드럽게 나타냅니다.

 

이전 게시글에서 사용하던 펭귄에 대한 dataset을 그대로 가져와서, plot을 그리면 되는데, kernel density를 그리기 위해서는 kind="kde" 파라미터를 추가해주면 됩니다.

sns.displot(penguins, x="flipper_length_mm", kind="kde")

KDE는 측정값을 부드럽게 나타내기 때문에, 자세한 정보가 그래프에 표시되지 않을 수 있습니다. Histogram에서 bin의 크기를 조절한 것처럼, KDE에서는 그래프의 bandwidth를 조절할 수 있는데, 이 bandwidth에 의해서 데이터가 얼마나 정확하게 나타나는지 결정됩니다. 즉, 너무 스무스한 그래프(bandwidth를 크게)는 중요한 특징들을 나타내지 않고, 반대로 bandwidth를 작게하면 noise가 많이 생기는 것을 볼 수 있다.

sns.displot(penguins, x="flipper_length_mm", kind="kde", bw_adjust=2)

sns.displot(penguins, x="flipper_length_mm", kind="kde", bw_adjust=.25)

Conditioning on other variables

histogram과 동일하게, 'hue' 파라미터를 사용해서 'hue'에 들어가는 각 변수값에 대해서 각각의 density를 나타낼 수 있습니다.

sns.displot(penguins, x="flipper_length_mm", hue="species", kind="kde")

또한, 'multiple' 파라미터를 통해서 KDE를 다양한 방법으로 표시할 수 있습니다.(default : 'layer')

sns.displot(penguins, x="flipper_length_mm", hue="species", kind="kde", multiple="stack")

하지만, 위와 같이 stacked plot은 데이터를 비교하기에 조금 힘들어 보입니다. 이때, 'fill' 파라미터를 사용하면, 밀도 내부를 opacity를 적용해서 채울 수 있습니다. (default : False)

sns.displot(penguins, x="flipper_length_mm", hue="species", kind="kde", fill=True)

 

Kernel density estimation pitfalls

KDE는 기본적으로 데이터의 분포가 매끄럽게 연결되어 있다고 가정합니다. 따라서, 데이터를 자세하게 나타내지 못할 수 있으며, 데이터에 이상치가 존재하는 경우나 경계에 가까이 있는 데이터가 있는 경우에는 KDE 그래프가 실제가 아닌 값으로 확장될 수 있습니다. 그래프를 보면서 다시 설명하도록 하겠습니다.

이번에는 histogram에서 사용했던, tips dataset을 불러와서, KDE 그래프를 그려보도록 하겠습니다.

tips = sns.load_dataset("tips")
sns.displot(tips, x="total_bill", kind="kde")

위 그래프를 살펴보면, total_bill이 음수인 경우까지 그래프가 표시되고 있습니다. 실제로 total_bill의 최소값은 3.07인데, 실제 값보다 더 확장되어서 그래프가 그려지고 있습니다.

tips.describe() 를 통해 확인 가능

이런 경우에는 'cut' 파라미터를 통해서 밀도 함수가 얼마나 확장되어야하는지 설정할 수 있습니다. 이 파라미터를 '0'으로 설정하면 그래프를 데이터의 limit값에서 잘라서 그리게 됩니다.

sns.displot(tips, x="total_bill", kind="kde", cut=0)

KDE를 사용할 때, 주의해야 할 점은 데이터 자체가 매끄럽지 않는 경우에도 KDE 그래프는 항상 부드러운 곡선으로 나타낸다는 것입니다. diamonds dataset을 읽어와서 kde 그래프가 실제 분포와 얼마나 다른지 비교해보도록 하겠습니다.

diamonds = sns.load_dataset("diamonds")
diamonds.head()

sns.displot(diamonds, x="carat", kind="kde")

실제 분포와 얼마나 다르게 표시되는지 확인해보도록 하겠습니다. displot()을 기본 histogram으로 사용하고, 'kde' 파라미터를 True로 설정하면, histogram과 kde를 한 그래프에 나타낼 수 있습니다.

sns.displot(diamonds, x="carat", kde=True)

그래프를 보면 알 수 있듯이, 특정값이 과하게 크다면 kde 그래프가 데이터를 제대로 나타내지 못하는 것을 볼 수 있습니다.

 

두 데이터의 분포 시각화

historgram과 마찬가지로, 'x', 'y' 변수를 모두 사용해서 kde 그래프를 나타낼 수 있습니다. 이 그래프를 2D 가우시안을 사용해서 등고선으로 나타냅니다.

sns.displot(penguins, x="bill_length_mm", y="bill_depth_mm", kind="kde")

마찬가지로 'hue' 파라미터를 통해서 조건부 등고선을 추가할 수 있습니다.

sns.displot(penguins, x="bill_length_mm", y="bill_depth_mm", hue="species", kind="kde")

다만, 등고선을 해석하기에 조금 어려움이 있습니다. 밀도를 직접 해설할 수 없기 때문에, 등고선은 밀도의 iso-proportions로 그려지게 됩니다. 각 곡선이 밀도의 일정 비율로 그려지게 된다는 의미로 해석이 되는데, 정확한 의미는 잘 모르겠네요..

등고선은 'thresh'로 어떤 비율로 등고선을 그릴지 설정하고, 표시할 등고선의 레벨의 개수는 'levels' 매개변수를 통해서 설정할 수 있습니다.

sns.displot(penguins, x="bill_length_mm", y="bill_depth_mm", kind="kde", thresh=.2, levels=4)

levels가 4이므로 등고선을 4개의 레벨로 나누어서 표시하고 있습니다. 그리고, thresh를 통해서 등고선의 밀도 비율이 설정되고 있습니다. thresh 매개변수의 값은 0에서 1사이의 값만 유효합니다.(0미만, 1이상일 경우 에러 발생)

 

'levels'는 list로도 입력할 수 있습니다.

sns.displot(penguins, x="bill_length_mm", y="bill_depth_mm", kind="kde", levels=[.01, .05, .1, .8])

 

 

여기까지 기본적인 kde 그래프 사용법이었습니다. 이전 글에서 relplot()에 대해서 설명할 때, col / col_wrap / row 등의 매개변수를 사용했었는데, 이런 기본적인 파라미터는 displot()에서도 거의 동일하게 사용됩니다. 튜토리얼에서 사용된 API나 파라미터 외에 더 많은 종류의 파라미터가 존재하니, 더 깊게 알고싶다고 한다면 공식 홈페이지를 참조하는 것을 추천드립니다 !

댓글