본문 바로가기
Python/Data analysis

데이터 시각화 2

by curious week 2025. 10. 10.

고급 시각화 기법 + 그래프 스타일링

1) 고급 시각화 기법

1.1 트리맵(Treemap) — 계층/구성 비율을 한 화면에

  • 언제: 제품군-상품, 부서-팀처럼 계층형 비중 비교.
  • 핵심: 사각형 면적이 값(매출/빈도)을 나타냄. 색으로 추가 차원 표현.
# pip install squarify matplotlib pandas
import squarify, matplotlib.pyplot as plt, pandas as pd

data = pd.DataFrame({
    "group": ["A","A","A","B","B","C"],
    "label": ["A1","A2","A3","B1","B2","C1"],
    "value": [40, 25, 10, 30, 20, 15]
})
data["full_label"] = data["group"] + "-" + data["label"]

fig, ax = plt.subplots(figsize=(6,4))
squarify.plot(
    sizes=data["value"], label=data["full_label"],
    ax=ax, text_kwargs={"fontsize":10, "weight":"bold"}
)
ax.set_title("Treemap — 그룹/하위 항목 비중")
ax.axis("off")
plt.tight_layout(); plt.show()

1.2 지도 시각화(Map) — 공간 패턴

  • 언제: 지역별 매출, 지점 분포, 인구/밀도 등 공간 데이터.
  • 선택: 정적은 geopandas + matplotlib, 인터랙티브는 folium(Leaflet 기반) 추천.
# pip install geopandas matplotlib
import geopandas as gpd, matplotlib.pyplot as plt
world = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
ax = world.plot(column="gdp_md_est", scheme="Quantiles", k=5, legend=True, figsize=(8,4))
ax.set_title("세계 GDP 추정(분위수 구간화)")
ax.set_axis_off()
plt.tight_layout(); plt.show()

용어: GeoJSON(지리 객체의 JSON 표현), CRS(Coordinate Reference System, 좌표계).

1.3 네트워크 그래프 — 관계(노드/간선)

  • 언제: 사용자-상품, 논문 인용, 시스템 의존성 등 관계 구조.
  • : 레이아웃에 따라 해석이 크게 달라짐(Force-directed, Circular 등).
# pip install networkx matplotlib
import networkx as nx, matplotlib.pyplot as plt
G = nx.Graph()
G.add_edges_from([("User","ItemA"),("User","ItemB"),("ItemA","ItemC"),("ItemB","ItemC")])
pos = nx.spring_layout(G, seed=7)  # 강체 모델(스프링) 레이아웃

fig, ax = plt.subplots(figsize=(5,4))
nx.draw_networkx(G, pos=pos, ax=ax, with_labels=True, node_size=900, font_size=10)
ax.set_title("Network — 사용자-아이템 관계")
ax.axis("off"); plt.tight_layout(); plt.show()

1.4 바이올린 플롯 — 분포 + 요약 통계

  • 언제: 그룹별 분포 비교(히스토그램+박스플롯의 장점 결합).
  • 주의: 데이터 수 적을 때 과도하게 매끈해 보일 수 있음.
# pip install seaborn matplotlib pandas numpy
import seaborn as sns, numpy as np, pandas as pd, matplotlib.pyplot as plt
rng = np.random.default_rng(42)
df = pd.DataFrame({
    "group": np.repeat(list("ABC"), 200),
    "value": np.r_[rng.normal(0,1,200), rng.normal(1,1.2,200), rng.normal(-0.5,0.8,200)]
})
fig, ax = plt.subplots(figsize=(6,3))
sns.violinplot(data=df, x="group", y="value", inner="quartile", ax=ax)
ax.set_title("Violin — 그룹 분포와 사분위")
plt.tight_layout(); plt.show()

약어: KDE = Kernel Density Estimation(커널 밀도 추정).

1.5 등고선(Contour) — 3D를 2D로

  • 언제: 표면 z=f(x,y)의 수준선(동일값 곡선).
  • 활용: 손실 지형, 응답면 최적화, 지형 고도.
import numpy as np, matplotlib.pyplot as plt
x = np.linspace(-3,3,200); y = np.linspace(-3,3,200)
X, Y = np.meshgrid(x, y)
Z = np.sin(X**2+Y**2)/(X**2+Y**2+1e-6)

