Именно так начиналась статья Вас не тормозят привычные инструменты бизнес‑аналитики? в надежде убедить, что в программировании все не так сложно, как кажется. Здесь хотелось бы показать, что все еще проще. Инфографика, которую вы увидите, ниже стимулировала продолжение поиска инструментов визуализации данных и в этой заметке ещё об одном замечательном и абсолютно бесплатном фреймворке Python — Bokeh, и небольшой отчет о проделанных недавно экспериментах, результаты которых представлены серией простейших приложений визуализации, в том числе, и интерактивной визуализации, которая реализуется на удивление просто.
Быстрый старт с Bokeh
Bokehએ — интересное такое современное понятие, но для нас это интерактивная библиотека визуализации для современных веб-браузеров. Фреймворк Python обеспечивает шикарную, сжатую конструкцию разнообразных графиков и обеспечивает высокопроизводительную интерактивность с большими или потоковыми наборами данных. Bokehએ может помочь всем, кто хочет быстро и легко создавать интерактивные сюжеты, информационные панели, и приложения для передачи и предоставления аналитической информации.
Это краткое руководство, где внимание сфокусировано на визуализации без использования сервера, т.е. с помощью этих техник сделать онлайн-мониторинг погоды невозможно, данные в нашем случае должны быть статическими. Результатом работы программы является html-файл, который можно просто загрузить на web-сервер. Смею вас заверить, что все приложения, скрипты и ссылки на демонстрашки которых ниже, в том числе и интерактивные приложения, просто положены под Apache на нашем университетском сервере.
Pycharm и Bokeh
С инструментарием для разработки мы уже определились и подробнее прочитать о нём можно в статье PyCharm — эффективная разработка на Python. Поэтому невзирая на то, что в оригинальной документации Bokeh предполагается использовать Jupiter Notebook, для своих упражнений будем использовать Pycharm. Для нас подойдёт любая версия от Education до Professional.
Что бы Bokeh заработал, надо создать новый проект и установить его в виртуальную среду нашего нового демо-проекта.
Теперь всё готово для начала наших экспериментов.
И наблюдаем результат в окне браузера, который в вашей системе установлен по умолчанию:
В папке вашего проекта появился новый файл bars.html
, который после работы скрипта вы видите в окне навигатора проекта Pycharm. Его можно прочитать своим браузером и/или загрузить куда-нибудь в интернет на доступные вам ресурсы хостинга. Вот так передаются сообществам результаты своих исследований, просто файл .html
и никакого «шаманства».
Теперь файл bars.html
можно опубликовать на любом сервере, что и сделано, и в чём вы можете убедиться, пройдя по ссылке Live Demo: Строим гистограмму.
С дальнейшими примера поступаем точно так-же.
- Строим Гистограмму:
from bokeh.io import show, output_file from bokeh.plotting import figure output_file("bars.html") fruits = ['Яблоки', 'Груши', 'Нектарин', 'Сливы', 'Виноград', 'Клубника'] counts = [5, 3, 4, 2, 4, 6] p = figure(x_range=fruits, plot_height=250, title="Фрукты (количество)", toolbar_location=None, tools="") p.vbar(x=fruits, top=counts, width=0.9) p.xgrid.grid_line_color = None p.y_range.start = 0 show(p)
Live Demo: Строим гистограмму
- Строим Отсортированную гистограмму:
from bokeh.io import show, output_file from bokeh.plotting import figure output_file("bar_sorted.html") fruits = ['Яблоки', 'Груши', 'Нектарин', 'Сливы', 'Виноград', 'Клубника'] counts = [5, 3, 4, 2, 4, 6] # sorting the bars means sorting the range factors sorted_fruits = sorted(fruits, key=lambda x: counts[fruits.index(x)]) p = figure(x_range=sorted_fruits, plot_height=350, title="Фрукты (количество)", toolbar_location=None, tools="") p.vbar(x=fruits, top=counts, width=0.9) p.xgrid.grid_line_color = None p.y_range.start = 0 show(p)
Live Demo: Строим отсортированную гистограмму
- Строим Цветную гистограмму:
from bokeh.io import show, output_file from bokeh.models import ColumnDataSource from bokeh.palettes import Spectral6 from bokeh.plotting import figure output_file("color_bars.html") fruits = ['Яблоки', 'Груши', 'Нектарин', 'Сливы', 'Виноград', 'Клубника'] counts = [5, 3, 4, 2, 4, 6] source = ColumnDataSource(data=dict(fruits=fruits, counts=counts, color=Spectral6)) p = figure(x_range=fruits, y_range=(0,9), plot_height=250, title="Количество фруктов", toolbar_location=None, tools="") p.vbar(x='fruits', top='counts', width=0.9, color='color', legend_field="fruits", source=source) p.xgrid.grid_line_color = None p.legend.orientation = "horizontal" p.legend.location = "top_center" show(p)
Live Demo: Строим цветную гистограмму
- Скрип Пример простой линии:
from bokeh.plotting import figure, output_file, show # prepare some data x = [1, 2, 3, 4, 5] y = [6, 7, 2, 4, 5] # output to static HTML file output_file("lines.html") # create a new plot with a title and axis labels p = figure(title="Пример простой линии", x_axis_label='x', y_axis_label='y') # add a line renderer with legend and line thickness p.line(x, y, legend="Обозначение", line_width=2) # show the results show(p)
Live Demo: Пример простой линии
- Скрипт Цветные графики с логарифмической шкалой:
from bokeh.plotting import figure, output_file, show # prepare some data x = [0.1, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0] y0 = [i**2 for i in x] y1 = [10**i for i in x] y2 = [10**(i**2) for i in x] # output to static HTML file output_file("log_lines.html") # create a new plot p = figure( tools="pan,box_zoom,reset,save", y_axis_type="log", y_range=[0.001, 10**11], title="Пример графиков с логарифмической шкалой", x_axis_label='область определения', y_axis_label='значение функций' ) # add some renderers p.line(x, x, legend="y=x") p.circle(x, x, legend="y=x", fill_color="white", size=8) p.line(x, y0, legend="y=x^2", line_width=3) p.line(x, y1, legend="y=10^x", line_color="red") p.circle(x, y1, legend="y=10^x", fill_color="red", line_color="red", size=6) p.line(x, y2, legend="y=10^x^2", line_color="orange", line_dash="4 4") # show the results show(p)
Live Demo: Цветные графики с логарифмической шкалой
- Аналитика Импорт/Экспорт фруктов по годам:
from bokeh.io import output_file, show from bokeh.models import ColumnDataSource from bokeh.palettes import GnBu3, OrRd3 from bokeh.plotting import figure output_file("stacked_split.html") fruits = ['Яблоки', 'Груши', 'Нектарин', 'Сливы', 'Виноград', 'Клубника'] years = ["2015", "2016", "2017"] exports = {'fruits' : fruits, '2015' : [2, 1, 4, 3, 2, 4], '2016' : [5, 3, 4, 2, 4, 6], '2017' : [3, 2, 4, 4, 5, 3]} imports = {'fruits' : fruits, '2015' : [-1, 0, -1, -3, -2, -1], '2016' : [-2, -1, -3, -1, -2, -2], '2017' : [-1, -2, -1, 0, -2, -2]} p = figure(y_range=fruits, plot_height=250, x_range=(-16, 16), title="Импорт/Экспорт фруктов по годам", toolbar_location=None) p.hbar_stack(years, y='fruits', height=0.9, color=GnBu3, source=ColumnDataSource(exports), legend_label=["%s экспорт" % x for x in years]) p.hbar_stack(years, y='fruits', height=0.9, color=OrRd3, source=ColumnDataSource(imports), legend_label=["%s импорт" % x for x in years]) p.y_range.range_padding = 0.1 p.ygrid.grid_line_color = None p.legend.location = "top_left" p.axis.minor_tick_line_color = None p.outline_line_color = None show(p)
Live Demo: Импорт/Экспорт фруктов по годам
- Комментарии к объектам на рисунках:
from bokeh.io import output_file, show from bokeh.models import GeoJSONDataSource from bokeh.plotting import figure from bokeh.sampledata.sample_geojson import geojson import json output_file("geojson.html") data = json.loads(geojson) for i in range(len(data['features'])): data['features'][i]['properties']['Color'] = ['blue', 'red'][i%2] geo_source = GeoJSONDataSource(geojson=json.dumps(data)) TOOLTIPS = [ ('Организация', '@OrganisationName') ] p = figure(background_fill_color="lightgrey", tooltips=TOOLTIPS) p.circle(x='x', y='y', size=15, color='Color', alpha=0.7, source=geo_source) show(p)
Live Demo: Комментарии к объектам на рисунках
- Посмотрим на карту Google:
from bokeh.io import output_file, show from bokeh.models import ColumnDataSource, GMapOptions from bokeh.plotting import gmap output_file("gmap.html") map_options = GMapOptions(lat=30.2861, lng=-97.7394, map_type="roadmap", zoom=11) # For GMaps to function, Google requires you obtain and enable an API key: # # https://developers.google.com/maps/documentation/javascript/get-api-key # # Replace the value below with your personal API key: p = gmap("GOOGLE_API_KEY", map_options, title="Austin") source = ColumnDataSource( data=dict(lat=[ 30.29, 30.20, 30.29], lon=[-97.70, -97.74, -97.78]) ) p.circle(x="lon", y="lat", size=15, fill_color="blue", fill_alpha=0.8, source=source) show(p)
Live Demo: Посмотрим на карту Google
Примечание: карта Google работает коряво, не прописал ключ API. Заведите свой GOOGLE_API_KEY и наслаждайтесь.
- Посмотрим как изменяются координаты «мышки»:
import numpy as np from bokeh.io import show, output_file from bokeh.plotting import figure from bokeh import events from bokeh.models import CustomJS, Div, Button from bokeh.layouts import column, row def display_event(div, attributes=[], style = 'float:left;clear:left;font_size=10pt'): "Build a suitable CustomJS to display the current event in the div model." return CustomJS(args=dict(div=div), code=""" var attrs = %s; var args = []; for (var i = 0; i<attrs.length; i++) { args.push(attrs[i] + '=' + Number(cb_obj[attrs[i]]).toFixed(2)); } var line = "" + cb_obj.event_name + "(" + args.join(", ") + ")\\n"; var text = div.text.concat(line); var lines = text.split("\\n") if (lines.length > 35) lines.shift(); div.text = lines.join("\\n"); """ % (attributes, style)) x = np.random.random(size=4000) * 100 y = np.random.random(size=4000) * 100 radii = np.random.random(size=4000) * 1.5 colors = ["#%02x%02x%02x" % (int(r), int(g), 150) for r, g in zip(50+2*x, 30+2*y)] p = figure(tools="pan,wheel_zoom,zoom_in,zoom_out,reset") p.scatter(x, y, radius=np.random.random(size=4000) * 1.5, fill_color=colors, fill_alpha=0.6, line_color=None) div = Div(width=400, height=p.plot_height, height_policy="fixed") button = Button(label="Кнопка для отправки", button_type="success") layout = column(button, row(p, div)) ## Events with no attributes button.js_on_event(events.ButtonClick, display_event(div)) # Button click p.js_on_event(events.LODStart, display_event(div)) # Start of LOD display p.js_on_event(events.LODEnd, display_event(div)) # End of LOD display ## Events with attributes point_attributes = ['x', 'y', 'sx', 'sy'] # Point events wheel_attributes = point_attributes + ['delta'] # Mouse wheel event pan_attributes = point_attributes + ['delta_x', 'delta_y'] # Pan event pinch_attributes = point_attributes + ['scale'] # Pinch event point_events = [ events.Tap, events.DoubleTap, events.Press, events.PressUp, events.MouseMove, events.MouseEnter, events.MouseLeave, events.PanStart, events.PanEnd, events.PinchStart, events.PinchEnd, ] for event in point_events: p.js_on_event(event, display_event(div, attributes=point_attributes)) p.js_on_event(events.MouseWheel, display_event(div, attributes=wheel_attributes)) p.js_on_event(events.Pan, display_event(div, attributes=pan_attributes)) p.js_on_event(events.Pinch, display_event(div, attributes=pinch_attributes)) output_file("js_events.html", title="Пример JS Events") show(layout)
Live Demo: Посмотрим как изменяются координаты «мышки»
- Интерактивность — Подвигаем полозок и поменяем график:
from bokeh.layouts import column from bokeh.models import CustomJS, ColumnDataSource, Slider from bokeh.plotting import Figure, output_file, show output_file("js_on_change.html") x = [x*0.005 for x in range(0, 200)] y = x source = ColumnDataSource(data=dict(x=x, y=y)) plot = Figure(plot_width=400, plot_height=400) plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6) 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 = Slider(start=0.1, end=4, value=1, step=.1, title="Двигаем этот полозок") slider.js_on_change('value', callback) layout = column(slider, plot) show(layout)
Live Demo: Подвигаем полозок и поменяем график
- Интерактивность — Просто карта:
from bokeh.plotting import figure, show, output_file from bokeh.tile_providers import get_provider, Vendors output_file("tile.html") tile_provider = get_provider(Vendors.CARTODBPOSITRON) # range bounds supplied in web mercator coordinates p = figure(x_range=(-2000000, 6000000), y_range=(-1000000, 7000000), x_axis_type="mercator", y_axis_type="mercator") p.add_tile(tile_provider) show(p)
Live Demo: Просто карта
Для ленивых, но любопытных все коды лежат на Github, так что идите туда.
Ещё одно заимствованное РЕПО с примерами для того, что бы ощутить вкус Bokeh.
Наслаждайтесь и наращивайте свои компетенции в Python … Счастливого и удачного PY-тонинга!