본문 바로가기
SW 개발/Data 분석 (RDB, NoSQL, Dataframe)

Python Dash를 활용한 Web App 구현 및 시계열 데이터 Visualization (Sample code)

by Kibua20 2021. 8. 21.

Dashplotly.js, React, Flask로  data visualization framework이며, MIT 라이선스로 배포하는 오픈소스입니다. Dash로 활용해서 Data 분석 결과를 웹 서비스로 구현할 수 있습니다.  아래 포스팅에서  Plotly와 Dash를 활용해서 Web 기반으로 Data를 Visualization 하는 간단한 설명을 하였으며, 이번에는 Dash에 대한 기본적인 동작 설명과 DataFrame의 시계열 데이터를 웹으로 구현하는 예제를 추가합니다.

 

 

Flask Server와 다수의 Dash App구성

앞으로 설명한 예제는 아래와 같이 구성하였습니다.  1개의 Flask 서버에 다수의 Dash app을 연결하는 방식을 사용하고, 각각의 Dash App은 각각 별도 URL을 처리하도록 하였습니다. 복수의 Dash app 객체는 메모리 사용량은 증가하지만, 코드 상의 간결함과 모듈로 분리가 쉬어 코드 관리가 용이할 것으로 보입니다. Dash App에서 처리해야 하는 URL은 /dashapp1, /dashapp2 등으로 고정하고, 그 외 URL은 Flask Server에서 처리할 예정입니다.  

 

Flase Sever 하나에 다수의 Dash App 연동

Flask Server인  application을 생성하고, Flask에서 처리해야 해야 하는 URL은 기존 Flask 사용법과 동일하게 구현하면 됩니다.  Dash App을 만들 때 ① server= application으로 전달하고, Dash에서 처리해야 하는 URL은 url_base_pathname으로 전달합니다.  

 

소스 코드 상의 구성은 아래와 같습니다.

import flask

from dash import Dash

 

application = flask.Flask(__name__)

dash_app1 = Dash(__name__, server = application, url_base_pathname='/dashapp1/')

dash_app2 = Dash(__name__, server = application, url_base_pathname='/dashapp2/')

dash_app3 = Dash(__name__, server = application, url_base_pathname='/dashapp3/')

dash_app4 = Dash(__name__, server = application, url_base_pathname='/dashapp4/')

dash_app5 = Dash(__name__, server = application, url_base_pathname='/dashapp5/')

 

# flask app

@application.route('/')

def index():

    print ('flask index()')

    return 'index'

 

# run the app.

if __name__ == "__main__":

    application.debug=True

    application.run()

 

 

Python Dash 객체를 HTML component로 변환 

아래와 같이 Hello Dash 예제로 Dash 웹서버를 구동을 시키고, 웹 브라우저에서 Html 소스를 확인합니다.

 

# dash app with simple component - https://dash.plotly.com/dash-html-components

dash_app1.layout = html.Div([

    html.H1('Hello Dash'),

    html.Div([

        html.P('Dash converts Python classes into HTML'),

        html.P("This conversion happens behind the scenes by Dash's JavaScript front-end"

)

    ])

])

 

dashapp1.layout은  <div id = "react-entry-point">에 하위에서 html을 지정하는 변수로 Html tag의 계층 구조로 정의할 수 있습니다.  Dash code 상에서 html.Div [ html.H1,  html.Div { html.p, html.p} ]는 Html 상에서도 <div> <div> <h1> <div> <p> <p>로 출력합니다. 즉 출력하고자 하는 Html tag의 계층 구조를 그대로 Python Dash 객체로 구현할 수 있고, FrontEnd에서는 Javascript로 대응됩니다. 

 

Python Dash 객체를 HTML component로 변환

 

Plotly figure 객체를 dash로 전달 

dcc.Graph (dcc: Dash Core Component)는 Graphics library인 plotly 객체인 figure를 전달하여 웹 페이지에서 그래프를 렌더링 할 수 있습니다.  이는 plotly로 구현한 내용이 있으면 dash 쉽게 web service로 구현할 수 있다는 의미입니다. 앞에서 설명한 것과 동일하게 Html tag를 확인하면 <div class='dash-graph'> 하위에 Graph 객체가 생성됩니다.

 

