본문 바로가기
데이터 분석/시각화

[Matplotlib] 모자이크 플롯(Mozaic Plot) 그리기

by 부자 꽁냥이 2022. 7. 16.

안녕하세요~ 꽁냥이에요. 얼마 전에 두 개 또는 세 개 범주형 변수를 시각화하는 모자이크 플롯(Mozaic Plot)이 있다는 것을 알게 되었는데요. 안타깝게도 Matplotlib에서는 자체적으로 모자이크 플롯(Mozaic Plot)을 그려주진 않더라고요.

 

그래서 꽁냥이가 자체적으로 Matplotlib을 이용해서 모자이크 플롯을 그리는 방법을 소개하려고 합니다.

 

꽁 냥이는 2개 범주형 변수에 대해서만 고려했어요.

3개가 되면 좀 지저분해 보이기도 하고 구현하기 귀찮기도 했어요 ㅎㅎ; 


   모자이크 플롯(Mozaic Plot) 함수

먼저 아래는 모자이크 플롯을 그려주는 함수입니다.

 

이 함수는 데이터프레임 df, x축에 표시할 범주형 칼럼 x_class_label, y축에 표시할 범주형 칼럼, y_class_label, 그리고 인접한 상자의 간격을 설정하는 padding인자가 있습니다. 이때 x_class_label, y_class_label은 데이터프레임의 칼럼 속에 포함되어야 하며 범주형 칼럼이라고 가정하고 있습니다. 예를 들면 아래와 같습니다.

 

 

그럼 이 함수에 대해서 알아보겠습니다. 핵심 부분이라고 생각되는 것만 설명하고 나머지는 주석을 참고해주세요.

 

def mozaic_plot(df, x_class_label, y_class_label, padding=0.025):
    fig = plt.figure(figsize=(10, 10))
    fig.set_facecolor('white')
    ax = fig.add_subplot()

    y_class = df[y_class_label].unique() # y축에 표시할 class 유니크 원소
    x_class = df[x_class_label].unique() # x축에 표시할 class 유니크 원소
    num_y_box = len(y_class) # y_class 범주 개수
    num_x_box = len(x_class) # x_class 범주 개수

    colors = sns.color_palette('hls', num_y_box) # 색상은 y 클래스 개수만큼 만들어준다.
    x_class_to_colors = dict(zip(y_class, colors)) # x_class를 key, 색상을 value로 하는 딕셔너리

    ## 가로 길이 비율
    width_weights = df[x_class_label].value_counts()/len(df)
    width_weights = width_weights*(1-padding*(num_y_box-1)) # 패딩을 고려하여 비율 조절
    width_weights = width_weights.sort_index() # 순서 바꿈

    left_lower_x = 0 # 상자 좌측 하단 x좌표
    xticks = [] # x 눈금 좌표
    for i in range(num_x_box):
        target_x_class = x_class[i]
        target_df = df[df[x_class_label]==target_x_class]
        height_weights = target_df[y_class_label].value_counts()/len(target_df)
        height_weights = height_weights*(1-padding*(len(height_weights)-1))
        height_weights = height_weights.sort_index()
        if i > 0:
            left_lower_x += width_weights[i-1]+padding # 상자 좌측 하단 x 좌표를 오른쪽으로 이동
            
        left_lower_y = 0 # 상자 좌측 하단 y좌표
        add_xtick = True # x 눈금 좌표를 xticks에 추가할지 말지 여부
        for j in range(len(height_weights)):
            target_y_class = height_weights.index[j]
            if j > 0:
                left_lower_y += height_weights[j-1]+padding # 상자 좌측 하단 y 좌표를 위로 이동
            
            # 상자 추가
            rect = patches.Rectangle(
                                    (left_lower_x, left_lower_y), # 좌측 하단 시작점
                                    width_weights[i], # 가로 길이
                                    height_weights[j], # 새로 길이
                                    facecolor = x_class_to_colors[target_y_class], # 면색상
                                    edgecolor='k', # 테두리 색상
                                    fill=True, # 색칠 여부
                                    alpha=0.5 # 투명도
                                 )
            ax.add_patch(rect)
            
            ## x class 텍스트 표시
            rx, ry = rect.get_xy()
            cx = rx+rect.get_width()/2
            if add_xtick:
                xticks.append(cx)
            cy = ry+rect.get_height()/2
            ax.annotate(target_y_class, (cx, cy), weight='bold', fontsize=15, ha='center', va='center')
            
            add_xtick = False # 한번 추가한 경우 중복되지 않도록 False로 바꿈

    ax.xaxis.tick_top() # x축 눈금과 라벨을 위쪽에 표시한다.
    ax.set_xticks(xticks) # x축 눈금 좌표 설정
    ax.set_xticklabels(x_class) # x축 눈금 라벨 설정
    
    ## x축 눈금 라벨 폰트 사이즈 및 눈금은 표시안되게~
    ax.tick_params(axis='x', which='major', labelsize=15, length=0)
    ax.tick_params(axis='y', which='major', length=0, labelsize=0)

    ## axis 축 제거
    for direction in ax.spines.keys():
        ax.spines[direction].set_visible(False)
        
    ## lim 조정
    ax.set_xlim(-padding/2, 1+padding/2)
    ax.set_ylim(-padding/2, 1+padding/2)
    plt.show()

 

