VSCode ์ Dash ์คํํ๊ธฐ
220927 ์์ฑ
<๋ณธ ๋ธ๋ก๊ทธ๋ dschloe ๋์ ๋ธ๋ก๊ทธ๋ฅผ ์ฐธ๊ณ ํด์ ๊ณต๋ถํ๋ฉฐ ์์ฑํ์์ต๋๋ค :-) >
https://dschloe.github.io/python/dash/dash_project/
Python Dash๋ฅผ ํ์ฉํ ๋์๋ณด๋ ๋ง๋ค๊ธฐ with Heroku
๊ฐ์ ํ๋ณด ์ทจ์ค์์ ์ํ ๊ฐ์๋ฅผ ์ ์ํ์์ต๋๋ค. ๋ณธ ๋ธ๋ก๊ทธ๋ฅผ ํตํด์ ๊ฐ์๋ฅผ ์๊ฐํ์ ๋ถ์ ๊ฒ์๊ธ ์ ๋ชฉ๊ณผ ๋งํฌ๋ฅผ ์๊ฐํ์ฌ ์ธํ๋ฐ ๋ฉ์์ง๋ฅผ ํตํด ๋ณด๋ด์ฃผ์๊ธฐ๋ฅผ ๋ฐ๋๋๋ค. ์คํ๋ฒ ์ค ์์ด์ค ์
dschloe.github.io
1๏ธโฃ ๋ฐ์ดํฐ ์์ง
https://www.kaggle.com/neuromusic/avocado-prices
avocado.csv
2๏ธโฃ anaconda ๊ฐ์ํ๊ฒฝ ๋ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
- ๊ฐ์ํ๊ฒฝ
$ conda create --name ๊ฐ์ํ๊ฒฝ์ด๋ฆ python=3.8
$ conda activate ๊ฐ์ํ๊ฒฝ์ด๋ฆ
- ๋ผ์ด๋ธ๋ฌ๋ฆฌ
$ conda install dash
$ conda install pandas
$ conda install colorama
- dash ์ค์น์์ error ๋ฐ์
๋ ๊ตฌ๊ธ๋งํด์ ํ๋ผ๋๊ฑฐ ๋ฌด์์ ..ํด๋ฒ๋ฆผ
๊ทธ๋ฌ๋ฉด ์ด๋ ์๊ฐ ๋์ด์์
sudo apt-get install python
sudo python -m pip install dash==version
โ test
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.express as px
import pandas as pd
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
# assume you have a "long-form" data frame
# see https://plotly.com/python/px-arguments/ for more options
df = pd.DataFrame({
"Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"],
"Amount": [4, 1, 2, 2, 4, 5],
"City": ["SF", "SF", "SF", "Montreal", "Montreal", "Montreal"]
})
fig = px.bar(df, x="Fruit", y="Amount", color="City", barmode="group")
app.layout = html.Div(children=[
html.H1(children='Hello Dash'),
html.Div(children='''
Dash: A web application framework for Python.
'''),
dcc.Graph(
id='example-graph',
figure=fig
)
])
if __name__ == '__main__':
app.run_server(debug=True,host = '127.0.0.1')
- socket error
sudo apt update -y
sudo apt-get install openssh-server
=> ๋งจ๋ฐ ์๋ฒ ๋ฐฐํฌ ์ฝ๋ ์์ host ๋ค์ ์ง์
if __name__ == '__main__':
app.run_server(debug=True,host = '127.0.0.1')
3๏ธโฃ ๋์ ๋ณด๋ ์ฝ๋ ์์ฑ
๐ ๋ฐ์ดํฐ ์์ง ๋ฐ Dash ํด๋์ค ์ ์
# 1. data load
import dash # ๋์๋ณด๋ ์ดํ๋ฆฌ์ผ์ด์
์ด๊ธฐํ
import dash_core_components as dcc # ๋์ ๊ตฌ์ฑ์์๋ค(์: ๊ทธ๋ํ, ๋๋กญ๋ค์ด ๋ฉ๋ด, ๋ ์ง ๊ธฐ๊ฐ ๋ฑ) ์์ฑํ ์ ์๋๋ก ๋์์ฃผ๋ ๊ธฐ๋ฅ
import dash_html_components as html # html ํ๊ทธ์ ์ ๊ทผ
import pandas as pd # ๋ฐ์ดํฐ ์์ง ๋ฐ ๊ฐ๊ณต์ ์ ๊ณตํ ์ ์๋ ํจ์ ์ง์
# step 2. Data Import
data = pd.read_csv("coding/220926/avocado.csv", index_col=0)
data = data.query("type == 'conventional' and region == 'Albany'") # type = conventional ๊ณผ, region = Albany ๋ง ์ถ์ถํ๋ ํ์ ์ถ์ถ
data["Date"] = pd.to_datetime(data["Date"], format="%Y-%m-%d")
data.sort_values("Date", inplace=True) # ๋ ์ง์ ์ค๋ฆ์ฐจ์์ผ๋ก ์ ๋ ฌํ๋ ์ฝ๋
# step 3. Dash Class
app = dash.Dash(__name__)
๐ ๋์๋ณด๋ HTML Layout
# step 4. HTML
app.layout = html.Div( # ์ผ์ข
์ parent component
# Header Message
children=[
html.H1(children="Temp Analytics",), # html.h1์ h1 ํ๊ทธ, html.p์ p ํ๊ทธ
html.P(
children="Temp",
),
# ๊ทธ๋ํ
dcc.Graph( # ๊ทธ๋ํ๊ฐ ๊ตฌํ๋๋ ์ฝ๋
figure={
"data": [
{
"x": data["Date"],
"y": data["AveragePrice"],
"type": "lines",
},
],
"layout": {"title": "Title_1"},
},
),
dcc.Graph( # ๊ทธ๋ํ๊ฐ ๊ตฌํ๋๋ ์ฝ๋
figure={
"data": [
{
"x": data["Date"],
"y": data["Total Volume"],
"type": "lines",
},
],
"layout": {"title": "Title_2"},
},
),
]
)
- Dash HTML Components
- HTML components์ฉ Wrappers๋ฅผ ์ ๊ณต => ๋ฌธ๋จ, ์ ๋ชฉ ๋๋ ๋ชฉ๋ก๊ณผ ๊ฐ์ ์์๋ฅผ ์์ฑ
-
Dash Core Components
-
๋ํํ ์ฌ์ฉ์ ์ธํฐํ์ด์ค๋ฅผ ๋ง๋ค๊ธฐ ์ํ Python ์ถ์ํ๋ฅผ ์ ๊ณต => ๊ทธ๋ํ, ์ฌ๋ผ์ด๋ ๋๋ ๋๋กญ๋ค์ด๊ณผ ๊ฐ์ interactive elements๋ฅผ ๋ง๋๋ ๋ฐ ์ฌ์ฉ
-
๐ ๋์๋ณด๋ ๋ฐฐํฌ (localhost)
# step 5. dash board release (localhost)
"""
Flask ๊ธฐ๋ฐ์ ์๋ฒ๋ก ์๋
ํ๋ผ๋ฏธํฐ debug=True ๋ฅผ ํ๊ฒ๋๋ฉด, ์์ ์
๋ ฅ์ ํด๋ ์๋ฒ๋ฅผ restarting ํ์ง ์๊ณ ์๋ก๊ณ ์นจ์ผ๋ก ๋ณํ๋ฅผ ํ์ธ
"""
if __name__ == "__main__":
app.run_server(debug=True,host = '127.0.0.1')
๐ ์คํ
4๏ธโฃ ๋์๋ณด๋์ Style ์ ํ๊ธฐ
๐ (1) ํ๊ทธ์ ์ง์ style
- H1 ํ๊ทธ์ ํฐํธ ์ฌ์ด์ฆ์ ์์ ์ถ๊ฐ
html.H1(
children="Temp Analytics",
style={"fontSize": "48px", "color": "blue"},
),
- but, css ํ์ผ์ ๋ค์ ๊ด๋ฆฌ ํด์ฃผ์! ์ ์ฝ๋์์ style ๋ค์ ์ง์ฐ๊ณ assets ํด๋ ํ์์ style.css ์ถ๊ฐ
- H1 ํ๊ทธ์๋ ํด๋์ค ์ถ๊ฐ
html.H1(
children="Temp Analytics",
className="header_title",
),
- className์ ํด๋นํ๋ css ์ฝ๋๋ฅผ style.css ์ ์ถ๊ฐ
.header_title {
font-size: 48px;
color: red;
}
๐ (2) ๋ก๊ณ ์ถ๊ฐ
- ์๋ณด์นด๋ ico ๋ค์ด๋ก๋ ํํ ํ์ผ๊ณผ assets ํด๋ ํ์์ ์ถ๊ฐ!
๐ (3) External Style Sheet
- ์ธ๋ถ์์ css ํ์ผ ๊ฐ๊ณ ์ค๊ธฐ
# step 3. Dash Class
external_stylesheets = [
{
"href": "https://fonts.googleapis.com/css2?"
"family=Lato:wght@400;700&display=swap",
"rel": "stylesheet",
},
]
# ์ธ๋ถ์์ css sheet ๊ฐ๊ณ ์ค๊ธฐ
app = dash.Dash(__name__, external_stylesheets=external_stylesheets) # app = dash.Dash(__name__)
app.title = "Avocado Analytics: Understand Your Avocados!"
๐ (4) Header Layout ์ปค์คํ ํ
- header ํ๋ฉด๊ณผ ๊ทธ๋ํ ๊ตฌ์ฑํ๋ ํ๋ฉด์ div ํ๊ทธ๋ก ๊ตฌ๋ถํ๋ ์ฝ๋๋ฅผ ์์ฑ
- className์ ๊ฐ ํ๊ทธ๋ง๋ค ์ ๋ ฅํ๊ธฐ
# step 4. HTML
app.layout = html.Div(
children=[
html.Div(
children=[
html.P(children="๐ฅ", className="header-emoji"),
html.H1(
children="Avocado Analytics", className="header-title"
),
html.P(
children="Analyze the behavior of avocado prices"
" and the number of avocados sold in the US"
" between 2015 and 2018",
className="header-description",
),
],
className="header",
),
html.Div(
children=[
html.Div(
children=dcc.Graph(
id="price-chart",
config={"displayModeBar": False},
figure={
"data": [
{
"x": data["Date"],
"y": data["AveragePrice"],
"type": "lines",
"hovertemplate": "$%{y:.2f}"
"<extra></extra>",
},
],
"layout": {
"title": {
"text": "Average Price of Avocados",
"x": 0.05,
"xanchor": "left",
},
"xaxis": {"fixedrange": True},
"yaxis": {
"tickprefix": "$",
"fixedrange": True,
},
"colorway": ["#17B897"],
},
},
),
className="card",
),
html.Div(
children=dcc.Graph(
id="volume-chart",
config={"displayModeBar": False},
figure={
"data": [
{
"x": data["Date"],
"y": data["Total Volume"],
"type": "lines",
},
],
"layout": {
"title": {
"text": "Avocados Sold",
"x": 0.05,
"xanchor": "left",
},
"xaxis": {"fixedrange": True},
"yaxis": {"fixedrange": True},
"colorway": ["#E12D39"],
},
},
),
className="card",
),
],
className="wrapper",
),
]
)
- cssํ์ผ
body {
font-family: "Lato", sans-serif;
margin: 0;
background-color: #F7F7F7;
}
.header {
background-color: #222222;
height: 256px;
display: flex;
flex-direction: column;
justify-content: center;
}
.header-emoji {
font-size: 48px;
margin: 0 auto;
text-align: center;
}
.header-title {
color: #FFFFFF;
font-size: 48px;
font-weight: bold;
text-align: center;
margin: 0 auto;
}
.header-description {
color: #CFCFCF;
margin: 4px auto;
text-align: center;
max-width: 384px;
}
.wrapper {
margin-right: auto;
margin-left: auto;
max-width: 1024px;
padding-right: 10px;
padding-left: 10px;
margin-top: 32px;
}
.card {
margin-bottom: 24px;
box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.18);
}
.header_emoji {
font-size: 48px;
margin: 0 auto;
text-align: center;
}
.header_title {
color: #FFFFFF;
font-size: 48px;
font-weight: bold;
text-align: center;
margin: 0 auto;
}
.header_description {
color: #CFCFCF;
margin: 4px auto;
text-align: center;
max-width: 384px;
}
.header {
background-color: #222222;
height: 256px;
display: flex;
flex-direction: column;
justify-content: center;
}
๐ ๊ฒฐ๊ณผ
5๏ธโฃ ๋์๋ณด๋์ Interactive ๊ตฌํ
- ๋ ์ง๋ฅผ ์ง์ ํ์ฌ ๊ทธ๋ํ๋ฅผ ์์ฑํ ์๋ ์์๊น?
- ์ง์ญ์ ์ ํํ ๋๋ง๋ค, ๋ผ์ธ ๊ทธ๋ํ๊ฐ ๋ณ๋์ํฌ ์๋ ์์๊น?
๐งก (1) ๋ฉ๋ด ๊ตฌ์ฑํ๊ธฐ
- ๋ฐ์ดํฐ๋ถํฐ ํ์ธ
# step 2. Data Import
data = pd.read_csv("coding/220927/avocado.csv", index_col=0)
data = data.query("type == 'conventional' and region == 'Albany'") # type = conventional ๊ณผ, region = Albany ๋ง ์ถ์ถํ๋ ํ์ ์ถ์ถ
data["Date"] = pd.to_datetime(data["Date"], format="%Y-%m-%d")
data.sort_values("Date", inplace=True) # ๋ ์ง์ ์ค๋ฆ์ฐจ์์ผ๋ก ์ ๋ ฌํ๋ ์ฝ๋
print(data.info())
- 3๊ฐ์ง ์ปฌ๋ผ์ ๋ฉ๋ด๋ก ํ์ฉ
- Region
- Type of avocado
- Date range
- type, region ํ์ธ
# step 2. Data Import
data = pd.read_csv("coding/220927/avocado.csv", index_col=0)
#data = data.query("type == 'conventional' and region == 'Albany'") # type = conventional ๊ณผ, region = Albany ๋ง ์ถ์ถํ๋ ํ์ ์ถ์ถ
data["Date"] = pd.to_datetime(data["Date"], format="%Y-%m-%d")
data.sort_values("Date", inplace=True) # ๋ ์ง์ ์ค๋ฆ์ฐจ์์ผ๋ก ์ ๋ ฌํ๋ ์ฝ๋
#print(data.info())
print(data[['region', 'type', 'Date']].head())
- 3๊ฐ์ง ๋ฉ๋ด๊ฐ ๊ตฌ์ฑ๋๋๋ก html.Div ๋ฅผ ์ถ๊ฐํ๋ ์ฝ๋๋ฅผ ์์ฑ
- ๋๋กญ๋ค์ด ๊ด๋ จ ํจ์ ์ค๋ช : https://dash.plotly.com/dash-core-components
- ๋ ์ง ๊ด๋ จ ํจ์ ์ค๋ช : https://dash.plotly.com/dash-core-components/datepickerrange
- html layout ๋ณ๊ฒฝ
# step 4. HTML
app.layout = html.Div(
children=[
html.Div(
children=[
html.P(children="๐ฅ", className="header-emoji"),
html.H1(
children="Avocado Analytics", className="header-title"
),
html.P(
children="Analyze the behavior of avocado prices"
" and the number of avocados sold in the US"
" between 2015 and 2018",
className="header-description",
),
],
className="header",
),
html.Div(
children=[
html.Div(
children=[
html.Div(children="Region", className="menu-title"),
dcc.Dropdown(
id="region-filter",
options=[
{"label": region, "value": region}
for region in np.sort(data.region.unique())
],
value="Albany",
clearable=False,
className="dropdown",
),
]
),
html.Div(
children=[
html.Div(children="Type", className="menu-title"),
dcc.Dropdown(
id="type-filter",
options=[
{"label": avocado_type, "value": avocado_type}
for avocado_type in data.type.unique()
],
value="organic",
clearable=False,
searchable=False,
className="dropdown",
),
],
),
html.Div(
children=[
html.Div(
children="Date Range",
className="menu-title"
),
dcc.DatePickerRange(
id="date-range",
min_date_allowed=data.Date.min().date(),
max_date_allowed=data.Date.max().date(),
start_date=data.Date.min().date(),
end_date=data.Date.max().date(),
),
]
),
],
className="menu",
),
html.Div(
children=[
html.Div(
children=dcc.Graph(
id="price-chart", config={"displayModeBar": False},
),
className="card",
),
html.Div(
children=dcc.Graph(
id="volume-chart", config={"displayModeBar": False},
),
className="card",
),
],
className="wrapper",
),
]
)
๐ ์ฐธ๊ณ
- id : Dropdown ๋ฉ๋ด์ ID๋ฅผ ๊ตฌ์ฑ. ํด๋น ID๋ ํฅํ Callback์ ์ ์ํ ๋, ๊ฐ์ด ํ์ฉ๋จ
- optinos : Dropdown ๋ฉ๋ด๊ฐ ์ต์ด ์ ํ์ด ๋ ๋, ๊ฐ(labesl ๋๋ values)์ ๋ณด์ฌ์ค
- value : Default๋ ๊ฐ์ ๋ณด์ฌ์ค
- className : style.css ์ด ์ ์ฉ๋๋ ์์ญ
html.Div(
children=[
html.Div(children="Region", className="menu-title"),
dcc.Dropdown(
id="region-filter",
options=[
{"label": region, "value": region}
for region in np.sort(data.region.unique())
- style.css ํ์ผ์ ๋ฉ๋ด ๊ตฌ์ฑ ์ถ๊ฐ
.menu {
height: 112px;
max-width: 1024px;
display: flex;
justify-content: space-evenly;
padding-top: 32px;
margin: 0px auto 0 auto;
background-color: #FFFFFF;
box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.18);
}
.menu-title {
margin-bottom: 6px;
font-weight: bold;
color: #079A82;
}
.Select-control {
width: 256px;
height: 48px;
}
.Select--single > .Select-control .Select-value, .Select-placeholder {
line-height: 48px;
}
.Select--multi .Select-value-label {
line-height: 32px;
}.
๐งก ๊ฒฐ๊ณผ
- ๊ทธ๋ฐ๋ฐ ๋ฉ๋ด๋ ๊ทธ๋ํ๊ฐ ์ ์์ ์ผ๋ก ์๋์จ๋ค
- Reactive Programming
- ์ค์๊ฐ์ผ๋ก ๋ฐ์์ ํ๋ ํ๋ก๊ทธ๋๋ฐ
- ex) ๋ค๋ฅธ ์ ์ ๊ฐ ํ์ด์ค๋ถ์ ‘์ข์์’ ๋ฒํผ์ ๋๋ฅด๋ฉด ํด๋น ํฌ์คํธ๋ฅผ ๋ณด๊ณ ์๋ ์ฌ์ฉ์๋ ์๋ก๊ณ ์นจ ํ ๊ฒ ์์ด ์ค์๊ฐ์ผ๋ก ‘์ข์์’์ ๊ฐ์๊ฐ ์ฌ๋ผ๊ฐ๋ ๊ฒ์ ๋งํ๋ค.
- ํ๋ก๊ทธ๋๋ฐ์ผ๋ก๋ ๋น๋๊ธฐ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๋ค๊ณ ๋งํจ
- ์ค์๊ฐ์ผ๋ก ๋ฐ์์ ํ๋ ํ๋ก๊ทธ๋๋ฐ
- Callbacks
- Reactive Programming์ ํต์ฌ์ด์, ๋ณธ Tutorial์ ํต์ฌ.
- Callback ํจ์๋, ๊ฐ๋ฐ์๋ ์ด๋ฒคํธ๋ฅผ ๋ฑ๋กํ๊ธฐ๋ง ํ ๋ฟ, ์ค์ ์ฌ์ดํธ ๋ฐฉ๋ฌธ์๊ฐ ํน์ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํค๋ฉด, ํน์ ์์ ์ ๋๋ฌํ์ ๋ ํด๋น ๊ธฐ๋ฅ์ ํ์ฑํ ์ํค๋ ๊ฒ
- Reactive Programming
๐งก ์ฝ๋ฐฑ ๋ฐ Reactive ์คํํ๋ ์ฝ๋ ์ถ๊ฐ
from dash.dependencies import Output, Input
@app.callback(
[Output("price-chart", "figure"), Output("volume-chart", "figure")],
[
Input("region-filter", "value"),
Input("type-filter", "value"),
Input("date-range", "start_date"),
Input("date-range", "end_date"),
],
)
def update_charts(region, avocado_type, start_date, end_date):
mask = (
(data.region == region)
& (data.type == avocado_type)
& (data.Date >= start_date)
& (data.Date <= end_date)
)
filtered_data = data.loc[mask, :]
price_chart_figure = {
"data": [
{
"x": filtered_data["Date"],
"y": filtered_data["AveragePrice"],
"type": "lines",
"hovertemplate": "$%{y:.2f}<extra></extra>",
},
],
"layout": {
"title": {
"text": "Average Price of Avocados",
"x": 0.05,
"xanchor": "left",
},
"xaxis": {"fixedrange": True},
"yaxis": {"tickprefix": "$", "fixedrange": True},
"colorway": ["#17B897"],
},
}
volume_chart_figure = {
"data": [
{
"x": filtered_data["Date"],
"y": filtered_data["Total Volume"],
"type": "lines",
},
],
"layout": {
"title": {"text": "Avocados Sold", "x": 0.05, "xanchor": "left"},
"xaxis": {"fixedrange": True},
"yaxis": {"fixedrange": True},
"colorway": ["#E12D39"],
},
}
return price_chart_figure, volume_chart_figure
๐งก ๊ฒฐ๊ณผ
์ค์๊ฐ์ผ๋ก ์์งใ ใ ฃ๋ ๊ทธ๋ํ๋ ํด๋ณด๊ณ ์ถ๋ค