- 시각화의 대상은 데이터만이 아닙니다.
- 데이터를 비롯해 이름, 단위를 써줘야 하고
- 데이터를의 분석결과를 함께, 또는 따로 강조해서 그려야 합니다.
- 데이터마다 붙는 꼬리표와 파생변수를 클래스를 이용해서 정리해 봅시다.
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()나름 깔끔하다고 볼 수 있지만, 위험이 있습니다.
- 데이터 종류가 많아지면 names와 units에서 적절한걸 찾아가기 힘듭니다.
- 데이터 순서가 바뀌면 names와 units 배열 순서를 바꿔야 합니다. 헷갈립니다.
- for loop 안에 데이터프레임과 names, units가 함께 돌고 있습니다.
나쁘지 않지만 머리 속에 세 가지를 넣어야 합니다.
- 혼동을 줄이고 코드를 깔끔하게 보완해 봅시다.
2.2. class
사용
python의 class는 장점이 많습니다.
인스턴스 하나 안에 여러 속성을 담을 수 있고, 점(.)으로 접근하기 쉽습니다.
Feature라는 클래스를 만들어 변수들을 담아봅니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14class 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
3X4.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
27def 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 = unitset_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
16fig, 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), 미분과 적분 등 활용처는 무궁무진합니다.
- 각자 용도에 맞게 잘 사용하시면 좋겠습니다.