«بوکه» (Bokeh)، کتابخانه‌ای برای بصری‌سازی تعاملی داده‌ها است. برخلاف دیگر کتابخانه‌های موجود برای بصری‌سازی داده‌ها در «زبان برنامه‌نویسی پایتون»، مانند «مَت‌پلات‌لیب» (Matplotlib) و «سی‌بورن» (Seaborn)، بوکه گرافیک‌های خود را با استفاده از «اچ‌تی‌ام‌ال» (HTML) و «جاوا اسکریپت» (JavaScript) رندر می‌کند. این موضوع موجب شده تا بوکه به گزینه‌ای مناسب برای ساخت دشبوردها و اپلیکیشن‌های مبتنی بر وب مبدل شود. به طور کلی باید گفت، کتابخانه بوکه ابزاری قدرتمند برای اکتشاف و درک داده‌ها یا ساخت نمودارهای زیبا برای پروژه‌ها و گزارش‌ها است. هدف از این راهنما آن است که روش کار با کتابخانه بوکه با بهره‌گیری از مجموعه داده‌های جهان واقعی، آموزش داده شود. سرفصل‌های زیر در این مطلب ارائه شده‌اند.

  • تبدیل داده‌ها به بصری‌سازی با استفاده از کتابخانه Bokeh
  • سفارشی‌سازی و مرتب‌سازی بصری‌سازی‌ها
  • افزودن قابلیت تعامل به بصری‌سازی‌ها

از داده تا بصری‌سازی

ساخت خروجی‌های بصری با استفاده از کتابخانه بوکه (Bokeh) شامل گام‌های زیر می‌شود:

  • آماده‌سازی داده‌ها
  • تعیین محل بصری‌سازی‌ها (روش رندر)
  • انتخاب اَشکال (Figures)
  • کار با داده‌ها و ترسیم آن‌ها
  • سازمان‌دهی قالب‌ها
  • پیش‌نمایش و ذخیره خروجی‌ها

اکنون، هر یک از مراحل بیان شده در بالا، همراه با جزئیات بیشتر مورد بررسی قرار می‌گیرد.

آماده‌سازی داده‌ها

بصری‌سازی داده‌ها همیشه با داده‌های ورودی آغاز می‌شود. در این گام معمولا از کتابخانه‌های «پیش‌پردازش» (Preprocessing) داده‌ها مانند «پانداس» (Pandas) و «نام‌پای» (NumPy) استفاده می‌شود و طی آن، گام‌های مورد نیاز برای تبدیل داده‌ها به فرم مناسب برای انجام پردازش‌ها، برداشته می‌شود.

تعیین محل بصری‌سازی‌ها

در این گام، چگونگی تولید و نمایش بصری‌سازی‌ها، به وسیله کاربر تعیین می‌شود. در این راهنما، دو گزینه متداولی که بوکه در این راستا فراهم می‌کند ارائه شده‌اند که عبارتند از: تولید یک فایل HTML استاتیک و رندر کردن بصری‌سازی‌ها به صورت خطی در «ژوپیتر نوت‌بوک» (Jupyter Notebook).

انتخاب اَشکال

در این مرحله، اشکال (Figure) توسط کاربر تعیین و بوم اولیه برای بصری‌سازی داده‌ها آماده می‌شود. در گام انتخاب اشکال، می‌توان همه چیز از عناوین گرفته تا علامت‌ها را سفارشی‌سازی کرد. همچنین، می‌توان با بهره‌گیری از مجموعه‌ای از ابزارها کاربران را قادر به تعامل با بصری‌سازی‌ها کرد.

کار با داده‌ها و ترسیم آن‌ها

اکنون، از قابلیت رندر کردن بوکه برای شکل دادن به داده‌ها استفاده می‌شود. در این فاز، باید از انعطاف‌پذیری موجود برای ترسیم داده‌ها از پایه با استفاده از اشکال و نشانه‌گرهای متعدد موجود استفاده کرد؛ همه این موارد به سادگی قابل سفارشی‌سازی هستند. این عملکرد، آزادی فوق‌العاده‌ای را جهت ارائه داده‌ها در اختیار کاربر قرار می‌دهد. علاوه بر موارد بیان شده، بوکه دارای توابع توکاری برای ساخت چیزهایی مانند «نمودار میله‌ای تجمعی» (Stacked Bar Charts) و قابلیت‌هایی برای ساخت بصری‌سازی‌های پیشرفته‌تر مانند گراف‌های شبکه و نقشه‌ها است.

سازمان‌دهی قالب

بوکه، برای افرادی که نیاز به بیش از یک تصویر برای تشریح داده‌های خود دارند نیز ابزاری مناسب محسوب می‌شود. این کتابخانه، نه فقط گزینه‌های «قالب» (layout) شبکه مانند استاندارد را فراهم می‌کند، بلکه این امکان را برای کاربر فراهم می‌کند تا به سادگی بصری‌سازی‌ها را  با تنها با چند خط کد در یک قالب قرار دهد. علاوه بر آن، نمودارها را می‌توان به سرعت به یکدیگر لینک کرد؛ بنابراین، انتخاب یکی از نمودارها، بر کلیه نمودارهای دیگر (هر ترکیبی از دیگر گزینه‌ها) نیز تاثیرگذار است.

پیش‌نمایش و ذخیره خروجی‌ها

در نهایت، زمان آن فرا می‌رسد که آنچه ساخته شده است را نمایش داد. اگرچه، بصری‌سازی‌ها در مرورگر یا نوت‌بوک قابل مشاهده هستند، کاربر قادر به بررسی خروجی‌ها (بصری‌سازی‌ها)، سفارشی‌سازی آن‌ها و ویرایش قسمت‌های تعاملی موجود در آن‌ها است. اگر کاربر آنچه که دیده را بپسندد، می‌تواند بصری‌سازی‌ها را به صورت یک فایل تصویر (Image) ذخیره‌سازی کند. در غیر این صورت (یعنی در صورتی که خروجی‌ها نظر کاربر را جلب نکرد)، کاربر می‌تواند گام‌های انجام شده در بالا را برای کسب خروجی‌هایی که در نظر دارد، مورد بازنگری قرار دهد. شش گام بیان شده در بالا، بلوک‌های سازنده یک الگوی سازمان‌یافته و انعطاف‌پذیر هستند که از مرحله دریافت داده‌ها از مجموعه داده تا ترسیم آن‌ها به صورت نمودارهای جذاب و قابل فهم را پوشش می‌دهند.

"""Bokeh Visualization Template

This template is a general outline for turning your data into a 
visualization using Bokeh.
"""
# Data handling
import pandas as pd
import numpy as np

# Bokeh libraries
from bokeh.io import output_file, output_notebook
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource
from bokeh.layouts import row, column, gridplot
from bokeh.models.widgets import Tabs, Panel

# Prepare the data

# Determine where the visualization will be rendered
output_file('filename.html')  # Render to static HTML, or 
output_notebook()  # Render inline in a Jupyter Notebook

# Set up the figure(s)
fig = figure()  # Instantiate a figure() object

# Connect to and draw the data

# Organize the layout

# Preview and save 
show(fig)  # See what I made, and save if I like it

یکی از قطعه کدهای متداولی که برای انجام کلیه گام‌هایی که پیش‌تر بیان شدند مورد استفاده قرار می‌گیرد در بالا نمایش داده شده است.

ساخت اولین تصویر با کتابخانه بوکه