fig, ax = plt.subplots(figsize=(6,4))
cs = ax.contour(X, Y, Z, levels=12)
ax.clabel(cs, inline=True, fontsize=8)
ax.set_title("Contour — f(x,y) 등고선")
plt.tight_layout(); plt.show()

2) 그래프 스타일링 — 전달력 극대화

2.1 컬러 전략

  • 의미 매핑: 범주형은 정성적 팔레트(서로 구분 잘 됨), 연속형은 연속/발산 컬러맵.
  • 색각 보정: viridis, plasma, cividis는 색각 이상자 친화적.
  • 강조: 핵심 시리즈는 채도↑/두께↑, 보조는 연한 회색으로 전경-배경 대비.
import matplotlib.pyplot as plt, numpy as np
x = np.linspace(0, 10, 200)
fig, ax = plt.subplots(figsize=(6,3))
ax.plot(x, np.sin(x), linewidth=3, label="핵심 시리즈")
ax.plot(x, np.cos(x), alpha=0.3, label="보조 시리즈")
ax.set_title("전경-배경 대비로 메시지 1개를 강조")
ax.legend(); ax.grid(True, alpha=0.3); plt.tight_layout(); plt.show()

약어: RGB(Red Green Blue), RGBA(RGB + Alpha 투명도), HSV(Hue Saturation Value, 색상-채도-명도).

2.2 레이블·제목·주석(Annotation)

  • 원칙: “메시지 1개”를 제목으로 명시. 숫자는 차트에 직접 라벨하기.
  • 주석: 화살표/텍스트로 핵심 변화 지점 지정.
import matplotlib.pyplot as plt, numpy as np
x = np.arange(12); y = np.array([5,6,7,8,10,13,12,15,18,17,21,25])
fig, ax = plt.subplots(figsize=(6,3))
ax.plot(x, y, marker="o")
peak = y.argmax()
ax.annotate("전환점", xy=(peak, y[peak]), xytext=(peak+0.5, y[peak]+3),
            arrowprops=dict(arrowstyle="->"))
ax.set_title("주석으로 스토리 포인트 강조"); ax.set_xlabel("월"); ax.set_ylabel("매출")
ax.grid(True, alpha=0.3); plt.tight_layout(); plt.show()

2.3 마커·선 스타일·폰트

  • 일관성: 같은 계열은 동일 마커/선형. 폰트는 프로젝트 전역 rcParams에서 통일.
import matplotlib as mpl, matplotlib.pyplot as plt, numpy as np
mpl.rcParams.update({"font.size": 11})
x = np.linspace(0, 4*np.pi, 200)
fig, ax = plt.subplots(figsize=(6,3))
ax.plot(x, np.sin(x), linestyle="--", marker="o", markevery=20, label="A")
ax.plot(x, np.cos(x), linestyle="-.", marker="s", markevery=25, label="B")
ax.legend(title="시리즈"); ax.set_title("선/마커 스타일 통일")
plt.tight_layout(); plt.show()

2.4 서브플롯(대시보드 구성)

  • 그리드: plt.subplots(r, c)로 빠르게, 복잡한 배치는 GridSpec.
  • 공유 축: sharex/sharey로 비교 용이.
  • 간격 자동 조정: plt.tight_layout() 또는 fig.subplots_adjust().
import matplotlib.pyplot as plt, numpy as np
x = np.linspace(0, 10, 200)
fig, axs = plt.subplots(2, 2, figsize=(9,5), sharex=True)
axs = axs.ravel()
axs[0].plot(x, np.sin(x)); axs[0].set_title("추세")
axs[1].hist(np.sin(x), bins=20); axs[1].set_title("분포")
axs[2].scatter(np.sin(x), np.cos(x), s=10); axs[2].set_title("상관")
axs[3].plot(x, np.sin(x)+np.cos(x)); axs[3].set_title("합성")
for ax in axs: ax.grid(True, alpha=0.25)
fig.suptitle("서브플롯 대시보드", y=1.02)
plt.tight_layout(); plt.show()

(고급) Inset/보조축

from mpl_toolkits.axes_grid1.inset_locator import inset_axes
import matplotlib.pyplot as plt, numpy as np
x = np.linspace(0, 10, 400); y = np.sin(x) + 0.2*np.random.randn(len(x))
fig, ax = plt.subplots(figsize=(6,3))
ax.plot(x, y); ax.set_title("Inset + Twin Axes")
# Inset (확대)
axins = inset_axes(ax, width="35%", height="45%", loc="upper left")
sel = (x>3) & (x<5); axins.plot(x[sel], y[sel]); axins.set_xticks([]); axins.set_yticks([])
# 보조 y축
ax2 = ax.twinx(); ax2.plot(x, np.cos(x), alpha=0.3)
plt.tight_layout(); plt.show()

