<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0">

<channel>

<title>Блог об аналитике, визуализации данных, data science и BI, заметки с тегом: untappd</title>
<link>http://test.leftjoin.ru/tags/untappd/</link>
<description></description>
<generator>E2 (v3365; Aegea)</generator>

<item>
<title>Создаём дашборд на Bootstrap с нуля (Часть 1)</title>
<guid isPermaLink="false">55</guid>
<link>http://test.leftjoin.ru/all/untappd-bootstrap-dashboard-part-1/</link>
<comments>http://test.leftjoin.ru/all/untappd-bootstrap-dashboard-part-1/</comments>
<description>
&lt;p&gt;В прошлых материалах мы познакомились с фреймворком Plotly Dash, &lt;a href="http://test.leftjoin.ru/all/scatter-plot-untappd"&gt;научились строить scatter plots&lt;/a&gt; и &lt;a href="http://test.leftjoin.ru/all/plotly-russian-map/"&gt;визуализировать данные на карте&lt;/a&gt;. Сегодня мы попробуем объединить имеющиеся части в одном веб-приложении и расскажем как можно создать полноценный дашборд используя сетчатую структуру Bootstrap.&lt;br /&gt;
В этом нам поможет &lt;a href="https://dash-bootstrap-components.opensource.faculty.ai/"&gt;dash-bootstrap-components&lt;/a&gt;, эта библиотека позволяет строить дашборды по принципу  “plug-and-play”,  добавлять любые компоненты Bootstrap и стилизовать их используя грид-дизайн.&lt;/p&gt;
&lt;h2&gt;Подготовка макета&lt;/h2&gt;
&lt;p&gt;Перед созданием любого приложения, не только дашборда нам просто необходим план, черновой макет, следуя которому, мы бы видели общую картину и могли быстро внести корректировки в структуру. Для создания макета дашборда мы использовали приложение &lt;a href="https://app.diagrams.net/"&gt;draw.io&lt;/a&gt;, этот инструмент позволяет быстро создавать диаграммы, графики, блок-схемы, формы и т. д. Наш дашборд будет построен по следующему макету:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/1-10.png" width="927" height="1045" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Как и сам дашборд, шапка будет оформлена в главных цветах &lt;a href="https://untappd.com/"&gt;Untappd&lt;/a&gt; — белом и золотом. Ниже расположится раздел с пивоварнями, состоящий из scatter plot и панели с настройками. А в самом низу будет карта, показывающая средний рейтинг напитка по регионам России.&lt;/p&gt;
&lt;p&gt;Итак, приступим, для начала создадим файл application.py, в нем будут храниться все фронтенд элементы дашборда, и папку assets в той же директории, структура должна быть такой:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;- application.py
- assets/
    |-- typography.css
    |-- header.css
    |-- custom-script.js
    |-- image.png&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Импортируем нужные библиотеки и инициализируем приложение:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;import dash
import dash_bootstrap_components as dbc
import dash_html_components as html
import dash_core_components as dcc
import pandas as pd
from get_ratio_scatter_plot import get_plot
from get_russian_map import get_map
from clickhouse_driver import Client
from dash.dependencies import Input, Output

standard_BS = dbc.themes.BOOTSTRAP
app = dash.Dash(__name__, external_stylesheets=[standard_BS])&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Главные аргументы для app:&lt;br /&gt;
&lt;span class="inline-code"&gt;__name__&lt;/span&gt; — для активации cтатических элементов из папки assets (картинки, CSS и JS файлы)&lt;br /&gt;
&lt;span class="inline-code"&gt;external_stylesheets&lt;/span&gt; — внешний CSS для стилизации дашборда, здесь мы используем стандартный BootstrapCDN, однако вы можете создать свой или &lt;a href="https://www.bootstrapcdn.com/bootswatch/"&gt;воспользоваться одним из уже готовых стилей&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Включаем ещё  несколько параметров для работы с локальными файлами и подключаемся к ClickHouse:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;app.scripts.config.serve_locally = True
app.css.config.serve_locally = True

client = Client(host='',
                user='default',
                password='',
                port='9000',
                database='default')&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Добавим палитру цветов:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;colors = ['#ffcc00', 
          '#f5f2e8', 
          '#f8f3e3',
          '#ffffff', 
          ]&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Верстка макета&lt;/h2&gt;
