От переводчика


Недавно наткнулся в python digest на туториал по Flask+Bokeh. Туториал ориентирован на новичков, не требуется даже знать синтаксис Python и HTML. Примеры работают под Ubuntu 16.04, на Windows немного отличается работа с виртуальными окружениями.


image


Вступление


Bokeh — это мощная библиотека с открытым исходным кодом, которая позволяет визуализировать данные для веб-приложений, не написав ни строчки на javascript. Изучение библиотек для визуализации вроде d3.js может оказаться полезным, но гораздо легче написать несколько строк кода на Python, чтобы решить задачу.


С Bokeh мы можем создавать поразительно детальные интерактивные визуализации или же более простые вещи, вроде столбчатых диаграмм.


Давайте разберёмся, как можно использовать Flask и Bokeh для визуализации данных в веб-приложении.


Инструменты


Всё, что описано далее, работает как на Python 2, так и на Python 3, однако, рекомендуется использовать Python 3 для новых приложений. Я использовал Python 3.6.1 на момент написания этой статьи. Помимо самого Python, нам потребуются следующие зависимости:


  • Веб-фреймворк Flask, версия 0.12.2
  • Библиотека визуализации данных Bokeh, версия 0.12.5
  • Библиотека анализа данных pandas, версия 0.20.1
  • pip и virtualenv поставляются вместе с Python 3. Они понадобятся, чтобы изолировать Flask, Bokeh и pandas от остальных проектов.

Если вам нужны дополнительные сведения по настройке окружения разработки, можете обратиться к руководству. Весь код примеров доступен по лицензии MIT на GitHub.


Установка Bokeh и Flask


Создайте чистое виртуальное окружение для проекта. Как правило, я запускаю эту команду в отдельной папке venvs, где которой находятся все мои виртуальные окружения.


python3 -m venv barchart

Активируйте виртуальное окружение.


source barchart/bin/activate

После активации виртуального окружения изменится приглашение командной строки:
image
Не забывайте, что вам понадобится активировать виртуальное окружение в каждом новом окне терминала, из которого вы захотите запустить своё приложение.


Теперь можно установить Bokeh и Flask в созданное виртуальное окружение. Выполните эту команду, чтобы установить Bokeh и Flask подходящих версий.


pip install bokeh==0.12.5 flask==0.12.2 pandas==0.20.1

После загрузки и установки необходимые библиотеки будут доступны в виртуальном окружении. Проверьте вывод, чтобы удостовериться, что всё установилось.


Installing collected packages: six, requests, PyYAML, python-dateutil, MarkupSafe, Jinja2, numpy, tornado, bokeh, Werkzeug, itsdangerous, click, flask, pytz, pandas
  Running setup.py install for PyYAML ... done
  Running setup.py install for MarkupSafe ... done
  Running setup.py install for tornado ... done
  Running setup.py install for bokeh ... done
  Running setup.py install for itsdangerous ... done
Successfully installed Jinja2-2.9.6 MarkupSafe-1.0 PyYAML-3.12 Werkzeug-0.12.2 bokeh-0.12.5 click-6.7 flask-0.12.2 itsdangerous-0.24 numpy-1.12.1 pandas-0.20.1 python-dateutil-2.6.0 pytz-2017.2 requests-2.14.2 six-1.10.0 tornado-4.5.1

Теперь мы можем перейти непосредственно к нашему приложению.


Запуск приложения на Flask


Мы напишем простое Flask-приложение и добавим столбчатую диаграмму на страницу. Создайте папку для своего прокта с файлом app.py с таким содержанием:


from flask import Flask, render_template

app = Flask(__name__)

@app.route("/<int:bars_count>/")
def chart(bars_count):
    if bars_count <= 0:
        bars_count = 1
    return render_template("chart.html", bars_count=bars_count)

if __name__ == "__main__":
    app.run(debug=True)

Это простое Flask-приложение, в котором есть функция chart. chart принимает целое число, которое позже будет использоваться для определения количества данных для отрисовки. Функция render_template внутри chart будет использовать шаблонизатор Jinja2 для генерации HTML.


Последние 2 строки позволяют нам запустить приложение из консоли на 5000 порту в режиме отладки. Никогда не используйте режим отладки в продакшене, для этого существуют WSGI-серверы проде Gunicorn.


Создайте папку templates внутри папки проекта. Внутри неё создайте файл chart.html. Он нужен функции chart из app.py, поэтому без него приложение не будет работать правильно. Заполните chart.html Jinja2-разметкой.


<!DOCTYPE html>
<html>
  <head>
    <title>Bar charts with Bokeh!</title>
  </head>
  <body>
    <h1>Bugs found over the past {{ bars_count }} days</h1>
  </body>
</html>

Заготовка chart.html будет показывать количество столбцов, переданное в функцию chart через URL.


