Plot with Variable Class

  • 시각화의 대상은 데이터만이 아닙니다.
  • 데이터를 비롯해 이름, 단위를 써줘야 하고
  • 데이터를의 분석결과를 함께, 또는 따로 강조해서 그려야 합니다.
  • 데이터마다 붙는 꼬리표와 파생변수를 클래스를 이용해서 정리해 봅시다.

1. 데이터

  • 10만개 정도의 상자 데이터가 있습니다.
  • 길이(length), 너비(width), 높이(height)가 있고,
  • 여기로부터 입체 대각선(diagonal), 표면적(area)의 역수, 부피(area)의 역수가 있습니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    %matplotlib inline

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

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

    df = pd.read_pickle("box.pkl")
    df.describe()

2. 시각화

  • 데이터의 분포를 확인합니다.
  • x축 이름을 제대로 써주고, 단위까지 달아줍니다.

2.1. 기존 방식

  • 이름단위를 list로 받아서 for loop에 axes와 함께 zip으로 넣어주는 것이 가장 효과적입니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # general

    fig, axes = plt.subplots(ncols=3, nrows=2, figsize=(9, 6), sharey=True)
    axs = axes.ravel()

    names = ["Length", "Width", "Height", "Diagonal", "Volume$^{-1}$", "Diagonal"]
    units = ["$cm$", "$cm$", "$cm$", "$cm$", "$cm^{-2}$", "$cm^{-3}$"]

    for ax, col, name, unit in zip(axs, df.columns, names, units):
    ax.hist(df[col], facecolor="skyblue")
    ax.set_xlabel(f"{name} [{unit}]")

    fig.tight_layout()


  • 나름 깔끔하다고 볼 수 있지만, 위험이 있습니다.

    1. 데이터 종류가 많아지면 names와 units에서 적절한걸 찾아가기 힘듭니다.
    2. 데이터 순서가 바뀌면 names와 units 배열 순서를 바꿔야 합니다. 헷갈립니다.
    3. for loop 안에 데이터프레임과 names, units가 함께 돌고 있습니다.
      나쁘지 않지만 머리 속에 세 가지를 넣어야 합니다.
  • 혼동을 줄이고 코드를 깔끔하게 보완해 봅시다.

2.2. class 사용

  • python의 class는 장점이 많습니다.

  • 인스턴스 하나 안에 여러 속성을 담을 수 있고, 점(.)으로 접근하기 쉽습니다.

  • Feature라는 클래스를 만들어 변수들을 담아봅니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Feature:
    def __init__(self, name=None, data=None, unit=None):
    self.name = name # str
    self.data = data # numpy.array
    self.unit = unit # str

    def set_name(self, name):
    self.name = name

    def set_data(self, data):
    self.data = data

    def set_unit(self, data):
    self.unit = unit
  • 클래스를 사용해서 인스턴스를 찍어내 봅니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 인스턴스 만들기. name과 unit은 생성시 설정
    X0 = Feature("Length", unit="$cm$")
    X1 = Feature("Width", unit="$cm$")
    X2 = Feature("Height", unit="$cm$")
    X3 = Feature("Diagonal", unit="$cm$")
    X4 = Feature("Surf.Area$^{-1}$", unit="$cm^{-2}$")
    X5 = Feature("Volume$^{-1}$", unit="$cm^{-3}$")

    # 인스턴스 관리를 위한 list 생성
    Xs = [X0, X1, X2, X3, X4, X5]

    # 인스턴스 list를 이용해서 데이터 담기
    for X, col in zip(Xs, df.columns):
    X.set_data(df[col].values)
  • 의도대로 잘 들어갔는지 확인합니다.

    1
    2
    3
    4
    # X4 확인
    print(f"X4.name= {X4.name}")
    print(f"X4.data= {X4.data}")
    print(f"X4.unit= {X4.unit}")
    • 실행 결과: 데이터가 잘 들어가 있습니다.
      1
      2
      3
      X4.name= Surf.Area$^{-1}$
      X4.data= [0.00026171 0.00018886 0.00017365 ... 0.0003015 0.00037055 0.00041062]
      X4.unit= $cm^{-2}$
  • 클래스를 사용해 같은 그림을 그려봅시다.

  • 색깔만 바꾸어 그립니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # class

    fig, axes = plt.subplots(ncols=3, nrows=2, figsize=(9, 6), sharey=True)
    axs = axes.ravel()

    for ax, X in zip(axs, Xs):
    ax.hist(X.data, facecolor="orange")
    ax.set_xlabel(f"{X.name} [{X.unit}]")

    fig.tight_layout()


  • 섞여있던 데이터프레임과 units, names 리스트가 Feature class X를 중심으로 정리되었습니다.