line 15~17

먼저 x_class_label의 범주 빈도수 비율에 따라 가로길이의 비율을 정해줍니다(line 15). 그리고 전체 길이에서 패딩 만큼을 빼고 나서 가로길이의 비율을 보정해줍니다(line 16). value_counts는 index 순서가 원하는 대로 나오지 않을 수 있어서 sort_index()로 고정시켰습니다(line 17).

 

line 19~57

이제 이중 루프를 돌면서 상자를 하나씩 그려나갈 것입니다. x축 범주형 원소를 하나 고정시켜놓으면 y축 범주의 빈도수 비율에 따라 세로 길이 비율을 정해줍니다(line 22~26). 그러고 나서 y축에 나타낼 2번째 범주형 변수의 원소 개수만큼 상자를 아래에서 위로 그려나갑니다(line 32~57).


   예제

이제 샘플 데이터를 이용하여 모자이크 플롯(Mozaic Plot)을 그려봅시다. 여기서는 두 범주의 조합이 모두 포함된 경우와 아닌 경우 모자이크 플롯이 제대로 그려지는지 확인할 거예요.

 

a. 두 범주의 조합이 모두 포함된 경우

먼저 다음과 같이 Year, Class 두 범주형 변수의 조합이 모두 포함된 경우에 대해서 모자이크 플롯을 그려볼게요.

 

df = pd.DataFrame()
df['Year'] = ['1960s']*20+['1970s']*35+['1980s']*50
df['Class'] = ['A']*10+['B']*5+['C']*5 + ['A']*10+['B']*20+['C']*5+['A']*15+['B']*19+['C']*16

 

x 축에 표시할 칼럼은 Year, y 축에 표시할 칼럼은 Class입니다.

x_class_label = 'Year'
y_class_label = 'Class'
padding = 0.025
mozaic_plot(df, x_class_label, y_class_label, padding=0.025)

 

Matplotlib을 이용한 모자이크 플롯(Mozaic Plot)

보시는 바와 같이 예쁘게 잘 그려진 것을 알 수 있어요.

 

a. 두 범주의 조합이 모두 포함되지 않는 경우

두 범주라고 해서 모든 조합이 다 포함되지 않을 수 있습니다. 아래와 데이터프레임과 같이 말이죠. 보시면 1960s에 대하여 Class 칼럼에는 A, B를 제외한 C만 있는 것을 확인할 수 있습니다.

 

df = pd.DataFrame()
df['Year'] = ['1960s']*20+['1970s']*35+['1980s']*50
df['Class'] = ['C']*20 + ['A']*10+['B']*25+['A']*15+['B']*19+['C']*16

 

이 경우에도 모자이크 플롯이 잘 그려지는지 확인해볼게요.

 

x_class_label = 'Year'
y_class_label = 'Class'
padding = 0.025
mozaic_plot(df, x_class_label, y_class_label, padding=0.025)

 

문제없이 잘 작동합니다~~ 짝짝!!


이번 포스팅에서는 Matplotlib을 이용하여 모자이크 플롯(Mozaic Plot)을 그리는 방법에 대해서 알아보았습니다. 부디 많은 분께 도움이 되기를 바라며 이상 포스팅 마치겠습니다. 지금까지 꽁냥이의 글 읽어주셔서 감사합니다.

 


댓글


맨 위로