2.5 인터랙티브(Matplotlib 생태계 vs JS)

  • 가벼운 인터랙션: mplcursors(데이터 포인트 툴팁), mpld3(줌/팬/툴팁).
  • 풀 인터랙티브/대시보드: Plotly, Bokeh, **Altair(Vega-Lite)**로 브라우저 렌더링.
# pip install mplcursors
import matplotlib.pyplot as plt, numpy as np, mplcursors
x = np.linspace(0, 10, 100); y = np.sin(x)
fig, ax = plt.subplots(figsize=(6,3)); pts = ax.plot(x, y, marker="o", ms=4)[0]
mplcursors.cursor(pts, hover=True).connect(
    "add", lambda sel: sel.annotation.set_text(f"x={x[sel.index]:.2f}\ny={y[sel.index]:.2f}")
)
ax.set_title("mplcursors — hover 툴팁"); plt.tight_layout(); plt.show()

약어: JS(JavaScript), DOM(Document Object Model, 브라우저 문서 객체).


3) 실전 설계 체크리스트(요약)

  1. 메시지 한 줄: 제목/주석으로 핵심 문장을 먼저 쓴다.
  2. 정렬·스케일: 범주는 값 기준 정렬, 시계열은 시간 순, 단위/스케일 명확히.
  3. 전경-배경 대비: 핵심만 굵고 선명하게, 나머지는 옅게.
  4. 라벨링: 숫자는 가능하면 그래프 위에 직접 표시.
  5. 색각 친화 팔레트: 연속은 viridis, 발산은 coolwarm류(중립점 명확).
  6. 대시보드: 서브플롯 간 스케일 통일 + 간격 최적화.
  7. 상호작용: 탐색이 목적이면 인터랙티브 라이브러리로 전환(Plotly/Bokeh).

보너스: “정적 → 프런트에서 수정 가능”으로 내보내기

  • SVG로 저장하면 웹에서 CSS/JS로 일부 스타일(선두께/색/텍스트) 수정 가능.
  • 완전한 상호작용과 동적 업데이트가 필요하면 **데이터(JSON)**를 보내 Plotly/Chart.js로 렌더.
import matplotlib.pyplot as plt, numpy as np
x = np.linspace(0, 2*np.pi, 200); y = np.sin(x)
fig, ax = plt.subplots(figsize=(6,3)); ax.plot(x, y); ax.set_title("Export SVG")
fig.savefig("chart.svg", format="svg", bbox_inches="tight")

정리

  • 트리맵/지도/네트워크/바이올린/등고선으로 계층·공간·관계·분포·표면을 정확히 표현.
  • 컬러·레이블·주석·서브플롯으로 메시지 중심의 디자인 일관성 확보.
  • 인터랙티브 도구(mplcursors/mpld3/Plotly/Bokeh)로 탐색성과 전달력을 높인다.

 

오픈소스 기반 데이터 분석 11강


 

Data-Analysis-with-Open-Source/오픈소스_데이터_분석_11강.ipynb at main · mors119/Data-Analysis-with-Open-Source

Data Analysis with Open Source. Contribute to mors119/Data-Analysis-with-Open-Source development by creating an account on GitHub.

github.com


11-0 (선택) Colab 한글 폰트 설정

# 역할: Colab 등 리눅스 환경에서 한글 폰트 깨짐 방지
# 대안(로컬):
#   - macOS: plt.rcParams['font.family'] = 'AppleGothic'
#   - Windows: plt.rcParams['font.family'] = 'Malgun Gothic'
!sudo apt-get install -y fonts-nanum
!sudo fc-cache –fv
!rm ~/.cache/matplotlib -rf

import matplotlib.pyplot as plt
plt.rc('font', family='NanumBarunGothic')

11-1 기본 색상 지정

# 역할: 선 색상을 직접 지정(hex)하여 단일 선 그래프 그리기
# 대안: [import seaborn as sns] 스타일 테마(sns.set_theme), [import plotly.express as px] 대화형
import matplotlib.pyplot as plt

plt.plot([1,2,3], [2,4,1], color='#ff0000')  # 옵션: linewidth, alpha, linestyle
plt.title('기본 색상 지정')
plt.show()

