modifying seaborn violin plot

  • Seaborn violin plot은 아름답습니다.
  • 매끈한 곡선은 데이터의 분포를 직관적으로 알게 해 줍니다.
  • colorpalette 매개변수로 violin plot의 색을 지정할 수 있습니다.
  • 그러나 특정 violin plot만 색을 바꾸어 강조하는 방법은 잘 알려져있지 않습니다.

1. 예제 데이터

  • seaborn의 tips 데이터셋을 사용합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 기본 시각화 설정
%matplotlib inline

import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np

sns.set_context("talk")
sns.set_style("white")

# 데이터셋 불러오기
df = sns.load_dataset("tips")
df


2. seaborn violin plot

2.1. 기본 plot

  • 요일별 팁 분포를 그려봅니다.
  • seaborn.violinplot()을 사용합니다.
1
2
fig, ax = plt.subplots(figsize=(10, 3), constrained_layout=True)
sns.violinplot(data=df, x="day", y="total_bill", ax=ax)


  • 기본 설정인 tab10 palette에 따라 violin plot이 채색되었습니다.

2.2. 일괄 색 변경: color

  • color 매개변수를 사용하면 violin plot들의 face color를 일괄적으로 바꿀 수 있습니다.
  • color="gold"를 적용한 결과입니다.
1
2
3
fig, ax = plt.subplots(figsize=(10, 3), constrained_layout=True)
sns.violinplot(data=df, x="day", y="total_bill",
color="gold", ax=ax)


2.3. 팔레트 변경: palette

  • color 대신 palette 매개변수를 사용하면 violin plot마다 다른 색이 적용됩니다.
  • 기본값으로 tab10이 적용되어 있지만, 만약 sns.set_palette()명령으로 기본값을 바꾸었다면 다른 색들이 입혀집니다.
  • 전체 palette를 바꾸지 않아도 sns.violinplot()안에 palette매개변수를 지정하면 해당 plot에 한해 palette가 바뀝니다.
1
2
3
fig, ax = plt.subplots(figsize=(10, 3), constrained_layout=True)
sns.violinplot(data=df, x="day", y="total_bill",
palette="rainbow", ax=ax)


  • color매개변수나 palette 매개변수나 전체를 일괄적으로 바꾸는 것입니다.
  • 만약 이 중에서 일요일만 색을 바꾸고 싶다면 옵션이 없습니다.

3. Matplotlib 객체지향 적용

3.1. seaborn violin plot 해부

matplotlib.artist
matplotlib: Artist tutorial

  • seaborn은 Matplotlib을 편하게 쓰기 위해 만든 라이브러리입니다.
  • 따라서 seaborn으로 그린 그림은 모두 Matplotlib 객체로 이루어져 있습니다.
  • Figure와 Axes를 제외한 구성 요소는 Artist로 이루어져 있고, Artist엔 여러 분류가 있습니다.

Artist class

  • 이 수많은 것들 중 Collection에 주목합시다.

  • 낯설게 보일 지 모르지만 파워포인트에서 여러 도형을 그룹으로 묶어놓은 것이라고 생각할 수 있습니다.

  • 파워포인트에서 그룹에 속한 도형의 선이나 색을 일괄적으로 바꿀 수 있는 것처럼 Matplotlib의 Collection도 포함된 여러 객체의 속성을 한번에 바꿀 수 있습니다.

  • violin plot으로 어떤 Collection들이 생성되었는지 확인합니다.

1
2
print(len(ax.collections))
ax.collections
  • 실행 결과: 8개의 collections가 있습니다.
1
2
3
4
5
6
7
8
9
8
[<matplotlib.collections.PolyCollection at 0x7f61db670a50>,
<matplotlib.collections.PathCollection at 0x7f61db719490>,
<matplotlib.collections.PolyCollection at 0x7f61db680ad0>,
<matplotlib.collections.PathCollection at 0x7f61db680550>,
<matplotlib.collections.PolyCollection at 0x7f61db68c9d0>,
<matplotlib.collections.PathCollection at 0x7f61db6e1cd0>,
<matplotlib.collections.PolyCollection at 0x7f61db699ad0>,
<matplotlib.collections.PathCollection at 0x7f61db699510>]
  • PolyCollectionPathCollection이 번갈아 등장합니다.

  • PolyCollection은 간단히 다각형 객체입니다.

  • PathCollection은 간단히 Matplotlib에서 사전 정의된 도형입니다.

  • 뭔지 잘 모르겠지만 일단 이렇게만 넘어갑시다.

  • 이번에는 Line2D객체를 살펴봅니다.

