Data Viz مع Python: Apps و Dashboards

Ismail Mebsout
٢٣ أكتوبر ٢٠٢٤
١٠ دقائق
جدول المحتويات

بصفتي Senior consultant data scientist، أُدرك جيّدًا أهمّية تلخيص عملي في Dashboards وApps.
هذا يُتيح لي تعميم خوارزمياتي وعملي ووضعها في رسومات بديهية لفهم أفضل وأسرع. في هذا المقال، سنتطرّق إلى واحدة من أشهر الأدوات ومكتبات python المستخدمة في تصميم dashboards وapplications.

الملخّص كالتالي:

  1. Dash by Plotly
  2. Streamlit
  3. Bokeh
  4. Elastic stack
  5. نشر Heroku

Dash by Plotly

Dash أداة مفتوحة المصدر طوّرتها Plotly. تسمح بإدراج عدّة أنواع من widgets مع إمكانية اختيار التوزيع والنمط لأنّ تخطيط dash مبني على HTML ويسمح بتنسيق CSS.
يتمّ الإعداد بتشغيل سطر الأوامر التالي:

pip install dash
pip install dash_core_components
pip install dash_html_components

layout لتطبيق dash هو شجرة هرمية من المكوّنات، مزيج بين عناصر HTML ورسومات مبنية باستخدام:

  • مكتبة dash_html_components توفّر فئات لجميع علامات HTML، وتصف الـ keyword arguments سمات HTML مثل style وclassName وid. باستخدام هذه المكتبة، يمكننا إضافة عناصر HTML مثل Div, H1, P,... etc. لمزيد من التفاصيل، أنصحك بفحص الوثائق.
  • مكتبة dash_core_components تُولّد مكوّنات أعلى مستوى مثل عناصر التحكّم والرسومات وتستخدم بنية Plotly. على سبيل المثال، يمكننا إدراج Button, Dropdown, Slider,DatePickerRange, ... etc.. لمزيد من التفاصيل، يُرجى زيارة الموقع الرسمي.

يمكن تطوير تطبيق Dash بسيط باستخدام أسطر Python التالية:

import dash
import dash_core_components as dcc
import dash_html_components as html

app = dash.Dash(__name__)
app.title='Hello World'
app.layout = html.Div(children=[

	html.H1(children='Hello Dash!', style={'textAlign':
		'center', 'color': '#7FDBFF'}),

	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'
				}
			}
	),
])
if __name__ == '__main__':
	app.run_server(debug=True)

يُتيح خيار debug=True أخذ التعديلات الأخيرة بعين الاعتبار دون الحاجة إلى إعادة تشغيل التطبيق.
لتشغيل التطبيق، نستخدم سطر الأوامر التالي:

python app_file.py

تحصل على التطبيق التالي الذي يعمل على http://127.0.0.1:8050/:

Hello Dash

التنسيق

من الممكن إضافة ملفّ CSS يستبدل النمط الأساسي للتطبيق، ممّا يسمح لك بتخصيص رسوماتك وعناصر HTML. للقيام بذلك، يجب أن تكون بنية المجلّد كالتالي:

app.py
assets/
   |-- style.css
   |-- custom-script.js
   |-- img.png
   |-- background.jpg
   |-- favicon.ico

من الإلزامي وضع ملفّ CSS وجميع الصور الأخرى المُدرَجة في مجلّد يُسمّى assets ليُقرَأ من قِبَل Dash. بمجرّد الانتهاء من ذلك، كلّ ما علينا فعله هو تحديد style sheet باستخدام أسطر Python التالية:

external_stylesheets=["assets/style.css"]
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

Bootstrap

عند بناء dash-app، أُفضّل شخصيًا العمل مع Bootstrap. وهي مكتبة Python لـ Plotly Dash، تحتوي على عناصر منسّقة ومتقدّمة.
يتمّ الإعداد باستخدام سطر الأوامر التالي:

pip install dash-bootstrap-components