11-2 RGB/RGBA 색상 지정

# 역할: RGB(불투명) / RGBA(마지막이 투명도) 색으로 막대 색상 제어
# 대안: 색 이름('red'), hex('#ff0000'), HLS/HSV 변환은 [import colorsys]
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(6, 4))
labels = ['Red','Green','Blue','Yellow','Cyan (Transparent)']
colors = [(1,0,0), (0,1,0), (0,0,1), (1,1,0), (0,1,1,0.5)]  # 마지막 0.5는 투명도
ax.bar(labels, [5,7,6,4,8], color=colors)
ax.set_title('RGB 및 RGBA 색상 예제')
plt.show()

11-3 다양한 컬러맵 시각화

# 역할: Matplotlib 내장 컬러맵 미리보기(연속형/범주형/발산형 등)
# 대안: [import cmocean] 해양/지형 특화, [import palettable] 팔레트 추가
import matplotlib.pyplot as plt
import numpy as np

colormaps = [
    'viridis','plasma','inferno','magma','cividis',
    'Blues','Greens','Reds','Purples','Oranges',
    'coolwarm','RdBu','Spectral','Set1','Set2','Set3',
    'tab10','tab20','PiYG','twilight','hsv','terrain','ocean',
    'gist_earth','hot','afmhot'
]

plt.figure(figsize=(8, len(colormaps)*0.5))
for i, cmap in enumerate(colormaps):
    gradient = np.linspace(0, 1, 256).reshape(1, -1)
    ax = plt.subplot(len(colormaps), 1, i+1)
    ax.imshow(gradient, aspect='auto', cmap=cmap)
    ax.axis('off')
    ax.set_title(cmap, fontsize=10, loc='left')
plt.tight_layout(); plt.show()

11-4 축/레이블/제목/범위

# 역할: 눈금·범위·제목·축 라벨 등 기본 스타일링
# 대안: [import seaborn as sns] rcParams 세트, [import matplotlib.ticker as mtick] 포맷터
import matplotlib.pyplot as plt

months = ['1월','2월','3월','4월','5월','6월']
sales = [120,150,140,170,190,210]

plt.figure(figsize=(8,5))
plt.plot(months, sales, 'o-', color='blue')
plt.xlabel('월'); plt.ylabel('매출(만원)')
plt.ylim(100, 220)                # 옵션: xlim, 로그축 set_yscale('log')
plt.title('2025년 상반기 월별 매출')
plt.grid(True, linestyle='--', alpha=0.5)
plt.show()

11-5 마커/선 스타일 비교

# 역할: 여러 시리즈를 서로 다른 마커/선으로 비교
# 대안: [import seaborn as sns] style='darkgrid', 마커 자동
import matplotlib.pyplot as plt

x = [1,2,3,4,5]
y1, y2, y3 = [10,2,14,16,4], [8,10,12,11,13], [6,9,11,10,12]

plt.plot(x, y1, marker='o', linestyle='-',  color='blue',  label='y1')
plt.plot(x, y2, marker='s', linestyle='--', color='red',   label='y2')
plt.plot(x, y3, marker='^', linestyle='-.', color='green', label='y3')
plt.title("마커/선 스타일 비교"); plt.xlabel("X"); plt.ylabel("Y")
plt.legend(); plt.show()

11-6 범례(legend)

# 역할: 라벨 지정 → plt.legend() 로 범례 추가
# 옵션: loc='upper right', bbox_to_anchor, ncol, framealpha
import matplotlib.pyplot as plt
x = [1,2,3,4,5]
y1,y2,y3 = [10,2,14,16,4], [8,10,12,11,13], [6,9,11,10,12]
plt.plot(x, y1, marker='o', linestyle='-',  color='blue',  label='데이터 1')
plt.plot(x, y2, marker='s', linestyle='--', color='red',   label='데이터 2')
plt.plot(x, y3, marker='^', linestyle='-.', color='green', label='데이터 3')
plt.title("여러 선 그래프 - 범례"); plt.xlabel("X"); plt.ylabel("Y")
plt.legend(); plt.show()

11-7 주석(annotate)

# 역할: 특정 지점에 텍스트/화살표 주석 표시
# 대안: ax.text(x,y,'문구'), [import adjustText] 겹침 최소화
import matplotlib.pyplot as plt
x = [1,2,3,4,5]
y1,y2,y3 = [10,2,14,16,4], [8,10,12,11,13], [6,9,11,10,12]