1
2
print(len(ax.lines))
ax.lines
  • 실행 결과: 8개의 lines객체가 있습니다.
1
2
3
4
5
6
7
8
9
8
[<matplotlib.lines.Line2D at 0x7f61db670f10>,
<matplotlib.lines.Line2D at 0x7f61db680290>,
<matplotlib.lines.Line2D at 0x7f61db680e90>,
<matplotlib.lines.Line2D at 0x7f61db68c2d0>,
<matplotlib.lines.Line2D at 0x7f61db68cdd0>,
<matplotlib.lines.Line2D at 0x7f61db699250>,
<matplotlib.lines.Line2D at 0x7f61db699e90>,
<matplotlib.lines.Line2D at 0x7f61db6a62d0>]
  • 이들의 정체는 바로 이렇습니다.
    • PolyCollectionviolin plot
    • PathCollectionmedian marker
    • 첫번째 Line2Dwhisker
    • 두번째 Line2Dbox입니다.
시각화 코드 보기/접기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
df_sample = df.query("day == 'Sun'")

fig = plt.figure(figsize=(10, 3), constrained_layout=True)
subfigs = fig.subfigures(1, 2, width_ratios=[1, 4])
axs_all = subfigs[0].subplots(1, 1)
sns.violinplot(data=df_sample, y="total_bill", ax=axs_all)
axs_all.set_yticks([])
axs_all.set_ylabel("")

subfigs[1].set_facecolor("beige")
axs_parts = subfigs[1].subplots(1, 4, sharey=True)
titles = ["violin plot\n\n", "median\n\n", "whisker\n\n", "box\n\n"]
artists = ["collections[0]\n", "collections[1]\n", "lines[0]\n", "lines[1]\n"]
objects = ["PolyCollection", "PathCollection", "Line2D", "Line2D"]
for i, (ax, title, artist, object) in enumerate(zip(axs_parts, titles, artists, objects)):
sns.violinplot(data=df_sample, y="total_bill", ax=ax)
parts = ax.collections + ax.lines
[p.remove() for pi, p in enumerate(parts) if i != pi]
ax.set_title(title, fontsize="small", fontweight="bold")
ax.text(0.5, 1.05, artist, transform=ax.transAxes, ha="center", color="brown", fontsize="x-small")
ax.text(0.5, 1.05, object, transform=ax.transAxes, ha="center", color="gray", fontsize="x-small")
ax.set_xticks([])
ax.set_yticks([])
ax.set_ylabel("")

subfigs[1].suptitle("Anatomy of seaborn violin plot")


  • whisker와 box는 box-whisker plot의 그것들이 맞습니다.

3.2. 객체 제어

  • 일단 객체로 분리되면 그 다음은 수월합니다.

  • .get_속성() 명령으로 속성을 가져오고 .set_속성(속성값) 명령으로 속성을 바꿀 수 있습니다.

  • 우리 목적은 일요일 데이터 강조입니다.

  • 일요일 데이터를 강조하기 위해 나머지 톤을 죽입니다.

1
2
3
fig, ax = plt.subplots(figsize=(10, 3), constrained_layout=True)
sns.violinplot(data=df, x="day", y="total_bill", ax=ax,
color="lavender", linewidth=0.1)
  • 매우 밋밋한 그림이 됩니다.
  • 여기에 몇 가지 코드를 추가합니다.
  • 먼저, 일요일 violin plot의 facecolor와 edgecolor, linewidth를 바꿉니다.
  • 하나 건너 하나 있는 PolyCollection을 가져오기 위해 if i%2 == 0을 사용합니다.
1
2
3
4
5
6
# violin plot: PolyCollection
violins = [c for i, c in enumerate(ax.collections) if i%2 == 0]
[v.set_edgecolor("k") for v in violins] # 전체 violin edgecolor 변경
violins[3].set_facecolor("gold") # Sunday violin facecolor 변경
violins[3].set_linewidth(1) # Sunday violin linewidth 변경
violins[3].set_edgecolor("k") # Sunday violin edgecolor 변경
  • 이번엔 marker를 조금 크고 귀엽게 만듭니다.
  • 일요일 뿐 아니라 전체 데이터에 적용합니다.
  • PolyCollection 밑에 있는PathCollection을 가져오기 위해 if i%2 == 1을 사용합니다.
