[Plotly 실전] 우리 집 도마뱀 체중 기록을 인터랙티브 차트로 만들기
한번 씩 재는 도마뱀 체중 기록, 숫자로만 쌓아두고 있나요? Plotly로 체중 분포 히스토그램부터 개체별 성장 차트, 드롭다운/버튼이 달린 대시보드까지 만들어 봅니다.
시작하며 - 체중 기록, 차트로 만들면 다르다
도마뱀을 키우면서 체중을 재고 있다면, 그 숫자들을 그냥 메모장에 쌓아두고 있지는 않나요? 이번 글에서는 그 체중 기록을 Plotly로 인터랙티브 차트로 만들어 봅니다.
이전 시각화 시리즈(Step 1~4)에서 matplotlib 기본기를 다졌으니, 이제 Plotly로 한 단계 올라갈 차례입니다. 마우스를 올리면 수치가 뜨고, 드롭다운으로 개체를 골라보고, 버튼으로 테마를 바꾸는 차트를 직접 만들어 봅니다.
구체적으로 이번 실습에서 다룰 내용은 세 가지입니다:
- Part 1: 체중 분포 파악하기 (히스토그램, 박스플롯, 바이올린 플롯)
- Part 2: 드롭다운으로 개체별 체중 골라보기
- Part 3: 버튼으로 차트 모양 바꾸기
Google Colab에서 바로 따라할 수 있도록 모든 코드를 준비했습니다. 아래는 이번 글의 최종 결과물 미리보기입니다.
완성본 미리보기 - 드롭다운 + 버튼이 달린 인터랙티브 차트
환경 세팅 - Google Colab 준비
이전 시리즈와 동일하게 Google Colab을 사용합니다. colab.research.google.com에 접속해서 "+ New notebook" 버튼을 클릭하면 바로 시작할 수 있습니다.
라이브러리 설치
첫 번째 셀에 아래 코드를 입력하고 Shift + Enter를 누르세요:
!pip install plotly pandas numpy
설치가 완료되면 "Successfully installed..." 메시지가 나타납니다.
각 라이브러리의 역할은 다음과 같습니다:
- plotly: 인터랙티브 차트를 그리는 라이브러리
- pandas: 데이터를 표(DataFrame) 형태로 다루는 라이브러리
- numpy: 숫자 계산과 랜덤 데이터 생성용 라이브러리
Part 1: 체중 분포 파악하기 - 통계용 차트 3종
"우리 집 도마뱀 체중이 정상 범위인가?" 이 질문에 답하려면 다른 개체들의 체중 분포를 알아야 합니다. Plotly의 통계용 차트 세 가지로 체중 데이터를 분석해봅니다.
1-1. 히스토그램 - 체중 분포를 한눈에 보기
"도마뱀 200마리의 성체 체중이 어느 구간에 몰려있을까?" 이런 질문에 답하는 차트가 히스토그램입니다. 데이터를 구간별로 나눠서 각 구간에 몇 마리가 있는지 막대로 보여줍니다.
import plotly.express as px
import numpy as np
# 도마뱀 성체 체중 데이터 만들기 (평균 55g, 표준편차 8)
np.random.seed(42)
weights = np.random.normal(55, 8, 200)
weights = np.clip(weights, 35, 85) # 성체 현실 범위로 제한 (35~85g)
fig = px.histogram(
weights,
nbins=15, # 구간을 15개로 나누기
title="도마뱀 200마리 성체 체중 분포",
labels={"value": "체중(g)", "count": "개체 수"},
color_discrete_sequence=["#667eea"] # 막대 색상
)
fig.update_layout(template="plotly_white")
fig.show()
히스토그램 - 55g 부근에 데이터가 집중된 것을 한눈에 확인
코드를 하나씩 살펴보면:
np.random.normal(55, 8, 200): 평균 55g, 표준편차 8인 정규분포 데이터 200개를 생성합니다.np.clip(weights, 35, 85): 성체 체중으로 비현실적인 값(20g대 등)을 제거합니다. 실제 성체는 35~85g 범위에 분포합니다.nbins=15: 데이터를 15개 구간으로 나눕니다. 숫자를 줄이면 구간이 넓어지고, 늘리면 좁아집니다.template="plotly_white": 깔끔한 흰색 배경 테마를 적용합니다.
실전 팁
마우스를 막대 위에 올리면 해당 구간의 정확한 범위와 개체 수가 표시됩니다. 이것이 matplotlib과 Plotly의 가장 큰 차이점입니다.
1-2. 박스플롯 - 해칭 월별 체중 비교하기
"1월에 태어난 개체, 3월에 태어난 개체, 6월에 태어난 개체 중 현재 체중이 가장 고른 그룹은?" 이럴 때 박스플롯이 유용합니다. 중앙값, 사분위수, 이상치를 한 번에 보여줍니다.
해칭 시기가 빠를수록 사육 기간이 길어 평균 체중이 높고, 최근 해칭일수록 아직 성장 중이라 체중이 낮은 패턴을 가정했습니다.
import plotly.express as px
import pandas as pd
import numpy as np
np.random.seed(42)
data = {
"해칭월": ["1월생"]*50 + ["3월생"]*50 + ["6월생"]*50,
"체중(g)": list(np.clip(np.random.normal(60, 7, 50), 35, 85)) + # 1월생: 평균 60g
list(np.clip(np.random.normal(52, 6, 50), 35, 85)) + # 3월생: 평균 52g
list(np.clip(np.random.normal(42, 5, 50), 25, 65)) # 6월생: 평균 42g (아직 성장 중)
}
df = pd.DataFrame(data)
fig = px.box(
df, x="해칭월", y="체중(g)",
title="해칭 월별 도마뱀 체중 분포 비교",
color="해칭월",
color_discrete_sequence=["#1a1a2e", "#667eea", "#ff9a3c"]
)
fig.update_layout(template="plotly_white")
fig.show()
박스플롯 - 해칭 시기별 체중 분포와 이상치를 한눈에 비교
박스플롯 읽는 법:
- 가운데 선: 중앙값 (전체의 50% 지점)
- 박스 아래/위: 25% ~ 75% 범위 (데이터의 절반이 이 안에)
- 수염 (위아래 선): 정상 범위
- 점: 이상치 (유난히 크거나 작은 개체)
1-3. 바이올린 플롯 - 분포 모양까지 보기
박스플롯에 데이터의 분포 곡선을 합친 것이 바이올린 플롯입니다. "데이터가 어디에 얼마나 몰려 있는지"를 곡선의 폭으로 보여줍니다.
fig = px.violin(
df, x="해칭월", y="체중(g)",
title="해칭 월별 체중 분포 (바이올린 플롯)",
color="해칭월",
box=True, # 내부에 박스플롯도 함께 표시
points="all", # 모든 데이터 점을 표시
color_discrete_sequence=["#1a1a2e", "#667eea", "#ff9a3c"]
)
fig.update_layout(template="plotly_white")
fig.show()
바이올린 플롯 - 곡선 폭이 넓을수록 해당 체중대에 개체가 많다는 뜻
box=True를 추가하면 바이올린 안에 박스플롯이 함께 표시됩니다.
points="all"로 개별 데이터 점도 확인할 수 있습니다.
통계 차트 비교 정리
| 차트 | 언제 쓰면 좋은지 | 핵심 정보 |
|---|---|---|
| 히스토그램 | 전체 개체의 체중 분포를 볼 때 | 구간별 빈도수 |
| 박스플롯 | 해칭월별 체중을 비교할 때 | 중앙값, 사분위수, 이상치 |
| 바이올린 | 분포 모양까지 비교할 때 | 분포 밀도 + 박스플롯 |
Part 2: 드롭다운으로 개체별 체중 골라보기
개체가 여러 마리면 차트가 복잡해집니다. "이 개체만 보고 싶은데" 할 때, 드롭다운 메뉴를 달아두면 원하는 개체를 골라서 바로 확인할 수 있습니다.
2-1. 기본 드롭다운 - 개체별 체중 전환하기
우리 집 도마뱀 베이비 3마리의 12주간 성장 기록이 있다고 가정합니다. 해칭 시기가 다르기 때문에 시작 체중이 각각 다릅니다. 드롭다운으로 특정 개체만 골라서 볼 수 있게 만들어 봅니다.
import plotly.graph_objects as go
weeks = ["1주차","2주차","3주차","4주차","5주차","6주차",
"7주차","8주차","9주차","10주차","11주차","12주차"]
gecko_1 = [28, 30, 32, 34, 36, 37, 38, 39, 40, 41, 42, 43] # 개체#1 (23년 11월생, 아성체)
gecko_2 = [5, 7, 9, 11, 13, 15, 17, 18, 20, 21, 23, 24] # 개체#2 (25년 6월생, 베이비)
gecko_3 = [4, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25] # 개체#3 (25년 7월생, 베이비)
fig = go.Figure()
# 3마리의 체중 데이터를 각각 추가
fig.add_trace(go.Bar(x=weeks, y=gecko_1, name="개체#1 (23년11월생)", marker_color="#1a1a2e"))
fig.add_trace(go.Bar(x=weeks, y=gecko_2, name="개체#2 (25년6월생)", marker_color="#667eea"))
fig.add_trace(go.Bar(x=weeks, y=gecko_3, name="개체#3 (25년7월생)", marker_color="#ff9a3c"))
fig.update_layout(
title="개체별 주간 체중 변화(g)",
yaxis_title="체중(g)",
updatemenus=[
dict(
type="dropdown", # 드롭다운 타입
direction="down", # 아래로 펼쳐지기
x=0.1, y=1.15, # 위치 (차트 왼쪽 상단)
buttons=[
dict(label="전체 보기",
method="update",
args=[{"visible": [True, True, True]},
{"title": "개체별 주간 체중 변화(g) - 전체"}]),
dict(label="개체#1만",
method="update",
args=[{"visible": [True, False, False]},
{"title": "개체별 주간 체중 변화(g) - 개체#1"}]),
dict(label="개체#2만",
method="update",
args=[{"visible": [False, True, False]},
{"title": "개체별 주간 체중 변화(g) - 개체#2"}]),
dict(label="개체#3만",
method="update",
args=[{"visible": [False, False, True]},
{"title": "개체별 주간 체중 변화(g) - 개체#3"}]),
]
)
],
template="plotly_white"
)
fig.show()
드롭다운을 열면 개체를 선택할 수 있습니다
코드 구조 살펴보기:
핵심은 updatemenus 속성입니다. 이 안에 드롭다운의 모든 설정이 들어갑니다.
type="dropdown": 드롭다운 형태로 표시 (접었다 펼쳤다)buttons: 선택 항목 목록. 각 항목마다 label(이름), method(동작), args(인자)를 지정method="update": 데이터와 레이아웃을 동시에 변경args[{"visible": [...]}]: 어떤 trace를 보이게/숨길지 결정. True는 보이기, False는 숨기기
Q. visible 배열의 순서는 뭘 기준으로 하나요?
add_trace()를 호출한 순서입니다. 개체#1을 첫 번째로 추가했으니[True, False, False]는 "개체#1만 보이기"가 됩니다. 개체#2가 두 번째이니[False, True, False]는 "개체#2만 보이기"가 됩니다.
2-2. 드롭다운 스타일 튜닝
기본 드롭다운은 조금 밋밋할 수 있습니다. 배경색, 테두리, 글꼴 등을 바꿔서 좀 더 보기 좋게 만들어 봅니다.
# 위 코드에서 updatemenus 부분만 수정
fig.update_layout(
updatemenus=[
dict(
type="dropdown",
direction="down",
x=0.0, y=1.15,
showactive=True, # 현재 선택된 항목 강조
bgcolor="rgba(102, 126, 234, 0.1)", # 배경색 (연한 파란색)
bordercolor="#667eea", # 테두리 색상
font=dict(size=13, color="#333"), # 글꼴 크기와 색상
buttons=[
dict(label=" 전체 보기 ",
method="update",
args=[{"visible": [True, True, True]},
{"title": "개체별 주간 체중 변화(g) - 전체"}]),
dict(label=" 개체#1만 ",
method="update",
args=[{"visible": [True, False, False]},
{"title": "개체별 주간 체중 변화(g) - 개체#1"}]),
dict(label=" 개체#2만 ",
method="update",
args=[{"visible": [False, True, False]},
{"title": "개체별 주간 체중 변화(g) - 개체#2"}]),
dict(label=" 개체#3만 ",
method="update",
args=[{"visible": [False, False, True]},
{"title": "개체별 주간 체중 변화(g) - 개체#3"}]),
]
)
]
)
fig.show()
주요 스타일 속성을 정리하면:
| 속성 | 역할 | 예시값 |
|---|---|---|
bgcolor |
배경색 | "rgba(102, 126, 234, 0.1)" |
bordercolor |
테두리 색상 | "#667eea" |
font |
글꼴 설정 | dict(size=13, color="#333") |
showactive |
선택 항목 강조 | True |
x, y |
위치 (0~1) | x=0.0, y=1.15 |
2-3. 드롭다운으로 차트 종류 바꾸기 (고급)
같은 데이터를 막대 차트, 선 차트, 영역 차트로 전환할 수도 있습니다.
핵심은 여러 종류의 trace를 미리 만들어두고, visible로 전환하는 것입니다.
fig = go.Figure()
# 같은 데이터, 다른 차트 타입으로 3개의 trace 생성
fig.add_trace(go.Bar(
x=weeks, y=gecko_1, name="개체#1 (막대)",
visible=True,
marker_color="#1a1a2e"
))
fig.add_trace(go.Scatter(
x=weeks, y=gecko_1, name="개체#1 (선)",
visible=False,
mode="lines+markers",
line=dict(color="#1a1a2e", width=3)
))
fig.add_trace(go.Scatter(
x=weeks, y=gecko_1, name="개체#1 (영역)",
visible=False,
fill="tozeroy",
line=dict(color="#1a1a2e")
))
fig.update_layout(
title="개체#1 주간 체중 변화(g)",
yaxis_title="체중(g)",
updatemenus=[
dict(
type="dropdown",
direction="down",
x=0.0, y=1.15,
showactive=True,
buttons=[
dict(label="막대 차트",
method="update",
args=[{"visible": [True, False, False]},
{"title": "개체#1 주간 체중 변화(g) - 막대 차트"}]),
dict(label="선 차트",
method="update",
args=[{"visible": [False, True, False]},
{"title": "개체#1 주간 체중 변화(g) - 선 차트"}]),
dict(label="영역 차트",
method="update",
args=[{"visible": [False, False, True]},
{"title": "개체#1 주간 체중 변화(g) - 영역 차트"}]),
]
)
],
template="plotly_white"
)
fig.show()
드롭다운으로 막대/선/영역 차트를 자유롭게 전환
Part 3: 버튼으로 차트 모양 바꾸기
드롭다운이 "목록에서 골라서 선택"하는 것이라면, 버튼은 "바로 클릭해서 전환"하는 것입니다.
Q. 드롭다운과 버튼은 언제 각각 쓰나요?
선택지가 4개 이하면 버튼이 직관적이고, 5개 이상이면 드롭다운이 깔끔합니다. 둘 다
updatemenus를 사용하며,type값만"buttons"또는"dropdown"으로 바꾸면 됩니다.
3-1. 기본 버튼 - 개체 필터링
드롭다운 코드에서 type 값만 "buttons"로 바꿔보면
드롭다운이 버튼으로 변합니다.
fig = go.Figure()
fig.add_trace(go.Bar(x=weeks, y=gecko_1, name="개체#1", marker_color="#1a1a2e"))
fig.add_trace(go.Bar(x=weeks, y=gecko_2, name="개체#2", marker_color="#667eea"))
fig.add_trace(go.Bar(x=weeks, y=gecko_3, name="개체#3", marker_color="#ff9a3c"))
fig.update_layout(
title="개체별 주간 체중 변화(g)",
yaxis_title="체중(g)",
updatemenus=[
dict(
type="buttons", # 여기만 "buttons"로 변경!
direction="right", # 가로로 나열
x=0.0, y=1.15,
buttons=[
dict(label="전체",
method="update",
args=[{"visible": [True, True, True]},
{"title": "개체별 주간 체중 변화(g) - 전체"}]),
dict(label="개체#1",
method="update",
args=[{"visible": [True, False, False]},
{"title": "개체#1 주간 체중 변화(g)"}]),
dict(label="개체#2",
method="update",
args=[{"visible": [False, True, False]},
{"title": "개체#2 주간 체중 변화(g)"}]),
dict(label="개체#3",
method="update",
args=[{"visible": [False, False, True]},
{"title": "개체#3 주간 체중 변화(g)"}]),
]
)
],
template="plotly_white"
)
fig.show()
버튼을 클릭하면 해당 개체 데이터만 표시됩니다
드롭다운 코드와 비교해보세요. 바뀐 부분은 딱 두 곳뿐입니다:
type="dropdown"→type="buttons"direction="down"→direction="right"(버튼은 가로 나열이 자연스러움)
3-2. 버튼으로 레이아웃 변경하기
지금까지는 method="update"로 데이터를 전환했습니다.
method="relayout"을 사용하면 차트의 레이아웃(배경, 축 범위 등)을 변경할 수 있습니다.
fig = go.Figure()
fig.add_trace(go.Scatter(
x=weeks, y=gecko_1,
mode="lines+markers",
name="개체#1",
line=dict(color="#1a1a2e", width=3)
))
fig.update_layout(
title="개체#1 주간 체중 변화(g)",
yaxis_title="체중(g)",
updatemenus=[
dict(
type="buttons",
direction="right",
x=0.0, y=1.15,
buttons=[
dict(label="밝은 테마",
method="relayout",
args=[{"template": "plotly_white",
"title": "개체#1 주간 체중 변화(g) (밝은 테마)"}]),
dict(label="어두운 테마",
method="relayout",
args=[{"template": "plotly_dark",
"title": "개체#1 주간 체중 변화(g) (어두운 테마)"}]),
dict(label="전반기만",
method="relayout",
args=[{"xaxis.range": [0, 5],
"title": "개체#1 체중 변화(g) (1~6주차)"}]),
dict(label="전체 기간",
method="relayout",
args=[{"xaxis.range": [0, 11],
"title": "개체#1 주간 체중 변화(g) (전체)"}]),
]
)
],
template="plotly_white"
)
fig.show()
버튼으로 테마 전환과 기간 필터링을 한 번에
3-3. 버튼 스타일 커스터마이징
드롭다운과 마찬가지로 버튼도 색상, 위치, 글꼴 등을 자유롭게 바꿀 수 있습니다.
# updatemenus의 스타일 속성 예시
dict(
type="buttons",
direction="right",
x=0.5, y=1.15,
xanchor="center", # x 위치 기준점을 중앙으로
bgcolor="white", # 버튼 배경색
bordercolor="#667eea", # 테두리 색상
font=dict(size=12, color="#333"), # 글꼴
buttons=[...]
)
xanchor="center"를 추가하면 버튼 그룹이 차트 중앙에 정렬됩니다.
direction="left"로 바꾸면 오른쪽에서 왼쪽으로 나열할 수도 있습니다.
Part 4: 종합 - 체중 대시보드 만들기
마지막으로, 지금까지 배운 것을 합쳐봅니다. 드롭다운으로 개체를 골라보고, 버튼으로 테마를 전환하는 미니 대시보드를 만들어 봅니다.
fig = go.Figure()
fig.add_trace(go.Bar(x=weeks, y=gecko_1, name="개체#1", marker_color="#1a1a2e"))
fig.add_trace(go.Bar(x=weeks, y=gecko_2, name="개체#2", marker_color="#667eea"))
fig.add_trace(go.Bar(x=weeks, y=gecko_3, name="개체#3", marker_color="#ff9a3c"))
fig.update_layout(
title="개체별 주간 체중 변화(g) - 대시보드",
yaxis_title="체중(g)",
updatemenus=[
# 첫 번째: 드롭다운 (개체 선택)
dict(
type="dropdown",
direction="down",
x=0.0, y=1.15,
showactive=True,
bgcolor="rgba(102, 126, 234, 0.1)",
bordercolor="#667eea",
buttons=[
dict(label="전체 개체", method="update",
args=[{"visible": [True, True, True]},
{"title": "개체별 주간 체중 변화(g) - 전체"}]),
dict(label="개체#1만", method="update",
args=[{"visible": [True, False, False]},
{"title": "개체#1 주간 체중 변화(g)"}]),
dict(label="개체#2만", method="update",
args=[{"visible": [False, True, False]},
{"title": "개체#2 주간 체중 변화(g)"}]),
dict(label="개체#3만", method="update",
args=[{"visible": [False, False, True]},
{"title": "개체#3 주간 체중 변화(g)"}]),
]
),
# 두 번째: 버튼 (테마 전환)
dict(
type="buttons",
direction="right",
x=1.0, y=1.15,
xanchor="right",
buttons=[
dict(label="밝은 테마", method="relayout",
args=[{"template": "plotly_white"}]),
dict(label="어두운 테마", method="relayout",
args=[{"template": "plotly_dark"}]),
]
)
],
template="plotly_white"
)
fig.show()
왼쪽 드롭다운으로 개체 선택, 오른쪽 버튼으로 테마 전환
updatemenus에 딕셔너리를 2개 넣으면 드롭다운과 버튼이 동시에 표시됩니다.
각각의 위치를 x와 y로 조절해서 겹치지 않게 배치하면 됩니다.
핵심 정리
method 3가지 비교
| method | 변경 대상 | 사용 예시 |
|---|---|---|
update |
데이터 + 레이아웃 동시 | 개체 전환 + 제목 변경 |
restyle |
데이터(trace)만 | 색상 변경, visible 전환 |
relayout |
레이아웃만 | 테마 전환, 축 범위 조정 |
updatemenus 핵심 속성 정리
| 속성 | 드롭다운 | 버튼 |
|---|---|---|
type |
"dropdown" | "buttons" |
direction |
"down" (아래로 펼치기) | "right" (가로 나열) |
x, y |
차트 내 위치 (0~1). y=1.15로 차트 위에 배치 | |
showactive |
True면 현재 선택 항목을 강조 표시 | |
buttons |
label, method, args를 가진 딕셔너리 리스트 | |
오늘 배운 것 요약
- 통계 차트:
px.histogram(),px.box(),px.violin()으로 데이터 분포를 다양한 방식으로 시각화 - 드롭다운:
updatemenus에type="dropdown"으로 목록 선택 UI 추가 - 버튼:
updatemenus에type="buttons"로 클릭 전환 UI 추가 - 조합:
updatemenus리스트에 여러 개를 넣으면 드롭다운 + 버튼 동시 배치 가능
마무리
한번 씩 재는 체중 숫자, 메모장에 쌓아두기만 하면 그냥 숫자입니다. 오늘 배운 것처럼 차트로 만들면 성장 추이가 보이고, 드롭다운으로 개체별 비교도 됩니다.
Plotly 공식 문서(plotly.com/python)에는 슬라이더, 범위 선택기 등 더 다양한 컨트롤도 있으니 관심 있으면 한 번 살펴보세요.
질문이나 의견은 인스타그램 DM으로 편하게 보내주세요. 다음에도 실전에서 바로 쓸 수 있는 내용으로 찾아뵙겠습니다.
이 글이 도움이 되셨나요? 더 좋은 글로 찾아뵐게요!