من المهمّ تضمين موضوع bootstrap كأحد style sheets:

external_stylesheets=["assets/style.css", dbc.themes.BOOTSTRAP]
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

يحتوي Boostrap على العديد من العناصر مثل DropDownMenu, Navbar, Progress, Button, ... etc. أحد أكثر العناصر فائدة هو Layout، فهو يسمح بهيكلة dashboard باستخدام Rows وColumns وهو مفيد جدًّا لبنية التطبيق.
لمزيد من التفاصيل، لا تتردّد في زيارة الوثائق الرسمية.

Callbacks

كأيّ تطبيق آخر، من المهمّ تمكين التفاعلية بين الرسومات وذلك باستخدام دوال callback.
كلّ عنصر مُدرَج له ميزتان مهمّتان

  • id: تسمية فريدة للعنصر
  • properties: تختلف من عنصر إلى آخر

ربط عنصرَين في تطبيق يتمّ على النحو التالي:

Dash callbacks

في هذه الحالة الخاصّة، يؤثّر العنصر الأوّل على الثاني: property_1 للعنصر الأوّل المُختار من خلال id_1 يؤثّر على property_2 للعنصر الثاني الذي يتمّ الوصول إليه من خلال id_2.

from dash.dependencies import Input, Output
@app.callback(
    Output(component_id='id_2', component_property='property_2'),
    [Input(component_id='id_1', component_property='property_1')]
)
def callback_function(input_value):
    """
    What to do
    """

ملاحظة: من الممكن أخذ العديد من Inputs في دالّة callback.

Cache

عند تطوير تطبيق، من المهمّ جدًّا أن يكون لديك عناصر في Cache من أجل تسهيل التحميل وتسريع التفاعلية، Cache يسمح بذلك.
لا يدعم Dash ميزة cache محدّدة، لكن إحدى الطرق للتغلّب على هذه المشكلة هي استخدام global variables في Python.

Tabs

يمكن إدراج Tabs في Dash باستخدام مكوّن dcc.Tab، حيث تكون عناصره هي العناصر المكوّنة لـ tab.

mport dash_html_components as html
import dash_core_components as dcc

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div([
    dcc.Tabs([
        dcc.Tab(label='1st Tab', children=[
            dcc.Graph(),
            ###
        ]),
        dcc.Tab(label='2nd Tab', children=[
            dcc.Graph(),
            ###
        ]),
        dcc.Tab(label='3rd Tab', children=[
            dcc.Graph(),
            ###
        ]),
    ])
])

المعرض

لدى Dash معرض حيث يمكنك تصفّح واكتشاف dashboards أخرى، فهو نقطة انطلاق جيّدة ومصدر إلهام.

Streamlit

Streamlit إطار عمل تطبيقات مفتوح المصدر طُوّر لإنشاء dashboards ML، في الغالب، بطريقة سريعة وفعّالة باستخدام Python. الإعداد بسيط جدًّا ويمكن إجراؤه باستخدام سطر الأوامر التالي:

pip install streamlit

مقارنةً بـ Dash، Streamlit لديه معالجة أفضل لـ Cache، كما هو مطوّر في الأقسام التالية، وبالتالي يسمح بتكرارات تطوير أسرع باستخدام العملية التالية:

streamlit.io

بمجرّد إنشاء ملفّك، يمكنك كتابة سطر الأوامر هذا الذي سيُشغّل تطبيقك على http://localhost:8501

streamlit run file.py

كما في Dash، من الممكن اختيار الوضع Always re-run للحصول على التعديلات مباشرة.

يحتوي معرض widgets الخاصّ بـ Streamlit على عدّة عناصر يمكن إدراجها جميعًا باستخدام سطر شيفرة واحد: Markdown، SelectBox، Slider، Plot، إلخ... لاحظ أنّه يمكن إدراج widgets إمّا في one-page area أو على sidebar وأنّ الإدراج يتمّ باتّباع vertical stacking:

import streamlit as st
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from sklearn import datasets
from sklearn.decomposition import PCA
import pandas as pd