1
2
3
4
5
# median marker: PathCollection
markers = [c for i, c in enumerate(ax.collections) if i%2 == 1]
markers[3].set_facecolor("w") # Sunday median facecolor 변경
markers[3].set_edgecolor("k") # Sunday median edgecolor 변경
[m.set_sizes([50]) for m in markers] # 전체 median size 변경
  • Line2D 객체들 차례입니다.
  • 일요일 box plot 색을 검정으로 만듭니다.
  • 굵기 조정은 모두에게 적용합니다.
  • i%2 == 0i%2 == 1을 번갈아 사용해서 box와 whisker를 번갈아 선택합니다.
1
2
3
4
5
6
# box-whisker: Line2D
ls = [l for i, l in enumerate(ax.lines)]
ls[-2].set_color("k") # Sunday whisker 색상 변경
ls[-1].set_color("k") # Sunday box 색 변경
[l.set_linewidth(1) for i, l in enumerate(ls) if i%2 == 0] # 전체 box, whisher 굵기 변경
[l.set_linewidth(5) for i, l in enumerate(ls) if i%2 == 1] # 전체 box, whisher 굵기 변경
  • 이 코드들을 더하면 이런 그림이 나옵니다.

  • 마지막으로 불필요한 요소를 제거합니다.

1
2
3
4
5
6
ax.set_ylim(0, )
ax.set_xlabel("")
ax.set_ylabel("")
ax.set_title("total bill ($)")
ax.spines[["left", "top", "right"]].set_visible(False)
ax.grid(axis="y", lw=1)


3.3. 응용: hue & split

  • seaborn violin plot에 hue와 split을 적용하면 의미 전달도 좋고 그림도 제법 멋집니다.
  • 여기에도 위에서 사용한 기술을 똑같이 적용할 수 있지만, 주의사항이 하나 있습니다.
  • 반쪽짜리 violin plot 두 개와 center marker가 번갈아 나오기 때문에 list comprehension을 조금 바꿔야 합낟.
  • i%3 == 0은 Male violin plot, i%3 == 1은 Female violin plot, i%3 == 2는 center marker입니다.
시각화 코드 보기/접기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
fig, ax = plt.subplots(figsize=(10, 3), constrained_layout=True)
sns.violinplot(data=df, x="day", y="total_bill", ax=ax,
color="pink", hue="sex", split=True, linewidth=0.1)

### violin plot: PolyCollection

# Male
violins_M = [c for i, c in enumerate(ax.collections) if i%3 == 0]
[v.set_edgecolor("gray") for v in violins_M]
[v.set_linewidth(1) for v in violins_M]
violins_M[3].set_facecolor("gold") # Sunday violin facecolor 변경
violins_M[3].set_linewidth(1) # Sunday violin facecolor 변경
violins_M[3].set_edgecolor("k") # Sunday violin edgecolor 변경

# Female
violins_F = [c for i, c in enumerate(ax.collections) if i%3 == 1]
[v.set_edgecolor("gray") for v in violins_F]
[v.set_linewidth(1) for v in violins_F]
violins_F[3].set_facecolor("orange") # Sunday violin facecolor 변경
violins_F[3].set_linewidth(1) # Sunday violin facecolor 변경
violins_F[3].set_edgecolor("k") # Sunday violin edgecolor 변경

# median marker: PathCollection
markers = [c for i, c in enumerate(ax.collections) if i%3 == 2]
markers[3].set_facecolor("w") # Sunday median facecolor 변경
markers[3].set_edgecolor("k") # Sunday median edgecolor 변경
[m.set_sizes([50]) for m in markers] # 전체 median size 변경

# # box-whisker: Line2D
ls = [l for i, l in enumerate(ax.lines)]
ls[-2].set_color("k") # Sunday whisker 색상 변경
ls[-1].set_color("k") # Sunday box 색 변경
[l.set_linewidth(1) for i, l in enumerate(ls) if i%2 == 0] # 전체 box, whisher 굵기 변경
[l.set_linewidth(5) for i, l in enumerate(ls) if i%2 == 1] # 전체 box, whisher 굵기 변경

# # spines 제거, grid 추가, xlabel 제거, ylabel 제거
ax.set_ylim(0, )
ax.set_xlabel("")
ax.set_ylabel("")
ax.set_title("total bill ($)")
ax.spines[["left", "top", "right"]].set_visible(False)
ax.grid(axis="y", lw=1)
legend = ax.legend()
legend.remove()
fig.legend(loc="upper right", ncol=2)


4. 결론

  • seaborn violin plot도 결국 Matplotlib 객체 모음입니다.
  • 여러분의 아이디어를 객체 지향 방식에 마음껏 실어 나르기 바랍니다.


도움이 되셨나요? 카페인을 투입하시면 다음 포스팅으로 변환됩니다

Share