plt.plot(x, y1, 'o-', color='blue',  label='데이터 1')
plt.plot(x, y2, 's--', color='red',   label='데이터 2')
plt.plot(x, y3, '^-.', color='green', label='데이터 3')

plt.annotate('Min Point', xy=(2,2), xytext=(2.5,5),
             arrowprops=dict(arrowstyle='->', color='gray'))  # 옵션: bbox, fontsize
plt.title("주석 예시"); plt.xlabel("X"); plt.ylabel("Y")
plt.legend(); plt.show()

11-8 색상맵(cmap) + 컬러바

# 역할: 산점도에 값(예: 온도)을 컬러로 매핑하고 컬러바 제공
# 대안: [import seaborn as sns] sns.scatterplot(hue=...), [import plotly.express as px] color=
import matplotlib.pyplot as plt
import numpy as np

x = np.random.rand(100); y = np.random.rand(100); temperature = np.random.rand(100)*30
sc = plt.scatter(x, y, c=temperature, cmap='plasma')  # 옵션: vmin/vmax로 범위 고정
plt.colorbar(sc, label='온도(℃)')
plt.title("위치별 온도 분포"); plt.xlabel("x"); plt.ylabel("y")
plt.show()

11-9 서브플롯(2×2) 레이아웃

# 역할: 한 화면에 여러 그래프를 구성(보고서/대시보드의 기초)
# 대안: plt.subplot_mosaic, [import seaborn as sns] FacetGrid
import matplotlib.pyplot as plt
import numpy as np

fig, axes = plt.subplots(2, 2, figsize=(12, 10))

axes[0,0].plot([1,2,3,4], [10,20,25,30]); axes[0,0].set_title("선 그래프")
axes[0,1].bar(['A','B','C','D'], [5,7,3,8], color='orange'); axes[0,1].set_title("막대 그래프")
axes[1,0].scatter([1,2,3,4], [15,25,10,30], color='red'); axes[1,0].set_title("산점도")
data = np.random.randn(100); axes[1,1].hist(data, bins=10, color='purple'); axes[1,1].set_title("히스토그램")

plt.tight_layout(); plt.show()

11-10 3D 산점도

# 역할: 3차원 공간의 점 분포(깊이감, 색으로 4차 변수 표현)
# 대안: [import plotly.graph_objects as go] go.Scatter3d(대화형)
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D  # 일부 환경에서 필요

np.random.seed(42)
x,y,z = np.random.rand(50), np.random.rand(50), np.random.rand(50)
colors = x + y

fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(111, projection='3d')
sc = ax.scatter(x, y, z, c=colors, cmap='viridis', s=40, depthshade=True)
ax.set_xlabel('X'); ax.set_ylabel('Y'); ax.set_zlabel('Z'); ax.set_title('3D 산점도 예제')
fig.colorbar(sc, ax=ax, shrink=0.5, aspect=5)
plt.show()

11-11 3D 표면 그래프

# 역할: 2D 격자(X,Y) 위의 함수 Z=f(X,Y)를 표면으로 시각화
# 대안: ax.contour, ax.plot_wireframe, [import plotly] go.Surface
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np

x = np.linspace(-5, 5, 50); y = np.linspace(-5, 5, 50)
X, Y = np.meshgrid(x, y); Z = np.sin(np.sqrt(X**2 + Y**2))

fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(projection='3d')
surf = ax.plot_surface(X, Y, Z, cmap='viridis', linewidth=0, antialiased=True)
ax.set_xlabel('X'); ax.set_ylabel('Y'); ax.set_zlabel('Z(높이)'); ax.set_title('3D 표면 그래프 예제')
fig.colorbar(surf, ax=ax, shrink=0.5, aspect=5)
plt.show()

11-12 Plotly 인터랙티브 시각화

# 역할: 마우스오버/줌/저장 등 인터랙티브 산점도
# 대안: [import bokeh], [import altair] (Vega-Lite), [import holoviews]
import plotly.express as px
import pandas as pd

df = pd.DataFrame({'X값':[1,2,3,4,5], 'Y값':[10,14,18,24,30]})
fig = px.scatter(df, x='X값', y='Y값', title='Plotly 산점도')
fig.show()

11-13 Bokeh 인터랙티브 선 그래프