3. 클래스 활용 데이터 전처리

  • 클래스 안에서 함수를 사용할 수 있습니다.
  • 바꾸어 말하면, 데이터를 넣는 순간 인자 추출과 같은 간단한 전처리가 가능하다는 뜻입니다.

3.1. 데이터 전처리 함수 추가

  • 클래스 함수를 조금 바꿔봅니다.

    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
    def get_stats(data):
    minval = data.min()
    maxval = data.max()
    rangeval = maxval - minval
    leftval = minval - 0.1*rangeval
    rightval = maxval + 0.1*rangeval

    return minval, maxval, rangeval, leftval, rightval

    class Feature:
    def __init__(self, name=None, data=None, unit=None):
    self.name = name # str
    self.data = data # numpy.array
    self.unit = unit # str
    # simple statistics
    if data:
    self.min, self.max, self.range, self.left, self.right = get_stats(self.data)

    def set_name(self, name):
    self.name = name

    def set_data(self, data):
    self.data = data
    self.min, self.max, self.range, self.left, self.right = get_stats(self.data)

    def set_unit(self, data):
    self.unit = unit
  • set_data부분이 바뀌었습니다.

  • 데이터를 읽어들이면 get_stats()함수가 적용되어 최소값, 최대값, 범위에 이어 10% 마진 포함한 범위가 계산됩니다.

  • 아까와 똑같이 데이터를 읽어주고,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 인스턴스 만들기. name과 unit은 생성시 설정
    X0 = Feature("Length", unit="$cm$")
    X1 = Feature("Width", unit="$cm$")
    X2 = Feature("Height", unit="$cm$")
    X3 = Feature("Diagonal", unit="$cm$")
    X4 = Feature("Surf.Area$^{-1}$", unit="$cm^{-2}$")
    X5 = Feature("Volume$^{-1}$", unit="$cm^{-3}$")

    # 인스턴스 관리를 위한 list 생성
    Xs = [X0, X1, X2, X3, X4, X5]

    # 인스턴스 list를 이용해서 데이터 담기
    for X, col in zip(Xs, df.columns):
    X.set_data(df[col].values)
  • 간단한 통계처리 결과를 확인합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #simple statistics
    print(f"X2.min = {X2.min}")
    print(f"X2.max = {X2.max}")
    print(f"X2.range = {X2.range}")
    print(f"X2.left = {X2.left}")
    print(f"X2.right = {X2.right}")
    ````
    * 실행 결과: 데이터를 넣기만 했는데 추가인자가 추출되었습니다.
    ```bash
    X2.min = 88.0199983215
    X2.max = 219.97999786399998
    X2.range = 131.9599995425
    X2.left = 74.82399836725
    X2.right = 233.17599781824998

3.1. 자동 전처리 활용 시각화

  • matplotlib은 그림을 그리면 어느 정도 자동으로 x, y 범위를 설정합니다.
  • 하지만 내 목적에 따라 일일이 범위를 지정해줘야 할 때가 있습니다.
  • 클래스에서 뽑아놓은 범위(10% 마진)를 적용합시다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    fig, axs = plt.subplots(ncols=3, figsize=(9, 4), constrained_layout=True) #, gridspec_kw={"width_ratios":[8,8,8,1]})

    xs = [X3, X4, X5]
    ys = [X4, X5, X3]
    c = X2

    for ax, x, y in zip(axs[:3], xs, ys):

    im = ax.scatter(x.data, y.data, c=c.data, cmap="Reds", s=10)
    ax.set_xlabel(f"{x.name} [{x.unit}]")
    ax.set_ylabel(f"{y.name} [{y.unit}]")
    ax.set_xlim(x.left, x.right)
    ax.set_ylim(y.left, y.right)

    cbar = fig.colorbar(im, ax=axs, location="top", aspect=40, shrink=1)
    cbar.set_label(f"{c.name} [{c.unit}]")

4. 결론

  • 인자마다 비슷한 처리를 반복해야 한다면 클래스가 좋은 해법일 수 있습니다.
  • 히스토그램 범위와 구간 경계 추출, 잡음 제거(smoothing), 미분과 적분 등 활용처는 무궁무진합니다.
  • 각자 용도에 맞게 잘 사용하시면 좋겠습니다.


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

Share