Сообщение внутри тега h1 отвечает теме нашего приложения. Мы будем строить график количества багов, найденных систимой автоматического тестирования за каждый день.


Теперь мы можем протестировать наше приложение.


Убедитесь, что виртуальное окружение всё ещё активно и вы находитесь в папке с app.py. Запустите app.py с помощью команды python.


$(barchart) python app.py

Перейдите на localhost:5000/16/. Вы должны увидеть большое сообщение, которой меняется, когда вы меняете URL.



Наше приложение уже запускается, но пока не впечатляет. Пришло время добавить график.


Генерация столбчатой диаграммы


Нам нужно всего лишь добавить немного кода, который будет использовать Bokeh. Откройте app.py и добавьте в него строки сверху.


import random
from bokeh.models import (HoverTool, FactorRange, Plot, LinearAxis, Grid,
                          Range1d)
from bokeh.models.glyphs import VBar
from bokeh.plotting import figure
from bokeh.charts import Bar
from bokeh.embed import components
from bokeh.models.sources import ColumnDataSource
from flask import Flask, render_template

Остальная часть файла будет использовать Bokeh вместе с модулем random для генерации данных и столбчатой диаграммы.


Данные для диаграммы будут генерироваться заново при каждой перезагрузке страницы. В реальном приложении используйте более надёжный и полезный источник данных!


Продолжайте изменять app.py. Код после импортов должен выглядеть следующим образом.


app = Flask(__name__)

@app.route("/<int:bars_count>/")
def chart(bars_count):
    if bars_count <= 0:
        bars_count = 1

    data = {"days": [], "bugs": [], "costs": []}
    for i in range(1, bars_count + 1):
        data['days'].append(i)
        data['bugs'].append(random.randint(1,100))
        data['costs'].append(random.uniform(1.00, 1000.00))

    hover = create_hover_tool()
    plot = create_bar_chart(data, "Bugs found per day", "days",
                            "bugs", hover)
    script, div = components(plot)

    return render_template("chart.html", bars_count=bars_count,
                           the_div=div, the_script=script)

Функция chart сгенерирует списки с данными с помощью встроенного модуля random. chart вызывает 2 функции: create_hover_tool и create_bar_chart. Мы пока не написали эти функции, поэтому продолжим добавлять код после функции chart:


def create_hover_tool():
    # эту функцию мы напишем чуть позже
    return None

def create_bar_chart(data, title, x_name, y_name, hover_tool=None,
                     width=1200, height=300):
    """Создаёт столбчатую диаграмму.
        Принимает данные в виде словаря, подпись для графика,
        названия осей и шаблон подсказки при наведении.
    """
    source = ColumnDataSource(data)
    xdr = FactorRange(factors=data[x_name])
    ydr = Range1d(start=0,end=max(data[y_name])*1.5)

    tools = []
    if hover_tool:
        tools = [hover_tool,]

    plot = figure(title=title, x_range=xdr, y_range=ydr, plot_width=width,
                  plot_height=height, h_symmetry=False, v_symmetry=False,
                  min_border=0, toolbar_location="above", tools=tools,
                  responsive=True, outline_line_color="#666666")

    glyph = VBar(x=x_name, top=y_name, bottom=0, width=.8,
                 fill_color="#e12127")
    plot.add_glyph(source, glyph)

    xaxis = LinearAxis()
    yaxis = LinearAxis()

    plot.add_layout(Grid(dimension=0, ticker=xaxis.ticker))
    plot.add_layout(Grid(dimension=1, ticker=yaxis.ticker))
    plot.toolbar.logo = None
    plot.min_border_top = 0
    plot.xgrid.grid_line_color = None
    plot.ygrid.grid_line_color = "#999999"
    plot.yaxis.axis_label = "Bugs found"
    plot.ygrid.grid_line_alpha = 0.1
    plot.xaxis.axis_label = "Days after app deployment"
    plot.xaxis.major_label_orientation = 1
    return plot

Здесь много кода, с которым нужно разобраться. Функция create_hover_tool пока только возвращает None, потому что пока нам не нужно отображать подсказки при наведении.


Внутри функции create_bar_chart мы преобразуем сгенерированные данные в объект типа ColumnDataSource, который мы можем передать на вход функциям Bokeh для построения графика. Мы задаём диапазоны осей x и y.


Пока у нас не настроены подсказки при наведении, список tools пуст. Вся магия происходит в вызове функции figure. Мы передаём ей всё необходимое для построения графика: размер, панель инструментов, границы, настройки поведения графика при изменении размера окна браузера.


Мы создаём вертикальные столбцы с помощью класса VBar и добавляем их на график с помощью функции add_glyph, которая задаёт правила, по которым данные превращаются в столбцы.


Последние 2 строки изменяют оформление графика. Для примера я убрал логотип Bokeh при помощи plot.toolbar.logo = None и добавил подписи к обеим осям. Я рекомендую держать документацию bokeh.plotting перед глазами, чтобы знать, как можно кастомизировать визуализацию.