img=mpimg.imread('imgs/streamlite_funct.png')
iris = datasets.load_iris()
feature_names=['sepal length (cm)',
  'sepal width (cm)',
  'petal length (cm)',
  'petal width (cm)']
database=pd.DataFrame(iris.data, columns=feature_names)
database["class"]=iris.target

# plot the first three PCA dimensions
fig = plt.figure(1, figsize=(8, 6))
ax = Axes3D(fig, elev=-150, azim=110)
X_reduced = PCA(n_components=3).fit_transform(iris.data)
ax.scatter(X_reduced[:, 0], X_reduced[:, 1], X_reduced[:, 2], c=iris.target,
           cmap=plt.cm.Set1, edgecolor='k', s=40)
ax.set_title("First three PCA directions")
ax.set_xlabel("1st eigenvector")
ax.w_xaxis.set_ticklabels([])
ax.set_ylabel("2nd eigenvector")
ax.w_yaxis.set_ticklabels([])
ax.set_zlabel("3rd eigenvector")
ax.w_zaxis.set_ticklabels([])

def main():
    ########### Sidebar ##############################

    st.sidebar.markdown("# Hello World")
    st.sidebar.selectbox('Select a tool', ["Dash", "Kibana", "Streamlit", "Bokeh"])

    st.markdown("## Multiselection")
    st.multiselect('Select a tool', ["Dash", "Kibana", "Streamlit", "Bokeh"])

    st.markdown("## Radio buttons")
    st.radio('Select a tool', ["Dash", "Kibana", "Streamlit", "Bokeh"])

    st.markdown("## Slider")
    st.slider('Select a Number', min_value=1, max_value=4, value=1)

    st.markdown("## Image")
    st.image(img, width=500)

    st.markdown("## DataBase")
    st.dataframe(database)

    st.markdown("## Plot")
    st.write(fig)

if __name__ == "__main__":
    main()

نحصل على التطبيق التالي:

تطبيق ويب Streamlit

الدالّة st.write مهمّة جدًّا ويمكن استخدامها لإدراج العديد من عناصر Python. لمزيد من التفاصيل، أنصحك بقراءة وثائق streamlit.

التفاعلية: يمكن تعيين كلّ عنصر لمتغيّر يمثّل قيمته الحالية، ثمّ يمكن استخدامه لتحديث عنصر آخر ومن ثَمَّ التفاعلية.

Cache

يُستخدم Cache عادةً لتخزين البيانات الثابتة التي لا تتغيّر بغضّ النظر عن الاختيارات التي تتمّ على التطبيق. قد نُعرّف في كثير من الأحيان دوال في التطبيق ليست هناك حاجة لإعادة تشغيلها في كلّ مرّة يُحدَّث فيها التطبيق، يمكننا تخزينها مؤقّتًا باستخدام شيفرة Python التالية:

@st.cache
def function_1():
	####

Tabs

في العديد من التطبيقات، يمكن أن تكون Tabs مهمّة جدًّا: إحدى الطرق للقيام بذلك في Streamlit هي إضافة selectbox في sidebar الذي سيؤثّر اختياره على بنية dashboard وذلك باستخدام عبارات if على قيمة الصندوق.

st.sidebar.markdown("Tabs")
tab=st.sidebar.selectbox('Select a Tab', ["Home", "Documentation", "Partners", "Contact"])
if tab=="Home":
	#Develop the one_pager
	##
elif tab=="Documentation":
	###
elif tab=="Partners":
	###
elif tab=="Contact":
	###

Health app, Streamlit

عندما بدأت اكتشاف Streamlit، قرّرت استخدامه بتطوير تطبيق صحّي بسيط يسمح بمعالجة الأمر مباشرة.

تطبيق صحّي

يمكنك زيارة التطبيق باستخدام هذا الرابط، السكربت مُستضاف في هذا المستودع. لمزيد من التطبيقات، يمكنك زيارة المعرض الرسمي.

