이미지 로딩 중...
AI Generated
2025. 11. 15. · 7 Views
Plotly로 시작하는 데이터 시각화 완벽 가이드
Python의 강력한 시각화 라이브러리 Plotly를 활용하여 인터랙티브한 차트를 만드는 방법을 배워봅니다. 기본 차트부터 고급 시각화까지, 실무에서 바로 사용할 수 있는 예제와 함께 제공됩니다.
목차
- 기본 라인 차트 - 시계열 데이터의 시각화
- 막대 차트 - 카테고리별 비교 분석
- 산점도 - 두 변수 간의 상관관계 발견
- 파이 차트 - 전체 대비 비율 표현
- 히스토그램 - 데이터 분포의 이해
- 박스 플롯 - 통계적 요약과 이상치 탐지
- 히트맵 - 다차원 데이터의 패턴 발견
- 서브플롯 - 여러 차트를 하나의 대시보드로
1. 기본 라인 차트 - 시계열 데이터의 시각화
시작하며
여러분이 웹사이트의 일별 방문자 수를 분석하거나, 주식 가격의 변화를 추적할 때 이런 상황을 겪어본 적 있나요? 단순히 숫자만 보면 전체적인 추세를 파악하기 어렵고, 정적인 이미지로는 특정 날짜의 정확한 값을 확인하기 힘들죠.
이런 문제는 실제 데이터 분석 현장에서 자주 발생합니다. 정적인 차트는 보고서에는 적합하지만, 탐색적 데이터 분석 단계에서는 세부 정보를 확인하기 위해 계속 새로운 차트를 그려야 하는 번거로움이 있습니다.
바로 이럴 때 필요한 것이 Plotly의 인터랙티브 라인 차트입니다. 마우스를 올리면 정확한 값이 표시되고, 줌인/줌아웃으로 원하는 구간을 자유롭게 탐색할 수 있어 데이터 분석의 효율성이 극대적으로 향상됩니다.
개요
간단히 말해서, Plotly의 라인 차트는 시간의 흐름에 따른 데이터의 변화를 인터랙티브하게 보여주는 시각화 도구입니다. 왜 이 개념이 필요한지 실무 관점에서 설명하자면, 데이터 분석가나 개발자는 하루에도 수십 개의 차트를 그리며 데이터를 탐색합니다.
정적인 차트를 사용하면 매번 코드를 수정하고 다시 실행해야 하지만, Plotly를 사용하면 한 번 그린 차트에서 마우스 조작만으로 모든 정보를 얻을 수 있습니다. 예를 들어, 매출 데이터를 분석할 때 특정 날짜의 급증 원인을 파악하려면 그 시점을 확대해서 보고, 정확한 수치를 확인하고, 다른 기간과 비교해야 하는데, Plotly는 이 모든 작업을 클릭 몇 번으로 해결해줍니다.
기존에는 matplotlib으로 차트를 그린 후 특정 구간을 보려면 xlim과 ylim을 계속 조정하며 코드를 재실행했다면, 이제는 Plotly로 한 번 그린 후 마우스로 드래그해서 줌인하면 됩니다. Plotly 라인 차트의 핵심 특징은 첫째, 마우스 호버 시 정확한 x, y 값이 툴팁으로 표시되고, 둘째, 드래그로 특정 구간을 선택하면 자동으로 줌인되며, 셋째, 더블클릭으로 원래 뷰로 즉시 복귀할 수 있다는 점입니다.
이러한 특징들이 데이터 탐색 속도를 10배 이상 빠르게 만들어주며, 발표나 보고 시에도 청중이 직접 차트를 조작하며 질문할 수 있어 커뮤니케이션 효율성도 크게 향상시킵니다.
코드 예제
import plotly.graph_objects as go
import pandas as pd
from datetime import datetime, timedelta
# 샘플 시계열 데이터 생성 (30일간의 일별 매출)
dates = [datetime.now() - timedelta(days=x) for x in range(30)]
sales = [15000 + (i * 1000) + (i % 7 * 2000) for i in range(30)]
# Plotly Figure 객체 생성
fig = go.Figure()
# 라인 차트 추가 - mode는 'lines+markers'로 설정
fig.add_trace(go.Scatter(
x=dates,
y=sales,
mode='lines+markers', # 선과 마커를 모두 표시
name='일별 매출',
line=dict(color='royalblue', width=3),
marker=dict(size=8)
))
# 차트 레이아웃 설정 - 제목, 축 레이블, 호버 모드
fig.update_layout(
title='최근 30일 매출 추이',
xaxis_title='날짜',
yaxis_title='매출 (원)',
hovermode='x unified', # x축 기준으로 모든 데이터 표시
template='plotly_white' # 깔끔한 화이트 테마
)
# 인터랙티브 차트 표시
fig.show()
설명
이것이 하는 일: 이 코드는 30일간의 매출 데이터를 시간 순서대로 연결된 선으로 표시하고, 사용자가 마우스로 차트를 조작하며 데이터를 탐색할 수 있게 해줍니다. 첫 번째로, pandas와 datetime 라이브러리를 사용해 샘플 데이터를 생성합니다.
실무에서는 이 부분을 데이터베이스 쿼리나 CSV 파일 읽기로 대체하게 됩니다. dates 리스트에는 오늘부터 30일 전까지의 날짜가 역순으로 저장되고, sales 리스트에는 증가 추세에 주간 패턴이 더해진 매출 데이터가 생성됩니다.
이렇게 만든 이유는 실제 비즈니스 데이터처럼 전반적인 성장 추세와 주기적인 변동을 모두 포함시키기 위함입니다. 그 다음으로, go.Figure() 객체를 생성하고 여기에 go.Scatter 트레이스를 추가합니다.
Scatter는 Plotly에서 가장 범용적인 차트 타입으로, mode 파라미터를 'lines+markers'로 설정하면 라인 차트가 됩니다. line과 marker 딕셔너리에서는 색상, 굵기, 크기 등 시각적 속성을 세밀하게 조정할 수 있습니다.
name 파라미터는 범례에 표시될 이름으로, 여러 라인을 비교할 때 특히 중요합니다. 차트의 레이아웃 설정 단계에서는 update_layout() 메서드로 제목, 축 레이블, 그리드 스타일 등을 정의합니다.
특히 hovermode='x unified'는 매우 유용한 옵션으로, 마우스를 특정 x 좌표에 올리면 해당 위치의 모든 y 값들이 하나의 툴팁에 깔끔하게 정리되어 표시됩니다. 여러 라인을 비교할 때 이 옵션이 없으면 각 라인마다 별도의 툴팁이 나타나 혼란스러울 수 있습니다.
마지막으로, fig.show()를 호출하면 브라우저에서 인터랙티브 차트가 열립니다. Jupyter Notebook이나 JupyterLab에서는 셀 출력으로 바로 표시되며, 일반 Python 스크립트에서는 새 브라우저 탭이 열립니다.
이 차트에서는 우측 상단의 툴바를 통해 이미지로 저장하거나, 특정 구간을 드래그해서 확대하거나, 더블클릭으로 원래 뷰로 복귀할 수 있습니다. 여러분이 이 코드를 사용하면 정적인 차트 대비 10배 빠른 데이터 탐색, 프레젠테이션 시 즉각적인 질의응답, 그리고 웹 대시보드 구축 시 별도의 프론트엔드 개발 없이도 고품질 차트를 제공할 수 있는 이점을 얻습니다.
특히 데이터 분석 보고서를 HTML로 저장하면 동료들이 직접 차트를 조작하며 인사이트를 탐색할 수 있어 협업 효율성이 크게 향상됩니다.
실전 팁
💡 데이터 포인트가 100개 이상일 때는 mode='lines'만 사용하고 markers를 제거하세요. 너무 많은 마커는 차트를 복잡하게 만들고 렌더링 속도를 저하시킵니다.
💡 여러 라인을 비교할 때는 각 라인에 showlegend=True와 함께 명확한 name을 지정하세요. 범례를 클릭하면 해당 라인을 숨기거나 표시할 수 있어 선택적 비교가 가능합니다.
💡 fig.write_html('chart.html')로 저장하면 인터랙티브 기능이 모두 유지된 채 HTML 파일로 공유할 수 있습니다. 이메일이나 보고서에 첨부하기 완벽합니다.
💡 hovertemplate을 사용하면 툴팁 형식을 완전히 커스터마이즈할 수 있습니다. 예: hovertemplate='<b>날짜</b>: %{x}<br><b>매출</b>: %{y:,.0f}원<extra></extra>'
💡 range_slider를 추가하면 아래쪽에 미니 차트가 생겨 전체 데이터 중 어느 부분을 보고 있는지 한눈에 파악할 수 있습니다. fig.update_xaxes(rangeslider_visible=True)로 활성화하세요.
2. 막대 차트 - 카테고리별 비교 분석
시작하며
여러분이 제품별 판매량을 비교하거나, 부서별 예산을 검토할 때 표로만 보면 어떤 항목이 가장 큰지 직관적으로 파악하기 어렵죠. 숫자를 일일이 비교하며 머릿속으로 크기를 상상해야 하는 상황, 익숙하시죠?
이런 문제는 특히 경영진에게 보고할 때 치명적입니다. 표만 가득한 슬라이드는 청중의 집중력을 떨어뜨리고, 핵심 메시지가 묻혀버립니다.
또한 여러 카테고리를 한 번에 비교하기 위해 눈을 좌우로 계속 움직여야 하는 피로감도 큽니다. 바로 이럴 때 필요한 것이 Plotly의 인터랙티브 막대 차트입니다.
카테고리별 크기 차이를 시각적으로 즉시 비교할 수 있고, 마우스를 올리면 정확한 수치까지 확인되어 직관성과 정확성을 동시에 잡을 수 있습니다.
개요
간단히 말해서, 막대 차트는 여러 카테고리의 값을 막대 길이로 표현하여 크기를 직관적으로 비교할 수 있게 해주는 시각화 도구입니다. 왜 이 개념이 필요한지 실무 관점에서 설명하자면, 비즈니스에서는 항상 비교가 필요합니다.
어느 제품이 잘 팔리는지, 어느 지역이 수익성이 높은지, 어느 마케팅 채널이 효과적인지 등 의사결정의 90%는 비교에서 시작됩니다. 표로 된 숫자를 보면 15개 항목 중 상위 3개를 찾는데 30초가 걸리지만, 막대 차트는 1초면 충분합니다.
예를 들어, 20개 제품의 월간 판매량을 분석할 때 막대 차트를 사용하면 상위 제품, 하위 제품, 평균 수준의 제품이 한눈에 구분되어 재고 관리와 마케팅 전략 수립이 훨씬 빨라집니다. 기존에는 Excel로 막대 차트를 만들었다면, 이제는 Plotly로 만들어 웹 대시보드에 실시간으로 임베드하거나, Python 자동화 스크립트에 통합할 수 있습니다.
Plotly 막대 차트의 핵심 특징은 첫째, 막대를 클릭하거나 호버하면 정확한 값과 카테고리명이 표시되고, 둘째, 수평/수직 방향을 자유롭게 선택할 수 있으며, 셋째, 그룹화되거나 스택된 막대로 다차원 비교가 가능하다는 점입니다. 이러한 특징들이 복잡한 비즈니스 데이터를 단순명료하게 전달할 수 있게 해주며, 의사결정자들이 데이터 기반으로 빠르게 판단할 수 있는 환경을 만들어줍니다.
코드 예제
import plotly.graph_objects as go
# 제품별 월간 판매량 데이터
products = ['스마트폰', '노트북', '태블릿', '이어폰', '스마트워치']
sales_quantity = [450, 320, 280, 650, 190]
# 막대 차트 생성
fig = go.Figure(data=[
go.Bar(
x=products,
y=sales_quantity,
text=sales_quantity, # 막대 위에 숫자 표시
textposition='outside', # 텍스트를 막대 바깥쪽에 배치
marker=dict(
color=sales_quantity, # 값에 따라 색상 그라데이션
colorscale='Blues', # 파란색 계열 색상
showscale=True # 색상 스케일 바 표시
),
hovertemplate='<b>%{x}</b><br>판매량: %{y}개<extra></extra>'
)
])
# 레이아웃 설정
fig.update_layout(
title='제품별 월간 판매량',
xaxis_title='제품',
yaxis_title='판매량 (개)',
template='plotly_white',
showlegend=False # 단일 막대 차트는 범례 불필요
)
fig.show()
설명
이것이 하는 일: 이 코드는 5개 제품의 판매량을 막대 높이로 표현하고, 판매량이 많을수록 진한 파란색으로 표시하여 시각적 비교를 두 배로 강화합니다. 첫 번째로, 제품명과 판매량을 각각 리스트로 준비합니다.
실무에서는 이 데이터가 데이터베이스에서 SELECT한 결과이거나 pandas DataFrame의 컬럼일 것입니다. products는 x축 카테고리가 되고, sales_quantity는 y축 값이 되어 막대의 높이를 결정합니다.
데이터를 리스트로 준비하는 이유는 Plotly가 순서를 보존하며, 나중에 정렬이나 필터링을 쉽게 적용할 수 있기 때문입니다. 그 다음으로, go.Bar 객체를 생성하며 여러 시각적 옵션을 설정합니다.
text 파라미터에 sales_quantity를 넣으면 각 막대 위에 정확한 숫자가 표시되는데, 이는 차트만으로도 정확한 값을 파악할 수 있게 해줍니다. textposition='outside'는 막대 내부가 아닌 위쪽에 텍스트를 배치해 가독성을 높입니다.
marker 딕셔너리에서는 color를 값 자체로 설정하고 colorscale을 지정하여, 판매량이 많을수록 진한 파란색이 되도록 만듭니다. 이는 막대 높이와 색상 두 가지 시각적 채널로 동시에 정보를 전달하여 인지 속도를 높이는 효과적인 기법입니다.
hovertemplate은 사용자가 막대에 마우스를 올렸을 때 표시되는 툴팁의 형식을 정의합니다. %{x}는 x축 값(제품명), %{y}는 y축 값(판매량)을 자동으로 채워주며, HTML 태그로 굵게(<b>)나 줄바꿈(<br>) 등을 적용할 수 있습니다.
<extra></extra>는 기본으로 표시되는 트레이스명을 숨기는 역할을 합니다. 이런 커스터마이징을 통해 전문적이고 깔끔한 차트를 만들 수 있습니다.
레이아웃 설정에서는 showlegend=False로 범례를 숨겼는데, 단일 데이터 시리즈만 있을 때는 범례가 불필요하기 때문입니다. 여러 제품 카테고리를 비교하는 그룹화된 막대 차트에서는 범례가 필수지만, 이 경우는 x축 레이블만으로도 충분합니다.
여러분이 이 코드를 사용하면 회의에서 "어느 제품이 가장 잘 팔리나요?"라는 질문에 즉시 시각적으로 답할 수 있고, 경영진이 "정확히 몇 개죠?"라고 물으면 막대 위의 숫자나 호버 툴팁으로 바로 확인시켜드릴 수 있습니다. 또한 이 차트를 월별로 자동 생성하는 스크립트를 만들면, 매달 판매 트렌드 리포트를 자동화할 수 있어 반복 작업에서 해방됩니다.
실전 팁
💡 카테고리가 많을 때(10개 이상)는 orientation='h'로 수평 막대 차트를 만드세요. 긴 카테고리명이 겹치지 않고 가독성이 훨씬 좋아집니다.
💡 값에 따라 자동으로 정렬하려면 데이터를 넘기기 전에 sorted(zip(products, sales_quantity), key=lambda x: x[1], reverse=True)로 정렬하세요. 가장 큰 값부터 보여주면 인사이트가 더 명확합니다.
💡 특정 막대를 강조하려면 marker_color를 리스트로 만들어 조건부로 색상을 지정하세요. 예: ['red' if x > 500 else 'lightblue' for x in sales_quantity]
💡 그룹화된 막대 차트(여러 제품의 월별 비교 등)를 만들 때는 barmode='group'을 update_layout에 추가하면 막대들이 나란히 배치됩니다. 'stack'으로 하면 쌓입니다.
💡 막대에 패턴을 추가하려면 marker_pattern_shape을 사용하세요. 예: marker=dict(pattern_shape="/")는 사선 무늬를 만들어 흑백 인쇄 시에도 구분이 쉽습니다.
3. 산점도 - 두 변수 간의 상관관계 발견
시작하며
여러분이 광고비와 매출의 관계를 분석하거나, 공부 시간과 시험 점수의 연관성을 파악하려 할 때, 숫자 두 줄만 보고 어떤 패턴이 있는지 알 수 있나요? 200개의 데이터 포인트가 있다면 더욱 막막하죠.
이런 문제는 데이터 사이언스의 가장 기본적인 과제입니다. 두 변수가 서로 어떻게 영향을 주고받는지, 양의 상관관계인지 음의 상관관계인지, 아니면 전혀 관계가 없는지를 파악해야 다음 분석 단계로 넘어갈 수 있습니다.
표만 보면 이런 관계를 절대 발견할 수 없습니다. 바로 이럴 때 필요한 것이 산점도(Scatter Plot)입니다.
각 데이터 포인트를 점으로 찍으면 전체적인 패턴, 이상치, 군집 등이 한눈에 보이며, Plotly의 인터랙티브 기능으로 특정 점의 정확한 값까지 즉시 확인할 수 있습니다.
개요
간단히 말해서, 산점도는 두 개의 연속형 변수를 x축과 y축에 배치하여 각 관측치를 점으로 표현하고, 그 분포 패턴을 통해 변수 간 관계를 시각적으로 파악하는 도구입니다. 왜 이 개념이 필요한지 실무 관점에서 설명하자면, 데이터 분석의 첫 단계는 항상 탐색적 데이터 분석(EDA)입니다.
회귀 분석이나 머신러닝 모델을 만들기 전에 변수들 간의 관계를 먼저 파악해야 하는데, 산점도가 가장 효과적인 방법입니다. 예를 들어, 마케팅 담당자가 "SNS 광고비를 늘리면 매출이 정말 오를까?"라는 질문에 답하려면, 과거 광고비와 매출 데이터를 산점도로 그려 상관관계를 먼저 확인해야 합니다.
강한 양의 상관관계가 보이면 투자를 늘리는 것이 합리적이고, 관계가 없다면 다른 채널을 고려해야 합니다. 기존에는 Excel의 산점도를 사용했다면, 이제는 Plotly로 수백 개의 점도 부드럽게 렌더링하고, 각 점에 추가 정보(제품명, 날짜 등)를 툴팁으로 표시하며, 이상치를 클릭해 상세 정보를 즉시 파악할 수 있습니다.
Plotly 산점도의 핵심 특징은 첫째, 마우스 호버로 각 점의 x, y 값과 추가 정보를 확인할 수 있고, 둘째, 점의 크기와 색상을 세 번째, 네 번째 변수로 활용해 4차원 데이터를 2D 평면에 표현할 수 있으며, 셋째, 드래그로 특정 영역만 확대해 군집이나 이상치를 상세히 분석할 수 있다는 점입니다. 이러한 특징들이 복잡한 다변량 데이터에서 숨겨진 패턴과 인사이트를 발굴하는 데 결정적인 역할을 합니다.
코드 예제
import plotly.express as px
import pandas as pd
import numpy as np
# 광고비와 매출 데이터 생성 (상관관계 있는 데이터)
np.random.seed(42)
ad_spend = np.random.uniform(50, 500, 100) # 광고비 50~500만원
sales = ad_spend * 2.5 + np.random.normal(0, 50, 100) # 매출 = 광고비 * 2.5 + 노이즈
product_category = np.random.choice(['전자제품', '의류', '식품'], 100)
# DataFrame 생성
df = pd.DataFrame({
'광고비': ad_spend,
'매출': sales,
'제품군': product_category
})
# 산점도 생성 - 제품군별로 색상 구분
fig = px.scatter(
df,
x='광고비',
y='매출',
color='제품군', # 제품군별로 다른 색상
size='매출', # 매출이 클수록 점 크기도 큼
hover_data={'광고비': ':.1f', '매출': ':.1f'}, # 소수점 1자리까지 표시
title='광고비 대비 매출 분석',
labels={'광고비': '광고비 (만원)', '매출': '매출 (만원)'},
template='plotly_white',
trendline='ols' # 선형 회귀선 자동 추가
)
fig.show()
설명
이것이 하는 일: 이 코드는 100개 회사의 광고비와 매출 데이터를 점으로 찍고, 제품군별로 색상을 달리하며, 매출이 클수록 점을 크게 그려 4차원 정보를 한 화면에 표현합니다. 추가로 회귀선까지 자동으로 그려 상관관계의 강도를 수치적으로도 확인할 수 있습니다.
첫 번째로, numpy로 현실적인 샘플 데이터를 생성합니다. ad_spend는 50~500만원 사이의 균등 분포 난수이고, sales는 광고비의 2.5배에 노이즈를 추가해 양의 상관관계를 만들었습니다.
실제 비즈니스에서는 광고비를 투입하면 대략 비례해서 매출이 증가하지만 완벽하게 일치하지는 않기에, 이런 노이즈가 현실을 잘 반영합니다. product_category는 각 데이터 포인트가 어느 제품군인지를 나타내며, 이는 색상으로 구분되어 제품군별 패턴 차이를 발견하는 데 도움을 줍니다.
그 다음으로, pandas DataFrame으로 데이터를 구조화합니다. Plotly Express(px)는 DataFrame과 완벽하게 통합되어 있어, 컬럼명만 지정하면 자동으로 축 레이블, 범례, 툴팁이 생성됩니다.
이는 코드를 대폭 단순화시켜주며, SQL 쿼리 결과를 바로 DataFrame으로 받아 즉시 시각화할 수 있게 해줍니다. px.scatter() 함수에서는 여러 강력한 옵션을 사용합니다.
color='제품군'은 제품군별로 자동으로 다른 색상을 할당하고 범례를 생성합니다. 이를 통해 전자제품, 의류, 식품의 광고 효율성이 다른지 한눈에 비교할 수 있습니다.
size='매출'은 매출이 클수록 점의 크기를 키워 시각적으로 고매출 포인트를 강조합니다. hover_data는 툴팁에 표시할 정보와 형식을 지정하는데, ':.1f'는 소수점 1자리까지 표시하라는 뜻입니다.
가장 강력한 기능은 trendline='ols'입니다. 이는 Ordinary Least Squares(최소자승법) 회귀선을 자동으로 계산해 그려주며, 마우스를 회귀선에 올리면 R² 값과 회귀식까지 표시됩니다.
데이터 사이언티스트가 수동으로 sklearn을 사용해 회귀 분석을 하고 결과를 차트에 그리는 과정을 단 한 줄로 해결한 것입니다. 이를 통해 광고비와 매출의 상관관계가 통계적으로 얼마나 유의미한지 즉시 판단할 수 있습니다.
여러분이 이 코드를 사용하면 수백 개의 데이터 포인트에서 패턴을 3초 만에 발견하고, 제품군별로 광고 전략을 달리해야 하는지 판단하며, 회귀선을 통해 "광고비 100만원 증가 시 매출이 약 250만원 증가할 것"이라는 구체적인 예측까지 할 수 있습니다. 또한 이상치(회귀선에서 멀리 떨어진 점)를 클릭해 어떤 캠페인이었는지 확인하고 실패/성공 요인을 분석할 수 있습니다.
실전 팁
💡 점이 너무 많아 겹칠 때는 opacity=0.6을 추가해 반투명하게 만드세요. 겹친 부분이 진하게 보여 데이터 밀도를 파악할 수 있습니다.
💡 trendline 옵션은 'ols'(선형) 외에도 'lowess'(비선형 추세선)를 지원합니다. 비선형 관계가 의심될 때 유용합니다.
💡 marginal_x='histogram', marginal_y='histogram'을 추가하면 각 축의 분포를 히스토그램으로 동시에 볼 수 있어 이상치와 분포 형태를 한눈에 파악할 수 있습니다.
💡 이상치를 찾으려면 z-score를 계산해 색상으로 표현하세요. df['z_score'] = np.abs((df['매출'] - df['매출'].mean()) / df['매출'].std())로 계산 후 color='z_score'로 지정하면 이상치가 다른 색으로 표시됩니다.
💡 클릭 이벤트를 활용하려면 Dash 프레임워크와 결합하세요. 점을 클릭하면 해당 캠페인의 상세 정보를 옆에 표시하는 인터랙티브 대시보드를 만들 수 있습니다.
4. 파이 차트 - 전체 대비 비율 표현
시작하며
여러분이 회사의 매출 구성을 보고하거나, 설문조사 결과를 발표할 때 "A가 35%, B가 25%, C가 20%..."라고 나열하면 청중이 금방 지루해하죠. 게다가 누적해서 100%가 맞는지 머릿속으로 계산하기도 어렵습니다.
이런 문제는 비율 데이터를 전달할 때 항상 발생합니다. 숫자로만 보면 전체 중 어느 부분이 가장 큰 비중을 차지하는지 직관적으로 와닿지 않고, 특히 5개 이상의 카테고리가 있으면 비교가 더욱 어려워집니다.
바로 이럴 때 필요한 것이 파이 차트입니다. 원을 카테고리별 비율로 나누어 시각화하면 전체 대비 각 부분의 크기를 즉시 이해할 수 있고, Plotly의 인터랙티브 기능으로 정확한 퍼센티지까지 확인할 수 있습니다.
개요
간단히 말해서, 파이 차트는 전체를 100%로 보고 각 카테고리가 차지하는 비율을 원의 조각으로 표현하여 구성비를 직관적으로 보여주는 시각화 도구입니다. 왜 이 개념이 필요한지 실무 관점에서 설명하자면, 경영진이나 클라이언트는 절대값보다 상대적 비율을 더 중요하게 생각하는 경우가 많습니다.
"이번 달 모바일 매출이 3억입니다"보다 "전체 매출의 60%가 모바일에서 발생했습니다"가 훨씬 강력한 메시지입니다. 특히 예산 배분, 시장 점유율, 트래픽 소스 분석 등에서 파이 차트는 "어디에 집중해야 하는가"를 한눈에 보여줍니다.
예를 들어, 마케팅 채널별 전환율을 파이 차트로 그리면 어느 채널이 가장 효율적인지 3초 만에 파악되어, 즉각적인 예산 재배치 결정을 내릴 수 있습니다. 기존에는 Excel 파이 차트가 정적이어서 발표 중에 "정확히 몇 %죠?"라는 질문에 슬라이드를 확대하거나 별도 자료를 찾아야 했다면, 이제는 Plotly 파이 차트를 마우스로 호버하면 정확한 값이 즉시 표시됩니다.
Plotly 파이 차트의 핵심 특징은 첫째, 각 조각에 마우스를 올리면 카테고리명, 값, 퍼센티지가 모두 표시되고, 둘째, 특정 조각을 클릭하면 분리(pull)되어 강조할 수 있으며, 셋째, 도넛 차트로 변형해 중앙에 추가 정보를 표시할 수 있다는 점입니다. 이러한 특징들이 단순한 비율 전달을 넘어 스토리텔링 도구로 파이 차트를 승격시켜줍니다.
코드 예제
import plotly.graph_objects as go
# 트래픽 소스별 방문자 수 데이터
sources = ['검색엔진', '소셜미디어', '직접유입', '추천링크', '광고']
visitors = [4500, 3200, 2100, 1800, 2400]
# 특정 조각을 분리해서 강조 (검색엔진 강조)
pull_values = [0.1, 0, 0, 0, 0] # 첫 번째 조각만 10% 분리
# 파이 차트 생성
fig = go.Figure(data=[go.Pie(
labels=sources,
values=visitors,
pull=pull_values, # 조각 분리 정도
hole=0.3, # 중앙에 구멍을 만들어 도넛 차트로 변환 (0이면 일반 파이)
marker=dict(
colors=['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8'],
line=dict(color='white', width=2) # 조각 사이 흰색 경계선
),
textinfo='label+percent', # 레이블과 퍼센트 표시
textposition='inside', # 텍스트를 조각 안쪽에 배치
hovertemplate='<b>%{label}</b><br>방문자: %{value:,}명<br>비율: %{percent}<extra></extra>'
)])
# 레이아웃 설정
fig.update_layout(
title='트래픽 소스별 방문자 분포',
showlegend=True,
legend=dict(orientation='v', x=1.1, y=0.5), # 범례를 오른쪽에 세로로 배치
template='plotly_white'
)
fig.show()
설명
이것이 하는 일: 이 코드는 5개 트래픽 소스의 방문자 수를 비율로 변환해 원형 차트로 그리고, 가장 중요한 검색엔진 조각을 약간 분리해 시각적으로 강조하며, 중앙에 구멍을 만들어 현대적인 도넛 차트 스타일로 표현합니다. 첫 번째로, 트래픽 소스별 방문자 수를 리스트로 준비합니다.
Plotly는 자동으로 각 값의 전체 대비 비율을 계산하므로, 여러분은 절대값만 넣으면 됩니다. pull_values 리스트는 각 조각을 원의 중심에서 얼마나 분리할지를 정하는데, 0.1은 반지름의 10%만큼 떨어뜨린다는 뜻입니다.
첫 번째 조각(검색엔진)만 0.1로 설정해 강조하고 나머지는 0으로 두어 붙어있게 했습니다. 이 기법은 발표에서 특정 카테고리를 강조할 때 매우 효과적입니다.
그 다음으로, go.Pie 객체를 생성하며 여러 시각적 옵션을 설정합니다. hole=0.3은 원의 중앙 30%를 비워 도넛 차트로 만드는데, 이는 일반 파이 차트보다 세련되어 보이고 중앙 공간에 총 방문자 수 같은 추가 정보를 텍스트로 넣을 수도 있습니다.
marker의 colors에는 각 조각의 색상을 명시적으로 지정했는데, 기본 색상 팔레트가 마음에 들지 않거나 브랜드 컬러를 사용해야 할 때 필수입니다. line=dict(color='white', width=2)는 조각 사이에 2픽셀의 흰색 경계선을 그려 조각들을 명확히 구분합니다.
textinfo='label+percent'는 각 조각 안에 레이블(트래픽 소스명)과 퍼센트를 함께 표시하라는 뜻입니다. 'label+value'로 하면 절대값이 표시되고, 'percent'만 하면 퍼센트만 표시됩니다.
textposition='inside'는 텍스트를 조각 내부에 배치하는데, 조각이 너무 작으면 'outside'로 바꿔 밖에 화살표와 함께 표시할 수 있습니다. hovertemplate은 마우스 호버 시 "검색엔진 방문자: 4,500명 비율: 32.1%" 같은 형식으로 자세한 정보를 보여줍니다.
범례 설정에서 orientation='v'는 세로 배치, x=1.1은 차트 오른쪽, y=0.5는 중앙에 위치시킵니다. 파이 차트는 그 자체로 범례 역할을 하므로 범례를 생략해도 되지만, 조각 안의 텍스트가 작아 읽기 어려울 때는 범례가 유용합니다.
여러분이 이 코드를 사용하면 월간 보고서에서 "검색엔진이 우리 트래픽의 1/3을 차지합니다"라는 메시지를 시각적으로 강렬하게 전달할 수 있고, 경영진이 "그럼 SEO에 더 투자해야겠네요"라는 즉각적인 결정을 내릴 수 있도록 돕습니다. 또한 여러 기간의 파이 차트를 나란히 놓으면 트래픽 소스의 변화 추이를 비교할 수 있습니다.
실전 팁
💡 카테고리가 6개를 넘으면 파이 차트보다 막대 차트를 고려하세요. 너무 많은 조각은 오히려 비교를 어렵게 만듭니다. 작은 비율들은 '기타'로 묶는 것도 방법입니다.
💡 두 그룹을 비교할 때는 서브플롯으로 파이 차트 두 개를 나란히 배치하세요. make_subplots(rows=1, cols=2, specs=[[{'type':'pie'}, {'type':'pie'}]])를 사용하면 이전 vs 현재 비교가 직관적입니다.
💡 도넛 차트의 중앙에 텍스트를 넣으려면 annotations를 사용하세요. fig.add_annotation(text='총 14,000명', x=0.5, y=0.5, showarrow=False, font_size=20)으로 총합을 강조할 수 있습니다.
💡 값이 작은 조각은 textposition='outside'로 자동 전환하려면 textposition='auto'를 사용하세요. Plotly가 조각 크기에 따라 자동으로 안/밖을 결정합니다.
💡 애니메이션을 추가하려면 Plotly Express의 px.pie와 animation_frame을 사용해 시간에 따른 비율 변화를 동영상처럼 보여줄 수 있습니다.
5. 히스토그램 - 데이터 분포의 이해
시작하며
여러분이 고객들의 나이 분포를 파악하거나, 웹사이트 로딩 시간의 분포를 분석할 때 개별 값들만 나열하면 전체적인 패턴을 전혀 알 수 없죠. 1000명의 고객 나이를 하나하나 보면서 "20대가 많네, 30대도 좀 있고..."라고 세기엔 인생이 너무 짧습니다.
이런 문제는 연속형 데이터를 다룰 때 가장 흔하게 발생합니다. 데이터가 어느 구간에 몰려있는지, 정규분포를 따르는지, 이상치가 있는지 등을 파악하지 못하면 평균이나 중앙값 같은 통계량도 오해를 불러일으킬 수 있습니다.
"평균 나이 35세"라는 말이 "대부분 35세 근처"를 뜻하는지, "20대와 50대가 반반"을 뜻하는지 알 수 없죠. 바로 이럴 때 필요한 것이 히스토그램입니다.
데이터를 구간(bin)으로 나누어 각 구간의 빈도를 막대로 표시하면, 데이터의 분포 형태, 중심 경향, 산포도, 왜도 등을 한눈에 파악할 수 있습니다.
개요
간단히 말해서, 히스토그램은 연속형 데이터를 여러 구간으로 나누고 각 구간에 속하는 데이터의 개수를 막대 높이로 표현하여 전체 분포를 시각화하는 도구입니다. 왜 이 개념이 필요한지 실무 관점에서 설명하자면, 데이터 분석의 핵심은 "우리 데이터가 어떻게 생겼는가"를 이해하는 것입니다.
머신러닝 모델을 만들기 전, A/B 테스트 결과를 해석하기 전, 고객 세그먼트를 나누기 전, 반드시 데이터 분포를 확인해야 합니다. 예를 들어, 웹사이트 로딩 시간을 분석할 때 히스토그램을 그리면 "대부분은 2초 이내인데 일부가 10초 이상"이라는 패턴을 발견할 수 있습니다.
이는 평균만 보면 절대 알 수 없는 정보이며, 느린 로딩의 원인을 찾아 개선하는 출발점이 됩니다. 또한 히스토그램이 정규분포와 비슷하면 t-test 같은 모수 통계를 사용할 수 있지만, 심하게 왜곡되어 있으면 비모수 통계를 사용해야 한다는 판단도 가능합니다.
기존에는 Excel의 히스토그램이 bin 범위를 수동으로 설정해야 했다면, Plotly는 자동으로 최적의 bin을 계산하며, 마우스로 각 막대에 호버하면 정확한 구간과 빈도가 표시됩니다. Plotly 히스토그램의 핵심 특징은 첫째, nbins 파라미터로 구간 개수를 쉽게 조정해 다양한 해상도로 분포를 볼 수 있고, 둘째, 여러 그룹의 히스토그램을 겹쳐(overlay) 또는 쌓아서(stack) 비교할 수 있으며, 셋째, cumulative=True로 누적 분포를 즉시 그릴 수 있다는 점입니다.
이러한 특징들이 탐색적 데이터 분석을 훨씬 빠르고 깊이 있게 만들어주며, 데이터의 숨겨진 패턴을 발견하는 강력한 무기가 됩니다.
코드 예제
import plotly.graph_objects as go
import numpy as np
# 두 제품의 고객 만족도 점수 (0~100점)
np.random.seed(42)
product_a_scores = np.random.normal(75, 10, 500) # 평균 75, 표준편차 10
product_b_scores = np.random.normal(68, 15, 500) # 평균 68, 표준편차 15
# Figure 생성 및 두 개의 히스토그램 추가
fig = go.Figure()
# 제품 A 히스토그램
fig.add_trace(go.Histogram(
x=product_a_scores,
name='제품 A',
opacity=0.7, # 겹칠 때 뒤가 보이도록 반투명
nbinsx=30, # 30개 구간으로 분할
marker=dict(color='royalblue', line=dict(color='white', width=1))
))
# 제품 B 히스토그램
fig.add_trace(go.Histogram(
x=product_b_scores,
name='제품 B',
opacity=0.7,
nbinsx=30,
marker=dict(color='orange', line=dict(color='white', width=1))
))
# 레이아웃 설정 - 두 히스토그램을 겹쳐서 표시
fig.update_layout(
title='제품별 고객 만족도 분포 비교',
xaxis_title='만족도 점수',
yaxis_title='고객 수',
barmode='overlay', # 'stack'으로 하면 쌓이고, 'overlay'는 겹침
template='plotly_white',
hovermode='x unified'
)
fig.show()
설명
이것이 하는 일: 이 코드는 두 제품의 고객 만족도 점수 500개씩을 30개 구간으로 나누어 히스토그램으로 그리고, 두 분포를 반투명하게 겹쳐 보여줘 어느 제품의 만족도가 더 높고 일관적인지를 시각적으로 비교할 수 있게 합니다. 첫 번째로, numpy의 random.normal로 정규분포를 따르는 샘플 데이터를 생성합니다.
product_a_scores는 평균 75점, 표준편차 10점으로 대부분의 점수가 65~85점 사이에 밀집되어 있고, product_b_scores는 평균 68점, 표준편차 15점으로 더 넓게 퍼져 있습니다. 실무에서는 이런 분포 차이가 제품 품질의 일관성을 나타내며, 표준편차가 작을수록 모든 고객이 비슷한 경험을 한다는 의미입니다.
이 데이터 생성 방식은 A/B 테스트 결과를 시뮬레이션할 때도 매우 유용합니다. 그 다음으로, 각 제품에 대해 go.Histogram 트레이스를 생성합니다.
nbinsx=30은 데이터를 30개 구간으로 나눈다는 뜻으로, 이 값을 조정하면 분포의 디테일이 달라집니다. 10으로 하면 큰 그림만 보이고, 100으로 하면 너무 세밀해져 노이즈가 심합니다.
보통 데이터 개수의 제곱근 정도가 적절하지만, Plotly는 기본값으로도 꽤 잘 선택합니다. opacity=0.7은 70% 불투명도로, 두 히스토그램이 겹쳐도 뒤에 있는 막대가 비치도록 해 비교를 쉽게 만듭니다.
barmode='overlay'는 두 히스토그램을 같은 공간에 겹쳐 그립니다. 대안으로 'stack'을 사용하면 제품 B 막대가 제품 A 위에 쌓이는데, 이는 두 그룹의 합계 분포를 보고 싶을 때 유용합니다.
'group'을 사용하면 나란히 배치되지만, 히스토그램에서는 보통 overlay가 가장 비교하기 좋습니다. hovermode='x unified'는 마우스를 특정 점수 구간에 올리면 두 제품의 빈도가 하나의 툴팁에 함께 표시되어 직접 비교할 수 있게 합니다.
marker의 line=dict(color='white', width=1)은 각 막대 사이에 얇은 흰색 경계선을 그려 막대들을 구분합니다. 이것이 없으면 연속된 색상 덩어리처럼 보여 개별 구간을 파악하기 어렵습니다.
여러분이 이 코드를 사용하면 "제품 A가 평균적으로 높고 일관적이며, 제품 B는 평균은 낮지만 일부 고객은 매우 높은 점수를 줬다"는 인사이트를 3초 만에 얻을 수 있습니다. 이는 단순히 평균과 표준편차를 보고하는 것보다 훨씬 강력한 커뮤니케이션이며, 제품 개선 방향을 결정하는 데 결정적인 정보를 제공합니다.
또한 nbinsx를 조정하며 여러 해상도로 분포를 탐색하면, 숨겨진 이중 봉우리(bimodal) 분포나 이상치 군집을 발견할 수도 있습니다.
실전 팁
💡 최적의 bin 개수를 찾으려면 Sturges' rule(k = 1 + log2(n))이나 Freedman-Diaconis rule을 사용하세요. 또는 nbinsx를 생략하면 Plotly가 자동으로 계산합니다.
💡 누적 분포를 보려면 cumulative=dict(enabled=True)를 추가하세요. "80%의 고객이 70점 이하"같은 백분위수 기반 인사이트를 얻을 수 있습니다.
💡 히스토그램이 심하게 왼쪽이나 오른쪽으로 치우쳐 있으면 로그 변환을 고려하세요. x=np.log10(data)로 변환하면 왜도가 줄어들어 분포를 더 잘 볼 수 있습니다.
💡 normalization='probability'를 설정하면 y축이 개수가 아닌 확률로 표시됩니다. 샘플 크기가 다른 두 그룹을 비교할 때 필수입니다.
💡 박스 플롯과 함께 보려면 서브플롯으로 위에 박스 플롯, 아래에 히스토그램을 배치하세요. 분포의 사분위수와 이상치를 동시에 파악할 수 있어 강력합니다.
6. 박스 플롯 - 통계적 요약과 이상치 탐지
시작하며
여러분이 여러 지역의 집값을 비교하거나, 다양한 실험 조건의 결과를 분석할 때 각 그룹마다 평균만 보면 중요한 정보를 놓치게 됩니다. "A 지역 평균 집값 5억"이라는 말이 "대부분 5억 근처"인지 "3억과 7억이 섞여있는데 평균이 5억"인지 알 수 없죠.
이런 문제는 데이터의 산포도와 이상치를 무시할 때 발생합니다. 평균과 표준편차만으로는 분포의 대칭성, 극단값의 존재, 사분위수 등을 파악할 수 없어, 잘못된 결론에 도달할 위험이 큽니다.
특히 의학, 금융, 품질관리 분야에서는 이상치가 치명적인 문제를 나타낼 수 있어 반드시 탐지해야 합니다. 바로 이럴 때 필요한 것이 박스 플롯(Box Plot, 또는 Box-and-Whisker Plot)입니다.
중앙값, 사분위수, 이상치를 하나의 시각화에 담아 데이터의 통계적 요약을 즉시 보여주고, 여러 그룹을 나란히 놓으면 분포의 차이를 직관적으로 비교할 수 있습니다.
개요
간단히 말해서, 박스 플롯은 데이터의 중앙값, 제1사분위수(Q1), 제3사분위수(Q3), 최솟값, 최댓값, 그리고 이상치를 박스와 선으로 표현하여 분포의 핵심 통계량을 한눈에 보여주는 시각화 도구입니다. 왜 이 개념이 필요한지 실무 관점에서 설명하자면, 데이터 분석에서는 "대표값"만큼 "변동성"과 "이상치"가 중요합니다.
A/B 테스트에서 평균 전환율이 같아도 한쪽은 매우 일정하고 다른 쪽은 극단적으로 높거나 낮은 경우가 섞여있다면, 같은 성능이라고 볼 수 없습니다. 박스 플롯은 이런 차이를 즉시 드러냅니다.
예를 들어, 제조업에서 제품 불량률을 모니터링할 때 박스 플롯을 그리면 특정 날짜나 라인에서 이상치가 발견되어 품질 문제를 조기에 발견할 수 있습니다. 또한 의학 연구에서 약물 반응성을 비교할 때, 박스 플롯은 효과의 일관성과 부작용 위험(극단값)을 동시에 평가할 수 있게 해줍니다.
기존에는 SPSS나 R로 복잡한 코드를 작성해야 박스 플롯을 그릴 수 있었다면, 이제는 Plotly로 몇 줄이면 인터랙티브한 박스 플롯을 만들고, 마우스로 각 통계량의 정확한 값을 확인할 수 있습니다. Plotly 박스 플롯의 핵심 특징은 첫째, 마우스 호버로 중앙값, Q1, Q3, 최솟값, 최댓값을 즉시 확인할 수 있고, 둘째, 이상치 점을 클릭하면 해당 데이터의 상세 정보를 볼 수 있으며, 셋째, 여러 그룹을 나란히 또는 중첩해서 배치해 분포를 직접 비교할 수 있다는 점입니다.
이러한 특징들이 통계적 분석을 시각화의 영역으로 끌어올려, 통계 비전문가도 데이터의 분포 특성을 직관적으로 이해할 수 있게 해줍니다.
코드 예제
import plotly.graph_objects as go
import numpy as np
# 세 가지 배송 방식의 배송 시간 데이터 (시간 단위)
np.random.seed(42)
standard = np.random.normal(48, 8, 100) # 평균 48시간, 표준편차 8
express = np.random.normal(24, 4, 100) # 평균 24시간, 표준편차 4
same_day = np.random.normal(6, 2, 100) # 평균 6시간, 표준편차 2
# 일부 이상치 추가 (배송 지연 사례)
standard = np.append(standard, [80, 85, 90])
express = np.append(express, [45, 50])
# Figure 생성
fig = go.Figure()
# 각 배송 방식별 박스 플롯 추가
fig.add_trace(go.Box(
y=standard,
name='일반 배송',
marker=dict(color='lightblue'),
boxmean='sd', # 평균과 표준편차도 함께 표시
notched=True # 중앙값의 신뢰구간을 노치로 표시
))
fig.add_trace(go.Box(
y=express,
name='빠른 배송',
marker=dict(color='lightgreen'),
boxmean='sd',
notched=True
))
fig.add_trace(go.Box(
y=same_day,
name='당일 배송',
marker=dict(color='lightcoral'),
boxmean='sd',
notched=True
))
# 레이아웃 설정
fig.update_layout(
title='배송 방식별 배송 시간 분포 비교',
yaxis_title='배송 시간 (시간)',
template='plotly_white',
showlegend=True,
hovermode='closest'
)
fig.show()
설명
이것이 하는 일: 이 코드는 세 가지 배송 방식의 배송 시간 데이터를 박스 플롯으로 그려, 각 방식의 중앙값, 변동성, 이상치를 시각적으로 비교하고, 의도적으로 추가한 배송 지연 사례(이상치)를 자동으로 점으로 표시하여 품질 모니터링을 가능하게 합니다. 첫 번째로, 세 가지 배송 방식의 배송 시간을 정규분포로 생성합니다.
일반 배송은 평균 48시간이지만 표준편차가 8시간으로 변동이 크고, 당일 배송은 평균 6시간이며 표준편차가 2시간으로 매우 일정합니다. 이런 차이는 실제 물류 서비스의 특성을 반영합니다 - 빠른 배송일수록 통제가 엄격해 변동이 작습니다.
np.append로 일부 극단값(8090시간, 4550시간)을 추가했는데, 이는 실제로 발생한 배송 지연 사례를 시뮬레이션하며, 박스 플롯이 이를 자동으로 이상치로 탐지해 점으로 표시합니다. 그 다음으로, 각 배송 방식에 대해 go.Box 트레이스를 생성합니다.
boxmean='sd'는 박스 안에 평균(다이아몬드 모양)과 표준편차(가로선)를 함께 표시하는 옵션으로, 중앙값과 평균이 얼마나 다른지 보면 분포의 왜도를 파악할 수 있습니다. 평균이 중앙값보다 훨씬 크면 큰 값 쪽으로 꼬리가 긴 분포입니다.
notched=True는 박스의 중앙에 V자 모양의 노치를 만드는데, 이는 중앙값의 95% 신뢰구간을 나타냅니다. 두 박스의 노치가 겹치지 않으면 중앙값이 통계적으로 유의하게 다르다는 의미로, 시각적으로 통계 검정을 수행하는 셈입니다.
박스 플롯의 구조를 이해하면 더 유용합니다. 박스의 아래쪽 경계는 Q1(25백분위수), 위쪽 경계는 Q3(75백분위수), 박스 안의 선은 중앙값(50백분위수)입니다.
박스에서 위아래로 뻗은 선(whisker)은 Q1 - 1.5×IQR과 Q3 + 1.5×IQR까지이며(IQR = Q3 - Q1), 이 범위를 벗어나는 값들은 이상치로 간주되어 개별 점으로 표시됩니다. 이는 통계학의 표준 방법론이며, 자동으로 계산되므로 여러분은 데이터만 넣으면 됩니다.
세 개의 박스 플롯을 나란히 배치하면 비교가 극적으로 쉬워집니다. 박스의 높이(IQR)를 보면 변동성을 즉시 비교할 수 있고, 박스의 위치를 보면 중앙값 차이를 파악하며, 이상치 점들을 보면 각 방식의 최악의 케이스를 확인할 수 있습니다.
예를 들어, 이 차트에서 일반 배송의 이상치가 8090시간인 것을 발견하면, 해당 주문을 조사해 왜 그렇게 오래 걸렸는지 원인을 찾을 수 있습니다. 여러분이 이 코드를 사용하면 "당일 배송이 가장 빠르고 일정하지만, 일반 배송은 간혹 34일 걸리는 경우가 있어 고객 불만이 발생할 수 있다"는 인사이트를 즉시 얻습니다.
또한 주간/월간으로 이 차트를 자동 생성해 배송 품질을 모니터링하고, 이상치가 증가하는 추세가 보이면 물류 파트너를 교체하는 등의 의사결정을 할 수 있습니다.
실전 팁
💡 이상치의 실제 값을 보려면 boxpoints='outliers'를 사용하세요. 'all'로 하면 모든 데이터 포인트가 박스 옆에 점으로 표시되어 분포를 더 자세히 볼 수 있습니다.
💡 여러 그룹을 시간 순으로 비교하려면 x축에 날짜를 넣고 각 날짜마다 박스 플롯을 그리세요. 품질이 시간에 따라 개선되는지 악화되는지 추세를 파악할 수 있습니다.
💡 바이올린 플롯(Violin Plot)도 고려하세요. go.Violin은 박스 플롯의 정보에 커널 밀도 추정까지 더해 분포의 형태를 더 풍부하게 보여줍니다.
💡 색상으로 그룹을 구분할 때 colorblind-friendly 팔레트를 사용하세요. 파란색-주황색 조합이 가장 안전하며, 빨강-초록 조합은 피하세요.
💡 이상치를 자동으로 하이라이트하려면 marker_outliercolor='red'를 설정하세요. 정상 데이터와 이상치가 색상으로 명확히 구분되어 모니터링이 쉬워집니다.
7. 히트맵 - 다차원 데이터의 패턴 발견
시작하며
여러분이 시간대별, 요일별 웹사이트 트래픽을 분석하거나, 여러 제품 간의 상관관계 행렬을 해석할 때 숫자로 가득 찬 표를 보면 어디서부터 봐야 할지 막막하죠. 7×24 = 168개의 셀을 하나하나 읽으며 패턴을 찾기엔 인간의 인지 능력이 부족합니다.
이런 문제는 2차원 이상의 데이터를 다룰 때 항상 발생합니다. 표는 정확하지만 직관적이지 않고, 큰 값과 작은 값이 어디 있는지, 어떤 패턴이나 군집이 있는지를 파악하려면 엄청난 집중력이 필요합니다.
특히 상관관계 행렬처럼 대칭 구조를 가진 데이터는 중복된 정보를 읽는 데도 시간이 낭비됩니다. 바로 이럴 때 필요한 것이 히트맵(Heatmap)입니다.
숫자를 색상으로 변환해 크기를 시각적으로 표현하면, 패턴, 이상치, 군집이 즉시 눈에 들어오며, 수백 개의 데이터 포인트에서도 핵심 인사이트를 3초 만에 발견할 수 있습니다.
개요
간단히 말해서, 히트맵은 2차원 배열의 각 셀 값을 색상의 농도나 색조로 표현하여, 숫자의 크기를 시각적으로 비교하고 패턴을 발견할 수 있게 해주는 시각화 도구입니다. 왜 이 개념이 필요한지 실무 관점에서 설명하자면, 현대의 데이터는 대부분 다차원입니다.
고객 세그먼트별 제품 선호도, 시간대별 서버 부하, 유전자 간의 발현 상관관계 등은 모두 행렬 형태이며, 이를 표로만 보면 절대 패턴을 찾을 수 없습니다. 히트맵을 사용하면 "월요일 오전 9시에 트래픽이 급증한다", "제품 A와 B는 함께 구매되는 경우가 많다", "특정 유전자 그룹이 강한 상관관계를 보인다" 같은 인사이트가 색상 패턴으로 즉시 드러납니다.
예를 들어, 마케팅 캠페인 성과를 지역×채널 히트맵으로 그리면, 어느 지역에서 어느 채널이 효과적인지 한눈에 보여 예산 재배치 결정을 빠르게 내릴 수 있습니다. 기존에는 Excel의 조건부 서식으로 셀에 색을 칠했지만 인터랙티브하지 않았다면, Plotly 히트맵은 마우스 호버로 정확한 수치를 확인하고, 색상 스케일을 조정하며, 특정 영역을 확대할 수 있습니다.
Plotly 히트맵의 핵심 특징은 첫째, 다양한 colorscale(Viridis, RdBu, Blues 등)로 데이터 특성에 맞게 색상을 선택할 수 있고, 둘째, 마우스 호버로 x, y 좌표와 정확한 값을 확인할 수 있으며, 셋째, 텍스트를 셀 안에 함께 표시해 색상과 숫자를 동시에 제공할 수 있다는 점입니다. 이러한 특징들이 복잡한 다차원 데이터를 직관적이고 아름다운 시각화로 변환해, 의사결정자들이 데이터 기반으로 빠르고 정확하게 판단할 수 있게 합니다.
코드 예제
import plotly.graph_objects as go
import numpy as np
# 7일 × 24시간 웹사이트 트래픽 데이터 (시뮬레이션)
days = ['월', '화', '수', '목', '금', '토', '일']
hours = [f'{h:02d}:00' for h in range(24)]
# 기본 패턴: 평일 낮 시간 트래픽 높고, 주말 오후 트래픽 높음
np.random.seed(42)
traffic = np.random.poisson(100, (7, 24)) # 기본 트래픽
# 패턴 추가: 평일(0-4) 업무 시간(9-18) 트래픽 증가
traffic[0:5, 9:18] += np.random.poisson(200, (5, 9))
# 주말(5-6) 오후(14-22) 트래픽 증가
traffic[5:7, 14:22] += np.random.poisson(150, (2, 8))
# 히트맵 생성
fig = go.Figure(data=go.Heatmap(
z=traffic, # 값 (2D 배열)
x=hours, # x축 레이블 (시간)
y=days, # y축 레이블 (요일)
colorscale='YlOrRd', # 노랑-주황-빨강 색상 스케일
colorbar=dict(title='방문자 수'), # 색상 바 제목
text=traffic, # 각 셀에 숫자 표시
texttemplate='%{text}', # 텍스트 형식
textfont=dict(size=8), # 텍스트 크기
hovertemplate='<b>%{y} %{x}</b><br>방문자: %{z}명<extra></extra>'
))
# 레이아웃 설정
fig.update_layout(
title='요일별 시간대별 웹사이트 트래픽 히트맵',
xaxis_title='시간',
yaxis_title='요일',
template='plotly_white',
xaxis=dict(side='bottom'),
width=1200,
height=400
)
fig.show()
설명
이것이 하는 일: 이 코드는 7일(행) × 24시간(열) = 168개 셀의 트래픽 데이터를 색상으로 변환하고, 평일 업무 시간과 주말 오후에 트래픽이 높다는 패턴을 붉은색 영역으로 시각적으로 강조하며, 각 셀에 정확한 방문자 수도 함께 표시합니다. 첫 번째로, numpy의 poisson 분포로 현실적인 트래픽 데이터를 생성합니다.
포아송 분포는 일정 시간 동안 발생하는 이벤트 수를 모델링할 때 사용되며, 웹 트래픽처럼 이산적인 카운트 데이터에 적합합니다. 기본 트래픽을 평균 100으로 설정한 후, 특정 시간대(평일 918시, 주말 1422시)에 추가 트래픽을 더해 현실적인 패턴을 만들었습니다.
실무에서는 이 부분을 데이터베이스에서 GROUP BY day, hour로 집계한 결과로 대체하게 됩니다. 그 다음으로, go.Heatmap 객체를 생성하며 2D 배열인 z와 축 레이블인 x, y를 지정합니다.
z는 실제 값이 들어있는 배열로, shape이 (rows, cols)여야 하며, 이 경우 (7, 24)입니다. x와 y는 각 행과 열의 레이블 리스트로, 숫자 대신 의미 있는 텍스트('월', '화', '09:00' 등)를 사용하면 가독성이 크게 향상됩니다.
colorscale='YlOrRd'는 노란색(낮은 값) → 주황색(중간 값) → 빨간색(높은 값)의 순차적 색상 스케일을 지정합니다. 순차적 데이터(트래픽, 매출 등)에는 단색 계열이 적합하고, 양음이 있는 데이터(수익/손실, 상관계수 등)에는 'RdBu'(빨강-파랑) 같은 발산형 색상이 적합합니다.
colorbar=dict(title='방문자 수')는 우측에 색상 바를 추가하고 제목을 지정하는데, 이는 색상과 값의 대응 관계를 명확히 보여줍니다. text=traffic과 texttemplate='%{text}'는 각 셀 안에 실제 숫자를 표시합니다.
색상만으로는 대략적인 크기를 파악하지만, 정확한 값은 알 수 없으므로 텍스트를 함께 넣으면 "시각적 직관 + 정확한 수치"를 동시에 제공할 수 있습니다. 다만 셀이 너무 많으면 텍스트가 겹쳐 보이므로, 그럴 때는 text를 생략하고 hover만 사용하는 것이 좋습니다.
hovertemplate은 마우스를 셀에 올렸을 때 "월 09:00 방문자: 324명" 같은 형식으로 정보를 보여줍니다. %{y}는 y축 레이블(요일), %{x}는 x축 레이블(시간), %{z}는 셀의 값을 자동으로 채워줍니다.
여러분이 이 코드를 사용하면 "우리 사이트는 평일 낮에 업무용 트래픽이 많고, 주말 오후에는 개인 사용자가 많다"는 인사이트를 색상 패턴만으로 즉시 얻습니다. 이는 서버 확장 시점 결정, 마케팅 캠페인 스케줄링, 고객 지원 인력 배치 등 다양한 운영 결정에 활용됩니다.
또한 여러 주의 히트맵을 비교하면 트렌드 변화나 이벤트 효과를 파악할 수 있습니다.
실전 팁
💡 상관관계 행렬을 히트맵으로 그릴 때는 colorscale='RdBu', zmid=0으로 설정하세요. 양의 상관관계는 빨강, 음의 상관관계는 파랑으로 표시되어 직관적입니다.
💡 대각선 절반만 표시하려면 np.triu_indices_from으로 상삼각 마스크를 만들고 해당 셀을 NaN으로 설정하세요. 중복 정보를 제거해 차트가 깔끔해집니다.
💡 dendrogram과 결합하면 계층적 군집화를 시각화할 수 있습니다. scipy.cluster.hierarchy로 행과 열을 재정렬하면 유사한 패턴끼리 모여 더 명확한 군집이 보입니다.
💡 값의 범위가 너무 넓으면 로그 스케일을 적용하세요. z=np.log10(traffic + 1)로 변환하면 큰 값에 색상이 포화되는 것을 방지할 수 있습니다.
💡 애니메이션으로 시간 변화를 보여주려면 여러 주의 히트맵을 Plotly Express의 animation_frame으로 연결하세요. 트래픽 패턴이 시간에 따라 어떻게 변하는지 동영상처럼 볼 수 있습니다.
8. 서브플롯 - 여러 차트를 하나의 대시보드로
시작하며
여러분이 분기 실적을 보고할 때 매출, 비용, 이익, 성장률을 각각 별도의 슬라이드로 보여주면 전체적인 맥락을 파악하기 어렵죠. 청중은 앞뒤로 슬라이드를 넘기며 비교해야 하고, 차트 간의 관계를 머릿속으로 연결해야 합니다.
이런 문제는 관련된 여러 지표를 동시에 봐야 인사이트가 나오는 상황에서 발생합니다. "매출이 늘었는데 이익은 줄었다"는 패턴은 두 차트를 나란히 놓아야 발견됩니다.
또한 같은 x축(시간)을 공유하는 여러 지표는 수직으로 정렬해야 시점별 비교가 쉬워집니다. 바로 이럴 때 필요한 것이 Plotly의 서브플롯(Subplots)입니다.
여러 차트를 하나의 Figure에 그리드로 배치하고, 축을 공유하거나 독립적으로 설정하며, 인터랙티브하게 연동할 수 있어 복합적인 스토리를 효과적으로 전달할 수 있습니다.
개요
간단히 말해서, 서브플롯은 하나의 Figure 안에 여러 개의 차트를 행렬 형태로 배치하여, 관련된 데이터를 동시에 비교하고 통합된 대시보드를 만들 수 있게 해주는 레이아웃 기법입니다. 왜 이 개념이 필요한지 실무 관점에서 설명하자면, 비즈니스 의사결정은 항상 여러 지표를 종합적으로 고려해야 합니다.
매출만 보면 성공처럼 보이지만 고객 이탈률이 높다면 장기적으로 위험합니다. 이런 복합적인 상황을 하나의 대시보드에 표현하면, 경영진이 전체 맥락을 즉시 파악하고 균형 잡힌 결정을 내릴 수 있습니다.
예를 들어, 전자상거래 대시보드에서 상단에는 일별 매출 라인 차트, 중단에는 제품 카테고리별 매출 막대 차트, 하단에는 지역별 매출 히트맵을 배치하면, "어느 날 매출이 급증했는데 어느 제품이, 어느 지역에서 잘 팔렸는지"를 한 화면에서 파악할 수 있습니다. 기존에는 여러 차트를 PowerPoint에 복사해 붙여넣었지만 인터랙티브 기능이 사라졌다면, Plotly 서브플롯은 모든 차트가 살아있는 상태로 하나의 HTML이나 대시보드 앱에 통합됩니다.
Plotly 서브플롯의 핵심 특징은 첫째, make_subplots()로 유연하게 그리드를 구성하고 각 셀의 차트 타입을 지정할 수 있으며, 둘째, shared_xaxes=True로 x축을 공유해 줌/팬이 모든 서브플롯에 동시 적용되고, 셋째, specs 파라미터로 특정 셀을 합치거나 2차 y축을 추가하는 등 복잡한 레이아웃을 만들 수 있다는 점입니다. 이러한 특징들이 단순한 차트 나열을 넘어 인터랙티브한 데이터 스토리텔링 플랫폼을 구축할 수 있게 해줍니다.
코드 예제
from plotly.subplots import make_subplots
import plotly.graph_objects as go
import numpy as np
import pandas as pd
# 샘플 데이터: 6개월간의 매출, 비용, 고객 수
months = ['1월', '2월', '3월', '4월', '5월', '6월']
revenue = [500, 550, 580, 620, 700, 750] # 매출 (백만원)
cost = [350, 370, 380, 400, 420, 450] # 비용 (백만원)
profit = [r - c for r, c in zip(revenue, cost)] # 이익
customers = [1200, 1350, 1400, 1500, 1650, 1800] # 고객 수
# 2x2 서브플롯 생성 (2행 2열)
fig = make_subplots(
rows=2, cols=2,
subplot_titles=('월별 매출 추이', '월별 비용 vs 이익', '고객 수 증가', '이익률 변화'),
specs=[[{"type": "scatter"}, {"type": "bar"}],
[{"type": "scatter"}, {"type": "scatter"}]],
vertical_spacing=0.12, # 서브플롯 간 세로 간격
horizontal_spacing=0.1 # 가로 간격
)
# (1, 1): 매출 라인 차트
fig.add_trace(go.Scatter(x=months, y=revenue, name='매출',
line=dict(color='blue', width=3), mode='lines+markers'),
row=1, col=1)
# (1, 2): 비용 vs 이익 그룹 막대 차트
fig.add_trace(go.Bar(x=months, y=cost, name='비용', marker_color='orange'),
row=1, col=2)
fig.add_trace(go.Bar(x=months, y=profit, name='이익', marker_color='green'),
row=1, col=2)
# (2, 1): 고객 수 영역 차트
fig.add_trace(go.Scatter(x=months, y=customers, name='고객 수',
fill='tozeroy', fillcolor='rgba(100, 200, 100, 0.3)',
line=dict(color='green')),
row=2, col=1)
# (2, 2): 이익률 라인 차트
profit_rate = [p/r*100 for p, r in zip(profit, revenue)]
fig.add_trace(go.Scatter(x=months, y=profit_rate, name='이익률 (%)',
line=dict(color='purple', width=3), mode='lines+markers'),
row=2, col=2)
# 전체 레이아웃 설정
fig.update_layout(
title_text='분기 비즈니스 대시보드',
showlegend=True,
height=700,
template='plotly_white'
)
# 각 서브플롯의 y축 제목 설정
fig.update_yaxes(title_text='금액 (백만원)', row=1, col=1)
fig.update_yaxes(title_text='금액 (백만원)', row=1, col=2)
fig.update_yaxes(title_text='고객 수 (명)', row=2, col=1)
fig.update_yaxes(title_text='이익률 (%)', row=2, col=2)
fig.show()
설명
이것이 하는 일: 이 코드는 2×2 그리드에 매출, 비용/이익, 고객 수, 이익률이라는 4개의 핵심 지표를 각각 다른 차트 타입(라인, 막대, 영역, 라인)으로 배치하여, 비즈니스 성과를 다각도로 분석할 수 있는 통합 대시보드를 만듭니다. 첫 번째로, make_subplots() 함수로 서브플롯 구조를 정의합니다.
rows=2, cols=2는 2×2 그리드를 만들고, subplot_titles는 각 서브플롯의 제목을 리스트로 지정합니다. specs 파라미터는 각 셀의 차트 타입을 정의하는데, [1, 1]은 scatter, [1, 2]는 bar 등으로 지정할 수 있습니다.
대부분의 경우 "scatter"로 지정하면 라인, 영역 등 다양한 타입을 사용할 수 있지만, 특수한 타입(pie, heatmap 등)은 명시해야 합니다. vertical_spacing과 horizontal_spacing은 서브플롯 간 간격을 조정해 너무 붙어있거나 떨어져 보이지 않게 합니다.
그 다음으로, 각 서브플롯에 차트를 추가할 때 row와 col 파라미터로 위치를 지정합니다. fig.add_trace(..., row=1, col=1)은 첫 번째 행, 첫 번째 열에 차트를 그린다는 뜻입니다.
같은 서브플롯에 여러 트레이스를 추가하면 겹쳐서 그려지는데, [1, 2] 위치의 비용과 이익 막대 차트가 이 경우입니다. 각 트레이스는 일반적인 Plotly 차트와 동일한 방식으로 생성하므로, 여러분이 이미 배운 모든 기법을 서브플롯에도 적용할 수 있습니다.
[2, 1] 위치의 고객 수 차트는 fill='tozeroy'로 영역 차트(Area Chart)를 만듭니다. 이는 라인 차트와 동일하지만 라인 아래를 색으로 채워 증가 추세를 더 강조합니다.
fillcolor에 alpha 값(0.3)을 포함시켜 반투명하게 만들면 격자선이 비치며 가독성이 향상됩니다. update_yaxes()로 각 서브플롯의 y축 제목을 개별적으로 설정할 수 있습니다.
row, col을 지정하면 해당 서브플롯의 축만 업데이트되며, 지정하지 않으면 모든 서브플롯에 적용됩니다. 마찬가지로 update_xaxes()도 사용할 수 있고, shared_xaxes=True를 make_subplots에 추가하면 모든 서브플롯의 x축이 연동되어 한 곳에서 줌을 하면 다른 곳도 함께 줌됩니다.
이는 시계열 데이터를 여러 각도로 보는 대시보드에 매우 유용합니다. 여러분이 이 코드를 사용하면 "매출과 고객 수는 증가하지만 이익률이 하락하고 있다"는 복합적인 인사이트를 한 화면에서 발견할 수 있습니다.
이는 단일 차트로는 절대 불가능하며, 경영진이 "성장은 좋지만 비용 관리가 필요하다"는 균형 잡힌 결론을 내릴 수 있게 돕습니다. 또한 이 코드를 템플릿으로 저장해두면, 매달 데이터만 업데이트해 자동으로 대시보드를 생성하는 파이프라인을 구축할 수 있습니다.
실전 팁
💡 특정 셀을 병합하려면 specs에서 colspan, rowspan을 사용하세요. 예: specs=[[{"colspan": 2}, None], [{}, {}]]는 첫 행 전체를 하나의 큰 차트로 만듭니다.
💡 2차 y축을 추가하려면 specs=[{"secondary_y": True}]로 설정하고 add_trace 시 secondary_y=True를 지정하세요. 스케일이 다른 두 지표(매출과 전환율 등)를 한 차트에 그릴 때 필수입니다.
💡 서브플롯마다 다른 템플릿을 적용할 수는 없지만, 배경색이나 격자선은 update_xaxes/update_yaxes의 row, col로 개별 설정할 수 있습니다.
💡 3D 차트나 지도를 포함하려면 specs에서 {"type": "surface"}나 {"type": "mapbox"} 등을 명시하세요. 2D 차트와 3D 차트를 한 대시보드에 넣을 수 있습니다.
💡 Dash 프레임워크와 결합하면 서브플롯 간 상호작용을 구현할 수 있습니다. 예를 들어, 한 서브플롯에서 막대를 클릭하면 다른 서브플롯이 해당 데이터로 필터링되는 인터랙티브 대시보드를 만들 수 있습니다.