Нам нужно сделать всего пару изменений в файле templates/chart.html, чтобы отобразить график. Откройте файл и замените его содержимое следующим.


<!DOCTYPE html>
<html>
  <head>
    <title>Bar charts with Bokeh!</title>
    <link href="http://cdn.pydata.org/bokeh/release/bokeh-0.12.5.min.css" rel="stylesheet">
    <link href="http://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.0.min.css" rel="stylesheet">
  </head>
  <body>
    <h1>Bugs found over the past {{ bars_count }} days</h1>
    {{ the_div|safe }}
    <script src="http://cdn.pydata.org/bokeh/release/bokeh-0.12.5.min.js"></script>
    <script src="http://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.5.min.js"></script>
    {{ the_script|safe }}
  </body>
</html>

2 из 6 добавленных строк нужны для загрузки CSS-файлов Bokeh, ещё 2 для загрузки его скриптов, и ещё 2 — для генерации графика.


Всё готово, давайте проверим наше приложение. Flask должен автоматически перезгрузить приложение, когда вы сохраните изменения в app.py. Если вы остановили веб-сервер, вы можете снова его запустить командой python app.py.


Откройте в браузере localhost:5000/4/



Выглядит немного пусто, но мы можем изменить количество столбцов на 16, если перейдём на /localhost:5000/16/



Ещё в 4 раза больше...



Выглядит довольно неплохо. Но что, если мы добавим подсказки при наведении, чтобы можно было подробно изучить каждый столбец? Для этого изменим функцию create_hover_tool.


Добавление подсказок при наведении


Внутри app.py измените функцию create_hover_tool


def create_hover_tool():
    """Generates the HTML for the Bokeh's hover data tool on our graph."""
    hover_html = """
      <div>
        <span class="hover-tooltip">$x</span>
      </div>
      <div>
        <span class="hover-tooltip">@bugs bugs</span>
      </div>
      <div>
        <span class="hover-tooltip">$@costs{0.00}</span>
      </div>
    """
    return HoverTool(tooltips=hover_html)

Встраивание HTML в приложение на Python может показаться довольно странным, но именно так мы определяем вид подсказки. Мы используем $x, чтобы показать x-координату столбца, @bugs, чтобы показать поле "bugs" источника данных и $@costs{0.00}, чтобы показать поле "costs" как числ с 2 знаками после запятой.


Убедитесь, что вы заменили return None на return HoverTool(tooltips=hover_html).
Вернитесь в браузер и перезагрузить страницу localhost:5000/128/



Отличная работа! Попробуйте поиграть с количеством столбцов в URL и посмотрите как график будет выглядеть.


График выглядит заполненным примерно при 100 столбцах, но вы можете попробовать задать любое значение. На 50,000 получается грустная картина:



Да уж, похоже, нам нужно сделать ещё что-то, чтобы можно было отображать больше пары сотен столбцов за раз.


Что дальше?


Мы создали отличный график на Bokeh. Далее вы можете изменить цветовую схему, подключить другой источник данных, попробовать другие типы графиков или придумать, как отображать огромные объёмы данных.


С помощью Bokeh можно делать гораздо больше, так что стоит посмотреть официальную документацию, репозиторий на GitHub, страничку Bokeh на Full Stack Python.

Поделиться с друзьями
-->

Комментарии (8)


  1. zxweed
    12.06.2017 18:10

    100К столбиков оно сможет пережевать?


    1. ivlevdenis_ru
      12.06.2017 21:19

      Так для этого нужно интерполировать график.


    1. gsedometov
      12.06.2017 21:28

      Проверил — может. Правда, не очень быстро (около 20с на моей машине). С другой стороны, зачем нужно показывать одновременно 100К столбцов?


    1. ls1
      13.06.2017 08:36
      +1

      AFAIK у bokeh есть сервер для решения задач масштабирования в том числе


      Intelligent server-side downsampling of large datasets.


  1. zxweed
    12.06.2017 21:35

    показывать нужно меньше, но грузится все равно весь датасет. А если 10m точек — браузер помрёт?


    1. tomzarubin
      13.06.2017 13:54

      Можно и 100 млн., но зачем, если надо отдавать в браузер уже экстраполированные данные когда их больше 50 000?


  1. Prostakov_Alexey
    16.06.2017 21:42

    Попробовал у себя запустить примеры из статьи (включая окружение и версии библиотек). В FF все нормально, в IE 11 вместо графика на экран выдает ошибку: Bokeh Error. Недопустимый вызывающий объект. Не нашел в описании библиотеки с какими браузерами она совместима, если у кого-то опыт использования под IE?


    1. gsedometov
      16.06.2017 21:57

      а в консоль что-нибудь пишет?