Bokeh

Bokeh مكتبة Python تسمح بإنشاء dashboard تفاعلي أيضًا. يمكن تثبيتها باستخدام سطر الأوامر التالي:

pip install bokeh

التطبيق أدناه، مثلًا، يمكن إنشاؤه باستخدام سكربت Python التالي:

Bokeh - تطبيق ويب
from bokeh.io import show, curdoc
from bokeh.plotting import figure, ColumnDataSource
from bokeh.layouts import row, column
from bokeh.models.widgets import Tabs, Panel
from bokeh.models import Slider, CheckboxGroup, RadioGroup, Button, CustomJS


#data source
x = [x*0.005 for x in range(0, 200)]
y = x
source = ColumnDataSource(data=dict(x=x, y=y))

#Plots
plot_1 = figure(plot_width=400, plot_height=400)
plot_1.line('x', 'y', source=source, line_width=3, line_alpha=0.6)

plot_2=figure(tools='box_zoom, lasso_select')
plot_2.circle(x=[1,2,3,4,5], y=[2,3,1,1,2])
plot_2.background_fill_color = 'black'

plot_3=figure(tools='box_zoom, lasso_select')
plot_3.circle(x=[1,2,3,4,5], y=[2,3,1,1,2])

#widgets
slider = Slider(start=0.1, end=4, value=1, step=.1, title="power")
button = Button(label='Click')
checkbox = CheckboxGroup(labels=['Kibana', 'Bokeh', 'Streamlit', 'Dash'])
radio = RadioGroup(labels=['Kibana', 'Bokeh', 'Streamlit', 'Dash'])

#Interactivity
callback = CustomJS(args=dict(source=source), code="""
        var data = source.data;
        var f = cb_obj.value
        var x = data['x']
        var y = data['y']
        for (var i = 0; i < x.length; i++) {
            y[i] = Math.pow(x[i], f)
        }
        source.change.emit();
    """)
slider.js_on_change('value', callback)

#Plots zoom-linking
plot_2.x_range=plot_3.x_range
plot_2.y_range=plot_3.y_range

#Tabs
first=Panel(child=row(column(slider, button, checkbox, radio), plot_1), title='first')
second=Panel(child=row(plot_2,plot_3), title='second')
tabs=Tabs(tabs=[first, second])

#App
curdoc().add_root(tabs)
show(tabs)

لاحظ أنّه بمجرّد تعريف الشكل plot، نستخدم بنية plot.graph لتعيين الرسم المرغوب فيه. تُعالَج البيانات باستخدام ColumnDataSource وLayout باستخدام row, column. نُخصّص widgets المُضافة، Slider, CheckboxGroup, RadioGroup, Button، ونربطها بالرسومات باستخدام JS callback CustomJS. لتشغيل تطبيقك على http://localhost:5006/myapp، يمكنك استخدام سطر الأوامر التالي:

bokeh serve --show myapp.py

المعرض

كجميع الأدوات الأخرى، لدى Bokeh معرض غني جدًّا للبدء به.

Elastic stack

Elasticsearch

Elasticsearch نوع خاصّ من فهرسة الجداول يوفّر محرّك بحث أسرع. يمكننا تحويل قاعدة بيانات pandas إلى Elasticbase ثمّ تصوّرها باستخدام Kibana وهي أداة التصوّر للـ stack Elastic. ستحتاج، أوّلًا، إلى تنزيل Kibana وElasticsearch بحسب نظام التشغيل الخاصّ بك.
بمجرّد التثبيت، سنرغب في فهرسة pandas dataframe الخاصّ بنا بشكل صحيح باستخدام elasticsearch. للقيام بذلك، نحتاج أوّلًا إلى إطلاق كلتا خدمتَي elasticsearch وkibana باستخدام أسطر الأوامر:

./elasticsearch-7.6.1/bin/elasticsearch
./kibana-7.6.1/bin/kibana

