3 분 소요

Plotly 인터랙티브 요소: 모드바부터 슬라이더까지

오늘은 Plotly의 인터랙티브 기능들을 본격적으로 다뤄봤다. 단순히 그래프를 그리는 것을 넘어서, 사용자가 직접 데이터를 조작하고 탐색할 수 있는 환경을 만드는 게 목표였다.

실습 데이터로는 Kaggle Netflix Movies and TV Shows 데이터셋을 사용했다.


0. 데이터 준비

Netflix 데이터는 인코딩 문제가 있어서 latin1으로 읽어야 한다. 그리고 불필요한 빈 컬럼들이 많아서 dropna()로 한 번에 정리해줬다.

import pandas as pd
import plotly.graph_objects as go

df = pd.read_csv('netflix_titles.csv', encoding='latin1')

# 비어있는 컬럼 전부 제거
df = df.dropna(axis=1, how='all')

처음에 26개였던 컬럼이 12개로 줄었다. 쓸모없는 컬럼을 미리 정리해두면 이후 작업이 훨씬 깔끔해진다.


1. 모드 바(Mode Bar) 커스터마이징

Plotly 그래프 우측 상단에는 기본적으로 줌, 이동, 저장 등의 버튼이 있는 툴바가 있다. 이걸 모드 바(Mode Bar)라고 한다.

fig.show(config={...})로 모드 바를 원하는 대로 조정할 수 있다.

year_count = df.groupby('release_year')['title'].count().reset_index()

fig = go.Figure()
fig.add_trace(go.Bar(x=year_count['release_year'], y=year_count['title']))

fig.show(config={
    'displayModeBar': True,                           # 모드 바 표시
    'displaylogo': False,                             # Plotly 로고 숨기기
    'modeBarButtonsToRemove': ['toImage', 'zoom2d']  # 특정 버튼 제거
})

제거할 수 있는 버튼 종류는 zoom2d, pan2d, zoomIn2d, zoomOut2d, toImage 등이 있다. 불필요한 버튼을 제거하면 사용자 경험이 훨씬 깔끔해진다.


2. 기본 차트 3종 (Bar, Scatter, Line)

2.1 Bar 차트

연도별 Netflix 콘텐츠 수를 bar 차트로 그려봤다.

year_count = df.groupby('release_year')['title'].count().reset_index()

fig = go.Figure()
fig.add_trace(go.Bar(
    x=year_count['release_year'],
    y=year_count['title']
))
fig.show()

2.2 Scatter 차트

Movie와 TV Show를 각각 다른 trace로 표현했다. 필터링 후 각각 변수에 저장하고 trace를 두 개 추가하는 방식이다.

type_year = df.groupby(['release_year', 'type'])['title'].count().reset_index()

fig2 = go.Figure()

fig2.add_trace(go.Scatter(
    x=type_year[type_year['type']=='Movie']['release_year'],
    y=type_year[type_year['type']=='Movie']['title'],
    name='Movie'
))

fig2.add_trace(go.Scatter(
    x=type_year[type_year['type']=='TV Show']['release_year'],
    y=type_year[type_year['type']=='TV Show']['title'],
    name='TV Show'
))

fig2.show()

2.3 Line 차트

Scatter에서 mode='lines'만 추가하면 line 차트가 된다.

fig3 = go.Figure()

fig3.add_trace(go.Scatter(
    x=type_year[type_year['type']=='Movie']['release_year'],
    y=type_year[type_year['type']=='Movie']['title'],
    name='Movie',
    mode='lines'
))

fig3.add_trace(go.Scatter(
    x=type_year[type_year['type']=='TV Show']['release_year'],
    y=type_year[type_year['type']=='TV Show']['title'],
    name='TV Show',
    mode='lines'
))

fig3.show()

3. Scientific Chart: Heatmap

Plotly 공식문서의 Scientific Charts 섹션에서 Heatmap을 골랐다. 연도별 x 콘텐츠 타입별 콘텐츠 수를 색깔로 표현하기에 딱 맞는 차트다.

