본문 바로가기

FootballAnalysis

[K리그 데이터 분석] 4. Dashboard.py by Dash

이번 포스팅에선 plotly.graph_object에서 PolarBarChart를 임포트하여 선수 데이터를 시각화하는 플롯을 만든 과정에 대해서 설명하려고 한다.

 

 

또한, PolarBarChart를 클래스로 구현하여 사용자가 접근하려는 데이터에 따라 유동적으로 재사용되게 하려고 한다.

import dash
from dash import dcc, html
import plotly.graph_objs as go
from dash.dependencies import Output, Input, State
import pandas as pd
from polar_bar_chart import PolarBarChart

 

필요한 라이브러리와 Dash Framework를 임포트 해주었다.

 

 

# Dash 애플리케이션 생성
app = dash.Dash(__name__)

 

Dash는 Flask 기반의 프레임워크이기 때문에 Flask의 규칙을 일부 따른다.

 

 

RECENTLY_UPDATED_ROUND = 29

data = pd.read_csv(f'data/preprocessed/{RECENTLY_UPDATED_ROUND}-round-preprocessed.csv')
df = pd.DataFrame(data)

app.layout = html.Div([

...

# 라운드 번호 입력을 위한 드래그바와 버튼
    html.Div([
        html.Label("Select Round Number"),
        dcc.Slider(
            id='round-slider',
            min=20,
            max=RECENTLY_UPDATED_ROUND,  # 예시로 1~38 라운드 설정
            value=RECENTLY_UPDATED_ROUND,  # 기본값으로 27라운드 설정
            marks={i: str(i) for i in range(20, RECENTLY_UPDATED_ROUND + 1)},
            step=1,
        ),
    ], style={'width': '540px', 'margin': '20px auto', 'textAlign': 'center'}),

    ...

    # 라운드 번호 입력을 위한 드래그바와 버튼
    html.Div([
        html.Label("Select Round Number"),
        dcc.Slider(
            id='round-slider',
            min=20,
            max=RECENTLY_UPDATED_ROUND,  # 예시로 1~38 라운드 설정
            value=RECENTLY_UPDATED_ROUND,  # 기본값으로 27라운드 설정
            marks={i: str(i) for i in range(20, RECENTLY_UPDATED_ROUND + 1)},
            step=1,
        ),
    ], style={'width': '540px', 'margin': '20px auto', 'textAlign': 'center'}),

 

대시보드를 구현하기 위해 RECENTLY_UPDATED_ROUND라는 상수로 대시보드가 포함하는 매치 라운드의 범위를 설정하고 df를 통해 대시보드가 나타낼 초기 화면에 필요한 데이터를 설정하였다.

이외에도 팀과 선수 선택을 위한 드롭다운, 피처를 선정하는 체크리스트, 그래프를 Dash가 요구하는 Html 형식에 맞춰 작성하였다.

다음에는 콜백함수를 적용하여 대시보드에 다양한 기능을 구현하였다.

 

 

라운드 슬라이더 - 드롭다운 상호작용 기능 구현

 

@app.callback(
    Output('team-dropdown', 'options'),
    [Input('round-slider', 'value')]
)
def update_team_dropdown(selected_round):
    # 슬라이드 바에서 선택한 라운드에 해당하는 CSV 파일을 로드
    df = pd.read_csv(f'data/preprocessed/{selected_round}-round-preprocessed.csv')
    options = [{'label': team, 'value': team} for team in df['구단'].unique()]
    return options

@app.callback(
    Output('player-dropdown', 'options'),
    [Input('team-dropdown', 'value')],
    [State('round-slider', 'value')]
)
def update_player_dropdown(selected_team, selected_round):
    if selected_team is None:
        return []

    # 슬라이드 바에서 선택한 라운드에 해당하는 CSV 파일을 로드
    df = pd.read_csv(f'data/preprocessed/{selected_round}-round-preprocessed.csv')
    options = [{'label': player, 'value': player} for player in df[df['구단'] == selected_team]['선수명'].unique()]
    return options

 

사용자가 라운드 슬라이더로 시각화할 특정 라운드를 선정하면 두 종류의 드롭다운이 해당 라운드의 데이터를 불러와서 팀과 선수를 선택하는 드롭다운에 반영한다.

 

 

슬라이더, 드롭다운, 체크리스트가 변경될 때 마다 차트를 갱신하는 콜백함수

 

@app.callback(
    Output('radar-chart', 'figure'),
    [Input('round-slider', 'value'),
     Input('player-dropdown', 'value'),
     Input('team-dropdown', 'value'),
     Input('column-checklist', 'value')]
)
def update_chart(selected_round, selected_player, selected_team, selected_columns):
    if not selected_player or not selected_team or not selected_columns:
        fig = go.Figure()
        fig.update_layout(
            annotations=[{
                'text': f"No data available for player {selected_player}",
                'xref': 'paper',
                'yref': 'paper',
                'showarrow': False,
                'font': {
                    'size': 20,
                    'family': 'MyCustomFont, sans-serif',
                    'color': 'white'
                },
                'x': 0.5,
                'y': 0.5,
                'xanchor': 'center',
                'yanchor': 'middle'
            }],
            plot_bgcolor='rgba(0,0,0,0)',
            paper_bgcolor='rgb(0,89,167)',
            width=960,
            height=540,
            xaxis=dict(
                showgrid=False,  # x축 그리드 라인 숨기기
                zeroline=False,  # x축 기준선 숨기기
                visible=False,  # x축 자체 숨기기
            ),
            yaxis=dict(
                showgrid=False,  # y축 그리드 라인 숨기기
                zeroline=False,  # y축 기준선 숨기기
                visible=False,  # y축 자체 숨기기
            )
        )
        return fig

    # 슬라이드 바에서 선택한 라운드에 해당하는 CSV 파일을 로드
    df = pd.read_csv(f'data/preprocessed/{selected_round}-round-preprocessed.csv')

    # RadarChart 클래스의 인스턴스를 생성하여 데이터를 처리하고 그래프를 생성
    chart = PolarBarChart(player_name=selected_player, team_name=selected_team,
                          round_number=selected_round, df=df, radar_columns=selected_columns)
    return chart.get_figure()

 

추후 다양한 종류의 플롯이 도입됨에 따라 다양한 클래스를 적용하여 플롯을 생성하도록 수정할 필요가 있어보인다.

 

 

 

GitHub - jeongbeenson19/K-league-pipeline-project

Contribute to jeongbeenson19/K-league-pipeline-project development by creating an account on GitHub.

github.com