&lt;p&gt;Все элементы дашборда будут помещены в Bootstrap Контейнер, который находится в блоке &amp;lt;div&amp;gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;- app 
    |-- div
     |-- container
      |-- logo&amp;amp;header
     |-- container
      |-- div
       |-- controls&amp;amp;scatter
       |-- map&lt;/code&gt;&lt;/pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;app.layout = html.Div(
                    [
                        dbc.Container(

                                         &amp;lt; шапка &amp;gt;
                         
                        dbc.Container(       
                            html.Div(
                                [
                        
                                    &amp;lt; основной код &amp;gt;
                        
                                ],
                            ),
                            fluid=False, style={'max-width': '1300px'},
                        ),
                    ],
                    style={'background-color': colors[1], 'font-family': 'Proxima Nova Bold'},
                )&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Здесь мы сразу задаем фиксированную ширину контейнера, цвет фона и стиль шрифта на странице, который берется из typography.css в папке assets. Стоит подробней остановится на первом элементе блока div, это и есть заголовок страницы, он включает в себя логотип Untappd:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;logo = html.Img(src=app.get_asset_url('logo.png'),
                        style={'width': &amp;quot;128px&amp;quot;, 'height': &amp;quot;128px&amp;quot;,
                        }, className='inline-image')&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;и главный заголовок:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;header = html.H3(&amp;quot;Статистика российских пивоварен в Untappd&amp;quot;, style={'text-transform': &amp;quot;uppercase&amp;quot;})&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Чтобы расположить эти два элемента на одной строке мы воспользовались Bootstrap Формами:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;logo_and_header = dbc.FormGroup(
        [
            logo,
            html.Div(
                [
                    header
                ],
                className=&amp;quot;p-5&amp;quot;
            )
        ],
        className='form-row',
)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В блоке html.Div параметр ’p-5’ позволяет добавить отступы и вертикально выровнять заголовок,  а ’form-row’ поставить logo и header в один ряд. На данном этапе шапка дашборда вылгядит следующим образом:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/2-10.png" width="1347" height="128" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Нам осталось выровнять их по центру и добавить красок. Для этого создаем отдельный контейнер, который будет состоять из одного ряда. В параметре &lt;span class="inline-code"&gt;className&lt;/span&gt; указываем &lt;span class="inline-code"&gt;’d-flex justify-content-center’&lt;/span&gt;, чтобы выровнять элементы контейнера по центру.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;dbc.Container(
                    dbc.Row(
                        [
                            dbc.Col(
                                html.Div(
                                    logo_and_header,
                                ),
                            ),
                        ],
                        style={'max-height': '128px',
                               'color': 'white',
                       }

                    ),
                    className='d-flex justify-content-center',
                    style={'max-width': '100%',
                           'background-color': colors[0]},
                ),&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;На данном этапе шапка дашборда готова:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/3-9.png" width="1343" height="118" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Далее в следующий Bootstrap Контейнер добавим первый подзаголовок:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;dbc.Container(
                    html.Div(
                        [
                            html.Br(),
                            html.H5(&amp;quot;Пивоварни&amp;quot;, style={'text-align':'center', 'text-transform': 'uppercase'}),
                            html.Hr(), # разделительная линия&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Сам дашборд будет состоять из Bootstrap Карт, это обеспечит структурированное расположение всех элементов, придаст каждому элементу четкие границы и сохранит white space. Давайте создадим один из элементов дашборда, панель с настройками:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;slider_day_values = [1, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
slider_top_breweries_values = [5, 25, 50, 75, 100, 125, 150, 175, 200]

controls = dbc.Card(
    [
       dbc.CardBody(
           [
               dbc.FormGroup(
                    [
                        dbc.Label(&amp;quot;Временной период&amp;quot;, style={'text-align': 'center', 'font-size': '100%', 'text-transform': 'uppercase'}),
                        dcc.Slider(
                            id='slider-day',
                            min=1,
                            max=100,
                            step=10,
                            value=100,
                            marks={i: i for i in slider_day_values}
                        ),
                    ], style={'text-align': 'center'}
               ),
               dbc.FormGroup(
                    [
                        dbc.Label(&amp;quot;Количество пивоварен&amp;quot;, style={'text-align': 'center', 'font-size': '100%', 'text-transform': 'uppercase'}),
                        dcc.Slider(
                            id='slider-top-breweries',
                            min=5,
                            max=200,
                            step=5,
                            value=200,
                            marks={i: i for i in slider_top_breweries_values}
                        ),
                    ], style={'text-align': 'center'}
               ),
           ],
       )
    ],
    style={'height': '32.7rem', 'background-color': colors[3]}
)&lt;/code&gt;&lt;/pre&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/4-5.png" width="357" height="179" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Панель включает два слайдера для управления scatter plot, они располагаются друг под другом в Bootstrap формах. Мы добавили слайдеры в один блок —  dbc.CardBody. остальные эдементы будут добавлены по такому же принципу, это позволяет поставить одинаковые  отступы со всех сторон . По умолчанию слайдеры оформлены в голубом цвете, для того чтобы изменить их стиль, воспользуйтесь файлом sliders.css, находящемся в папке assets.&lt;br /&gt;
Добавляем панель управления и scatter plot следующим блоком:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;dbc.Row(
                [
                    dbc.Col(controls, width={&amp;quot;size&amp;quot;: 4,
                                     &amp;quot;order&amp;quot;: 'first',
                                             &amp;quot;offset&amp;quot;: 0},
                     ),
                     dbc.Col(dbc.Card(
                                [
                                    dbc.CardBody(
                                        [
                                            html.H6(&amp;quot;Отношение количества отзывов к средней оценке пивоварни&amp;quot;,
                                                    className=&amp;quot;card-title&amp;quot;,
                                                    style={'text-transform': 'uppercase'}), 
                                            dcc.Graph(id='ratio-scatter-plot'),
                                        ],
                                    ),
                                ],
                                style={'background-color': colors[2], 'text-align':'center'}
                             ),
                     md=8),
                ],
                align=&amp;quot;start&amp;quot;,
                justify='center',
            ),
html.Br(),&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;И в конце страницы расположим карту:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;html.H5(&amp;quot;Заведения и регионы&amp;quot;, style={'text-align':'center', 'text-transform': 'uppercase',}),
                            html.Hr(), #разделительная линия
                            dbc.Row(
                                [
                                    dbc.Col(
                                        dbc.Card(
                                            [
                                                dbc.CardBody(
                                                    [
                                                        html.H6(&amp;quot;Средний рейтинг пива по регионам&amp;quot;,
                                                                className=&amp;quot;card-title&amp;quot;,
                                                                style={'text-transform': 'uppercase'},
                                                        ),  
                                                        dcc.Graph(figure=get_map())
                                                    ],
                                                ),
                                            ],
                                        style={'background-color': colors[2], 'text-align': 'center'}
                                        ),
                                md=12),
                                ]
                            ),
                            html.Br(),&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Callback-функции в Dash&lt;/h2&gt;
&lt;p&gt;Callback-функции позволяют сделать элементы дашборда интерактивными, если меняется входной элемент (Input), то и выход (Output) тоже изменится.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;@app.callback(
    Output('ratio-scatter-plot', 'figure'),
    [Input('slider-day', 'value'),
     Input('slider-top-breweries', 'value'),
     ]
)
def get_scatter_plots(n_days=100, top_n=200):
    if n_days == 100 and top_n == 200:
        df = pd.read_csv('data/ratio_scatter_plot.csv')
        return get_plot(n_days, top_n, df)
    else:
        return get_plot(n_days, top_n)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Входные/ выходные  (Input/Output) значения это, проще говоря, параметр value элемента с определенным id. Например, входное значение верхнего слайдера с id=’slider-day’, показывающего временной период по умолчанию 100.  При изменении этого значения функция, обернутая в декоратор будет вызвана автоматически и output на графике обновится. Больше примеров представлено &lt;a href="https://dash.plotly.com/basic-callbacks/на"&gt;сайте plotly&lt;/a&gt;.&lt;br /&gt;
Важный момент, чтобы scatter plot при загрузке страницы отображал данные нам нужно сперва считать их из сохраненного датафрейма в папке data (указать начальное состояние), иначе scatter plot будет пустым. В дальнейшем при изменении параметров слайдера данные будут загружаться из ClickHouse таблиц.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;div class="fotorama" data-width="831" data-ratio="1.6011560693642"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/5-7.png" width="831" height="519" alt="" /&gt;
&lt;img src="http://test.leftjoin.ru/pictures/6-6.png" width="831" height="519" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Далее добавим пару строк отвечающих за поднятие локального сервера и приложение готово к запуску:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;application = app.server

if __name__ == '__main__':
    application.run(debug=True, port=8000)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Теперь осталось только &lt;a href="http://test.leftjoin.ru/all/dashboard-python-2/"&gt;загрузить его на AWS с помощью BeansTalk&lt;/a&gt; и наш &lt;a href="http://unappd-part-1-ru.us-east-2.elasticbeanstalk.com/"&gt;дашборд на Bootstrap готов&lt;/a&gt;:&lt;/p&gt;
&lt;div class="embed-responsive embed-responsive-4by3" style="min-width:500"&gt;&lt;p&gt;&lt;iframe id="igraph" scrolling="yes" style="border:none;"seamless="seamless" src='http://unappd-part-1-ru.us-east-2.elasticbeanstalk.com/' height="1360px" width="1100px"&lt;/p&gt;
&lt;/iframe&gt;&lt;/div&gt;&lt;p&gt;Это была первая часть нашего выпуска, в следующей мы добавим больше новых bootstrap компонентов, callback-ов и расскажем про стилизацию таблиц.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/valiotti/leftjoin/tree/master/untappd_dashboard%20(part%201)/"&gt;Полный код проекта на GitHub&lt;/a&gt;&lt;/p&gt;
</description>
<pubDate>Thu, 13 Aug 2020 10:20:03 +0300</pubDate>
</item>

<item>
<title>Делаем дашборд с параметром на Python</title>
<guid isPermaLink="false">52</guid>
<link>http://test.leftjoin.ru/all/dashboard-python-1/</link>
<comments>http://test.leftjoin.ru/all/dashboard-python-1/</comments>
<description>
&lt;p&gt;В прошлом материале мы подготовили scatter plot, используя библиотеку plotly: он отображал отношение количества отзывов пивоварни к её рейтингу в социальной сети Untappd. Ещё мы добавили каждому маркеру характеристики: дату регистрации пивоварни и количество сортов пива в её ассортименте. Сегодня воспользуемся другим инструментом plotly — Dash, и построим дашборд с двумя параметрами для этого графика. Создадим новый файл — application.py, который будет импортировать функцию get_scatter_plot(n_days, top_n) из последнего материала.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;import dash
import dash_core_components as dcc
import dash_html_components as html
from get_plots import get_scatter_plot&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;После импорта библиотек загружаем css-стили и инициируем веб-приложение:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Опишем структуру дашборда:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;app.layout = html.Div(children=[
       html.Div([
           dcc.Graph(id='fig1'),
       ]) ,
       html.Div([
           html.H6('Временной период, дней'),
           dcc.Slider(
               id='slider-day1',
               min=0,
               max=100,
               step=1,
               value=30,
               marks={i: str(i) for i in range(0, 100, 10)}
           ),
           html.H6('Количество пивоварен в топе'),
           dcc.Slider(
               id='slider-top1',
               min=0,
               max=500,
               step=50,
               value=500,
               marks={i: str(i) for i in range(0, 500, 50)})
       ])
])&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Мы обозначили на панели график и два слайдера. У каждого слайдера есть свой идентификатор и параметры: минимальное значение, максимальное, шаг изменения, начальное значение. Так как данные из слайдеров будут передаваться в график, опишем callback для них: первый аргумент — Output — график, который будет изменяться, это наш вывод. Следующие два — Input — параметры, от которых график зависит.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;@app.callback(
   dash.dependencies.Output('fig1', 'figure'),
   [dash.dependencies.Input('slider-day1', 'value'),
    dash.dependencies.Input('slider-top1', 'value')])
def output_fig(n_days, top_n):
    fig = get_scatter_plot(n_days, top_n)
    return fig&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В конце файла добавим вызов локального сервера:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;if __name__ == '__main__':
   app.run_server(debug=True)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Теперь при запуске файла в терминале появится адрес локального сервера. Пройдя по нему, в браузере откроем наш интерактивный дашборд, который самостоятельно обновляется при изменении значений слайдеров.&lt;/p&gt;
&lt;div class="embed-responsive embed-responsive-4by3" style="min-width:500"&gt;&lt;iframe id="igraph" scrolling="no" style="border:none;"seamless="seamless" src="http://leftjoinbreweriesscatterplotrus-env.eba-bjmemw3u.us-east-1.elasticbeanstalk.com/" height="1100" width="800" &gt;&lt;/iframe&gt;
&lt;/div&gt;</description>
<pubDate>Wed, 15 Jul 2020 13:26:06 +0300</pubDate>
</item>

<item>
<title>Строим scatter plot по пивоварням Untappd</title>
<guid isPermaLink="false">51</guid>
<link>http://test.leftjoin.ru/all/scatter-plot-untappd/</link>
<comments>http://test.leftjoin.ru/all/scatter-plot-untappd/</comments>
<description>
&lt;p&gt;Сегодня построим scatter plot, который отобразит отношение количества отзывов российских пивоварен к их средней оценке за последние 30 дней. В качестве данных будем использовать чекины социальной сети Untappd, которые пользователи оставляют для оценки пива. Маркеры на графике будут иметь две характеристики: цвет и размер. Цвет будет зависеть от даты регистрации пивоварни на сервисе (то есть показывать сколько лет пивоварне в Untappd), а размер — от количества сортов пива в её ассортименте. Этот материал — первая часть цикла материалов, посвященных построению дашборда с библиотекой dash от plotly.&lt;/p&gt;
&lt;h2&gt;Пишем запрос к Clickhouse&lt;/h2&gt;
&lt;p&gt;Данные, по которым мы хотим построить дашборд для начала нужно обработать. Мы использовали открытые данные, собранные с сайта Untappd в материалах &lt;a href="http://test.leftjoin.ru/all/selenium-button/" class="nu"&gt;«&lt;u&gt;Обрабатываем нажатие кнопки в Selenium&lt;/u&gt;»&lt;/a&gt; и &lt;a href="http://test.leftjoin.ru/all/untappd-clickhouse-dictionaries/" class="nu"&gt;«&lt;u&gt;Использование словарей в Clickhouse на примере данных Untappd&lt;/u&gt;»&lt;/a&gt;.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;from datetime import datetime, timedelta
from clickhouse_driver import Client
import plotly.graph_objects as go
import pandas as pd
import numpy as np
client = Client(host='ec1-2-34-567-89.us-east-2.compute.amazonaws.com', user='default', password='', port='9000', database='default')&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;График будет строиться в функции &lt;span class="inline-code"&gt;get_scatter_plot(n_days, top_n)&lt;/span&gt;. Первый аргумент будет отвечать за временной период, который нужно обработать. Второй — какое количество пивоварен из топа отобразить на графике. Для начала напишем SQL-запрос, который возьмёт данные из таблицы Clickhouse и посчитает Brewery Pure Average. Для каждой пивоварни на &lt;a href="https://help.untappd.com/hc/en-us/articles/360034136372-How-are-ratings-determined-on-Untappd-"&gt;сервисе он считается так&lt;/a&gt;: умножаем рейтинг сорта пива на количество оценок этого сорта и делим на общее число оценок пивоварни. Ещё запрос возьмёт наименование пивоварни и количество сортов пива у пивоварни из словаря, с которым мы работали ранее: при помощи функции &lt;span class="inline-code"&gt;dictGet&lt;/span&gt; обратимся к нему прямо в запросе и возьмём нужные колонки. Зададим ограничение: нас интересуют только те пивоварни, у которых Brewery Pure Average отличен от нуля, а количество отзывов более 100.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;brewery_pure_average = client.execute(f&amp;quot;&amp;quot;&amp;quot;
SELECT
       t1.brewery_id,
       sum(t1.beer_pure_average_mult_count / t2.count_for_that_brewery) AS brewery_pure_average,
       t2.count_for_that_brewery,
       dictGet('breweries', 'brewery_name', toUInt64(t1.brewery_id)),
       dictGet('breweries', 'beer_count', toUInt64(t1.brewery_id)),
       t3.stats_age_on_service / 365
   FROM
   (
       SELECT
           beer_id,
           brewery_id,
           sum(rating_score) AS beer_pure_average_mult_count
       FROM beer_reviews
       WHERE created_at &amp;gt;= today()-{n_days}
       GROUP BY
           beer_id,
           brewery_id
   ) AS t1
   ANY LEFT JOIN
   (
       SELECT
           brewery_id,
           count(rating_score) AS count_for_that_brewery
       FROM beer_reviews
       WHERE created_at &amp;gt;= today()-{n_days}
       GROUP BY brewery_id
   ) AS t2 ON t1.brewery_id = t2.brewery_id
   ANY LEFT JOIN
   (
       SELECT
           brewery_id,
           stats_age_on_service
       FROM brewery_info
   ) AS t3 ON t1.brewery_id = t3.brewery_id
   GROUP BY
       t1.brewery_id,
       t2.count_for_that_brewery,
       t3.stats_age_on_service
   HAVING t2.count_for_that_brewery &amp;gt;= 150
   ORDER BY brewery_pure_average
   LIMIT {top_n}
    &amp;quot;&amp;quot;&amp;quot;)

scatter_plot_df_with_age = pd.DataFrame(brewery_pure_average, columns=['brewery_id', 'brewery_pure_average', 'rating_count', 'brewery_name', 'beer_count'])&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Обрабатываем значения из DataFrame&lt;/h2&gt;
&lt;p&gt;Добавим на график две пунктирные линии: они будут проходить в медианных значениях каждой оси. Так мы будем знать, какие пивоварни находятся выше медианного значения. Лучшими будут те, что находятся в верхнем правом квадрате.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;dict_list = []
dict_list.append(dict(type=&amp;quot;line&amp;quot;,
                     line=dict(
                         color=&amp;quot;#666666&amp;quot;,
                         dash=&amp;quot;dot&amp;quot;),
                     x0=0,
                     y0=np.median(scatter_plot_df_with_age.brewery_pure_average),
                     x1=7000,
                     y1=np.median(scatter_plot_df_with_age.brewery_pure_average),
                     line_width=1,
                     layer=&amp;quot;below&amp;quot;))
dict_list.append(dict(type=&amp;quot;line&amp;quot;,
                     line=dict(
                         color=&amp;quot;#666666&amp;quot;,
                         dash=&amp;quot;dot&amp;quot;),
                     x0=np.median(scatter_plot_df_with_age.rating_count),
                     y0=0,
                     x1=np.median(scatter_plot_df_with_age.rating_count),
                     y1=5,
                     line_width=1,
                     layer=&amp;quot;below&amp;quot;))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Добавим аннотации: они будут сообщать пользователю медианные значения.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;annotations_list = []
annotations_list.append(
    dict(
        x=8000,
        y=np.median(scatter_plot_df_with_age.brewery_pure_average) - 0.1,
        xref=&amp;quot;x&amp;quot;,
        yref=&amp;quot;y&amp;quot;,
        text=f&amp;quot;Медианное значение: {round(np.median(scatter_plot_df_with_age.brewery_pure_average), 2)}&amp;quot;,
        showarrow=False,
        font={
            'family':'Roboto, light',
            'color':'#666666',
            'size':12
        }
    )
)
annotations_list.append(
    dict(
        x=np.median(scatter_plot_df_with_age.rating_count) + 180,
        y=0.8,
        xref=&amp;quot;x&amp;quot;,
        yref=&amp;quot;y&amp;quot;,
        text=f&amp;quot;Медианное значение: {round(np.median(scatter_plot_df_with_age.rating_count), 2)}&amp;quot;,
        showarrow=False,
        font={
            'family':'Roboto, light',
            'color':'#666666',
            'size':12
        },
        textangle=-90
    )
)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Прибавим графику информативности: поделим пивоварни на 4 группы по сортам пива. В первой группе будут пивоварни, у которых менее 10 сортов пива, во второй 10 — 30 сортов, в третьей 30 — 50 и в четвертой те, у кого сортов более 50. Значения списка &lt;span class="inline-code"&gt;bucket_beer_count&lt;/span&gt; — размеры маркеров.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;bucket_beer_count = []
for beer_count in scatter_plot_df_with_age.beer_count:
   if beer_count &amp;lt; 10:
       bucket_beer_count.append(7)
   elif 10 &amp;lt;= beer_count &amp;lt;= 30:
       bucket_beer_count.append(9)
   elif 31 &amp;lt;= beer_count &amp;lt;= 50:
       bucket_beer_count.append(11)
   else:
       bucket_beer_count.append(13)
scatter_plot_df_with_age['bucket_beer_count'] = bucket_beer_count&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Следом поделим график ещё на четыре группы: теперь уже по возрасту.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;bucket_age = []
for age in scatter_plot_df_with_age.age_on_service:
   if age &amp;lt; 4:
       bucket_age.append(0)
   elif 4 &amp;lt;= age &amp;lt;= 6:
       bucket_age.append(1)
   elif 6 &amp;lt; age &amp;lt; 8:
       bucket_age.append(2)
   else:
       bucket_age.append(3)
scatter_plot_df_with_age['bucket_age'] = bucket_age&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Разделим один DataFrame на четыре, чтобы по каждому построить отдельный scatter plot со своим цветом и размером.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;scatter_plot_df_0 = scatter_plot_df[scatter_plot_df.bucket == 0]
scatter_plot_df_1 = scatter_plot_df[scatter_plot_df.bucket == 1]
scatter_plot_df_2 = scatter_plot_df[scatter_plot_df.bucket == 2]
scatter_plot_df_3 = scatter_plot_df[scatter_plot_df.bucket == 3]&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Описываем график&lt;/h2&gt;
&lt;p&gt;Построим график: поочередно добавим четыре группы пивоварен. Для каждой зададим своё имя и цвет маркера, название, прозрачность и текст.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;fig = go.Figure()
fig.add_trace(go.Scatter(
    x=scatter_plot_df_0.rating_count,
    y=scatter_plot_df_0.brewery_pure_average,
    name='&amp;lt; 4',
    mode='markers',
    opacity=0.85,
    text=scatter_plot_df_0.name_count,
    marker_color='rgb(114, 183, 178)',
    marker_size=scatter_plot_df_0.bucket_beer_count,
    textfont={&amp;quot;family&amp;quot;:&amp;quot;Roboto, light&amp;quot;,
              &amp;quot;color&amp;quot;:&amp;quot;black&amp;quot;
             }
))

fig.add_trace(go.Scatter(
    x=scatter_plot_df_1.rating_count,
    y=scatter_plot_df_1.brewery_pure_average,
    name='4 – 6',
    mode='markers',
    opacity=0.85,
    marker_color='rgb(76, 120, 168)',
    text=scatter_plot_df_1.name_count,
    marker_size=scatter_plot_df_1.bucket_beer_count,
    textfont={&amp;quot;family&amp;quot;:&amp;quot;Roboto, light&amp;quot;,
              &amp;quot;color&amp;quot;:&amp;quot;black&amp;quot;
             }
))

fig.add_trace(go.Scatter(
    x=scatter_plot_df_2.rating_count,
    y=scatter_plot_df_2.brewery_pure_average,
    name='6 – 8',
    mode='markers',
    opacity=0.85,
    marker_color='rgb(245, 133, 23)',
    text=scatter_plot_df_2.name_count,
    marker_size=scatter_plot_df_2.bucket_beer_count,
    textfont={&amp;quot;family&amp;quot;:&amp;quot;Roboto, light&amp;quot;,
              &amp;quot;color&amp;quot;:&amp;quot;black&amp;quot;
             }
))

fig.add_trace(go.Scatter(
    x=scatter_plot_df_3.rating_count,
    y=scatter_plot_df_3.brewery_pure_average,
    name='8+',
    mode='markers',
    opacity=0.85,
    marker_color='rgb(228, 87, 86)',
    text=scatter_plot_df_3.name_count,
    marker_size=scatter_plot_df_3.bucket_beer_count,
    textfont={&amp;quot;family&amp;quot;:&amp;quot;Roboto, light&amp;quot;,
              &amp;quot;color&amp;quot;:&amp;quot;black&amp;quot;
             }
))

fig.update_layout(
    title=f&amp;quot;Отношение количества отзывов к средней оценке пивоварни&amp;lt;br&amp;gt;за последние {n_days} дней, топ-{top_n} пивоварен&amp;quot;,
    font={
            'family':'Roboto, light',
            'color':'black',
            'size':14
        },
    plot_bgcolor='rgba(0,0,0,0)',
    yaxis_title=&amp;quot;Средняя оценка&amp;quot;,
    xaxis_title=&amp;quot;Количество отзывов&amp;quot;,
    legend_title_text='Возраст пивоварни&amp;lt;br&amp;gt;на Untappd, лет:',
    height=750,
    shapes=dict_list,
    annotations=annotations_list
)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Получили такой график. Каждый маркер — отдельная пивоварня. Цвет характеризует количество сортов пива этой пивоварни, а при наведении увидим среднюю оценку по данным за последние 30 дней, количество отзывов, название и количество сортов пива у пивоварни. Две пунктирные линии проходят в соответствующих медианных значениях, рассчитанных модулем numpy: пивоварни, оказавшиеся в верхнем правом квадрате — самые успешные. В следующем материале построим дашборд, и сделаем количество последних дней и количество пивоварен в топе динамически изменяемым параметром.&lt;/p&gt;
&lt;p&gt;&lt;details&gt;&lt;br /&gt;
&lt;summary&gt;&lt;span style="color:#7ea9b8"&gt;Код функции get_scatter_plot&lt;/span&gt;&lt;/summary&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def get_scatter_plot(n_days, top_n):
    brewery_pure_average = client.execute(f'''
    SELECT 
        t1.brewery_id, 
        sum(t1.beer_pure_average_mult_count / t2.count_for_that_brewery) AS brewery_pure_average, 
        t2.count_for_that_brewery, 
        dictGet('breweries', 'brewery_name', toUInt64(t1.brewery_id)), 
        dictGet('breweries', 'beer_count', toUInt64(t1.brewery_id)),
        t3.stats_age_on_service / 365

    FROM 
    (
        SELECT 
            beer_id, 
            brewery_id, 
            sum(rating_score) AS beer_pure_average_mult_count
        FROM beer_reviews
        WHERE created_at &amp;gt;= today()-{n_days}
        GROUP BY 
            beer_id, 
            brewery_id
    ) AS t1
    ANY LEFT JOIN 
    (
        SELECT 
            brewery_id, 
            count(rating_score) AS count_for_that_brewery
        FROM beer_reviews
        WHERE created_at &amp;gt;= today()-{n_days}
        GROUP BY brewery_id
    ) AS t2 ON t1.brewery_id = t2.brewery_id
    ANY LEFT JOIN
    (
        SELECT
            brewery_id,
            stats_age_on_service
        FROM brewery_info_new
    ) AS t3 ON t1.brewery_id = t3.brewery_id
    GROUP BY 
        t1.brewery_id, 
        t2.count_for_that_brewery,
        t3.stats_age_on_service
    HAVING t2.count_for_that_brewery &amp;gt;= 150
    ORDER BY brewery_pure_average
    LIMIT {top_n}
    ''')

    scatter_plot_df_with_age = pd.DataFrame(brewery_pure_average, columns=['brewery_id', 'brewery_pure_average', 'rating_count', 'brewery_name', 'beer_count', 'age_on_service'])
    brewery_name_and_beer_count = []
    for name, beer_count in zip(scatter_plot_df_with_age.brewery_name, scatter_plot_df_with_age.beer_count):
        brewery_name_and_beer_count.append(f'{name},&amp;lt;br&amp;gt;количество сортов пива: {beer_count}')
    scatter_plot_df_with_age['name_count'] = brewery_name_and_beer_count
    dict_list = []
    dict_list.append(dict(type=&amp;quot;line&amp;quot;,
        line=dict(
             color=&amp;quot;#666666&amp;quot;,
             dash=&amp;quot;dot&amp;quot;),
        x0=0,
        y0=np.median(scatter_plot_df_with_age.brewery_pure_average),
        x1=9000,
        y1=np.median(scatter_plot_df_with_age.brewery_pure_average),
        line_width=1,
        layer=&amp;quot;below&amp;quot;))
    dict_list.append(dict(type=&amp;quot;line&amp;quot;,
        line=dict(
             color=&amp;quot;#666666&amp;quot;,
             dash=&amp;quot;dot&amp;quot;),
        x0=np.median(scatter_plot_df_with_age.rating_count),
        y0=0,
        x1=np.median(scatter_plot_df_with_age.rating_count),
        y1=5,
        line_width=1,
        layer=&amp;quot;below&amp;quot;))
    annotations_list = []
    annotations_list.append(
        dict(
            x=8000,
            y=np.median(scatter_plot_df_with_age.brewery_pure_average) - 0.1,
            xref=&amp;quot;x&amp;quot;,
            yref=&amp;quot;y&amp;quot;,
            text=f&amp;quot;Медианное значение: {round(np.median(scatter_plot_df_with_age.brewery_pure_average), 2)}&amp;quot;,
            showarrow=False,
            font={
                'family':'Roboto, light',
                'color':'#666666',
                'size':12
            }
        )
    )
    annotations_list.append(
        dict(
            x=np.median(scatter_plot_df_with_age.rating_count) + 180,
            y=0.8,
            xref=&amp;quot;x&amp;quot;,
            yref=&amp;quot;y&amp;quot;,
            text=f&amp;quot;Медианное значение: {round(np.median(scatter_plot_df_with_age.rating_count), 2)}&amp;quot;,
            showarrow=False,
            font={
                'family':'Roboto, light',
                'color':'#666666',
                'size':12
            },
            textangle=-90
        )
    )
    bucket = []
    for beer_count in scatter_plot_df_with_age.beer_count:
        if beer_count &amp;lt; 10:
            bucket.append(7)
        elif 10 &amp;lt;= beer_count &amp;lt;= 30:
            bucket.append(9)
        elif 31 &amp;lt;= beer_count &amp;lt;= 50:
            bucket.append(11)
        else:
            bucket.append(13)
    scatter_plot_df_with_age['bucket_beer_count'] = bucket
    bucket_age = []
    for age in scatter_plot_df_with_age.age_on_service:
        if age &amp;lt; 4:
            bucket_age.append(0)
        elif 4 &amp;lt;= age &amp;lt;= 6:
            bucket_age.append(1)
        elif 6 &amp;lt; age &amp;lt; 8:
            bucket_age.append(2)
        else:
            bucket_age.append(3)
    scatter_plot_df_with_age['bucket_age'] = bucket_age
    scatter_plot_df_0 = scatter_plot_df_with_age[(scatter_plot_df_with_age.bucket_age == 0)]
    scatter_plot_df_1 = scatter_plot_df_with_age[scatter_plot_df_with_age.bucket_age == 1]
    scatter_plot_df_2 = scatter_plot_df_with_age[scatter_plot_df_with_age.bucket_age == 2]
    scatter_plot_df_3 = scatter_plot_df_with_age[scatter_plot_df_with_age.bucket_age == 3]
    
    fig = go.Figure()
    fig.add_trace(go.Scatter(
        x=scatter_plot_df_0.rating_count,
        y=scatter_plot_df_0.brewery_pure_average,
        name='&amp;lt; 4',
        mode='markers',
        opacity=0.85,
        text=scatter_plot_df_0.name_count,
        marker_color='rgb(114, 183, 178)',
        marker_size=scatter_plot_df_0.bucket_beer_count,
        textfont={&amp;quot;family&amp;quot;:&amp;quot;Roboto, light&amp;quot;,
                  &amp;quot;color&amp;quot;:&amp;quot;black&amp;quot;
                 }
    ))

    fig.add_trace(go.Scatter(
        x=scatter_plot_df_1.rating_count,
        y=scatter_plot_df_1.brewery_pure_average,
        name='4 – 6',
        mode='markers',
        opacity=0.85,
        marker_color='rgb(76, 120, 168)',
        text=scatter_plot_df_1.name_count,
        marker_size=scatter_plot_df_1.bucket_beer_count,
        textfont={&amp;quot;family&amp;quot;:&amp;quot;Roboto, light&amp;quot;,
                  &amp;quot;color&amp;quot;:&amp;quot;black&amp;quot;
                 }
    ))

    fig.add_trace(go.Scatter(
        x=scatter_plot_df_2.rating_count,
        y=scatter_plot_df_2.brewery_pure_average,
        name='6 – 8',
        mode='markers',
        opacity=0.85,
        marker_color='rgb(245, 133, 23)',
        text=scatter_plot_df_2.name_count,
        marker_size=scatter_plot_df_2.bucket_beer_count,
        textfont={&amp;quot;family&amp;quot;:&amp;quot;Roboto, light&amp;quot;,
                  &amp;quot;color&amp;quot;:&amp;quot;black&amp;quot;
                 }
    ))

    fig.add_trace(go.Scatter(
        x=scatter_plot_df_3.rating_count,
        y=scatter_plot_df_3.brewery_pure_average,
        name='8+',
        mode='markers',
        opacity=0.85,
        marker_color='rgb(228, 87, 86)',
        text=scatter_plot_df_3.name_count,
        marker_size=scatter_plot_df_3.bucket_beer_count,
        textfont={&amp;quot;family&amp;quot;:&amp;quot;Roboto, light&amp;quot;,
                  &amp;quot;color&amp;quot;:&amp;quot;black&amp;quot;
                 }
    ))

    fig.update_layout(
        title=f&amp;quot;Отношение количества отзывов к средней оценке пивоварни&amp;lt;br&amp;gt;за последние {n_days} дней, топ-{top_n} пивоварен&amp;quot;,
        font={
                'family':'Roboto, light',
                'color':'black',
                'size':14
            },
        plot_bgcolor='rgba(0,0,0,0)',
        yaxis_title=&amp;quot;Средняя оценка&amp;quot;,
        xaxis_title=&amp;quot;Количество отзывов&amp;quot;,
        legend_title_text='Возраст пивоварни&amp;lt;br&amp;gt;на Untappd, лет:',
        height=750,
        shapes=dict_list,
        annotations=annotations_list
    )
    fig.show()
    return fig&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;div class="embed-responsive embed-responsive-4by3" style="min-width:500"&gt;&lt;iframe id="igraph" scrolling="no" style="border:none;"seamless="seamless" src="https://plotly.com/~Elisejj/41.embed?showlink=false" height="800" width="800" &gt;&lt;/iframe&gt;
&lt;/div&gt;</description>
<pubDate>Fri, 10 Jul 2020 15:14:22 +0300</pubDate>
</item>


</channel>
</rss>