راهکارهای متعددی برای ارائه خروجی بصری از داده‌ها در کتابخانه «بوکه» (Bokeh) وجود دارد. در این راهنما، گزینه‌های معرفی شده در زیر مورد استفاده قرار می‌گیرند.

  • (‘output_file(‘filename.html: بصری‌سازی‌ها را روی یک فایل HTML استاتیک می‌نویسد.
  • ()output_notebook بصری‌سازی‌ها را به طور مستقیم در «ژوپیتر نوت‌بوک» رندر می‌کند.

شایان ذکر است که هیچ یک از دو تابع بالا بصری‌سازی‌ها را به کاربر نمایش نمی‌دهند. در واقع این اتفاق مادامی که ()show فراخوانی نشده نمی‌افتد. اگرچه، این توابع اطمینان حاصل می‌کنند که در صورت فراخوانی ()show، بصری‌سازی‌ها به گونه‌ای نمایش داده می‌شود که کاربر تمایل دارد. با فراخوانی هم‌زمان ()output_file و ()output_notebook در یک اجرای واحد، بصری‌سازی هم روی فایل HTML استاتیک و هم روی نوت‌بوک به صورت توکار انجام می‌شود. اگر به هر دلیلی کاربر چند دستور ()output_file را به طور هم‌زمان اجرا کند، تنها آخرین دستور برای رندر کردن مورد استفاده قرار می‌گیرد. اکنون، فرصت خوبی فراهم شده تا کد اولیه در ()figure پیش‌فرض بوکه با استفاده از ()output_file نوشته شود.

# Bokeh Libraries
from bokeh.io import output_file
from bokeh.plotting import figure, show

# The figure will be rendered in a static HTML file called output_file_test.html
output_file('output_file_test.html', 
            title='Empty Bokeh Figure')

# Set up a generic figure() object
fig = figure()

# See what it looks like
show(fig)
کتابخانه بوکه (Bokeh) در پایتون
برای مشاهده تصویر با ابعاد اصلی روی آن کلیک کنید.

همانطور که مشهود است، یک پنجره جدید مرورگر باز شده که به آن Empty Bokeh Figure و empty figure گفته می‌شود. آنچه در تصویر مشهود نیست و در پشت صحنه به وقوع می‌پیوندد، ساخته شدن فایلی با نام output_file_test.html در پوشه کاری جاری کاربر است. اگر کاربر قصد داشته باشد که قطعه کدی مشابه با ()output_notebook را به جای ()output_file اجرا کند، با این فرض که Jupyter Notebook در حال اجرا است می‌تواند از قطعه کد زیر استفاده کند.

# Bokeh Libraries
from bokeh.io import output_notebook
from bokeh.plotting import figure, show

# The figure will be right in my Jupyter Notebook
output_notebook()

# Set up a generic figure() object
fig = figure()

# See what it looks like
show(fig)
کتابخانه بوکه (Bokeh) در پایتون
برای مشاهده تصویر در ابعاد اصلی روی آن کلیک کنید.

همانطور که مشهود است، نتایج مشابه هستند و صرفا رندر کردن در محل‌های متفاوتی به وقوع پیوسته است. اطلاعات بیشتر پیرامون ()output_file و ()output_notebook در مستندات رسمی بوکه (+) موجود است. نکته شایان توجه آن است که گاهی هنگام رندر کردن چندین بصری‌سازی به ترتیب، مشاهده می‌شود که رندرهای قبلی مربوط به اجراهای پیشین پاک نشده‌اند. در صورت مواجه شدن با چنین شرایطی، می‌توان کد زیر را «وارد» (Import) و در میان اجراها اجرا کرد.

# Import reset_output (only needed once) 
from bokeh.plotting import reset_output

# Use reset_output() between subsequent show() calls, as needed
reset_output()

پیش از ادامه دادن این مطلب، لازم به ذکر است که بوکه به طور پیش‌فرض با یک جعبه ابزار از پیش بارگذاری شده می‌آید. اطلاعات بیشتر پیرامون این جعبه ابزار و چگونگی پیکربندی آن در بخش افزودن تعامل، در پایان این راهنما ارائه شده است.

آماده‌سازی اولین شکل برای داده‌ها

اکنون که چگونگی ساخت و نمایش یک تصویر کلی بوکه هم در مرورگر و هم در ژوپیتر نوت‌بوک آموزش داده شد، زمان آن رسیده تا بیشتر پیرامون چگونگی پیکربنی شی ()figure توضیح داده شود. شی ()figure فقط پایه بصری‌سازی داده‌ها نیست، بلکه شیئی است که ابزارهای بوکه برای بصری‌سازی داده‌ها را قفل‌گشایی می‌کند. figure در بوکه یک زیرکلاس از «Bokeh Plot Object» است که پارامترهای زیادی را برای پیکربندی عناصر زیبایی‌شناختی شکل فراهم می‌کند. برای درک بهتر این مطلب، در ادامه زشت‌ترین تصویر ممکن با قطعه کد زیر ساخته می‌شود.

# Bokeh Libraries
from bokeh.io import output_notebook
from bokeh.plotting import figure, show

# The figure will be rendered inline in my Jupyter Notebook
output_notebook()

# Example figure
fig = figure(background_fill_color='gray',
             background_fill_alpha=0.5,
             border_fill_color='blue',
             border_fill_alpha=0.25,
             plot_height=300,
             plot_width=500,
             h_symmetry=True,
             x_axis_label='X Label',
             x_axis_type='datetime',
             x_axis_location='above',
             x_range=('2018-01-01', '2018-06-30'),
             y_axis_label='Y Label',
             y_axis_type='linear',
             y_axis_location='left',
             y_range=(0, 100),
             title='Example Figure',
             title_location='right',
             toolbar_location='below',
             tools='save')

# See what it looks like
show(fig)

کتابخانه بوکه (Bokeh) در پایتون

هنگامی که شی ()figure معرفی شد، می‌توان آن را پیکربندی کرد. اکنون، فرض می‌شود که کاربر می‌خواهد خطوط شبکه‌ای را حذف کند. برای این کار می‌توان از قطعه کد زیر استفاده کرد.

# Remove the gridlines from the figure() object
fig.grid.grid_line_color = None

# See what it looks like 
show(fig)

مشخصات خطوط شبکه‌ای از طریق خصیصه grid شکل در دسترس هستند. در این مثال، تنظیم grid_line_color روی None، به شکل موثری همه خطوط شبکه‌ای را به صورت یکباره حذف می‌کند.

کتابخانه بوکه (Bokeh) در پایتون

نکته: اگر کاربر در نوت‌بوک یا «محیط توسعه یکپارچه» (Integrated Development Environment | IDE) با قابلیت «تکمیل خودکار» (auto-complete) کار می‌کند، می‌تواند از مزایای قابل توجه آن استفاده کند. ()figure دارای عناصر قابل سفارشی‌سازی متعددی است و قابلیت تکمیل خودکار برای کشف گزینه‌های موجود برای سفارشی‌سازی بسیار مفید محسوب می‌شود.

کتابخانه بوکه (Bokeh) در پایتون

در صورتی که محیط توسعه مورد استفاده کاربر دارای قابلیت تکمیل خودکار نیست، انجام یک جست‌و‌جوی سریع، با کلیدواژه Bokeh و آنچه که کاربر قصد دارد انجام دهد، معمولا او را به مسیر صحیحی هدایت می‌کند. مطالب بسیار زیادی برای آموزش دادن در همین رابطه وجود دارند، اما ورود بیش از حد به جزئیات، این مطلب را از هدف اصلی آن دور می‌کند. برای مطالعه بیشتر در این رابطه، می‌توان از موارد زیر استفاده کرد.

  • Bokeh Plot Class (+): سوپرکلاسی از شی ()figure است که شکل‌ها از آن خصیصه‌های زیادی را به ارث می‌برند.
  • The Figure Class (+): محل خوبی برای پیدا کردن جزئیات بیشتر پیرامون آرگومان‌های شی ()figure است.

در ادامه، برخی از گزینه‌های سفارشی‌سازی خاصی که فراگیری آن‌ها ارزشمند است بیان شده‌اند.

  • Text Properties (+): همه خصیصه‌های مربوط به تغییر استایل، سایز، رنگ و دیگر موارد مربوط به «قلم» (Font) در این قسمت تحت پوشش قرار می‌گیرند.
  • TickFormatters (+): اشیا توکاری هستند که برای قالب‌بندی محورها استفاده می‌شوند. نحو مورد استفاده برای انجام این کار، مشابه با نحو پایتون برای قالب‌بندی رشته‌ها است.

گاهی، تا هنگامی که بخشی از داده‌ها بصری‌سازی نشوند، مشخص نیست که تصویر چقدر نیاز به ویرایش و سفارشی‌سازی دارد. بنابراین، در ادامه روش انجام این کار آموزش داده خواهد شد.

ترسیم داده‌ها با Glyphs

یک شکل خالی، مساله جذاب و قابل توجهی نیست؛ بنابراین در ادمه به بررسی glyphs که بلوک سازنده بصری‌سازی‌های Bokeh است پرداخته می‌شود. glyph یک شکل گرافیکی برداری‌سازی شده یا «نشانگری» (Marker) است که برای ارائه داده‌ها در اشکالی مانند یک دایره یا مربع مورد استفاده قرار می‌گیرد. مثال‌های بیشتر در این رابطه در گالری بوکه (+) موجود هستند. پس از ساخت شکل، باید به دسته‌ای از متدهای قابل پیکربندی glyph دسترسی داده شود. اکنون، کار با یک مثال بسیار پایه‌ای آغاز می‌شود که طی آن چندین نقطه روی یک دستگاه مختصات x-y ترسیم می‌شوند.

# Bokeh Libraries
from bokeh.io import output_file
from bokeh.plotting import figure, show

# My x-y coordinate data
x = [1, 2, 1]
y = [1, 1, 2]

# Output the visualization directly in the notebook
output_file('first_glyphs.html', title='First Glyphs')

# Create a figure with no toolbar and axis ranges of [0,3]
fig = figure(title='My Coordinates',
             plot_height=300, plot_width=300,
             x_range=(0, 3), y_range=(0, 3),
             toolbar_location=None)

# Draw the coordinates as circles
fig.circle(x=x, y=y,
           color='green', size=10, alpha=0.5)

# Show plot
show(fig)

کتابخانه بوکه (Bokeh) در پایتون

پس از معرفی ()figure، می‌توان دید که چگونه می‌توان از آن برای ترسیم داده‌ها در دستگاه مختصات x-y با استفاده از glyph‌های circle سفارشی‌سازی شده استفاده کرد. در ادامه برخی از دسته‌های glyph معرفی شده‌اند.

  • Marker شامل اشکالی مانند دایره، لوزی، مربع و مثلث است و برای ساخت بصری‌سازی‌هایی مانند «نمودارهای نقطه‌ای» (Scatter chart) و «نمودار حبابی» (Bubble chart) مفید محسوب می‌شود.
  • Line مواردی مانند اشکال تک‌خطی، خطی پله‌ای و چندخطی را شامل می‌شود که می‌توان از آن‌ها برای ساخت نمودارهای خطی استفاده کرد.
  • Bar/Rectangle اشکالی هستند که برای ساخت نمودارهای میله‌ای سنتی یا پشته‌ای (hbar)، ستونی (vbar)، آبشاری و  «گانت» (gantt) مورد استفاده قرار می‌گیرند.

glyph‌ها را می‌توان ترکیب کرد تا قابلیت‌های لازم برای بصری‌سازی تامین شوند. اکنون فرض می‌شود که هدف ساخت تصویری است که تعداد کلمات نوشته شده در هر روز برای ساخت این راهنما را با یک خط نشانگر روند رشد تعداد کلمات (روزانه و تجمعی) نشان دهد.

import numpy as np

# Bokeh libraries
from bokeh.io import output_notebook
from bokeh.plotting import figure, show

# My word count data
day_num = np.linspace(1, 10, 10)
daily_words = [450, 628, 488, 210, 287, 791, 508, 639, 397, 943]
cumulative_words = np.cumsum(daily_words)

# Output the visualization directly in the notebook
output_notebook()

# Create a figure with a datetime type x-axis
fig = figure(title='My Tutorial Progress',
             plot_height=400, plot_width=700,
             x_axis_label='Day Number', y_axis_label='Words Written',
             x_minor_ticks=2, y_range=(0, 6000),
             toolbar_location=None)

# The daily words will be represented as vertical bars (columns)
fig.vbar(x=day_num, bottom=0, top=daily_words, 
         color='blue', width=0.75, 
         legend='Daily')

# The cumulative sum will be a trend line
fig.line(x=day_num, y=cumulative_words, 
         color='gray', line_width=1,
         legend='Cumulative')

# Put the legend in the upper left corner
fig.legend.location = 'top_left'

# Let's check it out
show(fig)

کتابخانه بوکه (Bokeh) در پایتون

برای ترکیب ستون‌ها و خط‌ها در شکل، این موارد با استفاده از شی ()figure ساخته شده‌اند. علاوه بر آن، می‌توان مشاهده کرد که با تنظیمات legend برای هر glyph چگونه می‌توان راهنمای نمودار را به صورت یکپارچه ترسیم کرد. راهنمای نقشه نیز با تخصیص «top_left» به «fig.legend.location» به گوشه بالا و سمت راست نمودار انتقال داده می‌شود.

نگاهی کوتاه به داده‌ها

کتابخانه بصری‌سازی بوکه فرصت خوبی را برای کار با داده‌های گوناگون فراهم می‌کند. جذابیت Bokeh در آن است که تقریبا پیاده‌سازی هر ایده بصری‌سازی با آن امکان دارد و این موضوع فقط به چگونگی استفاده از ابزارهایی که در اختیار کاربر قرار داده بستگی دارد. در مثالی که در ادامه به آن پرداخته می‌شود از مجموعه داده‌های عمومی Kaggle که مربوط به ۷۲ فصل از مسابقات بسکتبال اتحادیه ملی بسکتبال آمریکا ( the National Basketball Association’s (NBA) 2017-18 season) هستند، استفاده شده است.

  • 2017-18_playerBoxScore.csv (+): آمارهای بازیکنان به صورت بازی به بازی
  • 2017-18_teamBoxScore.csv (+): آمارهای تیم به صورت بازی به بازی
  • 2017-18_standings.csv (+): جدول رده‌بندی و امتیازات روزانه

پس از دانلود مجموعه داده، با استفاده از قطعه کدی که در ادامه آمده، داده‌ها در دیتافریم کتابخانه «پانداس» (Pandas) خوانده می‌شوند.

import pandas as pd

# Read the csv files
player_stats = pd.read_csv('2017-18_playerBoxScore.csv', parse_dates=['gmDate'])
team_stats = pd.read_csv('2017-18_teamBoxScore.csv', parse_dates=['gmDate'])
standings = pd.read_csv('2017-18_standings.csv', parse_dates=['stDate'])

این قطعه کد داده‌ها را از سه فایل CSV خوانده و به صورت خودکار ستون‌های داده‌ را به صورت اشیا datetime تفسیر می‌کند. اکنون زمان آن رسیده که روی داده‌های واقعی کار شود.

استفاده از شی ColumnDataSource

در مثال بالا از لیست‌های پایتون و آرایه‌های «نام‌پای» (Numpy Arrays) برای نمایش داده‌ها استفاده شده است. کتابخانه «بوکه» (Bokeh) برای مدیریت این نوع داده‌ها به خوبی تجهیز شده است. اگرچه، هنگامی که بحث داده‌ها در پایتون می‌شود، احتمال استفاده از دیکشنری‌ها و دیتافریم‌های پانداس نیز وجود دارد، به ویژه اگر داده‌ها از یک فایل یا منبع داده خارجی خوانده می‌شوند. Bokeh برای کار با این ساختارهای داده پیچیده‌تر توانمند و حتی دارای توابع توکاری برای مدیریت آن‌ها است؛ به عنوان مثالی از این توابع می‌توان به ColumnDataSource اشاره کرد.

اکنون، امکان دارد این پرسش مطرح شود که «اگر Bokeh دارای رابطی برای کار با داده‌ها به صورت مستقیم است چرا از ColumnDataSource استفاده شود؟». اولا، هنگامی که به یک لیست، آرایه یا دیتافریم به طور مستقیم ارجاع داده می‌شود، بوکه در پشت صحنه به هر حال آن را به یک ColumnDataSource تبدیل می‌کند. دوم و مهم‌تر آنکه، ColumnDataSource پیاده‌سازی قابلیت‌های تعاملی بوکه را ساده‌تر می‌سازد.

ColumnDataSource در انتقال دادن داده‌ها به glyph‌هایی که برای بصری‌سازی مورد استفاده قرار می‌گیرند نقش بنیادین دارد. کارکرد اصلی آن، برای نگاشت اسامی به ستون‌های داده‌ها است. این کار موجب می‌شود ارجاع به عناصر داده‌ها هنگام ساخت بصری‌سازی‌ها آسان‌تر شود. این تابع همچنین انجام کار مشابهی را هنگام ساخت بصری‌سازی‌ها آسان‌تر می‌کند. ColumnDataSource می‌تواند سه نوع شی داده بیان شده در زیر را تفسیر کند.

  • Python dict: «کلیدها» (keys)، اسامی مرتبط با توالی‌های مربوطه هستند (لیست‌ها، آرایه‌ها و به همین ترتیب).
  • DataFrame پانداس: ستون‌های DataFrame، اسامی مرجع برای ColumnDataSource هستند.
  • groupby در پانداس: ستون‌های ColumnDataSource را به شکل دیده شده با فراخوانی ()groupby.describe ارجاع می‌دهند.

اکنون، کار با بصری‌سازی مسابقات برای جایگاه اول در NBA’s Western Conference in 2017-18 برای مدافع قهرمانی یعنی تیم Golden State Warriors و رقیب آن Houston Rockets آغاز می‌شود. رکوردهای روزانه پیروزی-شکست برای این دو تیم در یک دیتافریم با نام west_top_2 ذخیره شده است.

>>> west_top_2 = (standings[(standings['teamAbbr'] == 'HOU') | (standings['teamAbbr'] == 'GS')]
...               .loc[:, ['stDate', 'teamAbbr', 'gameWon']]
...               .sort_values(['teamAbbr','stDate']))
>>> west_top_2.head()
        stDate teamAbbr  gameWon
9   2017-10-17       GS        0
39  2017-10-18       GS        0
69  2017-10-19       GS        0
99  2017-10-20       GS        1
129 2017-10-21       GS        1

از اینجا می‌توان این DataFrame را در دو شی ColumnDataSource بارگذاری و مسابقه را بصری‌سازی کرد.

# Bokeh libraries
from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.models import ColumnDataSource

# Output to file
output_file('west-top-2-standings-race.html', 
            title='Western Conference Top 2 Teams Wins Race')

# Isolate the data for the Rockets and Warriors
rockets_data = west_top_2[west_top_2['teamAbbr'] == 'HOU']
warriors_data = west_top_2[west_top_2['teamAbbr'] == 'GS']

# Create a ColumnDataSource object for each team
rockets_cds = ColumnDataSource(rockets_data)
warriors_cds = ColumnDataSource(warriors_data)

# Create and configure the figure
fig = figure(x_axis_type='datetime',
             plot_height=300, plot_width=600,
             title='Western Conference Top 2 Teams Wins Race, 2017-18',
             x_axis_label='Date', y_axis_label='Wins',
             toolbar_location=None)

# Render the race as step lines
fig.step('stDate', 'gameWon', 
         color='#CE1141', legend='Rockets', 
         source=rockets_cds)
fig.step('stDate', 'gameWon', 
         color='#006BB6', legend='Warriors', 
         source=warriors_cds)

# Move the legend to the upper left corner
fig.legend.location = 'top_left'

# Show the plot
show(fig)

کتابخانه بوکه (Bokeh) در پایتون

چگونگی ارجاع داده شدن اشیای ColumnDataSource هنگام ساخت دو خط قابل توجه است. اسامی ستون اصلی به سادگی به عنوان پارامترهای ورودی پاس داده می‌شوند و مشخص می‌شود که کدام ColumnDataSource با خصیصه source مورد استفاده قرار بگیرد. بصری‌سازی‌ها نشانگر وجود مسابقات تنگاتنگی در این فصل و همچنین، حاکی از تاثیر قابل توجه Warriors در میانه فصل هستند. اگرچه، یک بیت لغزش در فصل پیش امکان بالا رفتن را برای Rockets فراهم کرده و در نهایت از مدافعان قهرمانی پایان فصل، به عنوان تیم اول پیشی گرفته است.

نکته: در بوکه، می‌توان رنگ‌ها را بر اساس اسم، مقدار پایه ۱۶ (hex value)، یا کد رنگ RGB تعیین کرد. برای بصری‌سازی بالا، دو رنگ برای خطوط مربوطه تعیین شده‌اند که نشانگر دو تیم هستند. به جای استفاده از اسامی رنگ‌های CSS، کاربر ممکن است بخواهد که با استفاده از رنگ‌های رسمی تیم‌ها به صورت کدهای هگزادسیمال جلوه زیبایی به خروجی‌ها ببخشد. در عین حال، می‌توان از تاپل‌هایی که کدهای رنگ RGB را نشان می‌دهند (۲۰۶، ۱۷، ۶۵) برای Rockets و کدهای (۰، ۱۰۷ و ۱۸۲) برای Warriors استفاده کرد.

بوکه لیست مفیدی از اسامی رنگ‌های CSS که بر اساس فام اصلی آن‌ها دسته‌بندی شده‌اند را ارائه می‌کند. همچنین، می‌توان از سایت‌های متعددی که برای یافتن کدهای رنگی به صورت CSS ،RGB و hex وجود دارند، استفاده کرد. اشیای ColumnDataSource می‌توانند کاری بیش از ارجاع‌دهی به ستون‌های DataFrame انجام دهند. شی ColumnDataSource دارای سه فیلتر توکار است که می‌توانند برای ساخت نمایش‌هایی از داده‌ها با استفاده از شی CDSView مورد استفاده قرار بگیرند؛ این سه فیلتر در ادامه معرفی شده‌اند.

  • GroupFilter کار فیلتر کردن ColumnDataSource را با استفاده از لیستی از اندیس‌های صحیح (Integer) انجام می‌دهد.
  • IndexFilter که ColumnDataSource را با استفاده از لیستی از شاخص‌های صحیح فیلتر می‌کند.
  • BooleanFilter امکان استفاده از لیستی از مقادیر boolean را با سطرهای True که انتخاب شده‌اند فراهم می‌کند.

در مثال پیشین، دو شی ColumnDataSource ساخته شدند که هر یک از آن‌ها زیرمجموعه‌ای از دیتافریم west_top_2 هستند. مثال بعدی، خروجی مشابهی را از یک ColumnDataSource بر پایه west_top_2 با استفاده از GroupFilter می‌سازد که دیدی از داده‌ها فراهم می‌کند.

# Bokeh libraries
from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.models import ColumnDataSource, CDSView, GroupFilter

# Output to file
output_file('west-top-2-standings-race.html', 
            title='Western Conference Top 2 Teams Wins Race')

# Create a ColumnDataSource
west_cds = ColumnDataSource(west_top_2)

# Create views for each team
rockets_view = CDSView(source=west_cds,
                       filters=[GroupFilter(column_name='teamAbbr', group='HOU')])
warriors_view = CDSView(source=west_cds,
                        filters=[GroupFilter(column_name='teamAbbr', group='GS')])

# Create and configure the figure
west_fig = figure(x_axis_type='datetime',
                  plot_height=300, plot_width=600,
                  title='Western Conference Top 2 Teams Wins Race, 2017-18',
                  x_axis_label='Date', y_axis_label='Wins',
                  toolbar_location=None)

# Render the race as step lines
west_fig.step('stDate', 'gameWon',
              source=west_cds, view=rockets_view,
              color='#CE1141', legend='Rockets')
west_fig.step('stDate', 'gameWon',
              source=west_cds, view=warriors_view,
              color='#006BB6', legend='Warriors')

# Move the legend to the upper left corner
west_fig.legend.location = 'top_left'

# Show the plot
show(west_fig)

کتابخانه بوکه (Bokeh) در پایتون

چگونگی پاس دادن GroupFilter به CDSView در یک لیست قابل توجه است. این کار، امکان ترکیب چندین فیلتر با یکدیگر را برای ایزوله کردن داده‌هایی که از ColumnDataSource مورد نیاز هستند فراهم می‌کند. Western Conference به عنوان مسابقاتی مهیج پایان یافت که رقابت‌های آن حقیقتا تنگاتنگ برگزار شده بودند. این رقابت‌ها در ادامه به صورت بصری ارائه خواهند شد. بنابراین، در ادامه به موضوع بعدی یعنی layout‌ها پرداخته خواهد شد.

سازمان‌دهی بصری‌سازی‌های متعدد با Layout‌ها

جدول رده‌بندی Eastern Conference به دو دسته از رقابت‌ها در بخش آتلانتیک تقسیم می‌شود که عبارتند از: Boston Celtics و Toronto Raptors. پیش از تکرار گام‌هایی که برای ساخت west_top_2 مورد استفاده قرار گرفتند، بهتر است یکبار دیگر ColumnDataSource با استفاده از آنچه پیش‌تر توضیح داده شد مورد آزمون قرار بگیرد. در این مثال، چگونگی خوراک دادن کل DataFrame به یک ColumnDataSource و ساخت نمایش‌هایی برای ایزوله کردن داده‌های مرتبط، بیان خواهد شد.

# Bokeh libraries
from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.models import ColumnDataSource, CDSView, GroupFilter

# Output to file
output_file('east-top-2-standings-race.html', 
            title='Eastern Conference Top 2 Teams Wins Race')

# Create a ColumnDataSource
standings_cds = ColumnDataSource(standings)

# Create views for each team
celtics_view = CDSView(source=standings_cds,
                      filters=[GroupFilter(column_name='teamAbbr', 
                                           group='BOS')])
raptors_view = CDSView(source=standings_cds,
                      filters=[GroupFilter(column_name='teamAbbr', 
                                           group='TOR')])

# Create and configure the figure
east_fig = figure(x_axis_type='datetime',
           plot_height=300, plot_width=600,
           title='Eastern Conference Top 2 Teams Wins Race, 2017-18',
           x_axis_label='Date', y_axis_label='Wins',
           toolbar_location=None)

# Render the race as step lines
east_fig.step('stDate', 'gameWon', 
              color='#007A33', legend='Celtics',
              source=standings_cds, view=celtics_view)
east_fig.step('stDate', 'gameWon', 
              color='#CE1141', legend='Raptors',
              source=standings_cds, view=raptors_view)

# Move the legend to the upper left corner
east_fig.legend.location = 'top_left'

# Show the plot
show(east_fig)

کتابخانه بوکه (Bokeh) در پایتون

ColumnDataSource قادر به ایزوله کردن داده‌های مرتبط در یک دیتافریم ۵، ۰۴۰ در ۳۹ بدون سختی قابل توجه و تنها با چند خط کد «پانداس» (Pandas) است. با نگاه به بصری‌سازی‌ها، می‌توان مشاهده کرد که رقابت‌های Eastern Conference دارای هیچ مشکلی نیستند. پس از آنکه Celtics نتوانستند میادین را به خوبی حفظ کنند، Raptors‌ها همه راه را بازگشتند تا بر رقیب خود غلبه کرده و فصل را با پنج برد بیشتر به اتمام برسانند. با دو بصری‌سازی آماده، زمان آن رسیده تا این نمودارها در کنار هم قرار داده شوند و تحلیل‌هایی روی آن‌ها صورت پذیرد. مشابه با عملکرد subplot در «مَت‌پِلات‌لیب» ( Matplotlib)، بوکه نیز دارای توابع column، row و gridplot در ماژول bokeh.layouts است. این توابع می‌توانند به صورت عمومی‌تر با عنوان layout دسته‌بندی شوند. استفاده از این layout‌ها کار سختی نیست. اگر هدف، قرار دادن دو بصری‌سازی در پیکربندی عمودی باشد می‌توان به صورت زیر عمل کرد.

# Bokeh library
from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.layouts import column

# Output to file
output_file('east-west-top-2-standings-race.html', 
            title='Conference Top 2 Teams Wins Race')

# Plot the two visualizations in a vertical configuration
show(column(west_fig, east_fig))

کتابخانه بوکه (Bokeh) در پایتون

تنها با تغییر column با row در قطعه کد بالا، می‌توان به طور مشابه دو نمودار را به صورت افقی در کنار هم قرار داد.

نکته: برای افرادی که همراه با مطالعه این مطلب در تلاش برای نوشتن کدهای خودشان هستند، در ادامه نکاتی پیرامون خطایی که امکان دارد با آن مواجه شوند ارائه می‌شود. این خطا ممکن است ضمن دسترسی به west_fig و east_fig در مثالی که در ادامه ارائه شده رخ دهد. خطای مذکور، چیزی شبیه آنچه در زیر آمده، خواهد بود.

WARNING:bokeh.core.validation.check:W-1004 (BOTH_CHILD_AND_ROOT): Models should not be a document root...

این تنها یکی از خطاهای متعددی است که به عنوان ماژول اعتبارسنجی بوکه به وقوع می‌پیوندند که در آن W-1004 هشداری پیرامون استفاده مجدد از west_fig و east_fig در یک layout جدید است. برای اجتناب از این خطاها، همانطور که مثال‌ها تست می‌شوند، قطعه کدی که هر لایه را نمایش می‌دهد به صورت زیر آغاز می‌شود:

# Bokeh libraries
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, CDSView, GroupFilter

# Create a ColumnDataSource
standings_cds = ColumnDataSource(standings)

# Create the views for each team
celtics_view = CDSView(source=standings_cds,
                      filters=[GroupFilter(column_name='teamAbbr', 
                                           group='BOS')])

raptors_view = CDSView(source=standings_cds,
                      filters=[GroupFilter(column_name='teamAbbr', 
                                           group='TOR')])

rockets_view = CDSView(source=standings_cds,
                      filters=[GroupFilter(column_name='teamAbbr', 
                                           group='HOU')])
warriors_view = CDSView(source=standings_cds,
                      filters=[GroupFilter(column_name='teamAbbr', 
                                           group='GS')])

# Create and configure the figure
east_fig = figure(x_axis_type='datetime',
                  plot_height=300,
                  x_axis_label='Date',
                  y_axis_label='Wins',
                  toolbar_location=None)

west_fig = figure(x_axis_type='datetime',
                  plot_height=300,
                  x_axis_label='Date',
                  y_axis_label='Wins',
                  toolbar_location=None)

# Configure the figures for each conference
east_fig.step('stDate', 'gameWon', 
              color='#007A33', legend='Celtics',
              source=standings_cds, view=celtics_view)
east_fig.step('stDate', 'gameWon', 
              color='#CE1141', legend='Raptors',
              source=standings_cds, view=raptors_view)

west_fig.step('stDate', 'gameWon', color='#CE1141', legend='Rockets',
              source=standings_cds, view=rockets_view)
west_fig.step('stDate', 'gameWon', color='#006BB6', legend='Warriors',
              source=standings_cds, view=warriors_view)

# Move the legend to the upper left corner
east_fig.legend.location = 'top_left'
west_fig.legend.location = 'top_left'

# Layout code snippet goes here!

انجام این کار موجب نوسازی مولفه‌های مرتبط برای رندر کردن بصری‌سازی‌ها و حصول اطمینان از این موضوع می‌شود که هیچ هشداری مورد نیاز نیست. به جای استفاده از column یا row، کاربر ممکن است بخواهد که از gridplot استفاده کند. یک تفاوت کلیدی gridplot با دیگر موارد آن است که به طور خودکار نوار ابزار را در سراسر شکل‌های (Figures) فرزند تحکیم می‌کند. همانطور که مشهود است، در gridplot به جای آنکه یک تاپل به عنوان ورودی به آن داده شود، نیازمند لیستی از لیست‌ها است که در آن هر زیر لیست یک سطر از شبکه را نشان می‌دهد:

# Bokeh libraries
from bokeh.io import output_file
from bokeh.layouts import gridplot

# Output to file
output_file('east-west-top-2-gridplot.html', 
            title='Conference Top 2 Teams Wins Race')

# Reduce the width of both figures
east_fig.plot_width = west_fig.plot_width = 300

# Edit the titles
east_fig.title.text = 'Eastern Conference'
west_fig.title.text = 'Western Conference'

# Configure the gridplot
east_west_gridplot = gridplot([[west_fig, east_fig]], 
                              toolbar_location='right')

# Plot the two visualizations in a horizontal configuration
show(east_west_gridplot)

کتابخانه بوکه (Bokeh) در پایتون

در نهایت، gridplot امکان انتقال مقادیر None را فراهم می‌کند که به صورت زیرنمودارهای خالی تفسیر می‌شوند. از این رو، اگر کاربر قصد قرار دادن یک placeholder برای دو نمودار اضافه‌تر را داشته باشد، می‌تواند به صورت زیر عمل کند.

# Bokeh libraries
from bokeh.io import output_file
from bokeh.layouts import gridplot

# Output to file
output_file('east-west-top-2-gridplot.html', 
            title='Conference Top 2 Teams Wins Race')

# Reduce the width of both figures
east_fig.plot_width = west_fig.plot_width = 300

# Edit the titles
east_fig.title.text = 'Eastern Conference'
west_fig.title.text = 'Western Conference'

# Plot the two visualizations with placeholders
east_west_gridplot = gridplot([[west_fig, None], [None, east_fig]], 
                              toolbar_location='right')

# Plot the two visualizations in a horizontal configuration
show(east_west_gridplot)
کتابخانه بوکه (Bokeh) در پایتون
برای مشاهده تصویر در ابعاد اصلی روی لینک زیر کلیک کنید.

اگر کاربر قصد جابه‌جایی میان بصری‌سازی‌ها در سایز کامل آن‌ها را بدون پایین کشیدن آن‌ها برای متناسب شدن در کنار یا بر فراز هم داشته باشد، یک گزینه خوب tabbed layout است. یک tabbed layout شامل دو تابع ویجت Bokeh است: ()Tab و ()Panel از زیرماژول bokeh.models.widgets. همچون استفاده از ()gridplot، ساخت یک tabbed layout نیز کار ساده‌ای به حساب می‌آید.

# Bokeh Library
from bokeh.io import output_file
from bokeh.models.widgets import Tabs, Panel

# Output to file
output_file('east-west-top-2-tabbed_layout.html', 
            title='Conference Top 2 Teams Wins Race')

# Increase the plot widths
east_fig.plot_width = west_fig.plot_width = 800

# Create two panels, one for each conference
east_panel = Panel(child=east_fig, title='Eastern Conference')
west_panel = Panel(child=west_fig, title='Western Conference')

# Assign the panels to Tabs
tabs = Tabs(tabs=[west_panel, east_panel])

# Show the tabbed layout
show(tabs)

کتابخانه بوکه (Bokeh) در پایتون

اولین گام، ساخت یک ()Panel برای هر tab است. این موضوع ممکن است کمی گیج‌کننده به نظر برسد، در واقع باید به تابع ()Tabs به عنوان مکانیزمی که Tab‌های جداگانه ساخته شده با ()Panel را سازمان‌دهی می‌کند نگریست. هر ()Panel یک فرزند را به عنوان ورودی دریافت می‌کند که می‌تواند یک ()figure یا layout تنها باشد. (باید به خاطر داشت که layout یک نام عمومی برای column ،row یا gridplot است.) پس از آنکه پنل‌ها سرهم شدند، می‌توان آن‌ها را به ()Tabs در یک لیست انتقال داد. اکنون که چگونگی دسترسی، ترسیم و سازمان‌دهی داده‌ها مشخص شد، زمان آن رسیده که معجزه بوکه را روی داده‌های واقعی دید: تعامل!

افزودن تعامل

ویژگی که بوکه را از دیگر کتابخانه‌های موجود متمایز می‌کند، توانایی آن برای پیاده‌سازی آسان تعامل در بصری‌سازی‌ها است. بوکه حتی تا توصیف خودش به عنوان یک کتابخانه بصری‌سازی تعاملی پیش می‌رود.

Bokeh یک کتابخانه بصری‌سازی تعاملی است که مرورگرهای مدرن را برای ارائه، هدف قرار می‌دهد.

در این بخش، چهار راهکاریی که می‌توان با استفاده از آن‌ها تعامل را به بصری‌سازی‌ها افزود بیان شده‌اند.

  • پیکربندی نوار ابزار
  • انتخاب نقاط داده
  • افزودن اقدام شناور
  • پیوند دادن محورها و انتخاب‌ها
  • برجسته کردن داده‌ها با استفاده از legend

پیاده‌سازی این عناصر تعاملی امکان اکتشاف داده‌ها را به شیوه‌ای فراهم می‌کند که بصری‌سازی‌های استاتیک نمی‌توانند انجام دهند.

پیکربندی نوار ابزار

چنانکه پیش‌تر در بخش «ساخت شکل اولیه» بیان شد، ()figure پیش‌فرض بوکه با یک نوار ابزار عرضه می‌شود. نوار ابزار پیش‌فرض با ابزارهایی که در ادامه بیان شده‌اند ارائه می‌شود (از چپ به راست).

  • Pan
  • Box Zoom
  • Wheel Zoom
  • Save
  • Reset

نوار ابزار را می‌توان با پاس دادن toolbar_location=None هنگام معرفی یک شی ()figure حذف کرد و یا با پاس دادن هر یک از گزینه‌های above، below، left یا right تغییر مکان داد. علاوه بر آن، نوار ابزار را می‌توان برای در بر گرفتن هر ترکیبی از ابزارهایی که کاربر تمایل دارد پیکربندی کرد. بوکه ۱۰ ابزار اختصاصی در ۱۸ دسته ارائه می کند که در زیر بیان شده‌اند.

  • Pan/Drag: box_select, box_zoom, lasso_select, pan, xpan, ypan, resize_select
  • Click/Tap: poly_select, tap
  • Scroll/Pinch: wheel_zoom, xwheel_zoom, ywheel_zoom
  • Actions: undo, redo, reset, save
  • Inspectors: crosshair, hover

انتخاب نقاط داده

پیاده‌سازی رفتار انتخاب به سادگی افزودن چند کلیدواژه خاص هنگام اعلام glyph‌ها است. دومین مثال، یک نمودار پراکندگی می‌سازد که تعداد کل ضربات سه امتیازی را نسبت به درصد ضربات ساخته شده (برای بازیکنانی با دستکم ۱۰۰ ضربه سه امتیازی) نمایش می‌دهد. داده‌ها را می‌توان از دیتافریم player_stats تجمیع کرد.

# Find players who took at least 1 three-point shot during the season
three_takers = player_stats[player_stats['play3PA'] > 0]

# Clean up the player names, placing them in a single column
three_takers['name'] = [f'{p["playFNm"]} {p["playLNm"]}' 
                        for _, p in three_takers.iterrows()]

# Aggregate the total three-point attempts and makes for each player
three_takers = (three_takers.groupby('name')
                            .sum()
                            .loc[:,['play3PA', 'play3PM']]
                            .sort_values('play3PA', ascending=False))

# Filter out anyone who didn't take at least 100 three-point shots
three_takers = three_takers[three_takers['play3PA'] >= 100].reset_index()

# Add a column with a calculated three-point percentage (made/attempted)
three_takers['pct3PM'] = three_takers['play3PM'] / three_takers['play3PA']

در ادامه نمونه‌ای از دیتافریم‌های حاصل شده را می‌توان مشاهده کرد.

>>> three_takers.sample(5)
 name play3PA play3PM pct3PM
229 Corey Brewer 110 31 0.281818
78 Marc Gasol 320 109 0.340625
126 Raymond Felton 230 81 0.352174
127 Kristaps Porziņģis 229 90 0.393013
66 Josh Richardson 336 127 0.377976

اکنون، فرض می‌شود که کاربر قصد انتخاب گروهی از بازیکنان را در توزیع دارد و در این راستا رنگ glyph‌ها که نشانگر بازیکنان انتخاب نشده هستند را خاموش می‌کند.

# Bokeh Libraries
from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.models import ColumnDataSource, NumeralTickFormatter

# Output to file
output_file('three-point-att-vs-pct.html',
            title='Three-Point Attempts vs. Percentage')

# Store the data in a ColumnDataSource
three_takers_cds = ColumnDataSource(three_takers)

# Specify the selection tools to be made available
select_tools = ['box_select', 'lasso_select', 'poly_select', 'tap', 'reset']

# Create the figure
fig = figure(plot_height=400,
             plot_width=600,
             x_axis_label='Three-Point Shots Attempted',
             y_axis_label='Percentage Made',
             title='3PT Shots Attempted vs. Percentage Made (min. 100 3PA), 2017-18',
             toolbar_location='below',
             tools=select_tools)

# Format the y-axis tick labels as percentages
fig.yaxis[0].formatter = NumeralTickFormatter(format='00.0%')

# Add square representing each player
fig.square(x='play3PA',
           y='pct3PM',
           source=three_takers_cds,
           color='royalblue',
           selection_color='deepskyblue',
           nonselection_color='lightgray',
           nonselection_alpha=0.3)

# Visualize
show(fig)

کاربر ابتدا باید ابزارهای انتخابی که می‌خواهد در دسترس باشند را تعیین کند. در مثال بالا، box_select، lasso_select، poly_select و tap (به علاووه دکمه reset) در لیستی که select_tools نامیده می‌شود تعریف شده‌اند. هنگامی که شکل تعریف شد، نوار ابزار در موقعیت below در نمودار قرار می‌گیرد و لیست به tools برای در دسترس قرار دادن ابزارهای انتخاب شده در بالا انتقال داده می‌شود. هر بازیکن در ابتدا با یک مربع به رنگ آبی تیره نمایش داده می‌شود، اما پیکربندی‌های بیان شده در زیر هنگامی که یک بازیکن یا بازیکنان انتخاب می‌شوند تنظیم شده است.

  • تبدیل کردن بازیکنان انتخاب شده به آبی آسمانی
  • تغییر glyph‌های همه بازیکنان انتخاب نشده به رنگ خاکستری روشن (lightgray) با شفافیت ۰.۳

تنها با چند تغییر سریع، بصری‌سازی اکنون به چیزی مانند تصویر زیر مبدل شده است.

کتابخانه بوکه (Bokeh) در پایتون

افزودن Hover

ابزار ()HoverTool اندکی با ابزارهای انتخاب شده در بالا متفاوت و دارای مشخصه‌هایی و به ویژه tooltips است. ابتدا، می‌توان یک tooltip را با ساخت لیستی از تاپل‌ها که حاوی توصیف‌ها و ارجاعات به ColumnDataSource هستند ساخت. این لیست به عنوان ورودی به ()HoverTool انتقال داده شده و سپس به سادگی با استفاده از ()add_tools به شکل افزوده شده است. در ادامه، آنچه به وقوع پیوسته قابل مشاهده است.

کتابخانه بوکه (Bokeh) در پایتون

اضافه شدن دکمه Hover به جعبه ابزار قابلیت روشن و خاموش شدن را می‌افزاید که در تصویر بالا می‌توان آن را مشاهده کرد. در صورتی که کاربر قصد تاکید بیشتر بر بازیکنان در Hover را داشته باشد، بوکه انجام این کار را با وارسی Hover امکان‌پذیر ساخته است. در ادامه، کد اندکی ویرایش شده از قطعه کد بالا که tooltip به آن افزوده شده، ارائه شده است.

# Format the tooltip
tooltips = [
            ('Player','@name'),
            ('Three-Pointers Made', '@play3PM'),
            ('Three-Pointers Attempted', '@play3PA'),
            ('Three-Point Percentage','@pct3PM{00.0%}'),
           ]

# Configure a renderer to be used upon hover
hover_glyph = fig.circle(x='play3PA', y='pct3PM', source=three_takers_cds,
                         size=15, alpha=0,
                         hover_fill_color='black', hover_alpha=0.5)

# Add the HoverTool to the figure
fig.add_tools(HoverTool(tooltips=tooltips, renderers=[hover_glyph]))

# Visualize
show(fig)

این کار با ساخت یک glyph کاملا جدید انجام می‌شود، در این مثال، از دایره‌ها به جای مربع استفاده شده که به hover_glyph تخصیص داده شده‌اند. توجه به این نکته لازم است که شفافیت اولیه برابر با صفر تعیین شده، بنابراین تا هنگامی که نشانگر موس آن را لمس نکند، به صورت شفاف است. مشخصه‌هایی که برای hover ظاهر می‌شوند با تنظیم hover_alpha برابر با ۰.۵ در امتداد hover_fill_color تنظیم می‌شوند. اکنون، هنگام Hover کردن روی مارکرهای گوناگون، دایره کوچک مشکی روی مربع اصلی ظاهر می‌شود.

کتابخانه بوکه (Bokeh) در پایتون

پیوند دادن محورها و انتخاب‌ها

پیوند دادن، فرآیند همگام‌سازی عناصر از بصری‌سازی‌های گوناگون درون یک قالب است. برای مثال، ممکن است کاربر بخواهد که محورهای چند نمودار را برای اطمینان از اینکه اگر روی آن‌ها بزرگنمایی شود روی دیگری منعکس می‌شوند به یکدیگر پیوند دهد. برای این مثال، بصری‌سازی‌ها قادر به Pan کردن (اسکرول افقی) به بخش‌های مختلف از برنامه تیم و آزمودن وضعیت آمارهای بازی‌های گوناگون هستند. آمارها به وسیله نمودار در یک ()gridplot نمایش داده می‌شوند. داده‌ها از دیتافریم team_stats قابل گردآوری هستند. در ادامه، تیم Philadelphia 76ers به عنوان تیم مورد علاقه انتخاب می‌شود.

# Isolate relevant data
phi_gm_stats = (team_stats[(team_stats['teamAbbr'] == 'PHI') & 
                           (team_stats['seasTyp'] == 'Regular')]
                .loc[:, ['gmDate', 
                         'teamPTS', 
                         'teamTRB', 
                         'teamAST', 
                         'teamTO', 
                         'opptPTS',]]
                .sort_values('gmDate'))

# Add game number
phi_gm_stats['game_num'] = range(1, len(phi_gm_stats)+1)

# Derive a win_loss column
win_loss = []
for _, row in phi_gm_stats.iterrows():

    # If the 76ers score more points, it's a win
    if row['teamPTS'] > row['opptPTS']:
        win_loss.append('W')
    else:
        win_loss.append('L')

# Add the win_loss data to the DataFrame
phi_gm_stats['winLoss'] = win_loss

در ادامه، نتایج پنج بازی اول 76ers آورده شده است.

>>> phi_gm_stats.head()
 gmDate teamPTS teamTRB teamAST teamTO opptPTS game_num winLoss
10 2017-10-18 115 48 25 17 120 1 L
39 2017-10-20 92 47 20 17 102 2 L
52 2017-10-21 94 41 18 20 128 3 L
80 2017-10-23 97 49 25 21 86 4 W
113 2017-10-25 104 43 29 16 105 5 L

کار با وارد کردن (Import) کتابخانه‌های لازم بوکه، مشخص کردن پارامترهای خروجی و خواندن داده‌ها در ColumnDataSource آغاز می‌شود.

# Bokeh Libraries
from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.models import ColumnDataSource, CategoricalColorMapper, Div
from bokeh.layouts import gridplot, column

# Output to file
output_file('phi-gm-linked-stats.html',
                title='76ers Game Log')

# Store the data in a ColumnDataSource
gm_stats_cds = ColumnDataSource(phi_gm_stats)

هر بازی به وسیله یک ستون ارائه می‌شود و اگر نتیجه بُرد بود سبز و اگر باخت بود قرمز رنگ می‌شود. برای انجام این کار، CategoricalColorMapper بوکه، می‌تواند برای نگاشت مقادیر داده به رنگ‌های تعیین شده مورد استفاده قرار بگیرد.

# Create a CategoricalColorMapper that assigns a color to wins and losses
win_loss_mapper = CategoricalColorMapper(factors = ['W', 'L'], 
                                         palette=['green', 'red'])

برای این بررسی موردی، لیستی که مقادیر داده دسته‌ای را نگاشت می‌کند به factors و لیستی با رنگ‌های مورد نظر به palette پاس داده می‌شود. چهار آمار برای بصری‌سازی در یک gridplot دو در دو وجود دارد که عبارتند از امتیازها (point)، پاس‌های منجر به گل (assist)، ریباندها (rebounds) و گردش‌ها (turnovers). در ساخت چهار شکل و پیکربندی نمودارهای مربوط به آن‌ها، افزونگی زیادی در ویژگی‌ها وجود دارد. بنابراین، برای ساده کردن کد از یک حلقه for استفاده می‌شود.

# Create a dict with the stat name and its corresponding column in the data
stat_names = {'Points': 'teamPTS',
              'Assists': 'teamAST',
              'Rebounds': 'teamTRB',
              'Turnovers': 'teamTO',}

# The figure for each stat will be held in this dict
stat_figs = {}

# For each stat in the dict
for stat_label, stat_col in stat_names.items():

    # Create a figure
    fig = figure(y_axis_label=stat_label, 
                 plot_height=200, plot_width=400,
                 x_range=(1, 10), tools=['xpan', 'reset', 'save'])

    # Configure vbar
    fig.vbar(x='game_num', top=stat_col, source=gm_stats_cds, width=0.9, 
             color=dict(field='winLoss', transform=win_loss_mapper))

    # Add the figure to stat_figs dict
    stat_figs[stat_label] = fig

همانطور که مشهود است، تنها پارامترهایی که نیاز به تنظیم داشتند y-axis-label شکل و داده‌هایی که top را در vbar دیکته می‌کند هستند. این مقادیر به راحتی در یک dict ذخیره می‌شوند که در آن تکرار به منظور ساخت شکل برای هر آمار انجام می‌شود. همچنین، می‌توان پیاده‌سازی CategoricalColorMapper در پیکربندی glyph مربوط به vbar را مشاهده کرد. خصوصیت رنگ یک ٰdict را با فیلد درون ColumnDataSource پاس می‌دهد تا نگاشت شود؛ بدین شکل، نام CategoricalColorMapper در بالا ساخته شده است. دید اولیه فقط ۱۰ بازی اول از فصل 76ers را نشان می‌دهد، بنابراین، نیاز به راهی برای اسکرول افقی (pan horizontally) برای کاوش در کل بازی‌های این فصل است. بدین ترتیب، پیکربندی نوار ابزار امکان لازم برای داشتن یک ابزار xpan که قابلیت Pan کردن از طریق نمودار را بدون داشتن هرگونه نگرانی پیرامون چولگی تصادفی در نمایش در طول محور عمودی دارد، فراهم می‌کند. اکنون که شکل ساخته شد، gridplot را می‌توان با ارجاع به اشکالی از dict ساخته شده در بالا راه‌اندازی کرد.

# Create layout
grid = gridplot([[stat_figs['Points'], stat_figs['Assists']], 
                [stat_figs['Rebounds'], stat_figs['Turnovers']]])

پیوند دادن محورهای چهار نمودار به سادگی تنظیم کردن x_range برای کلیه اشکال به صورت مساوی با یکدیگر است.

# Link together the x-axes
stat_figs['Points'].x_range = \
    stat_figs['Assists'].x_range = \
    stat_figs['Rebounds'].x_range = \
    stat_figs['Turnovers'].x_range

برای افزودن نوار عنوان به بصری‌سازی، می‌توان این کار را روی شکل امتیازها انجام داد، اما در صورت انجام چنین کاری، فضای شکل محدود می‌شود. بنابراین، یک ترفند خوب استفاده از قابلیت‌های Bokeh برای تفسیر HTML جهت درج عنصر Div محسوب می‌شود که شامل اطلاعات عنوان است. پس از آنکه ساخته شد، به سادگی می‌توان آن را با ()gridplot در قالب column ترکیب کرد.

# Add a title for the entire visualization using Div
html = """<h3>Philadelphia 76ers Game Log</h3>
<b><i>2017-18 Regular Season</i>
<br>
</b><i>Wins in green, losses in red</i>
"""
sup_title = Div(text=html)

# Visualize
show(column(sup_title, grid))

کنار هم قرار دادن همه قسمت‌ها منجر به ساخت شکل زیر می‌شود.

کتابخانه بوکه (Bokeh) در پایتون

به طور مشابه، می‌توان به سادگی انتخاب‌های لینک شده را در جایی که انتخاب در یک نمودار روی دیگری نیز منعکس می‌شود، پیاده‌سازی کرد. برای اینکه مشخص شود این کار به چه صورت انجام می‌شود، بصری‌سازی بعدی حاوی دو نمودار پراکندگی خواهد بود، یکی درصد ضربات دو امتیازی 76ers را در مقایسه با فیلد درصد ضربات سه امتیازی نمایش می‌دهد و دیگری امتیاز تیم‌های 76ers را در مقایسه با امتیازهای حریف به صورت بازی به بازی نشان می‌دهد. هدف از این کار آن است که کاربر قادر به انتخاب نقاط داده در نمودار پراکندگی سمت چپ و همچنین، تشخیص اینکه آیا نقاط داده متناظر در نمودار سمت راست مربوط به بردها هستند یا باخت‌ها، باشد. دیتافریم برای این بصری‌سازی شباهت زیادی به دیتافریم مربوط به مثال اول دارد.

# Isolate relevant data
phi_gm_stats_2 = (team_stats[(team_stats['teamAbbr'] == 'PHI') & 
                             (team_stats['seasTyp'] == 'Regular')]
                  .loc[:, ['gmDate', 
                           'team2P%', 
                           'team3P%', 
                           'teamPTS', 
                           'opptPTS']]
                  .sort_values('gmDate'))

# Add game number
phi_gm_stats_2['game_num'] = range(1, len(phi_gm_stats_2) + 1)

# Derive a win_loss column
win_loss = []
for _, row in phi_gm_stats_2.iterrows():

    # If the 76ers score more points, it's a win
    if row['teamPTS'] > row['opptPTS']:
        win_loss.append('W')
    else:
        win_loss.append('L')

# Add the win_loss data to the DataFrame
phi_gm_stats_2['winLoss'] = win_loss

شکلی که داده‌ها به نظر می‌رسند:

>>> phi_gm_stats_2.head()
        gmDate  team2P%  team3P%  teamPTS  opptPTS  game_num winLoss
10  2017-10-18   0.4746   0.4286      115      120         1       L
39  2017-10-20   0.4167   0.3125       92      102         2       L
52  2017-10-21   0.4138   0.3333       94      128         3       L
80  2017-10-23   0.5098   0.3750       97       86         4       W
113 2017-10-25   0.5082   0.3333      104      105         5       L

کد لازم برای ساخت بصری‌سازی‌ها به صورت زیر است:

# Bokeh Libraries
from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.models import ColumnDataSource, CategoricalColorMapper, NumeralTickFormatter
from bokeh.layouts import gridplot

# Output inline in the notebook
output_file('phi-gm-linked-selections.html',
            title='76ers Percentages vs. Win-Loss')

# Store the data in a ColumnDataSource
gm_stats_cds = ColumnDataSource(phi_gm_stats_2)

# Create a CategoricalColorMapper that assigns specific colors to wins and losses
win_loss_mapper = CategoricalColorMapper(factors = ['W', 'L'], palette=['Green', 'Red'])

# Specify the tools
toolList = ['lasso_select', 'tap', 'reset', 'save']

# Create a figure relating the percentages
pctFig = figure(title='2PT FG % vs 3PT FG %, 2017-18 Regular Season',
                plot_height=400, plot_width=400, tools=toolList,
                x_axis_label='2PT FG%', y_axis_label='3PT FG%')

# Draw with circle markers
pctFig.circle(x='team2P%', y='team3P%', source=gm_stats_cds, 
              size=12, color='black')

# Format the y-axis tick labels as percenages
pctFig.xaxis[0].formatter = NumeralTickFormatter(format='00.0%')
pctFig.yaxis[0].formatter = NumeralTickFormatter(format='00.0%')

# Create a figure relating the totals
totFig = figure(title='Team Points vs Opponent Points, 2017-18 Regular Season',
                plot_height=400, plot_width=400, tools=toolList,
                x_axis_label='Team Points', y_axis_label='Opponent Points')

# Draw with square markers
totFig.square(x='teamPTS', y='opptPTS', source=gm_stats_cds, size=10,
              color=dict(field='winLoss', transform=win_loss_mapper))

# Create layout
grid = gridplot([[pctFig, totFig]])

# Visualize
show(grid)

این نمایش خوبی از قدرت موجود در استفاده از ColumnDataSource است. تا هنگامی که رندر کننده glyph (در این مثال، glyphs «دایره» (circle) برای درصدها و «مربع» (square) برای شکست‌ها و پیروزی‌ها) ColumnDataSource مشابهی را به اشتراک می‌گذارند، انتخاب‌ها به صورت پیش‌فرض به یکدیگر لینک می‌شوند. در شکل زیر نمایش آنچه بیان شد به صورت بصری ارائه شده و همانطور که مشهود است آنچه در نمودار یک سمت تصویر انتخاب می‌شود در سمت دیگر نیز منعکس می‌شود.

کتابخانه بوکه (Bokeh) در پایتون

با انتخاب یک نمونه تصادفی از نقاط داده در ربع بالا سمت راست از نمودار پراکندگی سمت چپ، نقاط متناظر آن‌ها در نمودار مقایسه ضربات دو امتیازی و سه امتیازی برجسته می‌شود. به طور مشابه، هنگامی که نقاط داده در نمودار سمت راست که مربوط به امتیاز تیم‌ها و رقبایشان است انتخاب می‌شوند، نقاط داده متناظر آن‌ها در نمودار سمت چپ نمایش داده می‌شود.

برجسته‌سازی داده‌ها با استفاده از Legend

این گام ما را به آخرین مثال تعاملی در این راهنما هدایت می‌کند: legend‌های تعاملی. در بخش «ترسیم داده‌ها با Glyph‌ها» مشهود بود که پیاده‌سازی یک legend هنگام ساخت نمودار چقدر آسان است. با استفاده از legend افزودن تعامل صرفا با تخصیص یک click_policy انجام‌پذیر خواهد بود. با استفاده از یک خط کد، می‌توان توانایی hide یا mute کردن داده‌ها را با بهره‌گیری از legend ایجاد کرد. در این مثال، می‌توان دو نمودار پراکندگی یکسان را دید که امتیازها و ریباندهای بازی به بازی برای James و Kevin Durant را مقایسه می‌کنند. تنها تفاوت آن‌ها در این است که یکی از hide به عنوان click_policy خود و دیگری از mute برای این منظور استفاده می‌کند. اولین گام برای پیکربندی خروجی و راه‌اندازی داده‌ها ساخت چشم‌اندازی برای هر بازیکن از دیتافریم player_stats است.

# Bokeh Libraries
from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.models import ColumnDataSource, CDSView, GroupFilter
from bokeh.layouts import row

# Output inline in the notebook
output_file('lebron-vs-durant.html',
            title='LeBron James vs. Kevin Durant')

# Store the data in a ColumnDataSource
player_gm_stats = ColumnDataSource(player_stats)

# Create a view for each player
lebron_filters = [GroupFilter(column_name='playFNm', group='LeBron'),
                  GroupFilter(column_name='playLNm', group='James')]
lebron_view = CDSView(source=player_gm_stats,
                      filters=lebron_filters)

durant_filters = [GroupFilter(column_name='playFNm', group='Kevin'),
                  GroupFilter(column_name='playLNm', group='Durant')]
durant_view = CDSView(source=player_gm_stats,
                      filters=durant_filters)

پیش از ساخت شکل، پارامترهای متداول در شکل، مارکرها و داده‌ها را می‌توان در دیکشنری‌ها تلفیق و استفاده مجدد کرد. این کار نه تنها از «افزونگی» (redundancy) در گام‌های بعدی جلوگیری می‌کند، بلکه راه ساده‌ای برای پیچش این پارامترها در صورت نیاز فراهم می‌کند.

# Consolidate the common keyword arguments in dicts
common_figure_kwargs = {
    'plot_width': 400,
    'x_axis_label': 'Points',
    'toolbar_location': None,
}
common_circle_kwargs = {
    'x': 'playPTS',
    'y': 'playTRB',
    'source': player_gm_stats,
    'size': 12,
    'alpha': 0.7,
}
common_lebron_kwargs = {
    'view': lebron_view,
    'color': '#002859',
    'legend': 'LeBron James'
}
common_durant_kwargs = {
    'view': durant_view,
    'color': '#FFC324',
    'legend': 'Kevin Durant'
}

اکنون که مشخصه‌های گوناگون تنظیم شدند، دو نمودار پراکندگی را می‌توان به شیوه خلاصه‌تری ساخت که کد آن در زیر آمده است.

# Create the two figures and draw the data
hide_fig = figure(**common_figure_kwargs,
                  title='Click Legend to HIDE Data', 
                  y_axis_label='Rebounds')
hide_fig.circle(**common_circle_kwargs, **common_lebron_kwargs)
hide_fig.circle(**common_circle_kwargs, **common_durant_kwargs)

mute_fig = figure(**common_figure_kwargs, title='Click Legend to MUTE Data')
mute_fig.circle(**common_circle_kwargs, **common_lebron_kwargs,
                muted_alpha=0.1)
mute_fig.circle(**common_circle_kwargs, **common_durant_kwargs,
                muted_alpha=0.1)

توجه به این نکته لازم است که mute_fig دارای یک پارامتر افزوده با عنوان muted_alpha است. این پارامتر شفافیت مارکرها را هنگامی که mute به عنوان click_policy مورد استفاده قرار می‌گیرد تعیین می‌کند. در نهایت، click_policy برای هر تصویر تنظیم و در پیکربندی افقی نمایش داده می‌شود.

# Add interactivity to the legend
hide_fig.legend.click_policy = 'hide'
mute_fig.legend.click_policy = 'mute'

# Visualize
show(row(hide_fig, mute_fig))

کتابخانه بوکه (Bokeh) در پایتون

هنگامی که legend در موقعیت خود قرار گرفت، تمام آنچه نیاز است تخصیص دادن hide یا mute به مشخصه click_policy شکل است. این کار موجب می‌شود legend پایه‌ای به یک legend تعاملی مبدل شود. شایان توجه است که به ویژه برای mute، مشخصه‌های اضافی برای muted_alpha در glyph‌های circle مربوطه برای LeBron James و Kevin Durant تنظیم شده‌اند. این کار، جلوه‌های بصری مشتق شده از تعامل‌های legend را دیکته می‌کند.

نتیجه‌گیری

در این راهنما چگونگی پیکربندی اسکریپت‌های پایتون با بهره‌گیری از کتابخانه بوکه، به منظور رندر کردن یک فایل HTML استاتیک یا نوت‌بوک ژوپیتر مورد بررسی قرار گرفت. همچنین، چگونگی نمونه‌سازی و سفارشی‌سازی شی ()figure، ساخت بصری‌سازی‌ها با استفاده از glyph‌ها، دسترسی و فیلتر داده‌ها با ColumnDataSource، سازمان‌دهی نمودارها در لایوت‌های شبکه‌ای و tab شده و همچنین، افزودن اشکال گوناگونی از تعامل شامل انتخاب، hover کردن، پیوند دادن و افزودن Legend‌های تعاملی آموزش داده شد.

اگر نوشته بالا برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

^^

بر اساس رای ۱ نفر
آیا این مطلب برای شما مفید بود؟
شما قبلا رای داده‌اید!
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.

«الهام حصارکی»، فارغ‌التحصیل مقطع کارشناسی ارشد مهندسی فناوری اطلاعات، گرایش سیستم‌های اطلاعات مدیریت است. او در زمینه هوش مصنوعی و داده‌کاوی، به ویژه تحلیل شبکه‌های اجتماعی، فعالیت می‌کند.