# dash 2 app with graph

import plotly.express as px

 

fig = px.scatter(x=[0, 1, 2, 3, 4, 5], y=[0, 1, 4, 9, 16, 17])

dash_app2.layout = html.Div([dcc.Graph(figure=fig)])

 

Plotly figure 객체를 dash로 전달

 

 

Dash에서 Div tag의 id와 class id 지정

Dash의 html.Div() 객체의  id와 className에 string  값을 지정하면, HTML 출력 상에서는 Div tag의 id와 class attribute가 지정됩니다.  또한 다수의 Dash html 객체를 [html.H1(), html.Div, dcc.Graph()] 와 같이 list로  추가할 수 있습니다.  dcc.Graph 객체에서도 동일한 방식으로 id와 className을 지정할 수 있습니다.

 

# dash app 3

dash_app3.layout = html.Div(

    id='my_id',

    className='my_class',

    children=[

        html.H1(children='Hello Dash'),

        html.Div(children='''Dash: A web application framework for Python.'''),

        dcc.Graph(

            id='example-graph',

            figure={

                'data': [

                    {'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},

                    {'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'bar', 'name': u'Montréal'},

                ],

            'layout': {'title': 'Dash Data Visualization'}

        }

    )

])

 

Dash에서 Div tag의 id와 class id 지정

Dash에서 URL처리 (dcc.Link와 dcc.Location)

Dash에서 URL처리는  dcc.Link와 dcc.Location 객체에서 합니다.  dcc.Link는  Html에서 표시할 string과 링크할 url을 전달하면 html에서는 <a href=link>로 대응됩니다.

 

dcc.Location 객체는 브라우저에서 접속하고 있는 웹 사이트 주소를 의미하고, href, pathname, search, hash의 property로 구성되어 있습니다.   URL의 query parameter나 hash에 따라서 다른 html을 출력하고자 할 때 활용할 수 있습니다. 예를 들어 브라우저에서 접속한 주소가 http://127.0.0.1:8050/page-2?a=test#quiz 라면 각각의 property는 다음과 같습니다.

 

  • href = "http://127.0.0.1:8050/page-2?a=test#quiz"
  • pathname = "/page-2"
  • search = "?a=test"
  • hash = "#quiz"

dcc.Location에 대한 URL값은 callback 함수로 전달받아야 합니다.  아래 예제에서는 'page-contents'는 callback함수에서 받은 결과를  출력할 위치를 지정합니다.  Dash에서 callback함수는 Decorator로 구현되어 있어, rule에 맞는 구현을 해야 합니다. callback함수 정의에서는 Input은 callback함수에 전달하는 ID 값과, property list를 전달하고 Output에는 출력해야 하는 id (='page_contents')과 attribute속성을 전달합니다. 아래  코드와 실행 예를 확인 부탁드립니다. 

 

from dash.dependencies import Input, Output

 

# dash app 4 https://dash.plotly.com/urls

dash_app4.layout = html.Div([

    # display link and html

    dcc.Link('Navigate to "/dashapp4"', href='/dashapp4'),

    html.Br(),

    dcc.Link('Navigate to "/dashapp4/page1"',href='/dashapp4/page1'),

 

    # represents the URL bar, doesn't render anything

    dcc.Location(id='url', refresh=False),

    # content will be rendered in this element

    html.Div(id='page-content'),

])

 

@dash_app4.callback(Output('page-content', 'children'),

      [Input('url', 'href'),

        Input('url', 'pathname'),

        Input('url', 'search'),

        Input('url', 'hash')])

def display_page(href, pathname, search, hash):

return html.Div([

              html.H2('Displayed by callback:'),

              html.H4('href = {}'.format(href)),

              html.H4('pathname = {}'.format(pathname)),

              html.H4('search = {}'.format(search)),

              html.H4('hash = {}'.format(hash)),

])

 

Dash에서 URL처리 (dcc.Link와 dcc.Location)

 

Dash에서 Dataframe 시계열 데이터 그래프(Dropdown menu, chart, Table Data)

Dash에서 시계열 데이터(time series data)의 Web graph를 구현하는 방법을 설명합니다.  최상단에 그래프 제목을 표시, 제목 하단에는 Drop down menu로 그래프 옵션을 선택, 중간에는  선택된 Graph 차트를 표시, 맨 하단에는 Dataframe의 값을 표시하는 예제입니다. Dash에서 아래와 같이 구성하기 위해서는 html.H1, dcc.Dropdown, dcc.Graph, Dash_table.DataTable을 사용합니다.

 

Dash에서 시계열 데이터 Graph (Dropdown menu, chart, Table Data)

 

dashapp의  전체 layout구성은 dashapp.layout 에서 html.Div()에서 리스트 형식 [html.H2, dcc.DropDown,  dash_table.DataTable]으로 정의합니다.  

 

dash_app5.layout = html.Div([

    html.Br(),

    html.H2('Time series graph with Dash'),

    html.Br(),

    # Drop Down menu

    dcc.Dropdown(

        id="ticker",

        options=[{"label": x, "value": x} for x in df.columns[1:]],

        value=df.columns[1],

        clearable=False,

    ),

    dcc.Graph(id="time-series-chart"),

    html.Br(),

    # Data Table

    dash_table.DataTable(

    id='df-stack-table',

    columns=[{"name": i, "id": i} for i in df.head().columns],

    data=df.head(15).to_dict('records'),)

])

 

 

상단 DropDown 메뉴에서 선택한 값 ticker 값을 callback 함수로 받아서 output으로 time-series-chart의 dcc.Graph의 figure 객체를 출력합니다. DropDown 메뉴에서는 options는 dataframe의 column list로 전달하고, 기본 값은 value parameter로 전달합니다.  

dcc.DropDown Menu에서 dcc.graph을 callback으로 처리

 

Dataframe에서 dash_table.DataTable을 구성합니다.  DataTable에서 id 값은 Html Div tag의 id을 의미하고,  columns 값은 dataframe의 columns 값으로 설정하고, data 값은 dataframe.to_dict('records') 함수를 이용해서 Dataframe의 values를 전달합니다.  

Dataframe을 dash의 dataframe으로 변환

 

Sample code

앞에서 설명한 Code는 GitHub에 올렸습니다. 12_dash_webserver.py 파일을 참고하세요. 

# -*- coding: utf-8 -*-

from dash_html_components.Br import Br
import flask

import dash
from dash import Dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.express as px
from dash.dependencies import Input, Output
import dash_table

# flask server
application = flask.Flask(__name__)

# dash app with flask server
dash_app1 = Dash(__name__, server=application, url_base_pathname='/dashapp1/')
dash_app2 = Dash(__name__, server=application, url_base_pathname='/dashapp2/')
dash_app3 = Dash(__name__, server=application, url_base_pathname='/dashapp3/')
dash_app4 = Dash(__name__, server=application, url_base_pathname='/dashapp4/')
dash_app5 = Dash(__name__, server=application, url_base_pathname='/dashapp5/')

# flask app
@application.route('/')
def index():
    print('flask app index()')
    return 'index'


# --------------------------------------------------------------------------------------------------------
# dash app with simple component -  https://dash.plotly.com/dash-html-components
dash_app1.layout = html.Div([
    html.H1('Hello Dash'),
    html.Div([
        html.P('Dash converts Python classes into HTML'),
        html.P("This conversion happens behind the scenes by Dash's JavaScript front-end")
    ])
])


# --------------------------------------------------------------------------------------------------------
# dash 2 app with graph
import plotly.express as px

fig = px.scatter(x=[0, 1, 2, 3, 4, 5], y=[0, 1, 4, 9, 16, 17])
dash_app2.layout = html.Div([dcc.Graph(figure=fig)])


# --------------------------------------------------------------------------------------------------------
# dash app 3
dash_app3.layout = html.Div(id='my_id', 
                            className='my_class',
                            children=[
                                html.H1(children='Hello Dash'),
                                html.Div(children='''Dash: A web application framework for Python.'''),
                                dcc.Graph(
                                    id='example-graph',
                                    figure={
                                        'data': [
                                            {'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},
                                            {'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'bar', 'name': u'Montréal'},
                                        ],
                                        'layout': {'title': 'Dash Data Visualization'}
                                    }
                                )
                            ])

# --------------------------------------------------------------------------------------------------------
# dash app 4 https://dash.plotly.com/urls
dash_app4.layout = html.Div([
    # display link and html
    dcc.Link('Navigate to "/dashapp4"',  href='/dashapp4'),
    html.Br(),
    dcc.Link('Navigate to "/dashapp4/page1"',href='/dashapp4/page1'),

    # represents the URL bar, doesn't render anything
    dcc.Location(id='url', refresh=False),

    # content will be rendered in this element
    html.Div(id='page-content'),
])

@dash_app4.callback(Output('page-content', 'children'), 
                    [Input('url', 'href'), 
                     Input('url', 'pathname'),
                     Input('url', 'search'),
                     Input('url', 'hash')])
def display_page(href, pathname, search, hash):
    return html.Div([
        html.H2('Displayed by callback:'),
        html.H4('href = {}'.format(href)),
        html.H4('pathname = {}'.format(pathname)),
        html.H4('search = {}'.format(search)),
        html.H4('hash = {}'.format(hash)),
    ])



# --------------------------------------------------------------------------------------------------------
# dash app 5 width pandas
df = px.data.stocks()

dash_app5.layout = html.Div([
    html.Br(),
    html.H2('Time series graph with Dash'),
    html.Br(),
    dcc.Dropdown(
        id="ticker",
        options=[{"label": x, "value": x} for x in df.columns[1:]],
        value=df.columns[1],
        clearable=False,
    ),
    dcc.Graph(id="time-series-chart"),
    html.Br(),
    dash_table.DataTable(
        id='df-stack-table',
        columns=[{"name": i, "id": i} for i in df.head().columns],
        data=df.head(15).to_dict('records'),)
])


@dash_app5.callback(Output("time-series-chart", "figure"), [Input("ticker", "value")])
def display_time_series(ticker):
    fig = px.line(df, x='date', y=ticker)
    return fig


# --------------------------------------------------------------------------------------------------------
# run the app.
if __name__ == "__main__":
    application.debug = True
    application.run()

 

관련 글

[SW 개발/Python] - Python 가상환경(Virtual Environment) 만들기 위한 virtualenv 명령어 및 실행 예제

[SW 개발/Data 분석 (RDB, NoSQL, Dataframe)] - Python plotly와 dash를 이용한 Web 기반 data visualization (sample code)

[SW 개발/Data 분석 (RDB, NoSQL, Dataframe)] - k-mean Clustering 알고리즘 개념 및 Sample code

[SW 개발/Data 분석 (RDB, NoSQL, Dataframe)] - Python Keras를 이용한 Linear regression 예측 (Sample code)

[SW 개발/Data 분석 (RDB, NoSQL, Dataframe)] - Python Pandas로 Excel과 CSV 파일 입출력 방법

[SW 개발/Data 분석 (RDB, NoSQL, Dataframe)] - Pandas Dataframe Groupby() 함수로 그룹별 연산하기: Split, Apply, Combine

[개발환경/우분투] - 대용량 파일을 작은 크기로 분할하는 방법: split

[SW 개발/Data 분석 (RDB, NoSQL, Dataframe)] - Python Keras를 이용한 다중회귀(Multiple regression) 예측 (Sample code)

[SW 개발/Python] - Python: xmltodict를 활용하여 XML를 JSON으로 변환하는 방법

[개발환경/Web Server] - 우분투 20.04에서 lighttpd Web Server 설치 (Embedded용으로 활용 가능)

 




댓글4