بمجرّد إطلاق Kibana، يمكننا استخدام سكربت Python هذا لإرسال البيانات إلى خدمات Elasticsearch

import pandas as pd
from elasticsearch import Elasticsearch

# Database loading and service openning
database=pd.read_excel("data/test_db.xlsx")
es_client = Elasticsearch(http_compress=True)

#Elasticsearch does not accept NAN values
print(database.isna().sum().sum())

df=database.copy()
INDEX="laposte" #Its name in Elasticsearch (laposte for example)
TYPE= "record"

def rec_to_actions(df):
    import json
    for record in df.to_dict(orient="records"):
        yield ('{ "index" : { "_index" : "%s", "_type" : "%s" }}'% (INDEX, TYPE))
        yield (json.dumps(record, default=int))

e = Elasticsearch()
r = e.bulk(rec_to_actions(df))

#Verify if everything went fine
print(not r["errors"])

Kibana

تُطلق خدمة kibana في local host على المنفذ 5601، يمكننا زيارتها في متصفّح باستخدام العنوان localhost:5601. بمجرّد الوصول إلى الصفحة الرئيسية، يمكننا النقر على Dashboard وCreate dashboard على النحو التالي:

Elastic Dashboard

يمكننا الآن إضافة widgets عبر هذه الخطوات:

تطبيق ويب Kibana

يمكن وضع الرسم المُدرَج باستخدام المؤشّر ممّا يجعل التخطيط سهل التخصيص. لاحظ وجود شريط بحث حيث تُفهرس البيانات باستخدام Elasticsearch، وهذا يجعل الاستعلام أقلّ استهلاكًا للوقت ويُضيف مزيدًا من التفاعلية إلى dashboard، خصوصًا عند التعامل مع قواعد بيانات كبيرة. التفاعلية تُعالَج تلقائيًا في kibana.

Tabs

يمكن إنشاء Tabs في Kibana باستخدام روابط مدمجة في widgets markdowns. أنشئ أوّلًا جميع tabs كلّ واحد في dashboard مختلف ثمّ أضف في كلّ واحد الروابط المدمجة للآخرين.

المعرض

لا تتردّد في زيارة معرض kibana الرسمي.

نشر Heroku

بمجرّد تطوير تطبيقك، يمكنك استضافته على الإنترنت بحيث يمكن الوصول إليه من قِبَل أيّ شخص باستخدام Url. إحدى الطرق للقيام بذلك هي استخدام Heroku الذي يُقدّم هذه الخدمة المجّانية مع بعض القيود.
تحتاج أوّلًا إلى التسجيل ثمّ إنشاء تطبيق heroku عبر الإنترنت سيُربط بمستودع git الخاصّ بك. يجب أن يكون للمستودع البنية التالية:

.
├── app.py
├── requirements.txt
├── setup.sh
└── Procfile

يمكنك التحقّق من مستودع git الخاصّ بي لمزيد من المعلومات. على مجلّد git المحلّي، شغّل أسطر الأوامر التالية:

heroku create
git push heroku master
heroku ps:scale web=1
heroku open

سيكون تطبيقك في حالة استعداد دائم لكلّ push تقوم به في git الخاصّ بك من أجل أخذ آخر التغييرات بعين الاعتبار.

الخاتمة

التطبيقات وdashboards مرحلة مهمّة وحاسمة جدًّا في كلّ مشروع، فهي تسمح بتلخيص عملنا وجعله أكثر سهولة للمستخدم عبر واجهة بديهية. يعتمد استخدام كلّ تقنية بشكل رئيسي على التسليم والمواعيد النهائية الخاصّة بك: بعض الأدوات تسمح بمرونة أكبر، والبعض الآخر أسرع في التطوير:

صورة بواسطة المؤلّف

من وجهة نظري الشخصية، إذا تمّ استيفاء جميع الشروط، يمكن أن يكون Dash الخيار الأفضل للذهاب معه.

ابقَ على تواصل

هل لديك سؤال؟ يسعدنا أن نسمع منك.