# 역할: 노트북 내 인터랙티브(줌, 팬, 툴팁) 선 그래프
# 대안: [import plotly], [import altair]
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, HoverTool
from bokeh.io import output_notebook
import pandas as pd

output_notebook()

df = pd.DataFrame({
    '날짜': pd.date_range(start='2023-01-01', periods=10, freq='D'),
    '매출': [200,300,250,400,500,450,600,700,650,800],
    '지점': ['A'] * 10
})
source = ColumnDataSource(df)

p = figure(x_axis_type='datetime', title='Bokeh 고급 인터랙티브 선 그래프',
           width=700, height=400, tools="pan,wheel_zoom,box_zoom,reset,save")
p.line('날짜', '매출', source=source, line_width=3, color='navy', legend_label='A 지점 매출')

hover = HoverTool(tooltips=[("지점","@지점"), ("날짜","@날짜{%F}"), ("매출","@매출{0,0}원")],
                  formatters={'@날짜':'datetime'}, mode='mouse')
p.add_tools(hover); p.legend.title = '지점 정보'
show(p)

개념 실습 정리 2 — 온라인 쇼핑몰 데이터(시각화 중심)

데이터 로드/전처리/파생

# 역할: UCI Online Retail 엑셀 → 전처리 → 파생 컬럼 생성
# 대안: [import pyjanitor] clean_names/transform_columns, [import pandas] .pipe 체이닝
import pandas as pd

url = "https://archive.ics.uci.edu/ml/machine-learning-databases/00352/Online%20Retail.xlsx"
retail = pd.read_excel(url)

retail_clean = (
    retail.dropna(subset=['CustomerID'])            # CustomerID 결측 제거
          .drop_duplicates()                         # 중복 제거
          .loc[(retail['Quantity']>0) & (retail['UnitPrice']>0)]
          .assign(TotalPrice=lambda d: d['Quantity']*d['UnitPrice'])
)
retail_clean['Year']  = retail_clean['InvoiceDate'].dt.year
retail_clean['Month'] = retail_clean['InvoiceDate'].dt.month
retail_clean['Day']   = retail_clean['InvoiceDate'].dt.day
retail_clean['DayOfWeek'] = retail_clean['InvoiceDate'].dt.dayofweek
retail_clean['Hour']  = retail_clean['InvoiceDate'].dt.hour

RFM 분석(3D) — 고객 세그먼트 힌트

# 역할: 고객별 Recency/Frequency/Monetary 계산 및 3D 산점도
# 대안: R/F/M을 등급화(분위수)하여 세그먼트 라벨링, [import lifetimes] 고객생애가치 모델
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import seaborn as sns
import pandas as pd

max_date = retail_clean['InvoiceDate'].max() + pd.Timedelta(days=1)
rfm = (retail_clean.groupby('CustomerID')
       .agg(Recency=('InvoiceDate', lambda x: (max_date - x.max()).days),
            Frequency=('InvoiceNo','nunique'),
            Monetary=('TotalPrice','sum'))
       .reset_index())

fig = plt.figure(figsize=(12,10))
ax = fig.add_subplot(111, projection='3d')
sc = ax.scatter(rfm['Recency'], rfm['Frequency'], rfm['Monetary'],
                c=rfm['Monetary'], cmap='viridis')  # 색으로 지출액 표현

# 가이드 라인(바닥까지 수직선)
for i in range(len(rfm)):
    x,y,z = rfm.loc[i, ['Recency','Frequency','Monetary']]
    ax.plot([x,x],[y,y],[0,z], color='gray', alpha=0.15)

ax.set_xlabel('최근성(일)'); ax.set_ylabel('구매 빈도(회)'); ax.set_zlabel('총 지출액(£)')
fig.colorbar(sc, ax=ax, pad=0.1, label='총 지출액(£)')
plt.title('3D RFM 분석'); plt.show()

제품 가격 vs 판매량 (회귀선)

# 역할: 가격과 판매량 관계 탐색(선형 경향선)
# 대안: 로그 스케일, 로버스트 회귀 [import statsmodels.api as sm] RLM
import seaborn as sns
import matplotlib.pyplot as plt

ppq = (retail_clean.groupby('StockCode')
       .agg(UnitPrice=('UnitPrice','mean'),
            Quantity=('Quantity','sum'))
       .reset_index())
filtered = ppq[(ppq['UnitPrice']<100) & (ppq['Quantity']<10000)]  # 단순 이상치 컷