먼저 pivot()으로 데이터를 Heatmap에 맞는 형태로 변환한다.

heatmap_data = type_year.pivot(
    index='type',
    columns='release_year',
    values='title'
)

fig = go.Figure()

fig.add_trace(go.Heatmap(
    x=heatmap_data.columns,  # 연도
    y=heatmap_data.index,    # Movie / TV Show
    z=heatmap_data.values,   # 콘텐츠 수 (색깔값)
    colorscale='aggrnyl'     # 공식문서에서 선택한 색상 스케일
))

fig.show()

colorscale은 Plotly 공식문서에서 원하는 걸 골라서 이름만 넣으면 된다. 2015년 이후 콘텐츠가 급격히 늘어난 게 색깔로 확연히 드러난다.


4. 슬라이더 (Slider)

슬라이더란 전체 범주 중 특정 범위의 값을 조정하여 원하는 시점의 데이터만 볼 수 있게 해주는 기능이다. Plotly에서는 두 가지 방식으로 구현할 수 있다.

4.1 싱글 슬라이더

처음 개념을 이해하는 게 가장 어려웠다. 핵심은 이렇다:

모든 연도의 trace를 미리 다 그려두고, 전부 숨겨놓은 뒤, 슬라이더로 선택한 연도의 trace만 보이게 한다.

df_recent = df[df['release_year'] >= 2010]
years_recent = sorted(df_recent['release_year'].unique())

fig = go.Figure()

# 1단계: 연도별 trace를 전부 추가 (숨김 상태)
for year in years_recent:
    year_data = df[df['release_year'] == year]
    count = len(year_data)
    fig.add_trace(go.Bar(
        x=[year],
        y=[count],
        name=str(year),
        visible=False  # 전부 숨김
    ))

# 2단계: 각 연도별 step 생성
steps = []
for i, year in enumerate(years_recent):
    step = dict(
        method='update',
        label=str(year),
        args=[{'visible': [j == i for j in range(len(years_recent))]}]
    )
    steps.append(step)

# 3단계: 슬라이더 추가
fig.update_layout(sliders=[dict(steps=steps)])

fig.show()

[j == i for j in range(len(years_recent))] 이 부분이 핵심인데, 선택한 연도(i)에 해당하는 trace만 True, 나머지는 전부 False로 만들어주는 리스트다.

4.2 더블 슬라이더 (Range Slider)

싱글 슬라이더보다 훨씬 간단하다. rangeslider_visible=True 한 줄이면 된다.

fig3.update_xaxes(
    rangeslider_visible=True,
    rangeslider=dict(
        bgcolor='lightgray',  # 슬라이더 배경색
        thickness=0.3         # 슬라이더 두께 (0~1)
    )
)

fig3.show()

그래프 하단에 범위를 드래그해서 원하는 구간만 확대해서 볼 수 있다. 시계열 데이터를 탐색할 때 특히 유용하다.


5. 정리

기능 메서드/옵션 용도
모드 바 설정 fig.show(config={...}) 툴바 버튼 추가/제거
로고 숨기기 displaylogo=False Plotly 로고 제거
버튼 제거 modeBarButtonsToRemove 특정 버튼 숨기기
Heatmap go.Heatmap() 2D 색깔 격자 차트
색상 스케일 colorscale='...' Heatmap 색상 변경
싱글 슬라이더 sliders=[dict(steps=...)] 특정 값 선택
더블 슬라이더 rangeslider_visible=True 범위 선택

슬라이더 개념을 처음 접했을 때 “모든 trace를 미리 다 그려두고 숨긴다”는 발상이 낯설었다. 근데 이해하고 나니까 왜 이렇게 구현하는지 납득이 됐다. 인터랙티브 요소는 결국 사용자에게 데이터 탐색의 자유를 주는 것이고, 그 자유를 구현하는 방식이 이런 구조인 것이다.


Reference

댓글남기기