plt.figure(figsize=(12,8))
sns.regplot(x='UnitPrice', y='Quantity', data=filtered,
            scatter_kws={'alpha':0.4}, line_kws={'lw':2})
plt.title('제품 가격과 판매량의 관계'); plt.xlabel('평균 단가(£)'); plt.ylabel('총 판매량')
plt.ylim(-100, None); plt.grid(ls='--', alpha=0.5)
plt.show()

월별 × 국가별 스택 영역

# 역할: 상위 5개국의 월별 매출 추이를 누적 영역으로 비교
# 대안: [import seaborn as sns] area plot 미지원 → line+fill_between, [import plotly] px.area
import matplotlib.pyplot as plt

country_sales = retail_clean.groupby('Country')['TotalPrice'].sum().reset_index()
top5 = country_sales.sort_values('TotalPrice', ascending=False).head(5)['Country']

mc = retail_clean[retail_clean['Country'].isin(top5)]
mc = (mc.groupby(['Year','Month','Country'])['TotalPrice'].sum().reset_index())
mc['YearMonth'] = mc['Year'].astype(str) + '-' + mc['Month'].astype(str).str.zfill(2)

pivot = mc.pivot_table(index='YearMonth', columns='Country', values='TotalPrice', fill_value=0)
pivot.sort_index(inplace=True)

ax = pivot.plot.area(stacked=True, figsize=(14,8), alpha=0.85)  # pandas 내장 area
ax.set_title('월별 및 국가별 매출 추이(Top5)')
ax.set_xlabel('연월'); ax.set_ylabel('총 매출(£)'); ax.grid(ls='--', alpha=0.4)
plt.tight_layout(); plt.show()

종합 대시보드(4패널)

# 역할: 한 Figure에 핵심 지표 4종(추이/구성/패턴/분포) 배치
# 대안: [import matplotlib.gridspec as gridspec], [import plotly.subplots] make_subplots
import matplotlib.pyplot as plt
import pandas as pd

monthly = retail_clean.groupby(['Year','Month'])['TotalPrice'].sum().reset_index()
monthly['YearMonth'] = monthly['Year'].astype(str) + '-' + monthly['Month'].astype(str)
top_countries = (retail_clean.groupby('Country')['TotalPrice']
                 .sum().reset_index().sort_values('TotalPrice', ascending=False).head(5))

fig = plt.figure(figsize=(15,12))
plt.subplots_adjust(hspace=0.4, wspace=0.4)
day_names = ['월','화','수','목','금','토','일']

# 1) 월별 총매출
ax1 = plt.subplot2grid((2,2), (0,0))
ax1.plot(monthly['YearMonth'], monthly['TotalPrice'], marker='o')
ax1.set_title('월별 총 매출 추이'); ax1.set_xlabel('연월'); ax1.set_ylabel('총 매출(£)')
ax1.tick_params(axis='x', rotation=45); ax1.grid(ls='--', alpha=0.5)

# 2) 국가별 매출 비중(파이)
ax2 = plt.subplot2grid((2,2), (0,1))
ax2.pie(top_countries['TotalPrice'], labels=top_countries['Country'], autopct='%1.1f%%')
ax2.set_title('국가별 매출 비중(Top5)'); ax2.axis('equal')

# 3) 요일별 주문 수(막대)
ax3 = plt.subplot2grid((2,2), (1,0))
day_orders = retail_clean.groupby('DayOfWeek').size()
ax3.bar([day_names[i] for i in day_orders.index], day_orders.values, color='#2ecc71')
ax3.set_title('요일별 주문 수'); ax3.set_xlabel('요일'); ax3.set_ylabel('주문 수')
ax3.tick_params(axis='x', rotation=0)

# 4) 제품 가격 분포(히스토그램)
ax4 = plt.subplot2grid((2,2), (1,1))
ax4.hist(retail_clean['UnitPrice'], bins=30, color='#9b59b6', alpha=0.7)
ax4.set_title('제품 가격 분포'); ax4.set_xlabel('단가(£)'); ax4.set_ylabel('빈도')

plt.suptitle('온라인 쇼핑몰 판매 데이터 대시보드', fontsize=16)
plt.show()

 

'Python > Data analysis' 카테고리의 다른 글

반정형 데이터 분석  (0) 2025.10.11
정형 데이터 분석  (0) 2025.10.11
데이터 시각화 1  (0) 2025.10.10
데이터 분석 2  (1) 2025.10.10
데이터 분석 1  (0) 2025.10.10