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

<channel>

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

<item>
<title>Проблема Roistat как готового решения для аналитики</title>
<guid isPermaLink="false">149</guid>
<link>http://www.test.leftjoin.ru/all/problema-roistat-kak-gotovogo-resheniya-dlya-analitiki/</link>
<comments>http://www.test.leftjoin.ru/all/problema-roistat-kak-gotovogo-resheniya-dlya-analitiki/</comments>
<description>
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/unnamed-1.png  border=“0” width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;Нередко компании, которые обращаются к нам за системой сквозной аналитики, уже используют какие-то инструменты для работы с данными. Иногда это «коробочные» решения. Сразу отказываться от них в пользу кастома не обязательно — на старте их можно интегрировать в новую систему, чтобы сэкономить время и деньги на разработку. Но в этом случае надо быть готовым к тому, что готовые решения не гарантируют точность данных на том же уровне, что специально построенная под вас система аналитики. Именно с этой проблемой мы столкнулись, когда начали строить аналитику для стартапа Refocus: до нас они использовали Roistat, но ошибки в его работе вынудили нас отказаться от него раньше, чем мы планировали.&lt;/p&gt;
&lt;p&gt;Способов построить сквозную аналитику, не обращаясь к готовым решениям, немало: можно собрать in-house отдел аналитики или найти специалистов на аутсорсе.&lt;/p&gt;
&lt;p&gt;При этом нужно учитывать, что работа целой команды аналитики может стоить порядка 5-7 тысяч евро или 500-700 тысяч рублей в месяц. И это не предел — все зависит от размеров компании-заказчика, состояния аналитики до начала работ и других факторов.&lt;/p&gt;
&lt;p&gt;На начальном этапе для многих компаний, у которых объем данных небольшой, такие расходы неоправданы. Из-за этого они часто предпочитают «коробочные» продукты — например, тот же &lt;b&gt;Roistat.&lt;/b&gt; Это самое популярное решение на рынке, которое предлагает премиум-тариф с максимумом функционала и полной поддержкой от 1500 евро / 150 тысяч рублей в месяц. Базовые тарифы у них и того дешевле. У этого инструмента больше 200 встроенных интеграций, и не только с популярными Google Analytics или Яндекс Директ, но и с целым списком нишевых кабинетов и систем, о которых вы даже не слышали.&lt;/p&gt;
&lt;p&gt;После настройки интеграций с вашими источниками Roistat позволит создать дашборды за пару кликов, а сверху предложит автоматизацию маркетинга и лидогенерации. Конкуренты Roistat вроде &lt;b&gt;OWOX BI&lt;/b&gt; или &lt;b&gt;Alytics&lt;/b&gt; также предоставляют базовые возможности для аналитики и визуализации. В общем, недорогих решений под любые потребности много.&lt;/p&gt;
&lt;p&gt;Хотя “коробка” гораздо лучше бесконечных таблиц и поначалу хорошо оптимизирует работу с данными, она далеко не идеальна. С точки зрения эксперта все минусы универсальных решений видны как на ладони:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;не заглянешь под капот, если что-то идет не так;&lt;/li&gt;
&lt;li&gt;их система интеграций может не включать нужных источников;&lt;/li&gt;
&lt;li&gt;интеграции могут устаревать, создавая ошибки в выгрузке данных.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Кроме того, у вендоров таких готовых решений почти никогда нет ресурсов и возможностей, чтобы подогнать его под специфические требования каждого клиента. Более того, чаще такие “кастомные” требования многократно превосходят стоимость самой коробки (в этом суть готового решения). Если что-то не так на бэке у юзера, если меняется воронка в CRM, если нужно настроить кастомные интеграции — велики шансы, что за это придется доплатить немаленькие деньги, если вендор вообще согласится реализовать эти “специфичные требования”.&lt;/p&gt;
&lt;p&gt;На одном из наших проектов нам довелось поработать именно с такой ситуацией: Roistat не смог обеспечить точность данных и интеграций со всеми нужными источниками, и с этим пришлось разбираться самой компании — и нам.&lt;/p&gt;
&lt;h2&gt;Как Refocus работал с аналитикой&lt;/h2&gt;
&lt;p&gt;Один из наших заказчиков,  который строил аналитику на самых ранних этапах — EdTech-стартап Refocus. Неудивительно, что когда мы пришли создавать для них кастомную систему, они уже работали с Roistat.&lt;/p&gt;
&lt;p&gt;Почему Refocus выбрали именно его? Во-первых, он доступен на русском языке, и это было удобно для команды, в которой много русскоязычных сотрудников. Во-вторых, им нужна была именно система сквозной аналитики, а не CRM с функцией автоматического построения графиков, поэтому альтернативы вроде HubSpot не подходили. В-третьих, все базовые потребности компании он удовлетворял за относительно небольшую сумму. Ну и в-четвертых — многие сотрудники из команды Refocus уже имели опыт работы с Roistat, так что для них это был удобный и знакомый инструмент.&lt;/p&gt;
&lt;p&gt;Roistat тянул данные из двух основных рекламных кабинетов — &lt;b&gt;Google и Facebook&lt;/b&gt;*. В нем трекались все базовые метрики — бюджеты, показы, клики, конверсия — и отражались на автоматически созданных внутри программы дашбордах.&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/unnamed-(2).png border=“0” width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;К сожалению, скрины дашбордов из Roistat у нас не сохранились, но мы нашли для вас пример в интернете — выглядели они примерно так.&lt;/p&gt;
&lt;p&gt;Там даже присутствовал свой пользовательский идентификатор — когда мы вводили общий айди студентов, мы думали попробовать использовать его для мэтчинга персональных данных. На практике оказалось, что сопоставление по нему проходит очень неточно, и эту идею оставили.&lt;/p&gt;
&lt;p&gt;Стартап рос, число требований и людей увеличилось, поэтому нас позвали  строить для ребят кастомную аналитику и более подробные дашборды, и подключились мы.&lt;/p&gt;
&lt;h2&gt;Как обнаружилась проблема&lt;/h2&gt;
&lt;p&gt;Мы провели аудит существующей системы, получили доступы к нужным ресурсам и начали выстраивать инфраструктуру и готовить первый дашборд — маркетинговый, самый актуальный для компании на тот момент.&lt;/p&gt;
&lt;p&gt;Откуда мы тянули данные для этого дашборда? Конечно, из Roistat, ведь именно там уже лежали готовые маркетинговые показатели. Настройка хранилища и выгрузка данных из источников, которые Roistat не трекал, еще не была закончена, а смотреть на классные, качественные дашборды хотелось уже сейчас.&lt;/p&gt;
&lt;p&gt;Например, так выглядел код для выгрузки данных о затратах на рекламу через API:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def get_costs(channel, channel_level, date_from, date_to, source):
         
     data = '{&amp;quot;dimensions&amp;quot;: [&amp;quot;marker_level_2&amp;quot;, &amp;quot;marker_level_3&amp;quot;, &amp;quot;marker_level_4&amp;quot;],\
     &amp;quot;metrics&amp;quot;:[&amp;quot;visitsCost&amp;quot;, &amp;quot;impressions&amp;quot;, &amp;quot;visits&amp;quot;],\
     &amp;quot;period&amp;quot;:{&amp;quot;from&amp;quot;:&amp;quot;' + date_from + '&amp;quot;,&amp;quot;to&amp;quot;:&amp;quot;' + date_to + '&amp;quot;},\
     &amp;quot;filters&amp;quot;:[{&amp;quot;field&amp;quot;:&amp;quot;' + channel_level + '&amp;quot;, &amp;quot;operation&amp;quot;:&amp;quot;=&amp;quot;, &amp;quot;value&amp;quot;:&amp;quot;' + channel + '&amp;quot;}],\
     &amp;quot;interval&amp;quot;: &amp;quot;1d&amp;quot;}'
     columns=['dimensions.marker_level_2.value',
             'dimensions.marker_level_2.title',
             'dimensions.marker_level_3.value',
             'dimensions.marker_level_3.title',
             'dimensions.marker_level_4.value',
             'dimensions.marker_level_4.title',
             'dateFrom'
             ]
     headers = {'content-type': 'application/json'}

     response = requests.request(&amp;quot;POST&amp;quot;, url, data=data, headers=headers)
     df = response.json()['data']

     l = []
     for i in range(len(df)):
         if df[i]['items'] != []:
             l.append(df[i])
     if len(l) &amp;gt; 0:
         res1 = pd.json_normalize(l, 'items', ['dateFrom'])[columns]
         res2 = pd.DataFrame()
         q = pd.json_normalize(l, ['items', 'metrics'])
         for x in [&amp;quot;visitsCost&amp;quot;, &amp;quot;impressions&amp;quot;, &amp;quot;visits&amp;quot;]:
             res2[x] = q.query('metric_name == @x')['value'].values
         res = pd.concat([res1, res2], axis=1)
         res['source'] = source
         return res
     else:
         return pd.DataFrame()&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Подробнее о &lt;tt&gt;marker_levels&lt;/tt&gt; можно прочитать &lt;a href="https://help-en.roistat.com/API/methods/analytics/#sample-report"&gt;здесь&lt;/a&gt; в документации API Roistat.&lt;/p&gt;
&lt;p&gt;Когда Refocus посмотрели на первую версию дашборда, они заметили, что цифры не сходятся с их реальными бюджетами, кликами, конверсией в рекламных кабинетах.&lt;/p&gt;
&lt;p&gt;Мы начали искать ошибку с нашей стороны, но убедились, что наш код работает исправно и данные в нашем дашборде один в один совпадают с цифрами в Roistat. Никаких сбоев между источником и дашбордом не происходило.&lt;/p&gt;
&lt;p&gt;Дело в том, что пока компания смотрела только на визуализации Roistat, расхождения в данных не бросались в глаза. А наш дашборд содержал несколько новых графиков, более подробных, чем мог предложить Roistat. Когда Refocus стали сравнивать детали по разным срезам, на которые Roistat не позволял посмотреть, расхождения стали очевидны.&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/unnamed-(1).png border=“0” width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;У нас дашборд был более гибкий — можно было отфильтровать данные по разным важным срезам, более тонко настроить гранулярность. И благодаря этому ошибки в метриках стали заметны.&lt;/p&gt;
&lt;p&gt;Об этом мы и сообщили коллегам, продемонстрировав совпадение. Как раз в этот момент сыграл роль недостаток закрытой системы — посмотреть внутрь Roistat и найти, где именно происходит ошибка, не могли ни мы, ни бэкенд-специалисты Refocus.&lt;/p&gt;
&lt;p&gt;Разумеется, кастомная система уже предполагала отключение этого источника в будущем — зачем платный посредник, если мы все равно будем грузить данные напрямую и из рекламных кабинетов, и из кучи других источников?&lt;/p&gt;
&lt;p&gt;После обнаружения ошибки стало ясно, что затягивать с этим не стоит, если мы хотим предоставить клиенту чистые данные.&lt;/p&gt;
&lt;p&gt;Полностью отказаться от Roistat сразу не получилось, поэтому мы добавили на дашборд фильтр &lt;tt&gt;“match with Roistat”.&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;На нем мы отмечали количество рекламных кампаний, данные по которым расходятся с показателями на нашем дашборде и на Roistat’овском.&lt;/p&gt;
&lt;h2&gt;Как мы наконец заменили Roistat на кастомную систему и уточнили данные о воронках&lt;/h2&gt;
&lt;p&gt;Мы сразу написали несколько скриптов для выгрузки тех же самых данных из GA4 и Facebook Ads* через API.&lt;br /&gt;
Например, так данные о рекламных показах, затратах и кликах  за день в разрезе {campaign, adset, adname} выгружались из кабинета GA4:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def get_google_costs(date_start, customer_id):

    request = RunReportRequest(
        property = f&amp;quot;properties/{property_id}&amp;quot;,
        dimensions = [Dimension(name = &amp;quot;date&amp;quot;),
                     Dimension(name = &amp;quot;sessionGoogleAdsCustomerId&amp;quot;),
                     Dimension(name = &amp;quot;sessionGoogleAdsCampaignId&amp;quot;),
                     Dimension(name = &amp;quot;sessionGoogleAdsCampaignName&amp;quot;),
                     Dimension(name = &amp;quot;sessionGoogleAdsAdGroupId&amp;quot;),
                     Dimension(name = &amp;quot;sessionGoogleAdsAdGroupName&amp;quot;),
                     Dimension(name = &amp;quot;sessionGoogleAdsCreativeId&amp;quot;)],
        metrics = [Metric(name=&amp;quot;advertiserAdCost&amp;quot;),
                   Metric(name=&amp;quot;advertiserAdImpressions&amp;quot;),
                   Metric(name='advertiserAdClicks')],
        date_ranges = [DateRange(start_date = date_start, end_date = date_start)],
        dimension_filter = FilterExpression(
            filter = Filter(
                field_name = &amp;quot;sessionGoogleAdsCustomerId&amp;quot;,
                string_filter = Filter.StringFilter(value = customer_id),
            )
        ),
    )
    response = client.run_report(request)

    date = map(lambda x: x.dimension_values[0].value, response.rows)
    customer_id = map(lambda x: int(x.dimension_values[1].value), response.rows)
    campname_id = map(lambda x: int(x.dimension_values[2].value), response.rows)
    campname = map(lambda x: x.dimension_values[3].value, response.rows)
    groupname_id = map(lambda x: int(x.dimension_values[4].value), response.rows)
    groupname = map(lambda x: x.dimension_values[5].value, response.rows)
    adnam_id = map(lambda x: int(x.dimension_values[6].value), response.rows)
    visitsCost = map(lambda x: float(x.metric_values[0].value), response.rows)
    impressions = map(lambda x: int(x.metric_values[1].value), response.rows)
    visits = map(lambda x: int(x.metric_values[2].value), response.rows)

    df = pd.DataFrame({'region': customer_id, 'date': date, 'campname': campname, 'groupname': groupname, 'campname_id': campname_id, 'groupname_id': groupname_id, 'adnam_id': adnam_id, 'visitsCost': visitsCost, 'impressions': impressions, 'visits': visits})
    return df

df = pd.DataFrame()&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;После этого обнаружилось еще одно несовпадение: метрики в самом кабинете были чуть меньше, чем у нас на дашбордах. Отклонение не было критичным, но клиент снова не был уверен в точности наших данных.&lt;/p&gt;
&lt;p&gt;Оказалось, что в GA4 по умолчанию включается настройка &lt;b&gt;Thresholding applied.&lt;/b&gt; Ее цель — предотвратить возможную идентификацию отдельных пользователей по данным о возрасте, гендере и интересах. В результате ее применения терялись данные о кампаниях с небольшим (меньше 50) количеством кликов. Чтобы этого избежать, нужно было или не трекать эти показатели, или не использовать в дашбордах метрики, касающиеся пользователей напрямую. Roistat тоже не выгружал эти “пограничные” кампании через свою дефолтную интеграцию с GA4, поэтому для Refocus расхождение стало неожиданностью. Зато при прямой выгрузке через API этого ограничения не было, и ускользнувшие поначалу кампании считались, так что данные были более полными без дополнительных ухищрений.&lt;/p&gt;
&lt;p&gt;С добавлением все новых и новых источников, конечно, пришлось еще и мэтчить данные рекламных кабинетов о затратах, кликах и показах с записями на вебинары и их посещением, со сделками в CRM — но Roistat этого вовсе не делал, и поэтому не мог дать полной картины о воронках.&lt;/p&gt;
&lt;p&gt;После введения прямой выгрузки из рекламных кабинетов данные наконец-то стали полностью совпадать с реальностью, и Roistat отключили, заменив другими источниками. Теперь Refocus могли не платить дважды и получить куда более точный анализ.&lt;/p&gt;
&lt;h2&gt;Выводы&lt;/h2&gt;
&lt;p&gt;Кастомная сквозная аналитика — конечно, не панацея, и есть много причин и возможностей обойтись без услуг агентств, которые ее строят. Тем не менее, даже самое популярное на рынке коробочное решение, очевидно, не всегда корректно работает. Обрубать его сразу не обязательно, и ничего не мешает сочетать Roistat и похожие продукты с кастомом. В конце концов, иногда удобно просто достроить интеграции, которые не поддерживает Roistat, и держать дашборды в нем. Но в этом случае важно понимать, что данные там могут быть неполными или некорректными, а сообщение с бэком компании — ненадежным. В результате обнаружить или исправить ошибки может быть сложнее, чем организовать прямую выгрузку данных в хранилище — и здесь кастомная система отлично справится.&lt;/p&gt;
&lt;p&gt;&lt;i&gt;*Facebook принадлежит компании Meta, которая признана экстремистской организацией в России.&lt;/i&gt;&lt;/p&gt;
</description>
<pubDate>Wed, 02 Oct 2024 15:25:45 +0300</pubDate>
</item>

<item>
<title>Сквозной идентификатор: решение проблемы мэтчинга персональных данных студентов Refocus</title>
<guid isPermaLink="false">148</guid>
<link>http://www.test.leftjoin.ru/all/skvoznoy-identifikator-reshenie-problemy-metchinga-personalnyh-d/</link>
<comments>http://www.test.leftjoin.ru/all/skvoznoy-identifikator-reshenie-problemy-metchinga-personalnyh-d/</comments>
<description>
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/cover.png  border=“0” width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;В системах сквозной аналитики ключевую роль играет правильная модель атрибуции. Без нее данные невозможно интерпретировать, и их ценность для бизнеса невелика. При этом важно понимать, что любая модель напрямую зависит от качества данных.&lt;/p&gt;
&lt;p&gt;Частая проблема с сырыми данными в том, что информация об одном клиенте дублируется или, напротив, противоречит друг другу в разных источниках.&lt;/p&gt;
&lt;p&gt;Кроме того, что предобработка данных — база для аналитика, без правильного объединения персональных данных в принципе сложно отследить клиентский путь. Значит, нужно настраивать процессы объединения неоднородных персональных данных.&lt;/p&gt;
&lt;p&gt;Сегодня в любом клиентском бизнесе воронки регистрации устроены таким образом, что клиенты попадают в базу множеством способов — часто через маркетинговые каналы, которых всегда много (рассылки, реклама, соцсети). В каждом таком канале может быть ссылка на форму подписки, регистрацию на платформе или чат, и один клиент часто проходит все эти этапы. Сразу же образуется путаница в идентификации, которая сильно влияет на качество данных и результаты аналитики, если ее не лечить.&lt;/p&gt;
&lt;p&gt;Мы столкнулись с этой проблемой, работая с одним из наших клиентов, и решили ее, создав сквозной идентификатор. Это уникальный номер, который присваивается реальному клиенту и дублируется во все источники, где есть данные об этом клиенте, тем самым избавляя от путаницы.&lt;/p&gt;
&lt;h2&gt;Кейс Refocus: данные и путь клиента&lt;/h2&gt;
&lt;p&gt;Мы разрабатывали кастомную систему сквозной аналитики для эдтех-стартапа Refocus. Данные каждого студента в системы Refocus попадали из нескольких источников и были записаны несколько раз — как минимум при регистрации на курс, при первом входе на образовательную платформу и при входе в чат сопровождения.&lt;/p&gt;
&lt;p&gt;В нашем случае мэтчинг был важнее всего по трем источникам из тринадцати:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;amoCRM,&lt;/b&gt; где фиксируется весь клиентский путь студента;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Discord,&lt;/b&gt; где проходило сопровождение студентов;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Thinkific,&lt;/b&gt; сама образовательная платформа с курсами.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Остальные источники, с которыми мы работали, либо не содержали данных студентов (например, цифры эффективности работы sales-менеджеров были завязаны на данных сотрудников и трекались через другие системы), либо дублировали информацию из указанных трех.&lt;/p&gt;
&lt;p&gt;В Discord и Thinkific данные попадали напрямую, от студентов при регистрации в системах, а затем подтягивались в amoCRM. Основные причины несовпадения клиентских данных как у Refocus, так и в похожих случаях — человеческий фактор (опечатки), наличие у людей более чем одного телефона или адреса почты и ограничения самих платформ, с которых приходят данные: разный заданный формат полей и их количество.&lt;/p&gt;
&lt;p&gt;Часть этих факторов может решаться корректировкой самой клиентской воронки. Правда, не все платформы позволяют одинаково настроить вводные поля, а просьбы вводить данные в конкретном формате не всегда работают и не страхуют от ошибок. Плюс, задача аналитиков — получить чистые данные в любом случае.&lt;/p&gt;
&lt;h2&gt;Задача и поиск решения&lt;/h2&gt;
&lt;p&gt;Данные в Refocus мы подгружали в хранилище в BigQuery напрямую из интересующих нас источников (рекламных кабинетов, LMS и т. д.), используя Python. В дальнейшем на этих данных строились дашборды в Tableau.&lt;/p&gt;
&lt;p&gt;Обнаружить проблему несложно — при создании хранилища и дальнейшей выгрузке данных из него мы в любом случае чистим датасет от дубликатов и несовпадений.&lt;/p&gt;
&lt;p&gt;Поля, в которых возникали ошибки и для которых нам важен был мэтчинг, чтобы правильно отследить клиентский путь:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;имя — да, люди иногда вводят разные вариации ФИО (Юлия, Юля и Бля — на деле один человек!);&lt;/li&gt;
&lt;li&gt;телефон — с кодом страны или без, с пробелами, дефисами или слитно;&lt;/li&gt;
&lt;li&gt;электронная почта — длинные строки сложного формата, в которых легко опечататься.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Поначалу, пока количество студентов Refocus было относительно небольшим, достаточно было скриптов, которые объединяли данные по одному из этих полей. В полученных таблицах в Tableau проводился поиск строк с пустым значением в соответствующем поле — и вот видно всех студентов, чьи данные не сошлись.&lt;/p&gt;
&lt;p&gt;Количество таких строк было в пределах пары десятков, и трекать и объединять их было несложно вручную. Это делалось прямо в первоисточниках сотрудниками Refocus, которые могли поправить опечатки и ошибки у себя в системах. После этого наш код выгрузки в хранилище перезапускался и тянул уже чистые данные. Если после этого что-то не сходилось, то наши аналитики правили информацию на уровне базы данных.&lt;/p&gt;
&lt;p&gt;Но при росте компании в какой-то момент число студентов, потерянных при мэтчинге, могло достигать сотни за месяц. Пока ошибка обнаружится, данные поправят в источниках, а мы перезапустим код выгрузки, могло пройти несколько часов — а это критичный интервал. Да и перезапускать выгрузку каждый день ради нескольких несовпадений — неэффективно. Стало понятно, что масштаб проблемы требует более точного и универсального решения.&lt;/p&gt;
&lt;p&gt;Вообще, в такой ситуации возможны несколько вариантов. Можно бесконечно править скрипты мэтчинга, учитывая новые и новые случаи и создавая костыли. А можно, например, настроить алерты в оркестраторе процессов (в нашем случае  — Airflow), которые позволят моментально узнавать о появившемся несовпадении и объединять “потерянные” клиентские сущности по паре за раз. Но это все еще неполная автоматизация, и она только ускоряет, а не упрощает процесс.&lt;/p&gt;
&lt;p&gt;Руководствуясь соображениями эффективности, мы предложили ввести сквозной идентификатор — одно значение ID, присваиваемое одному клиенту после автоматической интеграции его данных из разных источников.&lt;/p&gt;
&lt;h2&gt;Реализация решения и рабочий процесс&lt;/h2&gt;
&lt;p&gt;Чтобы понять масштаб проблемы, мы начали с того, что создали таблицы несовпадающих персональных данных. Для этого мы использовали скрипты на Python. Эти скрипты объединяли данные из разных источников и создавали из них большую сводную таблицу. Для того, чтобы свести данные о студенте в одну сущность, использовался мэтчинг по адресу электронной почты. Мы попробовали мэтчить по имени, фамилии, телефону (который сначала надо было привести к одному формату!) и почте, и именно последний вариант показал самую высокую точность. Возможно, дело в том, что из всех данных почта имеет самый однородный формат, поэтому остается учитывать только опечатки.&lt;/p&gt;
&lt;p&gt;Например, нам нужно было мэтчить данные для создания дашборда по возвратам, о которых информация объединялась как раз из наших трех основных источников. В ранней версии скрипта данные отбирались таким образом:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;WITH snapshot_ AS (
      SELECT DISTINCT s.*,
        IFNULL(ae.name, ap.name) as contact_name,
        ap.phone, ae.email,
        split(replace(trim(lower(ae.email)),' ',''),'@')[OFFSET(0)] as email_first_part,
        ai.thinkific_id, ai.intercom_id, ac.student_id
      FROM (
        SELECT *,
          ROW_NUMBER() OVER(PARTITION BY lead_id ORDER BY updated_at DESC) as num_,
        FROM `Differture.amocrm_leads_snapshot`
      ) s
      LEFT JOIN (
        SELECT DISTINCT lead_id, contact_id, name, email,
          ROW_NUMBER() OVER(PARTITION BY lead_id ORDER BY contact_id) as num1_
        FROM `Differture.amo_emails`
      ) ae using(lead_id)
      LEFT JOIN (
        SELECT DISTINCT lead_id, contact_id, name, phone,
          ROW_NUMBER() OVER(PARTITION BY lead_id ORDER BY contact_id) as num2_
        FROM `Differture.amo_phones`
      ) ap using(lead_id)
      LEFT JOIN `Differture.amo_contact_thinkific_intercom_match` ai using(lead_id)
      LEFT JOIN `Differture.AmoContacts` ac on cast(ae.contact_id as string)=ac.amo_id
      WHERE (num_=1 or num_ is null) and (num1_=1 or num1_ is null) and (num2_=1 or num2_ is null)
        and s.pipeline_id in (4920421,5245535) and s.status_id=142 and lower(s.lead_name) not like '%test%'
    )&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Как можно заметить, идея сквозного идентификатора здесь уже присутствует — фигурирует &lt;tt&gt;student_id&lt;/tt&gt;. На самом деле, в этой версии скрипта это графа из AmoContacts — таблицы, в которой хранятся только данные из amoCRM. Никаких джойнов по &lt;tt&gt;student_id&lt;/tt&gt; пока не происходит. А происходят по &lt;tt&gt;email_first_part&lt;/tt&gt;, адресу почты до символа @:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;select distinct * from th_amo_ds_rf
    left join calendly ce using(email_first_part)
    left join typeform_live tfl on email_first_part=tf_email_first_part
    left join typeform tf using(email_first_part)
    left join csat using(email_first_part)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Первым шагом по практическому введению идентификатора была таблица &lt;b&gt;students_main_info&lt;/b&gt;, созданная в BigQuery in-house специалистом Refocus. К сожалению, у нас нет доступа к коду, который использовался для присвоения идентификатора. Зато мы можем показать вид этой таблицы:&lt;/p&gt;
&lt;div class="e2-text-table"&gt;
&lt;table cellpadding="0" cellspacing="0" border="0"&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;student_full_name&lt;/td&gt;
&lt;td style="text-align: center"&gt;string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;student_email&lt;/td&gt;
&lt;td style="text-align: center"&gt;string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;student_country_id&lt;/td&gt;
&lt;td style="text-align: center"&gt;string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;student_country_name&lt;/td&gt;
&lt;td style="text-align: center"&gt;string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;student_courses_ids&lt;/td&gt;
&lt;td style="text-align: center"&gt;array&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;student_courses_names&lt;/td&gt;
&lt;td style="text-align: center"&gt;array&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;student_cohort_id&lt;/td&gt;
&lt;td style="text-align: center"&gt;string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;student_cohort_name&lt;/td&gt;
&lt;td style="text-align: center"&gt;string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;cohort_community_manager_name&lt;/td&gt;
&lt;td style="text-align: center"&gt;string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;cohort_community_manager_email&lt;/td&gt;
&lt;td style="text-align: center"&gt;string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;student_onboarding_live_session_id&lt;/td&gt;
&lt;td style="text-align: center"&gt;string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;student_onboarding_live_session_time&lt;/td&gt;
&lt;td style="text-align: center"&gt;string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;student_onboarding_live_session_zoom_url&lt;/td&gt;
&lt;td style="text-align: center"&gt;string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;amo_contact_id&lt;/td&gt;
&lt;td style="text-align: center"&gt;string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;intercom_contact_id&lt;/td&gt;
&lt;td style="text-align: center"&gt;string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;thinkific_student_id&lt;/td&gt;
&lt;td style="text-align: center"&gt;string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;discord_user_id&lt;/td&gt;
&lt;td style="text-align: center"&gt;string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;discord_user_discord_id&lt;/td&gt;
&lt;td style="text-align: center"&gt;string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;discord_guild_id&lt;/td&gt;
&lt;td style="text-align: center"&gt;string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;discord_channel_id&lt;/td&gt;
&lt;td style="text-align: center"&gt;string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;discord_roles&lt;/td&gt;
&lt;td style="text-align: center"&gt;string&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;В students_main_info хранились данные из нужных источников с общим идентификатором в первой строке, и объединение проходило через сравнение этого поля.&lt;/p&gt;
&lt;p&gt;При этом поле &lt;tt&gt;student_id&lt;/tt&gt; использовалось пока не везде; также использовались другие поля этой таблицы — например, &lt;tt&gt;thinkific_student_id&lt;/tt&gt; или &lt;tt&gt;discord_user_id&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;После выгрузки и мэтчинга данных с помощью students_main_info студентов, которые потерялись при объединении, стало меньше, чем при первой схеме мэтчинга. Так мы убедились, что движемся в верном направлении. Тем не менее, использование одной таблицы, которая содержит больше десятка полей обо всех имеющихся персональных данных, не очень эффективно. Данные в ней уже обработаны скриптом специалиста Refocus, и если надо сверить их с сырыми источниками или ввести новый критерий отслеживания, все придется менять на бэкенде.&lt;/p&gt;
&lt;h2&gt;Что получилось в итоге&lt;/h2&gt;
&lt;p&gt;После теста сквозного идентификатора через одну большую таблицу мы продолжили улучшать структуру данных на бэке. Вместо students_main_info усилиями специалиста Refocus появилась подробная сеть более мелких таблиц, которые могут обращаться друг к другу и лежат в одном хранилище с нашими таблицами сырых данных.&lt;/p&gt;
&lt;p&gt;Вот так выглядела схема соотношения этих таблиц:&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/image-2.png  border=“0” width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;А вот так выглядела основная таблица Students:&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/students.png border=“0” width=40% height=40%&gt;&lt;/p&gt;
&lt;p&gt;В ней-то и находились основные персональные данные студентов с присвоенным идентификатором, и к ней можно было обращаться для мэтчинга из остальных источников.&lt;/p&gt;
&lt;p&gt;Остальные таблицы выглядели похоже: всегда было поле с идентификатором и информация о какой-то характеристике студента — когорта, курс, роль в дискорде и так далее.&lt;/p&gt;
&lt;p&gt;Финальный код, написанный нашими аналитиками,  объединял данные при выгрузке из хранилища, и больше не опирался на ненадежный мэтчинг через имейл.&lt;/p&gt;
&lt;p&gt;Сначала он отбирал собранные нами данные из amoCRM &lt;tt&gt;(amocrm_leads_snapshot)&lt;/tt&gt; и объединял их с контактной информацией клиентов. Затем в таблицу добавлялось поле &lt;tt&gt;student_id&lt;/tt&gt; и отбирались данные, которые понадобятся нам дальше.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;WITH snapshot_ AS (
      SELECT DISTINCT s.*,
        ac.name as contact_name, ac.phone, ac.email,
        split(replace(trim(lower(ac.email)),' ',''),'@')[OFFSET(0)] as email_first_part,
        ac.intercom_id, ac.student_id
      FROM (
        SELECT *,
          ROW_NUMBER() OVER(PARTITION BY lead_id ORDER BY updated_at DESC) as num_,
        FROM `Differture.amocrm_leads_snapshot`
      ) s
      LEFT JOIN (
        select cast(al.amo_id as INT64) as lead_id, cast(ac.amo_id as INT64) as contact_id,
          ac.name, emails as email, phone, student_id, ic.intercom_id,
          ROW_NUMBER() OVER(PARTITION BY al.amo_id ORDER BY ac.amo_id) as num1_
        from `Differture.AmoContacts` ac
        left join `Differture.AmoLeads` al on al.amo_contact_id=ac.id
        left join `Differture.IntercomContacts` ic using(student_id)
        , unnest(ac.emails) emails
      ) ac using(lead_id)
      WHERE (num_=1 or num_ is null) and (num1_=1 or num1_ is null)
        and s.pipeline_id in (4920421,5245535) and s.status_id=142 and lower(s.lead_name) not like '%test%'
    )&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Теперь при создании общей таблицы о возвратах с данными из amo, Thinkific и Discord объединение проходило через student_id:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;th_amo_ds_rf as (
      select distinct * except (channel_id, channel),
        ifnull(channel_id, 'Not in discord') as channel_id,
        ifnull(channel, 'Not in discord') as channel
      from thinkific_amo_refunds
      full outer join discord using(student_id)
    )&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Когда объединенные таблицы данных студентов были созданы, получить таблицы несовпадений можно было простой строкой кода в Tableau:&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/filter.png border=“0” width=50% height=50%&gt;&lt;/p&gt;
&lt;p&gt;Пустое значение поля student_id означает, что мэтча не случилось — где-то информация расходилась слишком сильно и не подтянулась в таблицы с идентификатором. Раньше, до введения идентификатора, поиск был таким же, но обращался к полям почты, телефона или имени-фамилии.&lt;/p&gt;
&lt;p&gt;Ниже можно увидеть таблицу, где данные из Thinkific не совпадали с amoCRM после перехода на Student ID. В этом случае студент есть в LMS, значит, на курсе учится — но его либо нет в системе учета, либо данные в ней разнятся с LMS.&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/unnamed.png  border=“0” width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;А вот таблица, где данные из Discord не совпадали с amoCRM. Все так же, как выше — студент есть в чатах сопровождения, но не ищется по своим данным в amoCRM.&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/discord.png  border=“0” width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;Оба скриншота показывают количество несовпадений примерно за месяц. Как видно по этим таблицам, количество несовпадений уменьшилось с 80-90 до пары десятков — примерно на 75%. Это позволило сократить количество перезапусков кода выгрузки вручную и уменьшить затраты времени и технических ресурсов на поддержание системы.&lt;/p&gt;
&lt;h2&gt;Выводы&lt;/h2&gt;
&lt;p&gt;Сквозной идентификатор — эффективное решение проблемы мэтчинга персональных данных. Он позволяет максимально автоматизировать процесс отслеживания и устранения несовпадений или дубликатов клиентских сущностей при выгрузке данных для анализа. В случаях, когда объем данных в системе невелик, а у компании нет возможности выделить ресурсы на реализацию такого решения, можно воспользоваться и другими вариантами. Например, алерты в оркестраторе процессов хорошо справятся в ситуации, когда объединить данные — вопрос ручного запуска одного скрипта раз в неделю. Но сквозной идентификатор — наверное, самое универсальное из доступных решений, которое покроет большинство ошибок и заметно уменьшит погрешность в качестве данных.&lt;/p&gt;
</description>
<pubDate>Mon, 16 Sep 2024 17:57:31 +0300</pubDate>
</item>

<item>
<title>Как переверстка помогает превратить хороший дашборд в отличный</title>
<guid isPermaLink="false">147</guid>
<link>http://www.test.leftjoin.ru/all/kak-pereverstka-pomogaet-prevratit-horoshiy-dashbord-v-otlichny/</link>
<comments>http://www.test.leftjoin.ru/all/kak-pereverstka-pomogaet-prevratit-horoshiy-dashbord-v-otlichny/</comments>
<description>
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/0oblogka.png  border=“0” width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;В кастомных системах сквозной аналитики конечный продукт, с которым взаимодействует пользователь — дашборды. Он не видит хранилище, витрины данных, скрипты, а дашборды видит каждый день, поэтому важно сделать их не только функциональными, но и понятными. Не всегда удается сразу нащупать оптимальный вариант, и полезно вести работу поэтапно.&lt;/p&gt;
&lt;p&gt;Дело в том, что в современных BI-тулах, которые мы используем — в частности, в популярном Tableau — возможности кастомизации гораздо шире, чем может себе представить сотрудник, мигрирующий с excel-таблиц. Часто пользователь просто не знает, каким вообще может быть дашборд и как составить ТЗ.&lt;/p&gt;
&lt;p&gt;В итоге дашборд содержит огромное количество данных, но возможность их продуктивно использовать спрятана за фильтрами, визуальной сложностью, долгой загрузкой.&lt;/p&gt;
&lt;p&gt;Именно такая ситуация сложилась у нас в процессе работы с образовательным стартапом Refocus.&lt;/p&gt;
&lt;p&gt;Один из главных их дашбордов — обзор &lt;b&gt;SLA (Service Level Agreement).&lt;/b&gt; Его пользователи — руководители отдела продаж и сейлз-менеджеры. Именно здесь их интересует прежде всего скорость обработки входящих лидов в зависимости от ряда факторов, от источника лида до региона.&lt;/p&gt;
&lt;p&gt;Нормативное значение этого показателя для Refocus — 15 минут, значит, там, где он выше, есть потенциал для оптимизации процессов и роста выручки.&lt;/p&gt;
&lt;p&gt;Мы работаем над дашбордами &lt;b&gt;итеративно:&lt;/b&gt; интервью с пользователем — прототип — фидбэк — следующая версия — фидбэк, и так далее. Но в случае с SLA после утверждения финальной версии остались нерешенные аналитиками функциональные проблемы.&lt;/p&gt;
&lt;p&gt;У заказчика было желание оптимизировать дашборды и бюджет на это, так что мы предложили общую переверстку и подключили к проекту датавиз-эксперта.&lt;/p&gt;
&lt;h2&gt;Версия 1: с чем мы работаем&lt;/h2&gt;
&lt;p&gt;Первая версия SLA-дашборда, которой некоторое время пользовался клиент, выглядела так:&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/1-32.png  border=“0” width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;В фокусе три графика:&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Скаттерплот по &lt;b&gt;времени ответа&lt;/b&gt; с момента получения лида.&lt;/li&gt;
&lt;li&gt;Гистограмма по &lt;b&gt;медианному времени ответа.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Гистограмма по &lt;b&gt;средней длительности первого звонка&lt;/b&gt; с лидом.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Справа — 15 фильтров, от региона и гранулярности до имени сотрудника, работавшего с лидом.&lt;/p&gt;
&lt;p&gt;Сверху — много текста, поясняющего тонкости работы с фильтрами по времени.&lt;/p&gt;
&lt;h2&gt;Задача&lt;/h2&gt;
&lt;p&gt;Представьте, что ваш технический бэкграунд испарился, и вы на месте пользователя: о чем вам говорят эти графики? Сколько кликов вам нужно совершить, чтобы оценить перформанс подчиненных в вашем отделе? Чтобы сравнить SLA для лидов из разных источников? Ясен ли текст-легенда?&lt;/p&gt;
&lt;p&gt;А главное — есть ли у вас понимание, как сделать понятнее и что сказать исполнителю проекта?&lt;/p&gt;
&lt;p&gt;У аналитиков в этой области может быть слепая зона — нам-то на картине выше все понятно. А в прямые задачи датавиз-спеца входит проинтерпретировать запрос пользователя в контексте существующего функционала и создать визуализацию на стыке предпочтений заказчика и здравого смысла.&lt;/p&gt;
&lt;p&gt;Важно определить и причины этих проблем. Иногда ошибки — просто неудачная попытка выполнить запрос заказчика. Конечно, всегда можно отмести «плохой» запрос и сделать, как правильно — но тогда можно потерять важные для заказчика функции.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Проблемы этого дашборда:&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Основной график — скаттерплот с SLA для всех лидов — выстроен на логарифмической шкале по оси Y без возможности переключиться на линейную. Есть сомнения, что все сейлз-менеджеры, глядя на дашборд, понимают, как работает логарифмическая шкала.&lt;/li&gt;
&lt;li&gt;При выборе гранулярности по неделям масштаб позволяет показать распределение точек на оси X по дням — но они распределены случайно с помощью функции RANDOM().&lt;/li&gt;
&lt;li&gt;Ось Y не синхронизирована между графиками, поэтому сложно оценить связь всех трех значений.&lt;/li&gt;
&lt;li&gt;Невозможно сравнить медианный SLA по следующим параметрам: курс, источник лида, команда, сейлз-менеджер — все они расположены в фильтрах. Сравнение требует скриншотов разных отображений или нескольких вкладок с одним дашбордом.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;Установленные причины:&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Логарифмическая шкала: заказчик, который являлся только одним из юзеров дашборда, попросил реализовать логарифмическую шкалу, чтобы смотреть на значения SLA, близкие к нулю. Видимо, это было релевантно для целей именно его отдела.&lt;/li&gt;
&lt;li&gt;Рандомное распределение по оси X: попытка сделать график проще и красивее, потому что скаттерплот уже перегружен информацией, и оценить распределение значений внутри недели — не приоритет.&lt;/li&gt;
&lt;li&gt;Неэффективные фильтры: до ввода дашборда заказчик не знал, какие именно сравнения окажутся ему полезны.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Итог — невозможно оценить общую картину SLA в компании в отдельно взятый период, а значит и корректировать процессы и стратегии продаж.&lt;/p&gt;
&lt;h2&gt;Решение&lt;/h2&gt;
&lt;p&gt;Наш датавиз-специалист рисовал макеты карандашом на бумаге, а после утверждения переносил в Tableau: аналитический функционал и UX-дизайн для готового дашборда можно оптимизировать и без специальных тулов. Здесь хорошо ориентироваться на заказчика: если ему приятнее смотреть на файл в Figma — замечательно. Все понятно и на бумаге — отлично.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Предложенные изменения:&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Фильтры справа сделать бар-чартами вместо выпадающих меню, чтобы разбивку по категориям было сразу видно.&lt;/li&gt;
&lt;li&gt;Реорганизовать дашборд в соответствии с принципами дизайна, учитывающими движение глаз человека слева-направо, сверху-вниз.&lt;/li&gt;
&lt;li&gt;Добавить линейную ось на скаттерплот, сделать именно ее форматом по умолчанию. Оставить возможность переключиться на логарифмическую ось.&lt;/li&gt;
&lt;li&gt;Нормализовать шкалу X, убрав рандомное распределение точек.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Версия 2: рабочая версия после переверстки&lt;/h2&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/2-31.png  border=“0” width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Что поменялось:&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;В левой части дашборда появилась таблица-фильтр по командам и сейлз-менеджерам. По ней можно оценить показатели работы отдельных сотрудников и отделов и получить общее представление о том, как обстоят дела с SLA в компании — а именно это изначальное назначение дашборда.&lt;/li&gt;
&lt;li&gt;В центральной части дашборда остался скаттерплот с SLA по всем лидам, только теперь формат отображения оси (линейная или логарифмическая) можно переключить.&lt;/li&gt;
&lt;li&gt;Фильтры курсов, источников лидов и категорий превратились в кликабельные бар-чарты в правой части экрана. Они позволяют сразу визуально оценить медианный SLA по этим параметрам.&lt;/li&gt;
&lt;li&gt;Все глобальные фильтры по категориям, которые нет необходимости сравнивать между собой, оказались в верхней части дашборда, там же — фильтр по дате. Теперь пространство экрана организовано эффективнее.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;Фидбэк:&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Потерялся график по средней длительности первого звонка.&lt;/li&gt;
&lt;li&gt;На центральном графике исчезли отсечки с медианным SLA.&lt;/li&gt;
&lt;li&gt;Не подписаны столбцы в таблице слева.&lt;/li&gt;
&lt;li&gt;Центральный скаттерплот очень долго загружается при первом открытии дашборда, так как Tableau пытается вывести все лиды из датасета — по умолчанию ни один фильтр не применяется и никак не ограничивает количество информации для отображения.&lt;/li&gt;
&lt;li&gt;Необходимы мелкие правки по оформлению и тултипам.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Версия 3: доработки и брендирование&lt;/h2&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/3-27.png  border=“0” width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Что поменялось:&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;В таблице слева появились подписи столбцов — теперь половина информации о работе отделов понятна при первом взгляде на дашборд.&lt;/li&gt;
&lt;li&gt;Центральный скаттерплот теперь отображается только после выбора фильтра по команде или конкретному сейлзу — это снижает нагрузку на хранилище и сильно ускоряет загрузку графика.&lt;/li&gt;
&lt;li&gt;Добавились отсечки с медианным SLA за конкретный период (месяц, неделю или день в зависимости от выбранной гранулярности).&lt;/li&gt;
&lt;li&gt;Вернулся график с длительностью первого звонка.&lt;/li&gt;
&lt;li&gt;Ось времени всех трех графиков теперь синхронизирована — не нужно сверять единицы измерения, чтобы выявить общие тренды.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Когда функционал доработан и утвержден, вводится &lt;b&gt;брендирование&lt;/b&gt; по корпоративным гайдлайнам. Они у Refocus выглядели так:&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/image-1.png  border=“0” width=100% height=100%&gt;&lt;/p&gt;
&lt;h2&gt;Обзор функционала&lt;/h2&gt;
&lt;p&gt;Итоговый дашборд стал гораздо проще в использовании и настройке, а функционал стал интуитивно понятен.&lt;/p&gt;
&lt;p&gt;Так выглядит финальный дашборд при открытии, пока не выбран ни один фильтр:&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/final.png  border=“0” width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;А так, если выбрать сейлз-менеджера из таблицы слева:&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/leads.png  border=“0” width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;Использование фильтров справа (например, отобразить только лидов, пришедших с вебинаров):&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/web.png  border=“0” width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;Тултипы со ссылкой на первоисточник на скаттерплоте с лидами (время в них отображается в минутах при выборе любой шкалы):&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/tip.png  border=“0” width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;Наконец, переключение между линейной и логарифмической шкалами:&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/log.png.jpg  border=“0” width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;Этот кейс отлично демонстрирует важность чуткой работы с заказчиком. Всегда важен баланс между экспертностью и ориентацией на пользователя. Конечно, стоит показывать ему разные возможности и предлагать оптимальные с технической точки зрения решения. Но не менее важно доверять клиенту и разбираться в причинах его запросов, чтобы сделать по-настоящему полезно и красиво. Скажем, вдруг он просит логарифмическую шкалу не потому, что не подумал о других, а потому, что без нее в его отделе не обойтись? Внимание к таким моментам — огромный плюс к компетенциям аналитика.&lt;/p&gt;
</description>
<pubDate>Wed, 04 Sep 2024 15:44:06 +0300</pubDate>
</item>

<item>
<title>Как и зачем мы сделали три дашборда по LinkedIn</title>
<guid isPermaLink="false">146</guid>
<link>http://www.test.leftjoin.ru/all/kak-i-zachem-my-sdelali-tri-dashborda-po-linkedin/</link>
<comments>http://www.test.leftjoin.ru/all/kak-i-zachem-my-sdelali-tri-dashborda-po-linkedin/</comments>
<description>
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/-(2)-(1).png  border=“0" width=100% height=100%&gt;&lt;br /&gt;
Скажу без лишней скромности — мне есть что рассказать про аналитику и про свою работу. Опытом и кейсами я и моя команда делимся в социальных сетях и в том числе — на LinkedIn, который активно ведем с 2023 года. У нас там три аккаунта:  &lt;a href="https://www.linkedin.com/in/valiotti/"&gt;мой личный профиль&lt;/a&gt;, страницы &lt;a href="https://www.linkedin.com/company/valiotti-analytics/mycompany/"&gt;Valiotti Analytics&lt;/a&gt; и &lt;a href="https://www.linkedin.com/showcase/cyprus-data/"&gt;кипрского дата-коммьюнити.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Соцсети ­— это инструмент, эффективность которого надо контролировать:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;следить за приростом подписчиков,&lt;/li&gt;
&lt;li&gt;выявлять тренды, популярные посты и рубрики,&lt;/li&gt;
&lt;li&gt;держать руку на пульсе, чтобы быстро отреагировать, если упадут вовлеченность или охваты.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Это можно делать вручную, но это будет долго и неэффективно, и чем больше становится соцсетей, тем больше времени это отнимает. Да и в конце концов, аналитики мы или нет? Зачем делать руками то, что можно автоматизировать?&lt;/p&gt;
&lt;p&gt;Так мы решили создать дашборд.&lt;/p&gt;
&lt;h2&gt;Наш подход&lt;/h2&gt;
&lt;p&gt;Делали для себя, но по тому же алгоритму, что для клиентов.&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Собрали команду из BI-специалиста и дата-инженеров. Подобные дашборды часто создаются силами одного человека, но у нас была возможность выделить нескольких сотрудников, чтобы ускорить работу.&lt;/li&gt;
&lt;li&gt;Они познакомились с доступными данными, API LinkedIn и статистикой, которую предоставляет сайт. Не надо недооценивать этот этап — перед первым интервью с заказчиком разобраться, с чем предстоит иметь дело. Это поможет сразу понять, какие требования из его ТЗ выполнимы.&lt;/li&gt;
&lt;li&gt;Затем BI-специалист провел серию интервью с главными пользователями — со мной и с лидом отдела контента. На этом этапе он выясняет &lt;b&gt;контекст использования дашборда&lt;/b&gt; — об этом ниже.&lt;/li&gt;
&lt;li&gt;Работали итеративно: интервью — первый макет дашборда — согласование и внесение правок — следующая версия — и так до создания финального, рабочего варианта.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Что отличает хороший дашборд от плохого?&lt;/h2&gt;
&lt;p&gt;При создании дашборда главное — понять &lt;b&gt;контекст использования.&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Кто будет пользоваться дашбордом?&lt;/li&gt;
&lt;li&gt;Для каких целей он нужен?&lt;/li&gt;
&lt;li&gt;Какие метрики и зачем пользователь будет отслеживать?&lt;/li&gt;
&lt;li&gt;Как часто пользователь будет обращаться к дашборду?&lt;/li&gt;
&lt;li&gt;Будет ли он использовать дашборд только для своих рабочих задач или собирается показывать его на конференциях?&lt;/li&gt;
&lt;li&gt;Какие нужны показатели, фильтры, периоды и гранулярность?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Важно уметь говорить с заказчиком на его языке и погружаться в его проблемы и задачи. Только так можно создать рабочий и полезный инструмент, а не просто красивый, но неудобный и перегруженный дашборд.&lt;/p&gt;
&lt;p&gt;Отличный фреймворк для создания дашбордов создал Роман Бунин — &lt;a href="https://revealthedata.com/blog/all/vebinar-algoritm-razrabotki-dashborda/"&gt;Dashboard Canvas&lt;/a&gt;. Он описал алгоритм работы, подготовил примеры вопросов для интервью с заказчиком и шаблоны.&lt;/p&gt;
&lt;h2&gt;Инжиниринг: два решения для одного проекта&lt;/h2&gt;
&lt;p&gt;&lt;b&gt;Стек проекта:&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python,&lt;/li&gt;
&lt;li&gt;Airflow (оркестратор процессов),&lt;/li&gt;
&lt;li&gt;AWS (облако, где это все развернуто),&lt;/li&gt;
&lt;li&gt;PostgreSQL (база данных),&lt;/li&gt;
&lt;li&gt;Tableau (BI-инструмент),&lt;/li&gt;
&lt;li&gt;Selenium (надо было установить драйвер для парсинга, мы использовали Firefox; скрипты писали на Python)&lt;/li&gt;
&lt;li&gt;Fivetran&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Из наших аккаунтов два корпоративные, и один личный. В зависимости от типа страниц LinkedIn по-разному «делится» аналитикой.&lt;/p&gt;
&lt;p&gt;Данные &lt;b&gt;из корпоративных аккаунтов Valiotti Analytics и Cyprus Data Community мы собираем с помощью &lt;a href="https://fivetran.com/"&gt;Fivetran.&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Это инструмент для автоматической выгрузки данных в базу из разных источников. А еще — наши партнеры, с которыми мы давно работаем над разными проектами для наших клиентов. Так что мы и не сомневались, что будем использовать для своего дашборда.&lt;/p&gt;
&lt;p&gt;Чтобы настроить передачу данных, Необходимо было просто подключить Fivetran к аккаунту и базе данных. Дальше он автоматически вытягивает данные из соцсети, преобразует и передает базе.&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/f1.png border=“0" width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;С моим личным аккаунтом все оказалось сложнее.&lt;/b&gt; LinkedIn неохотно делится данными по таким страницам.&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Подключить к нему Fivetran нельзя — это вариант только для корпоративных страниц.&lt;/li&gt;
&lt;li&gt;Сервис, специально созданный для сбора информации с личных аккаунтов на LinkedIn, inlytics.io через какое-то время начал требовать заполнить капчу. Для нас была важна полная автоматизация процесса, поэтому такой вариант нам категорически не подходил.&lt;/li&gt;
&lt;li&gt;Токен к официальному API LinkedIn не дал — их перестали раздавать из-за повышенного спроса.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;В итоге пришлось ограничиться тем, что LinkedIn выдает сам — Excel-файл с данными по приросту подписчиков, демографии и взаимодействиям, а также топ-50 самых популярных постов.&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/f2.png border=“0" width=100% height=100%&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Выгрузку Excel-файлов автоматизировали через связку Airflow + Selenium.&lt;/li&gt;
&lt;li&gt;В Notion ведется таблица с постами — в ней указывается заголовок, тема и ссылка на публикацию.&lt;/li&gt;
&lt;li&gt;Данные выгружаются с помощью Airflow и объединяются их со статистикой из LinkedIn. Чтобы сметчить данные используются ссылки — они в обеих таблицах одинаковые.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://public.tableau.com/app/profile/nikolay.valiotti/viz/LinkedInProfilePageOverview/LinkedInOverview?publish=yes"&gt;Вот такой дашборд получился в итоге.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Оба подхода позволяют видеть отслеживать тренды, вовлеченность, прирост подписчиков. Изначально хотели также собирать данные о числе посетителей, количестве личных сообщений, а еще подглядывать за конкурентами, но LinkedIn такой возможности не дает.&lt;/p&gt;
&lt;h2&gt;Визуализация в Tableau&lt;/h2&gt;
&lt;p&gt;Ну вот, контекст использования выяснили, данные собрали, макеты согласовали… пора наконец-то делать дашборд!&lt;/p&gt;
&lt;p&gt;Мы выбрали Tableau, во-первых, за богатый функционал. Его возможностей хватит, чтобы воплотить практически любые идеи в жизнь.&lt;/p&gt;
&lt;p&gt;Для дашборда для другого нашего проекта мы даже сделали календарь! Это наше изобретение — в Tableau такой функции нет.&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/f3.png border=“0" width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;Во-вторых, в Tableau приятные решения для визуализации. Даже без специальных знаний о дизайне и датавизе в нем можно собрать визуально приятный, читабельный дашборд.&lt;/p&gt;
&lt;p&gt;Ну а у нас-то знания есть. А еще — свой стайлгайд, по которому мы оформляем все свои дашборды. Можете убедиться, взглянув на скрины выше: дашборды в корпоративных цветах не только выглядят хорошо, но и получаются очень наглядными — а значит, функцию свою выполняют.&lt;/p&gt;
&lt;h2&gt;Выводы&lt;/h2&gt;
&lt;p&gt;Со стороны может показаться, что задача тривиальная — всего-то визуализировать данные по нескольким аккаунтам в соцсетях. Но она подкинула немало челленджей и несколько раз заставила хорошо задуматься над поиском решения.&lt;/p&gt;
&lt;p&gt;В итоге пользу получили все — у меня теперь есть отличный инструмент для мониторинга за соцсетями, а  у всей команды — ценный опыт, который можно применить в работе над другими проектами.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Что на практике дают эти дашборды?&lt;/b&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Анализ аудитории. Дашборд собирает данные, откуда меня читают мои подписчики, в каких компаниях и на каких должностях работают. Это позволяет строить гипотезы, какие темы и форматы будут наиболее интересны.&lt;/li&gt;
&lt;li&gt;Регулярная проверка основных показателей аккаунта: роста аудитории, вовлеченности подписчиков, охватов с детальной статистикой по каждому посту. Если я вижу спад или рост какого-то из них, можно отследить, когда он произошел, и корректировать контент-стратегию. Например, поэкспериментировать с подачей или темами.&lt;/li&gt;
&lt;li&gt;Выделение трендовых тем. Провожу долгосрочный анализ по 10 темам постов и выясняю, про что аудитории интереснее читать — нейросети, аналитику и данные или же больше про бизнес и предпринимательство. Затем список тем сужается до 5 самых популярных, на которых я концентрируюсь в дальнейшем. Уже успел сделать вывод, что моей аудитории интереснее всего читать про лайфхаки из анализа данных, личные истории и бизнес-кейсы.&lt;/li&gt;
&lt;li&gt;AB-тест визуального оформления — экспериментирую с оформлением постов и смотрю на реакцию.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Как вам такой проект? Стали бы делать дашборд для сбора данных из соцсетей или вам хватает их встроенной аналитики? Пишите в комментариях!&lt;/p&gt;
</description>
<pubDate>Wed, 28 Feb 2024 11:48:17 +0300</pubDate>
</item>

<item>
<title>Что такое ACID и причем тут базы данных?</title>
<guid isPermaLink="false">145</guid>
<link>http://www.test.leftjoin.ru/all/acid-and-databases/</link>
<comments>http://www.test.leftjoin.ru/all/acid-and-databases/</comments>
<description>
&lt;p&gt;Реляционные СУБД могут применяться для решения аналитических и транзакционных задач, и сегодня мы хотим рассказать вам о последних.&lt;/p&gt;
&lt;p&gt;Транзакция — это несколько операций чтения и записи, сгруппированные в одну логически неделимую операцию. Самый наглядный пример — банковский перевод, для которого нужно совершить несколько действий: снять деньги с одного счета и перекинуть их на другой.&lt;/p&gt;
&lt;p&gt;Пример исчерпывающе доносит смысл, но дьявол, как всегда, кроется в деталях, а точнее в деталях реализации. Во-первых, СУБД, поддерживающие транзакции, должны удовлетворять гарантиям функциональной безопасности ACID (это аббревиатура, которая ничего общего с химией не имеет). Во-вторых, есть понятие уровней изоляции транзакции, всего существует 4 различных уровня. Давайте начинать!&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/preview_acid.png.jpg  border=“0" width=100% height=100%&gt;&lt;/p&gt;
&lt;h2&gt;A — Atomicity&lt;/h2&gt;
&lt;p&gt;Как мы уже сказали, ACID — аббревиатура, каждая буква которой обозначает свойство транзакций. Первая буква «А» — atomicity, то есть атомарность.&lt;/p&gt;
&lt;p&gt;В большинстве СУБД для того, чтобы начать транзакцию, необходимо выполнить запрос с выражением &lt;i&gt;START TRANSACTION&lt;/i&gt;. Все дальнейшие действия будут относиться к начатой транзакции. Чтобы завершить транзакцию, нужно выполнить выражение &lt;i&gt;COMMIT&lt;/i&gt;, а если вы хотите отменить все действия транзакции, то можно «откатиться», используя выражение &lt;i&gt;ROLLBACK&lt;/i&gt;.&lt;/p&gt;
&lt;p&gt;И именно атомарность гарантирует, что ВСЕ операции между &lt;i&gt;START TRANSACTION&lt;/i&gt; и &lt;i&gt;COMMIT&lt;/i&gt; либо выполнятся, либо не выполнятся — промежуточного состояния не будет.&lt;/p&gt;
&lt;p&gt;В качестве примера вспомним опять про банковское приложение:&lt;/p&gt;
&lt;p&gt;Николай хочет перевести с одного счета на другой 200 рублей. Для этого сначала с первого счета будет списано 200 рублей, а уже потом на второй счет эти деньги будут зачислены. На первом счету у него 400 рублей, на втором — 500.&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/1ACID+--33.png  border=“0" width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/2ACID+--32.png.jpg  border=“0" width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;Если после первого UPDATE питание в здании отключится и сервер базы данных выключится, то на счетах останется 400 и 500 рублей, как будто никакой транзакции и не было.&lt;/p&gt;
&lt;h2&gt;C — Consistency&lt;/h2&gt;
&lt;p&gt;Вторая буква «C» — consistency, то есть согласованность или консистентность.&lt;/p&gt;
&lt;p&gt;Согласованность в рамках баз данных можно понимать как выполнение некоторых утверждений относительно данных в таблицах. На примере про перевод между счетами Николая можно сформировать такое утверждение: «При переводе между счетами сумма денег на обоих счетах не должна измениться». Это мы и наблюдаем: до транзакции было 400+500=900 рублей, а после стало 600+300=900 рублей.&lt;/p&gt;
&lt;h2&gt;I — Isolation&lt;/h2&gt;
&lt;p&gt;Третья буква — «I» — isolation, то есть изолированность одной транзакции от любой другой.&lt;/p&gt;
&lt;p&gt;Иногда случается так, что несколько пользователей в рамках своих транзакции норовят изменить одни и те же данные. В таком случае, эти транзакции должны быть изолированы друг от друга.&lt;/p&gt;
&lt;p&gt;Чтобы разобраться, чем чревато отсутствие изоляции двух транзакций, достаточно в наш пример добавить Маргариту, которая хочет через банкомат внести на счет Николая 300 рублей в момент перевода Николая. Если реализовать ее действия через механизм считывания текущего баланса счета, прибавления к нему 300 рублей и его перезаписи, то появляется риск нарушения консистентности.&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/3ACID+--33.png  border=“0" width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;Последнее действие, выполненное в рамках начисления через банкомат, перезатрёт все изменения от другой транзакции, что вызовет нарушение целостности данных в базе: у Николая на первом счету будет 600 рублей, а на втором 800 рублей, хотя должно быть 600. Эта ситуация называется «потерей обновления».&lt;/p&gt;
&lt;p&gt;Транзакции должны быть изолированы друг от друга, как будто они выполняются одна за другой. Но на самом деле такой жесткий уровень изоляции редко применяется из-за своей низкой производительности. Вместо него используют более слабые уровни изоляции, о которых мы расскажем чуть ниже.&lt;/p&gt;
&lt;h2&gt;D — Durability&lt;/h2&gt;
&lt;p&gt;Последняя буква аббревиатуры отвечает за принцип сохраняемости.&lt;/p&gt;
&lt;p&gt;Тут все просто: все транзакции, которые были успешно «закоммичены», не должны быть потеряны ни при каких сбоях. Если база данных реплицируется (запись идет на еще один или несколько работающих БД), то сохраняемость может означать успешную запись в БД на другом сервере. Если база данных не реплицируется, то сохраняемость означает запись на энергонезависимый носитель информации (например, жесткий диск).&lt;/p&gt;
&lt;h2&gt;Уровни изоляции&lt;br /&gt;
READ UNCOMMITED&lt;/h2&gt;
&lt;p&gt;Самый первый уровень изоляции, который мы рассмотрим — &lt;b&gt;&lt;i&gt;READ UNCOMMITED&lt;/i&gt;&lt;/b&gt;. То есть он позволяет читать все изменения незавершенных (или «незакоммиченных») транзакций.&lt;/p&gt;
&lt;p&gt;Чтение изменений незафиксированных транзакций называется «грязным чтением». Этот уровень изоляции не позволяет изменять внутри транзакции объекты (строки или таблицы или что-то другое, это зависит от конкретной СУБД и движка таблицы), уже измененные другой одновременной транзакцией («грязная запись»). Транзакция просто повиснет в ожидании COMMITа другой транзакции.&lt;/p&gt;
&lt;p&gt;Рассмотрим похожий пример, в котором Николай хочет оплатить покупку на 100 рублей с первого счет, а чуть позже Маргарита захотела начислить ему 100 рублей через банкомат. Изначально у него на счетах было по 500 рублей. Тогда схема выполнения будет следующей:&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/4ACID+--37.png.jpg  border=“0" width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;Обновление баланса второй транзакции — это «грязная» запись, именно на этом моменте транзакция и повиснет, изменение произойдет после фиксации перевода Николая, но его можно будет отменить командой &lt;i&gt;ROLLBACK&lt;/i&gt;, ведь будет понятно, что произошла «грязная запись».&lt;/p&gt;
&lt;h2&gt;READ COMMITED&lt;/h2&gt;
&lt;p&gt;Следующий уровень изоляции &lt;b&gt;&lt;i&gt;READ COMMITED&lt;/i&gt;&lt;/b&gt;. Как можно догадаться из названия, он позволяет читать только те изменения, которые были зафиксированы (или «закоммичены»).&lt;/p&gt;
&lt;p&gt;Этот уровень изоляции, как и read uncommitted, позволяет избежать ситуации «грязной записи», но также позволяет избежать ситуации «грязного чтения». То есть внутри транзакции можно прочитать только те изменения объектов, которые были совершены внутри уже завершенных транзакций.&lt;/p&gt;
&lt;p&gt;Вспоминая пример с предыдущей карточки, если бы уровень изоляции был «READ COMMITED», то чтение внутри операции пополнения счета Маргаритой вернуло бы неизмененные балансы счетов Николая (по 500 рублей на каждом, а не 500 и 400, как в предыдущем примере).&lt;/p&gt;
&lt;h2&gt;Как же эти два уровня изоляции реализованы технически?&lt;/h2&gt;
&lt;p&gt;Для предотвращения «грязных записей» транзакция перед изменением данных в объекте устанавливает на него блокировку. Таким образом, никакая другая транзакция не сможет изменить содержимое заблокированного объекта, но читать эти изменения можно.&lt;/p&gt;
&lt;p&gt;Применять блокировки для предотвращения «грязного чтения» слишком затратно, ведь тогда доступ может быть закрыт для множества читающих транзакций. Для решения этой проблемы после изменения объекта БД запоминает старую версию объекта, которую видят читающие транзакции.&lt;/p&gt;
&lt;p&gt;Например, если сотрудник банка до конца покупки Николая захочет увидеть, сколько у него было денег, то он увидит баланс счетов до начала транзакции, то есть по 500 рублей на каждом счету.&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/5ACID+--38.png.jpg  border=“0" width=100% height=100%&gt;&lt;/p&gt;
&lt;h2&gt;REPEATABLE READ&lt;/h2&gt;
&lt;p&gt;И вот, кажется, что вроде бы достаточно таких уровней для работы с базой данных, ведь никто не увидит незафиксированные изменения других.&lt;/p&gt;
&lt;p&gt;Но что, если во время выполнения одной очень длинной транзакции прочитать данные до изменения и после? Тогда можно заметить базу данных в неконсистентном состоянии.&lt;/p&gt;
&lt;p&gt;Например, если Николай перевел деньги между счетами во время аудита, который подразумевает чтение всех данных внутри одной очень долгой транзакции.&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/6ACID+--39.png.jpg  border=“0" width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;Сначала аудитор узнал, что на втором счету Николая 500 рублей (до перевода), затем увидел на втором счету уже 400 рублей, так как транзакция Николая была зафиксирована. У аудитора сложится впечатление, что у Николая 900 рублей суммарно на двух счетах. Но это не так, а возникшая ситуация называется «асимметрией чтения». Пример может показаться надуманным, ведь аудитор может заново прочитать баланс второго аккаунта, но если представить, что длинная транзакция — копирование данных на резервную копию, могут возникнуть большие проблемы.&lt;/p&gt;
&lt;p&gt;Во избежание такой ситуации используют уровень изоляции &lt;b&gt;&lt;i&gt;REPEATABLE READ&lt;/i&gt;&lt;/b&gt;. При таком уровне внутри транзакции чтение происходит из согласованного снимка состояния базы, то есть будут видны только те изменения, которые были зафиксированы на момент начала транзакции.&lt;/p&gt;
&lt;h2&gt;MVCC&lt;/h2&gt;
&lt;p&gt;Для реализации такого уровня используется мультиверсионное управление конкурентным доступом (MVCC).&lt;/p&gt;
&lt;p&gt;При таком механизме хранится несколько версий каждого зафиксированного объекта, вместе с ней хранятся номер транзакции — txid, которая создала версию объекта, и номер транзакции, которая ее удалила. Каждой новой транзакции присваивается свой номер, и она читает только те версии объектов, номер создания которых меньше собственного.&lt;/p&gt;
&lt;p&gt;&lt;img src=http://test.leftjoin.ru/pictures/7ACID+--40.png.jpg  border=“0" width=100% height=100%&gt;&lt;/p&gt;
&lt;p&gt;Идентификатор транзакции аудитора 9. Таким образом, все данные, которые будут прочитаны или изменены, должны иметь идентификатор не больше 9. Txid транзакции Николая — 10, внутри нее было произведено изменение двух объектов, которые были созданы транзакциями с txid 6 и 4. Изменение будет означать удаление этих версий объектов и создание новых. Когда аудитор попытается прочитать баланс первого счета Николая, СУБД увидит, что объект был создан транзакцией с бОльшим txid и возьмет версию, созданную ранее.&lt;/p&gt;
&lt;h2&gt;SERIALIZABLE&lt;/h2&gt;
&lt;p&gt;Закончим нашу статью рассказом про самый сильный уровень изоляции — &lt;b&gt;&lt;i&gt;SERIALIZABLE&lt;/i&gt;&lt;/b&gt;.&lt;/p&gt;
&lt;p&gt;Как мы уже говорили, этот уровень гарантирует полную изоляцию, то есть даже при конкурентном выполнении транзакций, их изменения будут такими же, как будто бы они выполнялись последовательно.&lt;/p&gt;
&lt;p&gt;Этот уровень изоляции может быть реализован тремя способами:&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Действительно последовательное выполнение транзакций&lt;/li&gt;
&lt;li&gt;Применение двухфазной блокировки (2PL)&lt;/li&gt;
&lt;li&gt;Использование сериализуемой изоляции снимков состояния (SSI).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Обо всем по порядку:&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;По-настоящему последовательное выполнение предполагает выполнение всех транзакций одна за другой, в одном потоке. Такой уровень изоляции имеет смысл применять только если транзакции выполняются быстро, чтобы другим не пришлось ждать выполнения одной большой и долгой транзакции. К тому же, если объем данных одной транзакции не помещается в память, то обращение к диску может очень сильно замедлить работу с БД.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Двухфазная блокировка — это усиленная версия уже упоминавшейся блокировки. Во время одновременного выполнения нескольких транзакций допускается чтение одного объекта (строки или нескольких строк), но для его изменения нужен монопольный доступ. То есть если транзакция «А» читает строки, которые транзакция «В» хочет изменить, то вторая должна дождаться завершения первой. И наоборот.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol start="3"&gt;
&lt;li&gt;Сериализуемая изоляция снимков состояния — относительно свежий способ обеспечения сериализуемости транзакций. По сравнению с предыдущими двумя этот метод обеспечивает оптимистичное управление доступом, то есть без блокировок. Все чтение из базы выполняется из согласованного снимка состояния базы данных, а изменения происходят свободно, в расчете, что они не затрагивают объекты других транзакций. При фиксации транзакции происходит проверка, не были ли прочитаны или изменены уже измененные другой транзакцией объекты. Если да, то транзакция прерывается и ее выполнение приходится выполнить еще раз.&lt;br /&gt;
Все транзакции при этом методе должны иметь уровень изоляции Serializable.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Уровень изоляции Serializable может очень сильно ударить по производительности, хотя последний метод является самым перспективным среди всех трех, и может быть в дальнейшем будет улучшен по производительности до слабых уровней изоляции.&lt;/p&gt;
&lt;p&gt;Контакты автора: &lt;a href="https://www.linkedin.com/in/suroque/"&gt;LinkedIn&lt;/a&gt; | &lt;a href="https://t.me/Suroque/"&gt;Telegram&lt;/a&gt;&lt;/p&gt;
</description>
<pubDate>Tue, 30 Jan 2024 09:58:55 +0300</pubDate>
</item>

<item>
<title>Привет, Hydra!</title>
<guid isPermaLink="false">144</guid>
<link>http://www.test.leftjoin.ru/all/privet-hydra/</link>
<comments>http://www.test.leftjoin.ru/all/privet-hydra/</comments>
<description>
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/image-1.png.jpg"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Сегодня мы хотим познакомить вас с еще одной СУБД для аналитики данных. Мы поговорим про Hydra. Расскажем, как начать ее использовать, объясним основные концепции, загрузим данные и погоняем запросы.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Итак, что же такое Hydra? Это аналитическая СУБД с открытым исходным кодом, который вы можете найти на &lt;a href="https://github.com/hydradatabase/hydra"&gt;GitHub&lt;/a&gt;. Hydra разработана на основе другой очень популярной транзакционной СУБД, PostgreSQL, что позволяет ей быть более универсальной по сравнению с прочими СУБД, созданными для аналитики данных. Но как такое возможно? Фундаментальное различие между аналитической и транзакционной СУБД заключается в том, что первая хранит данные каждого поля в отдельном файле, а вторая — хранит данные нескольких записей в одном файле. С Hydra все немного интереснее.&lt;/p&gt;
&lt;p&gt;Создатели Hydra предлагают облачные решения на любой вкус в зависимости от ваших потребностей.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/Screenshot-2023-05-19-142827.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Но вы можете собрать СУБД из исходного кода с GitHub. Мы же для написания этой статьи воспользовались бесплатной версией доступа к облачной платформе.&lt;/p&gt;
&lt;h2&gt;Особенности хранения данных&lt;/h2&gt;
&lt;p&gt;Как и во многих аналитических СУБД, в Hydra реализовано колоночное хранение данных. Такой подход позволяет лучше сжимать данные, ведь в одном поле могут быть значения только одного типа. Эффективное сжатие не только сокращает занимаемое место, но и позволяет читать данные более эффективно. Для выполнения аналитических запросов требуется прочитать только несколько полей из всей таблицы — при колоночном хранении файлы ненужных столбцов не будут прочитаны вообще в отличие от строкового подхода к хранению данных.&lt;/p&gt;
&lt;p&gt;В то же время, для решения транзакционных задач, когда нужно прочитать одну запись целиком и заменить в ней значения некоторых полей, наиболее эффективно строковое хранение, когда записи таблицы лежат в одном файле одна за другой. Но что самое интересное — в Hydra вы можете выбрать, какой из этих двух вариантов использовать. Тот факт, что данная СУБД была разработана на основе PostgreSQL, делает ее полноценной транзакционной СУБД. В то же время возможность колоночного хранения таблиц добавляет Hydra универсальности.&lt;/p&gt;
&lt;p&gt;В одной базе данных могут быть таблицы с разными вариантами хранения данных, каждая под свою задачу.&lt;br /&gt;
Например, основная таблица с действиями клиентов может хранить данные построчно и быть обычной PostgreSQL таблицей, а рядом с ней, на том же сервере, может быть такая же таблица с колоночным хранилищем, которая используется аналитиками с их тяжелыми запросами на чтение. А комбинирование различных типов таблиц в одном запросе может поражать воображение!&lt;/p&gt;
&lt;p&gt;Давайте приведем пример создания таблиц с колоночным и со строковым хранением данных. Создадим таблицу для известного по серии статей о ClickHouse набора &lt;a href="https://www.gov.uk/government/statistical-data-sets/price-paid-data-downloads"&gt;данных&lt;/a&gt; о проданном жилье в Великобритании с 1995 по настоящее время.&lt;/p&gt;
&lt;p&gt;Вот так будет выглядеть скрипт создания стандартной таблицы из PostgreSQL со строковым хранением. Этот же скрипт подойдет и для Hydra.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;CREATE TABLE uk_price_paid_row (
    price INT,
    date DATE,
    postcode1 TEXT,
    postcode2 TEXT,
    TYPE SMALLINT,
    is_new SMALLINT,
    duration SMALLINT,
    addr1 TEXT,
    addr2 TEXT,
    street TEXT,
    locality TEXT,
    town TEXT,
    district TEXT,
    county TEXT
);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Для того, чтобы такая таблица стала хранить данные по столбцам, достаточно добавить всего лишь одну строчку в конце скрипта:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;CREATE TABLE uk_price_paid_col (
    price INT,
    date DATE,
    postcode1 TEXT,
    postcode2 TEXT,
    TYPE SMALLINT,
    is_new SMALLINT,
    duration SMALLINT,
    addr1 TEXT,
    addr2 TEXT,
    street TEXT,
    locality TEXT,
    town TEXT,
    district TEXT,
    county TEXT
) USING columnar;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Теперь одну таблицу можно использовать как основную для хранения данных и для транзакционной нагрузки, а другую — для аналитических задач.&lt;/p&gt;
&lt;p&gt;В англоязычных источниках транзакционные задачи обычно описываются таким термином, как «OLTP нагрузка» (OLTP — аббревиатура, которая расшифровывается как &lt;i&gt;OnLine Transaction Processing&lt;/i&gt;). С другой стороны, решение аналитических задач характеризуется OLAP нагрузкой (&lt;i&gt;OnLine Analytical Processing&lt;/i&gt;). Зачастую каждая СУБД выбирается под свою нагрузку, но Hydra отличается тем, что предназначена как для OLTP, так и для OLAP нагрузки. Такое смешение двух типов нагрузки породило термин «HTAP нагрузки» (&lt;i&gt;Hybrid Transaction/Analytical Processing&lt;/i&gt;) — как раз под такую нагрузку и была разработана Hydra.&lt;/p&gt;
&lt;h2&gt;Партиционирование&lt;/h2&gt;
&lt;p&gt;Еще одной очень интересной функцией таблиц Hydra является &lt;i&gt;партиционирование&lt;/i&gt;, то есть разделение таблицы на несколько логически независимых друг от друга кусков. Партиционирование — это логическое разбиение всех записей таблицы на группы. Это очень удобный функционал для работы сразу с большим количеством строк.&lt;/p&gt;
&lt;p&gt;В Hydra партиционирование концептуально похоже на партиционирование таблицы из PostgreSQL. При создании таблицы необходимо указать поле, по значениям которого будет разделение на партиции, а также тип разделения; подробнее можете узнать в русскоязычной документации к  &lt;a href="https://postgrespro.ru/docs/postgrespro/14/ddl-partitioning"&gt;документации к PostgreSQL&lt;/a&gt;. Давайте при создании нашей таблицы из предыдущего примера укажем партиционирование по месяцам продажи дома, тогда наш скрипт будет выглядеть так:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;CREATE TABLE uk_price_paid (
    price INT,
    date DATE,
    postcode1 TEXT,
    postcode2 TEXT,
    TYPE SMALLINT,
    is_new SMALLINT,
    duration SMALLINT,
    addr1 TEXT,
    addr2 TEXT,
    street TEXT,
    locality TEXT,
    town TEXT,
    district TEXT,
    county TEXT
) PARTITION BY RANGE(date);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Мы намеренно не указывали в скрипте тип хранения данных таблицы, об этом позже. В одной партиции будут храниться данные за определенный промежуток значений поля &lt;i&gt;date&lt;/i&gt;. Нам бы хотелось хранить данные за каждый год в своей партиции. Далее нам необходимо создать каждую партицию вручную, с указанием границ поля &lt;i&gt;date&lt;/i&gt;; например, вот так выглядел бы скрипт создания партиции для записей 1995 года:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;CREATE TABLE uk_1995 PARTITION OF uk_price_paid 
FOR VALUES FROM ('1995-01-01') TO ('1996-01-01');&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Скрипт создания партиции недвусмысленно говорит о том, что мы создали таблицу, которая является партицией изначальной таблицы. Здесь кроется еще одна «киллер-фича» СУБД Hydra. Раз каждая партиция — это отдельная таблица, то для каждой партиции можно указать свой способ хранения данных. В примере выше партиция создана со строковым типом хранения данных, но можно создать ее же с колоночным способом хранения. Скрипт такого запроса будет следующий:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;CREATE TABLE uk_1995 PARTITION OF uk_price_paid 
FOR VALUES FROM ('1995-01-01') TO ('1996-01-01')
USING COLUMNAR;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Для таблицы &lt;i&gt;uk_price_paid&lt;/i&gt; можно создать партицию на каждый год, причем создать партицию текущего года со строковым хранением данных, а остальные со столбцовым. Самую позднюю партицию мы предложили создать со строковым типом из предположения, что нагрузка на эти данные будет преимущественно транзакционной. Данные за остальные года изменяться не будут, они нужны только для построения аналитических отчетов.&lt;/p&gt;
&lt;p&gt;То есть указывать в изначальной таблице способ хранения не нужно, ведь у каждой партиции он будет свой.&lt;/p&gt;
&lt;p&gt;Такая гибкость Hydra очень удобна при совмещении двух разных типов нагрузки OLTP и OLAP, чего часто тяжело добиться в классических колоночных СУБД.&lt;/p&gt;
&lt;h2&gt;Обновление и удаление данных&lt;/h2&gt;
&lt;p&gt;Лучше всего разделять таблицу по нагрузкам, ведь обновление и удаление записей в таблице с колоночным типом хранения происходят менее эффективно.&lt;/p&gt;
&lt;p&gt;Сейчас процесс удаления данных реализован следующим образом: при удалении из таблицы запросом &lt;i&gt;DELETE&lt;/i&gt; запись помечается просто удаленной и не будет прочитана при следующем &lt;i&gt;SELECT&lt;/i&gt; запросе. Физическое удаление произойдет при вызове запроса на сборку мусора &lt;i&gt;VACUUM FULL&lt;/i&gt;, который хорошо известен пользователям PostgreSQL &lt;a href="https://postgrespro.ru/docs/postgresql/14/sql-vacuum"&gt;PostgreSQL&lt;/a&gt;. Это рабочий способ, но малоэффективный, потому что при вызове сборщика мусора с таким параметром произойдет полная блокировка таблицы.&lt;/p&gt;
&lt;p&gt;Процесс обновления записей в таблице при колоночном хранении мало отличается от удаления. Запрос &lt;i&gt;UPDATE&lt;/i&gt; помечает каждую обновляемую строку как удаленную (как и при удалении данных запросом &lt;i&gt;DELETE&lt;/i&gt;), а новые версии записей просто будут дописаны в конце. По сути, запрос &lt;i&gt;UPDATE&lt;/i&gt; — это запрос &lt;i&gt;DELETE&lt;/i&gt;, за которым следует запрос &lt;i&gt;INSERT&lt;/i&gt;.&lt;/p&gt;
&lt;p&gt;Если, например, мы захотим обновить цену во второй записи из такого куска таблицы:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/5hydra-38.png.jpg"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;То нам придется удалить строку целиком, а потом вставить такую же, но с новым значением поля &lt;i&gt;price&lt;/i&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/6hydra-39.png.jpg"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Кстати, стоит помнить еще и о том, с каким уровнем изоляции происходят удаления и обновления записей. Каждый запрос по умолчанию будет создавать отдельную транзакцию с уровнем изоляции READ COMMITED. Если такой уровень изоляции не подходит для вашего сценария обновления и удаления данных, то вы можете изменить уровень изоляции по умолчанию на другой или явно запускать один или несколько запросов на изменение или удаление данных внутри транзакции с заданным уровнем изоляции.&lt;/p&gt;
&lt;p&gt;Удалять и обновлять данные лучше в рамках таблиц или партиций, созданных со строковым типом хранения данных, а потом уже эти данные отправить в хранилище со столбцовым типом.&lt;/p&gt;
&lt;h2&gt;Как выполняются аналитические запросы?&lt;/h2&gt;
&lt;p&gt;Не только колоночное хранение данных позволяет оптимизировать выполнение аналитических запросов. Для выполнения каждого запроса используются ресурсы сразу же нескольких вычислительных ядер процессора, то есть запрос выполняется параллельно.&lt;/p&gt;
&lt;p&gt;Давайте разберемся с этим механизмом на примере вот такого простого запроса к нашей таблице uk_price_paid, которая была создана с колоночным хранением:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;SELECT
    DATE_PART('year', date) AS year,
    ROUND(AVG(price)) AS avg_price
FROM
    uk_price_paid
GROUP BY
    year
ORDER BY
    year ASC;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Запрос определяет среднюю стоимость недвижимости за каждый год. Если таблица была предварительно разделена на партиции по году, то агрегатная функция &lt;i&gt;AVG&lt;/i&gt; будет рассчитываться одновременно для каждой партиции параллельно.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/8hydra-41.png.jpg"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Данные каждого столбца для вычисления разделяются на векторы, и каждый поток будет обрабатывать свой вектор. Мы обозначили каждый вектор своим цветом (всего на рисунке 6 векторов). Так как таблица была создана с партиционированием по годам, то скорее всего вычисление каждого столбца изначально будет разделено между потоками по партициям. Если в распоряжении Hydra потоков будет больше, чем партиций, то файлы столбцов будут разделены на еще бОльшее число векторов, тем самым увеличивая параллельность выполнения запроса. Такой алгоритм выполнения запроса сильно уменьшает время отклика, а соответственно, увеличивает время выполнения запроса.&lt;/p&gt;
&lt;p&gt;Для сравнения: в PostgreSQL такой запрос выполнялся бы в один поток, который бы читал все строки (так как у PostgreSQL строковый тип хранения данных) и делал вычисление среднего арифметического каждого года последовательно.&lt;/p&gt;
&lt;h2&gt;Источники данных&lt;/h2&gt;
&lt;p&gt;В Hydra также есть средства для интеграции с другими СУБД, такими как MySQL и PostgreSQL. Эти СУБД могут быть источником данных для таблиц Hydra. Также источником могут служить данные, хранящиеся в S3 и в Google Sheets.&lt;/p&gt;
&lt;p&gt;Для каждого способа есть своя инструкция по интеграции, но все они очень сильно похожи друг на друга, так как реализованы при помощи модуля обертки для внешнего источника данных, например, для интеграции с PostgreSQL используется модуль postgres_fdw.&lt;/p&gt;
&lt;p&gt;В данном посте мы не будем погружаться в инструкцию по интеграции, поскольку каждый способ очень хорошо описан в документации.&lt;/p&gt;
&lt;h2&gt;Испытание запросами&lt;/h2&gt;
&lt;p&gt;Давайте наконец посмотрим, на что способна Hydra в деле. Мы потестим ее производительность на тестовых данных о продаже жилья в Великобритании с 1995 года. Структуру таблицы мы приводили ранее. Наша таблица будет поделена на партиции по году, каждая партиция при этом содержит данные по колонкам. Всего в таблице 28 миллионов записей.&lt;/p&gt;
&lt;p&gt;Ну и встречайте сами запросы!&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Посчитать общее число размещенных объявлений за все время.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;SELECT COUNT(*)
FROM uk_price_paid;&lt;/code&gt;&lt;/pre&gt;&lt;ol start="2"&gt;
&lt;li&gt;Найти среднюю цену за каждый год.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;SELECT
DATE_PART('year', date) AS year,
ROUND(AVG(price)) AS avg_price
FROM uk_price_paid
GROUP BY year
ORDER BY year ASC;&lt;/code&gt;&lt;/pre&gt;&lt;ol start="3"&gt;
&lt;li&gt;Найти среднюю цену за каждый год в Лондоне.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;SELECT
DATE_PART('year', date) AS year,
ROUND(AVG(price)) AS avg_price
FROM uk_price_paid
WHERE town = 'LONDON'
GROUP BY year
ORDER BY year ASC;&lt;/code&gt;&lt;/pre&gt;&lt;ol start="4"&gt;
&lt;li&gt;Топ 100 районов городов по средней цене с 2020 года с более чем 100 объявлений.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;SELECT
town,
district,
COUNT(*) AS c,
ROUND(AVG(price)) AS avg_price
FROM uk_price_paid
WHERE date &amp;gt;= '2020-01-01'
GROUP BY town, district
HAVING COUNT(*) &amp;gt;= 100
ORDER BY avg_price DESC
LIMIT 100;&lt;/code&gt;&lt;/pre&gt;&lt;ol start="5"&gt;
&lt;li&gt;Вывести все возможные комбинации типов домов и количество объявлений с ними по убыванию их суммарной цены из объявлений.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;SELECT
    t.name AS type,
    d.name AS duration,
    COUNT(*) AS c,
    ROUND(SUM(price)) AS sum_price
FROM uk_price_paid AS upp
INNER JOIN type AS t ON upp.type = t.id
INNER JOIN duration AS d ON upp.duration
= d.id
WHERE date &amp;gt;= '2010-01-01'
GROUP BY t.name, d.name
HAVING COUNT(*) &amp;gt;= 100
ORDER BY sum_price DESC;&lt;/code&gt;&lt;/pre&gt;&lt;ol start="6"&gt;
&lt;li&gt;Вывести информацию об улицах с самым дорогим и самым дешевым жильем.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;SELECT
    price,
    date,
    postcode1,
    t.name AS type,
    d.name AS duration,
    street,
    locality,
    town,
    district,
    county
FROM uk_price_paid AS upp
INNER JOIN type AS t ON upp.type = t.id
INNER JOIN duration AS d ON upp.duration = d.id
WHERE street IN (
    (SELECT street
    FROM uk_price_paid
    ORDER BY price DESC
    LIMIT 1)
 
    UNION ALL
 
    (SELECT street
    FROM uk_price_paid
    ORDER BY price ASC
    LIMIT 1)
)
ORDER BY price DESC;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Время выполнения запросов мы представили в таблице ниже:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/15hydra-07.png.jpg"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Результаты впечатляют, ведь мы запускали эти запросы на самой слабой конфигурации железа для нашей базы.&lt;/p&gt;
&lt;h2&gt;Заключение&lt;/h2&gt;
&lt;p&gt;СУБД Hydra — это отличное решение для построения вашего хранилища данных. Ее поддержка нативного диалекта PostgreSQL многим поможет быстро внедрить Hydra в свои проекты, а колоночный тип хранения данных позволит быстро и эффективно выполнять аналитические запросы высоких степеней сложности. В то же время возможность создавать обыкновенные PostgreSQL таблицы ставит Hydra на один уровень с одной из самых часто используемых СУБД для OLTP нагрузки.&lt;/p&gt;
&lt;p&gt;Hydra — это очень молодой проект, который вобрал в себя все преимущества PostgreSQL и дополнил их колоночным способом хранения данных таблиц и параллельным выполнением запросов. Очень ждем, когда разработчики этой СУБД представят новый функционал, который поможет выполнять более сложные аналитические запросы.&lt;/p&gt;
</description>
<pubDate>Mon, 22 May 2023 12:50:03 +0300</pubDate>
</item>

<item>
<title>Неочевидный выбор: количественная или качественная цветовая палитра?</title>
<guid isPermaLink="false">142</guid>
<link>http://www.test.leftjoin.ru/all/quantitative-vs-qualitative-color-scales/</link>
<comments>http://www.test.leftjoin.ru/all/quantitative-vs-qualitative-color-scales/</comments>
<description>
&lt;p class="note"&gt;Перевод статьи &lt;a href="https://blog.datawrapper.de/quantitative-vs-qualitative-color-scales/"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;«When to use quantitative and when to use qualitative color scales»&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Лиза Шарлотта Мут, дизайнер из Берлина, опубликовала на Datawrapper статью о сложностях выбора между качественной и количественной цветовой палитрой при визуализации данных разных типов. Заинтересовавшись ее мнением и аргументами по данному вопросу, мы решили перевести ее для вас.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/quantitative-vs-qualitative-color-scales1.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Эта статья попытается ответить на следующий вопрос: когда следует использовать &lt;i&gt;количественную&lt;/i&gt; цветовую палитру (последовательную или расходящуюся; например, голубой, синий, темно-синий), а когда — &lt;i&gt;качественную&lt;/i&gt; палитру (например, красный, желтый, голубой)?&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/quantitative-vs-qualitative-color-scales2.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;И небольшой дисклеймер: чтобы мне не оперировать длинными терминами &lt;i&gt;количественная цветовая схема&lt;/i&gt; и &lt;i&gt;качественная цветовая схема&lt;/i&gt; в этой статье, я буду часто употреблять менее корректные, но более удобные для чтения слова «оттенки» и «градиенты» для количественных цветовых схем и «тона» для качественных.&lt;/p&gt;
&lt;p&gt;Итак, когда надо использовать оттенки? А когда — тона?&lt;/p&gt;
&lt;h2&gt;Используйте тона, когда у значений нет изначального порядка&lt;/h2&gt;
&lt;p&gt;Тона необходимо использовать, когда невозможно упорядочить данные. Например, если в графике представлены промышленности или страны (Иран, Марокко, Пакистан): Марокко ничем не уступает Пакистану, как и наоборот.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/quantitative-vs-qualitative-color-scales3.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;h2&gt;Используйте оттенки или градиенты, когда у значений есть порядок&lt;/h2&gt;
&lt;p&gt;А вот когда вы можете упорядочить данные, представляемые цветом, используйте последовательную или расходящуюся цветовую палитру. Если, например, необходимо передать данные о безработице (4%; 6%; 8%), лучше отдать предпочтение именно количественной цветовой палитре.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/quantitative-vs-qualitative-color-scales4.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Это правило применимо не только к тексту или числам. У шкалы Лайкерта («полностью не согласен», «не согласен», «где-то посередине», «согласен», «полностью согласен») или размеров одежды (XS, S, M, L, XL, XXL) также есть специфический порядок. Они тоже относятся к количественному типу данных, поэтому для их визуализации крайне рекомендуется использовать именно оттенки.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/quantitative-vs-qualitative-color-scales5.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Но давайте пойдем дальше. Если мы изучим другие примеры визуализации данных, то заметим, что качественные данные иногда передаются с помощью количественных цветовых палитр и наоборот. А вот и ответ почему:&lt;/p&gt;
&lt;h2&gt;Используйте оттенки, чтобы подчеркнуть подразумеваемый порядок&lt;/h2&gt;
&lt;p&gt;Здесь разобраться помогут древовидные карты:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/quantitative-vs-qualitative-color-scales6.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Да, можно красить категории в дереве тоном (например, чтобы определить страны) как на примере слева. Но дерево станет более удобным для чтения, если покрасить категории &lt;i&gt;по размеру ячейки&lt;/i&gt;, который выбран для передачи определенного численного значения (например, показателя безработицы в разных странах).&lt;/p&gt;
&lt;p&gt;И тут вы могли бы подумать: «Но я хочу обозначить цветом совершенно новое значение!» Например, если вы отсортируете ячейки по показателю безработицы, возможно, вам захочется покрасить их по ВВП на душу населения. Но такой график было бы крайне тяжело прочитать:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/quantitative-vs-qualitative-color-scales7.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Дерево слева пытается убить одним выстрелом слишком много зайцев. Даже при наличии отличной цветовой легенды, такой график все равно остается тяжелым для восприятия.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Старайтесь не красить графики по значениям, которые не отображаются дополнительно с помощью, например, позиции или порядка.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;А если все-таки покрасить подразумеваемые значения в удобочитаемом графике?&lt;/p&gt;
&lt;p&gt;Предварительно убедитесь, что эти значения уже заметны не прибегая к цвету. Это непростая задача, ведь использование цвета для передачи новой переменной часто делает график тяжелым для прочтения.&lt;/p&gt;
&lt;p&gt;Давайте посмотрим на один пример, чтобы прокачать ваше интуитивное понимание, когда стоит прибегнуть к такому решению.&lt;/p&gt;
&lt;p class="note"&gt;Страница с деталями графика «Алгоритм Google» из &lt;a href="https://www.economist.com/graphic-detail/2019/06/08/google-rewards-reputable-reporting-not-left-wing-politics"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;The Economist&lt;/span&gt;&lt;/a&gt; от 8 июня, 2019&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/quantitative-vs-qualitative-color-scales8.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Сосредоточимся на точечной диаграмме в левом верхнем углу. Точечные диаграммы — это один из немногих видов графиков, где покраска по скрытым данным без их представления каким-либо дополнительным способом (позицией, длиной, и т. д.) удивительно хорошо работает.&lt;/p&gt;
&lt;p class="note"&gt;Точечную диаграмму проблематично разобрать в частности из-за того, что точки покрашены расходящейся, а не последовательной цветовой палитрой. По сути, The Economist использует цвета для передачи двух различных переменных: тон для презентации политической идеологии («левые»/«правые») и насыщенность и яркость для демонстрации политической крайности. Тот же самый график, который передавал лишь политическую крайность (не важно, в сторону «левых» или «правых») с помощью последовательной цветовой палитры было бы проще читать.&lt;/p&gt;
&lt;p&gt;И все же, чтобы понять этот график из The Economist, требуется несколько секунд. Обратите внимание, что точечную диаграмму справа проще прочитать, так как в ней совмещены два приема визуализации: политическая идеология показана позицией («левые» и «правые») &lt;i&gt;и&lt;/i&gt; оттенком.&lt;/p&gt;
&lt;p&gt;Но я готова поспорить, что диаграмма слева все же работает — именно благодаря диаграмме справа и нормированной линейчатой диаграмме снизу. Они обе как цветовые ключи к тому, как следует читать цвета на первой точечной диаграмме.&lt;/p&gt;
&lt;p&gt;И эти большие цветовые подспорья здесь критически важны. &lt;b&gt;Используйте цвет только тогда, когда читателю будет легко понять, какое значение ваши оттенки отображают, даже если вы используете какой-то другой прием визуализации для этого же значения.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Итак, мы рассмотрели древовидные карты, столбчатые графики и точечные диаграммы. Теперь, давайте возьмем в качестве примера линейный график:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/quantitative-vs-qualitative-color-scales9.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;В этих линейных графиках оттенки использованы для того, чтобы продублировать значение порядка линий. Но в графике справа это гораздо проще заметить, так как линии еще и упорядочены. График же слева может читателя только запутать.&lt;/p&gt;
&lt;h2&gt;Используйте оттенки для выделения подкатегорий&lt;/h2&gt;
&lt;p&gt;Есть и другие причины красить качественные данные количественными цветовыми палитрами, а не качественными. Например, чтобы выделить &lt;b&gt;под&lt;/b&gt;категории. Ниже представлен график из The Economist, который показывает, как это сделать:&lt;/p&gt;
&lt;p class="note"&gt;Население в занимаемых территориях, сгруппированное по религиозной принадлежности (The Economist, &lt;a href="https://infographics.economist.com/2019/AChristmasGiftForYou/AYearInGraphicDetail.pdf"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;PDF&lt;/span&gt;&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/quantitative-vs-qualitative-color-scales10.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Мы видим две категории: «евреи» (голубой) и «арабы» (желтый). &lt;b&gt;Под&lt;/b&gt;категориями же являются территории, которые представлены оттенками синего и желтого.&lt;/p&gt;
&lt;p&gt;Вот похожий пример:&lt;/p&gt;
&lt;p class="note"&gt;График &lt;a href="https://www.nzz.ch/gesellschaft/schweizerdeutsch-viel-wandel-im-freiburger-dialekt-kaum-in-baar-ld.1390868"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;NZZ&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/quantitative-vs-qualitative-color-scales11.jpg"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;NZZ использует оттенки (зеленый и синий), чтобы помочь читателям заметить разницу между двумя категориями: Ältere («пожилые») и Jüngere («молодые»). Оттенки помогают различать подкатегории: какие слова пожилые люди и молодежь используют, чтобы сказать «сердцевина яблока».&lt;/p&gt;
&lt;p&gt;(Кстати, и тому и другому графику цвета, в целом-то, и &lt;i&gt;не нужны&lt;/i&gt;: все секции могут быть серыми с разделяющими их белыми линиями, так как каждая из подкатегорий объяснена текстом. Слова &lt;i&gt;Gaza&lt;/i&gt;, &lt;i&gt;Israel&lt;/i&gt;, &lt;i&gt;Ältere&lt;/i&gt;, &lt;i&gt;Gräutschi&lt;/i&gt; и так далее показаны на самих графиках, делая цветовое решение лишним.)&lt;/p&gt;
&lt;p&gt;Когда вы работаете с подкатегориями, ваш читатель будет подсознательно понимать, что функция оттенков заключается в том, чтобы помочь ему видеть разницу между подкатегориями.&lt;/p&gt;
&lt;h2&gt;Используйте оттенки, чтобы разграничить первостепенные и второстепенные категории&lt;/h2&gt;
&lt;p&gt;Вам не нужно давать всем категориям одинаковый вес. Если вы хотите выделить какую-то из них, вы вполне можете покрасить остальные в один цвет (зачастую, градиентами серого):&lt;/p&gt;
&lt;p class="note"&gt;График &lt;a href="https://daten.berlin.de/datensaetze/einwohnerregister-berlin"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;Лизы Шарлотты Рост&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/quantitative-vs-qualitative-color-scales12.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;По сути, этот график превращает категории («в браке», «не замужем/не женат», «разведен/а», «вдова/вдовец») в подкатегории, так как они все поделены на две большие группы («первостепенные» и «второстепенные»). Если покрасить подкатегории градиентом, наш читатель все отлично поймет.&lt;/p&gt;
&lt;h2&gt;Используйте оттенки, чтобы сделать отдельные категории менее яркими&lt;br /&gt;
и более удобными для чтения для людей, страдающих дальтонизмом&lt;/h2&gt;
&lt;p&gt;Существует такая мантра в визуализации данных, которая призвана помочь специалистам сделать их графики более доступными для людей с проблемами зрения: «Сделай так, чтобы визуализация работала в черно-белом исполнении». Суть в том, что цвета должны иметь разные уровни яркости, чтобы их было легко отличить при переводе в серую шкалу.&lt;/p&gt;
&lt;p&gt;Ну, допустим. Тогда какой же цвет темнее, этот &lt;span style='color:#5794b2'&gt;⬤&lt;/span&gt; или этот &lt;span style='color:#e62600'&gt;⬤&lt;/span&gt;? И как насчет &lt;span style='color:#60dfc4'&gt;⬤&lt;/span&gt; и &lt;span style='color:#ffff00'&gt;⬤&lt;/span&gt;? Сюрприз! Два первых превратятся в &lt;span style='color:#6b6b6b'&gt;⬤&lt;/span&gt; в серой градации. А два последних будут выглядеть как &lt;span style='color:#c1c1c1'&gt;⬤&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Для простых смертных судить о светлоте двух цветов, если у них разные тона, не так-то и просто. Поэтому «сделать так, чтобы визуализация работала в черно-белом исполнении» проще, если специалист по дата визуализации использует градиенты только одного тона.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/quantitative-vs-qualitative-color-scales13.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Две причины — доступность и строгость — могут убедить вас выбрать оттенки, а не тона, при работе с категориями. Вот пример из The Financial Times:&lt;/p&gt;
&lt;p class="note"&gt;График из &lt;a href="https://www.ft.com/content/1ae439b1-75e7-4b55-876c-66533ac37db8"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;The Financial Times&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/quantitative-vs-qualitative-color-scales14.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Но имейте в виду следующие особенности: во-первых, &lt;i&gt;скорее всего, некоторые читатели будут пытаться придать цветам какой-то смысл&lt;/i&gt;. Даже если это не входило в ваши планы, они могут попытаться найти объяснение тому, почему использован градиент. «США изображены более темным цветом, так как имеют самые высокие показатели». Или: «... так как их данные имеют большую значимость в этой статье». Красить наобум нельзя.&lt;br /&gt;
Синтия Брюэр отмечала то же самое, когда говорила о представлении бинарных данных («да»/«нет», «общественный»/«частный», «присутствует»/«отсутствует») на картах:&lt;/p&gt;
&lt;p class="note"&gt;Синтия Брюэр: «Рекомендации по выбору цвета для карт и визуализации», с. 136 по цитате в: «Визуализация в современной картографии», том 2, 1994. Курсив автора статьи.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;«Создатель карты решает, какая из категории значительней, основываясь на цели создания карты, и &lt;i&gt;самая важная из двух категорий должна быть темнее цветом&lt;/i&gt;. Если бинарные категории обладают одинаковым весом, красьте график качественной цветовой палитрой с незначительной разницей по светлоте между тонами».&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Во-вторых, опыт показывает, что &lt;b&gt;чем больше оттенков одного тона для передачи категорий, тем сложнее прочитать такой график&lt;/b&gt;. Вполне реально различить между двумя, тремя оттенками. Но если их будет четыре, пять или шесть, ваши читатели полезут на стенку. Особенно если цветовые индикаторы не упорядочены и/или прокомментированы и/или выполнены лишь в одном тоне (от светло-голубого до темно-синего) вместо нескольких (от светло-желтого до темно-синего).&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/quantitative-vs-qualitative-color-scales15.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;В-третьих, если хочется использовать градиент, &lt;b&gt;старайтесь использовать оттенки только одного тона, если нет необходимости вводить еще один&lt;/b&gt;.&lt;/p&gt;
&lt;p&gt;Спросите себя, какое ключевое послание вы хотите передать, и выделите участки, которые нужно сделать более заметными, с помощью другого тона и его оттенков, как «Europe» и «US» в этом графике:&lt;/p&gt;
&lt;p class="note"&gt;График &lt;a href="https://www.ft.com/content/1caa7059-2e4e-4b9e-b365-8944478509a4"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;The Financial Times&lt;/span&gt;&lt;/a&gt;, который я слегка отредактировала&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/quantitative-vs-qualitative-color-scales16.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Если вы поймали себя на том, что используете слишком много тонов, используйте другой тип визуализации.&lt;/p&gt;
&lt;p&gt;До этого момента, мы в основном смотрели на причины использовать &lt;i&gt;оттенки&lt;/i&gt; вместо тонов. А вот причина использовать &lt;i&gt;тона&lt;/i&gt; вместо оттенков:&lt;/p&gt;
&lt;h2&gt;Используйте тона, чтобы лучше обособить категории&lt;/h2&gt;
&lt;p&gt;В 2020 году, The Financial Times опубликовали график, в котором отобранные страны Европы были сгруппированы в зависимости от значительного/умеренного/небольшого роста чисел новых случаев заболевания Covid-19.&lt;/p&gt;
&lt;p class="note"&gt;График &lt;a href="https://www.ft.com/content/bcddc297-b7f2-444d-908f-54e8ce6f4f98"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;The Financial Times&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/quantitative-vs-qualitative-color-scales17.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Стивен Бернард и Джон Берн-Мердок, создатели этого графика, решили использовать тона для разных стран в каждой группе.&lt;/p&gt;
&lt;p&gt;Я спросила Стивена Бернарда, почему он предпочел тона вместо оттенков. И вот что он ответил:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;«Логика в такой покраске в том, чтобы сделать линии &lt;b&gt;максимально отличными друг от друга&lt;/b&gt; на каждом графике, но, в то же время, сохранить сортировку во всех четырех графиках. [...] Тот подход, который я применил, позволяет зрителю понять из первого графика, что темно-синий представляет собой первое место, розовый — второе, голубой — третье, а зеленый — четвертое. Так, зритель может легко считать данные с других трех графиков».&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Когда я посмотрела на график Стивена, я не поняла, что темно-синяя линяя расценивается как первая, как и то, что розовая линия идет второй, и так далее. Читатели (как и я) не ожидают, что категории, которые отображаются тонами, могут обыгрывать еще и порядок. Если бы у категорий был порядок, их стоило бы покрасить градиентом.&lt;/p&gt;
&lt;p&gt;И все же мне кажется, что тона неплохо работают в графике Стивена. Ведь, как всегда, стоит вопрос: чем оправдан ваш выбор типа палитры? В данном случае, The Financial Times выбрали качественную палитру, чтобы помочь читателям лучше различать линии, переплетающиеся на графике, вместо того, чтобы подчеркнуть их порядок.&lt;/p&gt;
&lt;h2&gt;Выводы&lt;/h2&gt;
&lt;p&gt;Итак, благодаря Лизе Шарлотте Мут, мы можем подвести следующие итоги:&lt;br /&gt;
• Используйте &lt;i&gt;тона&lt;/i&gt;, когда у значений &lt;i&gt;нет&lt;/i&gt; изначального &lt;i&gt;порядка&lt;/i&gt;&lt;br /&gt;
• Используйте &lt;i&gt;оттенки&lt;/i&gt; или градиенты, когда у значений &lt;i&gt;есть порядок&lt;/i&gt;&lt;br /&gt;
• Используйте &lt;i&gt;оттенки&lt;/i&gt;, чтобы подчеркнуть &lt;i&gt;подразумеваемый порядок&lt;/i&gt;&lt;br /&gt;
• Используйте &lt;i&gt;оттенки&lt;/i&gt; для выделения &lt;i&gt;под&lt;/i&gt;категорий&lt;br /&gt;
• Используйте &lt;i&gt;оттенки&lt;/i&gt;, чтобы разграничить &lt;i&gt;первостепенные&lt;/i&gt; и &lt;i&gt;второстепенные&lt;/i&gt; категории&lt;br /&gt;
• Используйте &lt;i&gt;оттенки&lt;/i&gt;, чтобы сделать отдельные категории менее яркими и более удобными для чтения для людей, страдающих &lt;i&gt;дальтонизмом&lt;/i&gt;&lt;br /&gt;
• Используйте &lt;i&gt;тона&lt;/i&gt;, чтобы лучше &lt;i&gt;обособить категории&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;Список литературы&lt;/i&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Синтия Брюэр: «Рекомендации по выбору цвета для карт и визуализации», с. 136 по цитате в: «Визуализация в современной картографии», том 2, 1994.&lt;/li&gt;
&lt;li&gt;Adobe Spectrum: &lt;a href="https://spectrum.adobe.com/page/color-for-data-visualization/"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;Цвет для визуализации данных&lt;/span&gt;&lt;/a&gt;, 2019.&lt;/li&gt;
&lt;li&gt;Юусо Копонен, Джонатан Хильден: «Руководство по визуализации данных», 2019, с. 73-79.&lt;/li&gt;
&lt;/ol&gt;
</description>
<pubDate>Fri, 04 Nov 2022 12:00:31 +0300</pubDate>
</item>

<item>
<title>How-to: модель GPT-2 для получения логических выводов с помощью Amazon SageMaker</title>
<guid isPermaLink="false">141</guid>
<link>http://www.test.leftjoin.ru/all/how-to-model-gpt-2/</link>
<comments>http://www.test.leftjoin.ru/all/how-to-model-gpt-2/</comments>
<description>
&lt;p&gt;Не так давно, на конференции Linq мы представили &lt;a href="http://whatmuskwouldtweet.com/"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;генератор твитов в стиле Илона Маска&lt;/span&gt;&lt;/a&gt;. Для его создания мы взяли готовую модель GPT-2 Medium и дообучили/стилизовали ее на своем датасете. По-английски такой процесс называется &lt;i&gt;fine-tuning&lt;/i&gt;. Модель требует достаточное количество ресурсов для этого, так что не каждый может это сделать локально на своем ПК. Однако, вопрос решается, если использовать, например, Google Colab. Мы же дообучали модель на платформе Kaggle.&lt;/p&gt;
&lt;p&gt;Но после этого появляется новая задача — модель нужно развернуть, чтобы создать полноценный сервис. Конечно, существует множество различных решений. Ранее, при создании &lt;a href="https://habr.com/ru/post/596035/"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;генератора телеграм-постов в стиле Артемия Лебедева&lt;/span&gt;&lt;/a&gt; мы обращались к сервису Yandex DataSphere, а после даже написали небольшой &lt;a href="http://test.leftjoin.ru/all/how-to-yandex-datasphere/"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;гайд&lt;/span&gt;&lt;/a&gt; о том, как там развернуть модель. Теперь же мы расскажем о развертывании модели GPT-2 для получения логических выводов в режиме реального времени (Real-time inference) с помощью Amazon SageMaker.&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Заходим в аккаунт AWS.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Так как мы хотим задеплоить стилизованную модель, нам необходимо загрузить в облако необходимые файлы. В поиске вбиваем S3, выбираем первый сервис.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(1).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="3"&gt;
&lt;li&gt;Нажимаем &lt;b&gt;Create bucket&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(2).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="4"&gt;
&lt;li&gt;Далее проводим конфигурацию бакета: вводим его имя, выбираем регион, настраиваем права доступа и т. д. Для простоты достаточно ввести данные только в графе &lt;b&gt;General configuration&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(3).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="5"&gt;
&lt;li&gt;После конфигурирования нажимаем &lt;b&gt;Create bucket&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(4).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="6"&gt;
&lt;li&gt;Затем нас перебросит на страницу Amazon S3, где можно будет увидеть что-то подобное:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(5).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="7"&gt;
&lt;li&gt;Далее надо загрузить сами файлы модели. Нажимаем на имя созданного бакета и в открывшемся окне нажимаем &lt;b&gt;Upload&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(6).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="8"&gt;
&lt;li&gt;Перетаскиваем архив с файлами модели в нужную область или нажимаем &lt;b&gt;Add files&lt;/b&gt;. При необходимости можно выстроить иерархию внутри бакета путем создания папок с помощью &lt;b&gt;Add folders&lt;/b&gt;. Здесь важно отметить, что файлы модели должны быть в архиве с расширением &lt;b&gt;tar.gz&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(7).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="9"&gt;
&lt;li&gt;Нажимаем &lt;b&gt;Upload&lt;/b&gt; и ждем завершение загрузки.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(8).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="10"&gt;
&lt;li&gt;После успешной загрузки архива, перейдем непосредственно к деплою модели. В поиске вбиваем SageMaker, выбираем первый сервис.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(9).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="11"&gt;
&lt;li&gt;Для работы с с этим сервисом необходимо предварительно настроить &lt;a href="https://docs.aws.amazon.com/sagemaker/latest/dg/gs-studio-onboard.html"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;SageMaker Domain&lt;/span&gt;&lt;/a&gt;, для этого нажимаем &lt;b&gt;Get Started&lt;/b&gt; на баннере &lt;b&gt;New to Sagemaker?&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(10).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="12"&gt;
&lt;li&gt;Для простой конфигурации 1 пользователя выбираем &lt;b&gt;Quick setup&lt;/b&gt; и нажимаем &lt;b&gt;Set up SageMaker Domain&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(11).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="13"&gt;
&lt;li&gt;Заполняем имя пользователя и настраиваем роль для исполнения. Для этого можем создать новую роль и указать в ней то, к каким бакетам S3 у пользователя будет доступ. Для простоты дадим доступ ко всем бакетам.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(12).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="14"&gt;
&lt;li&gt;Нажимаем &lt;b&gt;Submit&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(13).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="15"&gt;
&lt;li&gt;Придется немного подождать, пока SageMaker Domain и пользователь будут сконфигурированы.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(14).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="16"&gt;
&lt;li&gt;После завершения настройки, среди пользователей появится созданный нами и можно будет запустить &lt;b&gt;Studio&lt;/b&gt;, нажав на &lt;b&gt;Launch app&lt;/b&gt;. &lt;a href="https://aws.amazon.com/ru/sagemaker/studio/"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;SageMaker Studio&lt;/span&gt;&lt;/a&gt; — IDE, позволяющая работать работать с Jupyter  ноутбуками в облаке AWS.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(15).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="17"&gt;
&lt;li&gt;Тут тоже придется немного подождать.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(16).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="18"&gt;
&lt;li&gt;Наконец, мы попадем в SageMaker Studio. Переключаясь между вкладками с помощью панели слева, можно:&lt;br /&gt;
• Просмотреть рабочий репозиторий, где будут храниться ноутбуки и прочие файлы;&lt;br /&gt;
• Просмотреть запущенные инстансы и приложения, Kernel и Terminal Sessions;&lt;br /&gt;
• Работать с Git репозиторием;&lt;br /&gt;
• Управлять ресурсами SageMaker;&lt;br /&gt;
• Устанавливать разрешения для Jupyter ноутбуков.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(17).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="19"&gt;
&lt;li&gt;Отдельно выделим &lt;a href="https://docs.aws.amazon.com/sagemaker/latest/dg/studio-jumpstart.html"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;SageMaker JumpStart&lt;/span&gt;&lt;/a&gt;. Этот сервис предлагает предварительно обученные модели с открытым исходным кодом для широкого спектра задач. Вы можете обучить и настроить эти модели перед тем как развернуть их. JumpStart также предоставляет шаблоны решений для настройки инфраструктуры для распространенных случаев использования и исполняемые ноутбуки для машинного обучения с помощью SageMaker.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(18).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="20"&gt;
&lt;li&gt;Несмотря на наличие готовых решений, для деплоя нашей fine-tuned модели GPT-2 мы создадим новый ноутбук, где пропишем все, что нам нужно. Для этого нажмем на + в голубом прямоугольнике сверху слева. Откроется вкладка &lt;b&gt;Launcher&lt;/b&gt;, пролистаем вниз до секции &lt;b&gt;Notebooks and compute resources&lt;/b&gt; и выберем там &lt;b&gt;Notebook Python 3&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(19).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="21"&gt;
&lt;li&gt;Придется немного подождать, прежде чем ядро ноутбука будет готово к работе.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol start="22"&gt;
&lt;li&gt;Наконец, можно писать код.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(20).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="23"&gt;
&lt;li&gt;Отдельно стоит отметить, что можно выбрать инстанс, на котором будет выполняться ноутбук. Например, если для вашей модели нужно больше ресурсов, вы запросто сможете переключиться. Но стоит помнить, что и платить придется соответственно.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(21).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="24"&gt;
&lt;li&gt;Во время работы с ноутбуком вы &lt;a href="https://aws.amazon.com/ru/sagemaker/pricing/"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;платите&lt;/span&gt;&lt;/a&gt; за время его использования с учетом типа выбранного инстанса.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol start="25"&gt;
&lt;li&gt;Для простого деплоя нашей модели можем воспользоваться &lt;a href="https://huggingface.co/gpt2-medium"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;готовой конфигурацией от Hugging Face&lt;/span&gt;&lt;/a&gt;. Нажимаем на кнопку Deploy, выбираем там Amazon SageMaker, выбираем задачу (в нашем случае это Text Generation) и конфигурацию (в нашем случае это AWS), копируем код в наш ноутбук.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(22).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="26"&gt;
&lt;li&gt;Так как мы используем свою дообученную модель, а не готовую из репозитория Hugging Face, нам надо сделать небольшие изменения в коде. Комментируем в словаре &lt;b&gt;hub&lt;/b&gt; строку с ключом &lt;b&gt;‘HF_MODEL_ID’&lt;/b&gt; и в конструкторе &lt;b&gt;HuggingFaceModel&lt;/b&gt; добавляем ключ &lt;b&gt;model_data&lt;/b&gt;, куда пишем путь до нашего архива с файлами модели:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;# Hub Model configuration. https://huggingface.co/models
hub = {
	# 'HF_MODEL_ID':'gpt2-medium',
	'HF_TASK':'text-generation'
}
 
# create Hugging Face Model Class
huggingface_model = HuggingFaceModel(
	transformers_version='4.17.0',
	pytorch_version='1.10.2',
	py_version='py38',
	env=hub,
	role=role, 
      model_data='s3://my-bucket-for-gpt2/gpt2-medium-musk.tar.gz',
)&lt;/code&gt;&lt;/pre&gt;&lt;ol start="27"&gt;
&lt;li&gt;В методе &lt;b&gt;deploy&lt;/b&gt; объекта &lt;b&gt;huggingface_model&lt;/b&gt; мы можем выбрать, на каком инстансе произойдет развертывание нашей модели, указав его в параметре &lt;b&gt;instance_type&lt;/b&gt;. Большинство инстансов может быть недоступно в связи с отсутствием нужных квот и их придется запрашивать в поддержке AWS. В этом случае вы увидите подобную ошибку:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(23).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="28"&gt;
&lt;li&gt;Если модель была успешно создана и развернута (для этого придется немного подождать), то можно вызвать метод &lt;b&gt;predict&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(24).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="29"&gt;
&lt;li&gt;Для того, чтобы обращаться к инстансу извне AWS, придется создать Access key.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;В поиске вбиваем IAM, выбираем первый сервис.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(25).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;В открывшемся окне выбираем вкладку &lt;b&gt;User&lt;/b&gt; и нажимаем на имя пользователя, под которым мы работаем.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(26).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Переходим на вкладку &lt;b&gt;Security credentials&lt;/b&gt; и нажимаем &lt;b&gt;Create access key&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(27).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Копируем &lt;b&gt;Access key ID&lt;/b&gt; и &lt;b&gt;Secret access key&lt;/b&gt; и сохраняем их в надежном месте.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="30"&gt;
&lt;li&gt;Далее нужно узнать имя созданного эндпоинта с моделью. В студии на левой панели выбираем вкладку &lt;b&gt;SageMaker resources&lt;/b&gt;, выбираем ресурс &lt;b&gt;Endpoints&lt;/b&gt; и дважды кликаем по имени нашего эндпоинта. Откроется вкладка с деталями, откуда мы сможем скопировать его имя.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(28).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="31"&gt;
&lt;li&gt;Теперь напишем код для обращения к модели извне.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;import boto3
import json
import time
 
endpoint_name = '&amp;lt;my_endpoint_name&amp;gt;'
aws_access_key_id = '&amp;lt;my_aws_access_key_id&amp;gt;'
aws_secret_access_key = '&amp;lt;my_aws_secret_access_key&amp;gt;'
 
sagemaker_runtime = boto3.client(
    &amp;quot;sagemaker-runtime&amp;quot;, 
    region_name='us-east-1',
    aws_access_key_id=aws_access_key_id, 
    aws_secret_access_key=aws_secret_access_key
)
 
data = {
    &amp;quot;inputs&amp;quot;: &amp;quot;Weed is&amp;quot;,
}
 
response = sagemaker_runtime.invoke_endpoint(
    EndpointName=endpoint_name, 
    ContentType='application/json',
    Body=json.dumps(data, ensure_ascii=False).encode('utf8')
)
 
print(response['Body'].read().decode('utf-8'))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;И протестируем:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(29).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="32"&gt;
&lt;li&gt;Стоит отметить, что если следовать описанным выше шагам, то модель будет использовать для генерации параметры по умолчанию. Чтобы добавить кастомную логику загрузки модели, пред- и постобработки данных, предсказания, можно создать файл &lt;b&gt;inference.py&lt;/b&gt; в студии рядом с вашим ноутбуком и там переопределить нужные вам методы. Подробнее о них можно почитать &lt;a href="https://huggingface.co/docs/sagemaker/inference"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;тут&lt;/span&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(30).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Чтобы этот скрипт использовался при развертывании модели, в конструкторе HuggingFaceModel нужно добавить еще один параметр:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;huggingface_model = HuggingFaceModel(
	transformers_version='4.17.0',
	pytorch_version='1.10.2',
	py_version='py38',
	env=hub,
	role=role, 
      model_data='s3://my-bucket-for-gpt2/gpt2-medium-musk.tar.gz',
      entry_point='inference.py'
)&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;Разумеется, для уже созданных эндпоинтов такое изменение не будет учтено. Нужно будет заново задеплоить модель.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;Приведем пример файла &lt;b&gt;inference.py&lt;/b&gt;, который можно использовать для модели GPT-2:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;import json
import torch
from transformers import GPT2Config, GPT2Tokenizer, GPT2LMHeadModel
 
def model_fn(model_dir):
    configuration = GPT2Config.from_pretrained(model_dir, output_hidden_states=False)
    tokenizer = GPT2Tokenizer.from_pretrained(
        model_dir,
        bos_token='&amp;lt;|sos|&amp;gt;', 
        eos_token='&amp;lt;|eos|&amp;gt;', 
        pad_token='&amp;lt;|pad|&amp;gt;'
    )
    model = GPT2LMHeadModel.from_pretrained(model_dir, config=configuration)
    model.resize_token_embeddings(len(tokenizer))
    model.eval()
    return (model, tokenizer)
 
def input_fn(request_body, request_content_type):
    if request_content_type == &amp;quot;application/json&amp;quot;:
        request = json.loads(request_body)
    else:
        request = request_body
    return request
 
def predict_fn(data, model_tokenizer):
    model, tokenizer = model_tokenizer
 
    inputs = data.pop(&amp;quot;inputs&amp;quot;, &amp;quot;&amp;quot;)
    max_length = data.pop(&amp;quot;max_length&amp;quot;, 50)
 
    input_ids = torch.tensor(tokenizer.encode(f'&amp;lt;|sos|&amp;gt;{inputs}')).unsqueeze(0)
    outputs = model.generate(
                input_ids, 
                max_length=max_length,
                bos_token_id=tokenizer.bos_token_id,
                pad_token_id=tokenizer.pad_token_id,
                eos_token_id=tokenizer.eos_token_id, 
                do_sample=True,
                top_k=0,
                top_p=0.95,
                no_repeat_ngram_size=4
    )
    decoded_output = tokenizer.decode(outputs[0])
 
    return {&amp;quot;decoded_output&amp;quot;: decoded_output}&lt;/code&gt;&lt;/pre&gt;&lt;ol start="33"&gt;
&lt;li&gt;В конце работы с ноутбуком в студии &lt;a href="https://docs.aws.amazon.com/sagemaker/latest/dg/notebooks-run-and-manage-shut-down.html"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;нужно будет обязательно вырубить все используемые для этого ресурсы&lt;/span&gt;&lt;/a&gt;. К сожалению, при простом закрытии вкладки со студией, ресурсы не освобождаются, поэтому приходится это делать самостоятельно. В противном случае, с вас будет списываться плата за их использование. Итак, вырубить все ненужное можно в самой студии, выбрав на панели слева вкладку &lt;b&gt;Running Terminal and Kernels&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker2.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;После закрытия ноутбука проверить то, что все ресурсы освобождены, можно на странице Amazon SageMaker. Для этого нужно будет нажать на имя пользователя и посмотреть на статус вашего приложения, тип которого &lt;b&gt;KernelGateway&lt;/b&gt;. Статус должен быть &lt;b&gt;Deleted&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(32).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="34"&gt;
&lt;li&gt;После того, как вы перестанете нуждаться в развернутой модели, нужно будет удалить эндпоинт. Если вы не освободили ресурсы, используемые ноутбуком в студии, то это можно будет сделать прямо оттуда, прописав строку:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;predictor.delete_endpoint()&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;Иначе вы можете удалить эндпоинт, перейдя на страницу сервиса Amazon SageMaker. Там на левой панели нужно будет выбрать вкладку &lt;b&gt;Inference&lt;/b&gt;, в выпадающем списке нажать &lt;b&gt;Endpoints&lt;/b&gt;, затем справа выбрать нужный эндпоинт, нажать &lt;b&gt;Actions&lt;/b&gt; и &lt;b&gt;Delete&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(33).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Также можно будет удалить созданные модели, перейдя в &lt;b&gt;Inference→Models&lt;/b&gt;, и конфигурации эндпоинтов, перейдя в &lt;b&gt;Inference→Enpoint Configurations&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Итак, мы рассказали о том, как развертывать стилизованную модель GPT-2 для получения логических выводов в режиме реального времени (Real-time inference) с помощью Amazon SageMaker. Стоит отметить, что существует несколько вариантов развертывания, каждый из которых имеет свои особенности, например, асинхронность, пакетная обработка, наличие холодного старта, т.д. Использование того или иного варианта зависит от поставленных требований.&lt;/p&gt;
&lt;p&gt;Подробнее про другие механизмы деплоя с помощью Amazon SageMaker читайте &lt;a href="https://docs.aws.amazon.com/sagemaker/latest/dg/deploy-model.html"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;тут&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
</description>
<pubDate>Fri, 28 Oct 2022 10:48:49 +0300</pubDate>
</item>

<item>
<title>Тексты песен: статистика и выводы</title>
<guid isPermaLink="false">140</guid>
<link>http://www.test.leftjoin.ru/all/lyrics-statistical-analysis/</link>
<comments>http://www.test.leftjoin.ru/all/lyrics-statistical-analysis/</comments>
<description>
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/music-data-15.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Разложить песни по нотам — а, точнее, словам — показалось нам весьма амбициозной и интригующей задачей. Не посчитать ли нам данные, которые можно почерпнуть из информации об исполнителях, альбомах, самих песнях, и не сопоставить ли нам эти данные, чтобы узнать немного больше о том, чем мы иногда, по нашему желанию или против нашей воли, кормим наши уши?&lt;/p&gt;
&lt;p&gt;«Отличная мысль!» — подумали мы и приступили к сбору данных.&lt;/p&gt;
&lt;h2&gt;А что в плейлисте?&lt;/h2&gt;
&lt;p&gt;Наш анализ текстов проводился на основе набора данных, взятых с &lt;a href="https://txtsong.ru/"&gt;txtsong.ru&lt;/a&gt;, который включал в себя названия исполнителей, песен, альбомов, год выпуска, жанр, тексты и перевод песен (если они были написаны на иностранном языке).&lt;/p&gt;
&lt;p&gt;C сайта эти данные собирались с помощью HTTP-запросов и парсинга HTML-кода страниц с использованием библиотеки BeautifulSoup. Весь набор мы сначала загрузили в Google Sheets, затем, с помощью библиотеки gspread, метода get, он был считан в DataFrame.&lt;/p&gt;
&lt;p&gt;В результате исходный датасет в DataFrame состоял из 7 колонок: «Исполнитель», «Название песни», «Название альбома», «Год выпуска», «Жанр музыки», «Текст песни» и «Перевод текста песни», и 115 318 строк, каждая из которых — одна песня. В анализируемом наборе данных было представлено 25 265 уникальных российских и иностранных исполнителей, 80 473 уникальных названий песен, 102 жанра и 11 150 альбомов за последние 68 лет.&lt;/p&gt;
&lt;p&gt;Самыми многочисленными по количеству песен оказались такие жанры, как «русский рэп» — 34 334 песни, «русская поп-музыка» — 21 980 песен, «русский рок» — 10 111 песен, «иностранный рэп и хип-хоп» — 8 591 песня и «русская эстрада» — 7 914.&lt;/p&gt;
&lt;h2&gt;Давайте миксовать!&lt;/h2&gt;
&lt;p&gt;Далее мы обработали тексты и переводы всех песен с помощью регулярных выражений и методов библиотеки Pandas: были удалены ненужные символы, знаки препинания, стоп-слова, лишние пробелы. Также мы перевели буквы в нижний регистр.&lt;/p&gt;
&lt;p&gt;Анализ текстов песен мы провели по нескольким направлениям:&lt;/p&gt;
&lt;p&gt;• по жанрам;&lt;br /&gt;
• по годам;&lt;br /&gt;
• по лексическому содержанию песен;&lt;br /&gt;
• по показателю &lt;i&gt;repetitiveness&lt;/i&gt;.&lt;/p&gt;
&lt;p&gt;Для каждой песни мы рассчитали показатель &lt;i&gt;repetitiveness&lt;/i&gt; тремя способами. Первый заключался в том, что для текста каждой песни мы сформировали два списка: из уникальных строк и из всех строк текста. Затем мы рассчитали длины списков, и &lt;i&gt;repetitiveness&lt;/i&gt; была найдена по следующей формуле (1):&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/music-data-1.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Второй способ расчета — это подобие алгоритма Лемпеля—Зива. Он заключался в вычислении размера текста с уникальными строками и всего текста песни в байтах. &lt;i&gt;Repetitiveness&lt;/i&gt; в данном случае рассчитывалась по следующей формуле (2):&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/music-data-2.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Третий способ расчета &lt;i&gt;repetitiveness&lt;/i&gt; заключался в составлении двух списков — списка с уникальными словами песни и списка со всеми словами песни. В этом случае, repetitiveness рассчитывалась по следующей формуле (3):&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/music-data-3.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;h2&gt;Анализ песен по жанрам&lt;/h2&gt;
&lt;p&gt;Итак, приступим! Здесь мы исследовали уникальность лексики в текстах песен разных жанров, выявили наиболее часто встречаемые слова в текстах, проанализировали частоты встречаемости матерных слов и имен собственных, провели кластерный анализ песен и сентимент-анализ текстов по жанрам.&lt;/p&gt;
&lt;p&gt;Распределение числа уникальных слов в песнях по жанрам показало, что на общем фоне заметно выбиваются «рэп и хип-хоп», «русский рэп», «иностранный рэп и хип-хоп» и «регги». Для данных жанров размах количества уникальных слов в песнях более широкий, что может указывать на разнообразность песен.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/music-data-4.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Также стоит отметить, что медианное значение числа уникальных слов в песнях иностранного рэпа и хип-хопа значительно выше по сравнению с остальными жанрами музыки. У таких жанров, как русский рок, русская поп-музыка, русская эстрада, шансон, кавказская музыка, детская музыка, фолк-рок и металкор достаточно маленький размах уникальных слов и строк в песнях, что может говорить об однообразности и схожести песен между собой.&lt;/p&gt;
&lt;p&gt;Любопытно, что среди русского рэпа песен с отсутствием матерных слов больше (51%), чем среди русской поп-музыки (49%), хотя в песнях русского рэпа матерные слова используются в большем количестве.&lt;/p&gt;
&lt;p&gt;Далее: анализ использования имен собственных в песнях различных жанров показал следующее распределение наиболее популярных имен собственных в различных жанрах музыки:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/music-data-5.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Мы обнаружили, что в значительной части текстов песен, относящихся к жанру «рок», используется имя российского комика Александра Долгополова. Возможно, такой шквал популярности связан с его резкими высказываниями по поводу власти и отъездом за границу. Также, в целом, достаточно популярно использование имени «Саша» в песнях данного жанра.&lt;/p&gt;
&lt;h2&gt;Анализ по годам&lt;/h2&gt;
&lt;p&gt;Мы попытались выявить тенденции в текстах песен со всего мира на протяжении 2000—2022 на основании наиболее часто встречаемых слов в текстах, частоты употребляемости матерных слов, кластерного и сентимент-анализа текстов. Также мы посчитали, какие года были более продуктивными в музыкальной индустрии с точки зрения количества релизов песен.&lt;/p&gt;
&lt;p&gt;При анализе наиболее часто встречающихся слов в русской музыке мы выявили, что по мере ухода от 2013 и приближения к 2022 году тексты песен из категории «русский рэп» становятся более мягкими, все чаще начинают звучать слова, касающиеся любви. В текстах песен русского рока сквозь все года за исследуемый период прослеживается тема небес, молитвы и веры во что-то неземное. Тексты песен 2020 года заметно меняют настрой на более позитивный, по сравнению с более ранним периодом.&lt;/p&gt;
&lt;p&gt;Стоит также отметить, что с 2000 по 2022 гг. процент русских и иностранных песен, в которых используются матерные слова, постепенно растет с небольшими перепадами в сторону его уменьшения. Резкий скачок вниз наблюдается в 2003 и 2021 гг. Своего максимума данный показатель достиг в 2022 году и составил 26%:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/music-data-6.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Аналогично, сентимент-анализ песен по годам показал, что процент русских и иностранных песен с негативной эмоциональной окраской в 2019-2020 и 2022 гг. более высокий по сравнению со значениями данного показателя за остальные годы. Это может расцениваться как попытка авторов передать в песнях накал страстей, нарастающий в разных странах или же в отдельно взятом «лирическом герое».&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/music-data-7.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;В целом, такая заметная эмоциональная эмансипация в музыкальном творчестве и обширное прибегание к матерной лексике за последнее десятилетие могли иметь несколько причин, среди которых мы можем предположить:&lt;br /&gt;
• большую свободу, дозволенность в выражении негативных эмоций а также личного опыта в текстах песен нежели ранее (и отсутствие необходимости вуалировать определенные темы в текстах во благо несовершеннолетних слушателей);&lt;br /&gt;
• открытый социальный протест как реакцию на реалии жизни в современном мире;&lt;br /&gt;
• начало пандемии и карантина (2019-2020 гг.);&lt;br /&gt;
• обострение политической ситуации в России и мире (2022 г.).&lt;/p&gt;
&lt;p&gt;Также мы можем отметить, что наибольшее количество песен было выпущено в последние 4 года, а рекордное количество песен увидело свет в 2021 году. В 2022 году количество выпускаемых песен резко сократилось по сравнению с 2021 годом. Хотя 2022 год еще не подошел к концу, мы можем предположить, что к концу года, показатель 2021 года достигнут уже не будет.&lt;/p&gt;
&lt;h2&gt;Анализ по лексическому содержанию песен&lt;/h2&gt;
&lt;p&gt;В рамках этого подхода, мы изучили схожесть и разнообразие лексического состава текстов песен, наиболее часто встречающиеся слова и, отдельно, частоты матерных слов. Также мы провели кластерный и сентимент-анализ текстов песен и сопоставили данные о лексическом содержании текстов песен отдельных исполнителей и конкретных альбомов, чтобы попытаться определить взаимосвязи между показателями в различных анализируемых категориях.&lt;/p&gt;
&lt;p&gt;При выявлении наиболее схожих по текстам пар песен мы получили следующий результат: наиболее похожие песни были обнаружены в русской поп-музыке, поскольку их названия «кричат» о тематике любви — очень часто используются такие слова, как «любовь» и «сердце». Вряд ли эти данные можно было бы отнести к рэпу, инди, року или любому другому жанру музыки. В топ 5 пар схожих песен в категории «русская поп-музыка» вошли такие пары песен, как «Налетели вдруг дожди» и «Али ты», «Где же ты, где» и «Только ты»,  «Я тебя не люблю» и «Зелёный свет», «Смотрю на тебя» и «Не зарекайся», «Шатер из звезд» и «Ритмы людей». Об этом может свидетельствовать приведенная ниже тепловая карта (&lt;i&gt;heatmap&lt;/i&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/music-data-8.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Если говорить наоборот о разнообразии лексического состава текстов песен, среди исполнителей, выпустивших более 150 альбомов, первое место по наибольшему медианному числу уникальных слов в песнях занимает Александр Розенбаум. Примерно на том же уровне по данному показателю находятся Владимир Высоцкий и ДДТ.&lt;/p&gt;
&lt;p&gt;Остальные исполнители отстают по данному показателю. Наименьшее медианное число уникальных слов в песнях имеет Ленинград — всего 55 слов. Кстати, наибольший размах числа уникальных слов наблюдается в песнях Владимира Высоцкого, наименьший — у Ирины Алегровой, Ани Лорак и Стаса Михайлова:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/music-data-9.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;В рамках сентимент-анализа песен мы установили, что в исследуемом наборе данных преобладают песни с негативной коннотацией (их примерно половина):&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/music-data.jpg"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Также стоит отметить, что песни с позитивной коннотацией в основном несут в себе тематику светлой и доброй любви. Кстати, не только к противоположному полу, но и к родителям! Такие песни характеризуются почти полным отсутствием матерных слов и ругательств, использованием в достаточно малом количестве слов с негативной окраской и, наоборот, большим количеством позитивных и добрых слов, например «люблю», «бурно», «прикосновения», «счастье», «мечты», «целуй», «мама», «любимые», «вселенная» и пр.&lt;/p&gt;
&lt;p&gt;Матерные слова в текстах таких песен, как правило, обыгрываются с позитивной окраской, например, в таком словосочетании, как «восхитительно ох*енная». Кроме того, названия таких песен несут в себе по большей части только позитивную лексику, например «Обняла. Люблю. Целую», «Восхитительно» или «Лучшая ночь».&lt;/p&gt;
&lt;p&gt;А теперь разбавим лирику: при анализе по отдельным исполнителям, наиболее матерными песнями оказались «Fuck You» исполнителя Lil Butterfly («иностранный рэп и хип-хоп»), текст которой включает 132 матерных слова и песня Ноггано «Застрахуй» из категории «руский рэп», которая также включает в себя более 100 матерных слов.&lt;/p&gt;
&lt;p&gt;Затем, на примере русского рэпа мы определили, для каких отдельных исполнителей и в каких конкретных альбомах наблюдается частое использование матерных слов: наибольшее число матерных слов использует Скриптонит и 104, наименьшее — 10AGE.&lt;/p&gt;
&lt;p&gt;Отметим, что панк музыканты, даже больше, чем русские рэпперы и рокеры вместе взятые, употребляют матерные слова в своих песнях, особенно группы Мятная Фанта, кис-кис, Казенный Унитаз и Порнофильмы.&lt;/p&gt;
&lt;h2&gt;Анализ показателя &lt;i&gt;Repetitiveness&lt;/i&gt;&lt;/h2&gt;
&lt;p class="note"&gt;Repetitiveness — «повторяемость»&lt;/p&gt;
&lt;p&gt;Изучив данные, мы пришли к выводу, что среднее значение показателя &lt;i&gt;repetitiveness&lt;/i&gt; в последние 3 года возросло на свой рекордный уровень и достигло 34%. Также в 2011 году наблюдалось резкое падение данного показателя до 25%. Кроме того, интересно заметить, что за период с 2015 по 2020 гг. наблюдался равномерный рост среднего значения &lt;i&gt;repetitiveness&lt;/i&gt; на 1 п.п. в год. С 2000 по 2011 гг. среднее значение данного показателя было весьма нестабильно и колебалось в интервале от 25% до 33%.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/music-data-11.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Медианное значение &lt;i&gt;repetitiveness&lt;/i&gt;, по сравнению со средним, в последние 2 года различается. Пик был достигнут в 2021 году, а в 2022 году значение данного показателя снизилось на 1 п.п. Так же, как и для среднего значения, медианное значение &lt;i&gt;repetitiveness&lt;/i&gt; в 2011 году достигает своего минимума, но более низкого, нежели для среднего, и равного 23%.&lt;/p&gt;
&lt;p&gt;Равномерный рост, который был справедлив для среднего значения &lt;i&gt;repetitiveness&lt;/i&gt; с 2015 по 2020 гг., для медианного значения отсутствует. Он здесь наблюдается только с 2018 по 2021 гг.:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/music-data-12.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;При анализе среднего значения &lt;i&gt;repetitiveness&lt;/i&gt; по жанрам мы заметили, что для всех представленных жанров музыки наблюдается тенденция к росту &lt;i&gt;repetitiveness&lt;/i&gt;.&lt;/p&gt;
&lt;p&gt;Наибольшее среднее значение показателя было выявлено среди песен поп-музыки в 2002 году, оно равнялось 50%, но затем в последующие году снизилось и находилось преимущественно в интервале от 30% до 40%. Среднее значение же в этом жанре отличается своей стабильностью в течение 22 лет и находится около 40%.&lt;/p&gt;
&lt;p&gt;Еще один примечательный факт: у песен, в текстах которых отсутствуют матерные слова, среднее значение &lt;i&gt;repetitiveness&lt;/i&gt; всегда выше (по крайней мере с 2000 по 2022 гг.):&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/music-data-13.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;И на десерт: любопытно, что среди всех прочих исполнителей особое место занимает феномен Григория Лепса. В его дискографии присутствует как альбом с достаточно высоким средним значением &lt;i&gt;repetitiveness&lt;/i&gt;, достигающим почти 50%, так и альбом с нулевым значением этого показателя:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/music-data-14.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
</description>
<pubDate>Mon, 24 Oct 2022 00:34:15 +0300</pubDate>
</item>

<item>
<title>Альтернатива голубому и розовому: какие цвета использовать для визуализации гендерных данных?</title>
<guid isPermaLink="false">137</guid>
<link>http://www.test.leftjoin.ru/all/colors-for-gender-data/</link>
<comments>http://www.test.leftjoin.ru/all/colors-for-gender-data/</comments>
<description>
&lt;p class="note"&gt;Перевод статьи &lt;a href="https://blog.datawrapper.de/gendercolor/"&gt;“An Alternative to Pink &amp; Blue: Colors for Gender Data”&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;В своем блоге Лиза Шарлотта Мут решила взяться за вопрос классической комбинации «голубой + розовый» для отображения данных о мужчинах и женщинах. Проведя исследование альтернативных цветовых дуэтов, она делится новаторскими решениями ведущих новостных изданий. Готовы открыть для себя новые способы цветового обозначения гендерных данных? Тогда поехали!&lt;/p&gt;
&lt;p class="note"&gt;Чжон Ми Юн, проект &lt;b&gt;&lt;a href="http://www.jeongmeeyoon.com/aw_pinkblue.htm" class="nu"&gt;«&lt;u&gt;Голубое и Розовое&lt;/u&gt;»&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/JeongMee-Yoon.jpg"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Несколько недель назад, я написала статью в своем блоге о том, &lt;b&gt;&lt;a href="https://blog.datawrapper.de/colors/"&gt;что учесть при выборе цветов для визуализации данных&lt;/a&gt;&lt;/b&gt;. В ней я отметила, что необходимо избегать стереотипной пары &lt;strong&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;«голубой для мужчин»&lt;/span&gt;&lt;/strong&gt; и &lt;strong&gt;&lt;span style="color:#d4639b" class="has-inline-color"&gt;«розовый для женщин»&lt;/span&gt;&lt;/strong&gt; при визуализации гендерно-значимых данных. Джон Берн-Мердок из Financial Times &lt;b&gt;&lt;a href="https://twitter.com/jburnmurdoch/status/1002253114949300225"&gt;твитнул в ответ&lt;/a&gt;&lt;/b&gt;, как разные новостные редакции используют определенные цвета в своих репортажах о неравенстве в оплате труда мужчин и женщин в Великобритании.&lt;/p&gt;
&lt;p&gt;Эта статья идет в продолжение того твита. Я начну сразу же с примеров новостной графики, где используются розовый и голубой, а также добавлю примеры, где обыграны другие цвета, чтобы объяснить, почему классическая комбинация «розовый + голубой» — не самый лучший выбор. После этого, мы рассмотрим два варианта как уйти от этого цветового сочетания при визуализации гендерно-значимых данных. А начнем с плохим новостей:&lt;/p&gt;
&lt;h2&gt;Розовый + голубой все еще с нами&lt;/h2&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/pink-and-blue1.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Розово-голубая эпоха еще не канула в Лету. Как ни странно, достаточно крупные проекты использовали эти цвета в последние четыре года, чтобы наглядно рассказать о гендерно-ориентированной статистике, например, The New York Times, Bloomberg и The Wall Street Journal. Два проекта &lt;b&gt;&lt;a href="https://pudding.cool/"&gt;The Pudding&lt;/a&gt;&lt;/b&gt;, увидевшие свет в 2017, которые исследовали тему гендера в кино, использовали &lt;strong&gt;&lt;span style="color:#FF0000" class="has-inline-color"&gt;красный&lt;/span&gt;&lt;/strong&gt; и &lt;strong&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;голубой&lt;/span&gt;&lt;/strong&gt;, а третий проект, тоже из 2017, &lt;a href="https://pudding.cool/2017/07/comics/" class="nu"&gt;«&lt;u&gt;Гендерный анализ персонажей комиксов&lt;/u&gt;»&lt;/a&gt;, использовал &lt;strong&gt;&lt;span style="color:#285572" class="has-inline-color"&gt;темно-синий для мужчин&lt;/span&gt;&lt;/strong&gt; и &lt;strong&gt;&lt;span style="color:#edb43a" class="has-inline-color"&gt;золотой для женщин&lt;/span&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Слева — оттенки для обозначения мужчин, справа — женщин.&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://pudding.cool/2017/03/film-dialogue/"&gt;The Pudding, 2017: «Диалог о кино»&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://pudding.cool/2017/08/screen-direction/"&gt;The Pudding, 2017: «Она хихикает, он на коне»&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://www.bloomberg.com/graphics/2016-who-marries-whom/"&gt;Bloomberg, 2016: «Наглядно: кто выбирает себе в пару директоров, врачей, поваров и дворников»&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://www.nytimes.com/interactive/2015/02/23/business/economy/the-changing-nature-of-middle-class-jobs.html"&gt;The New York Times, 2015: «Как меняется характер работ среднего класса»&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://www.nytimes.com/interactive/2017/11/07/upshot/modern-love-what-we-write-when-we-write-about-love.html"&gt;The New York Times, 2017: «Слова, которые используют мужчины и женщины, когда пишут о любви»&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="http://graphics.wsj.com/gender-pay-gap/"&gt;The Wall Street Journal, 2016: «Неравенство в оплате труда»&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="http://www.dailymail.co.uk/health/article-5581849/The-gender-pay-gap-NHS.html"&gt;DailyMail, 2018&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt; &lt;b&gt;&lt;a href="https://www.zeit.de/gesellschaft/familie/2016-07/kinderwunsch-studie-frauen-sinus-milieus-elterngeld-kinderbetreuung/seite-2"&gt;ZEIT, 2016&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://www.zeit.de/feature/strassenverzeichnis-strassennamen-herkunft-deutschland-infografik"&gt; ZEIT, 2018&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/pink-and-blue-table0.png"  border="0" width="60%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Почему? Итак, существует один аргумент в пользу использования розового и голубого: &lt;b&gt;читатели смогут быстрее разобраться в данных, представленных на графике, если используются эти стереотипные цвета&lt;/b&gt;. Нет даже нужды сначала объяснять значения цветов. Алан Смит из Financial Times написал об этом в колонке &lt;a href="https://www.ft.com/content/c4b7d8be-2eb0-11e8-9b4b-bc4b9f08f381" class="nu"&gt;«&lt;u&gt;The Chart Doctor&lt;/u&gt;»&lt;/a&gt;: после того, как он создал таблицу с детскими именами, используя розовый и голубой,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;«Я привел аргумент в пользу такого решения, так как устойчивая культурная традиция в использовании этих цветов снимала с меня обязанность добавлять отдельную легенду на таблицу. Это, в свою очередь, делало мою иллюстрацию “чище”, к чему все дизайнеры и стремятся».&lt;br /&gt;
&lt;i&gt;Алан Смит&lt;/i&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p class="note"&gt;1. Неудивительно, что именно женщин заботит эта проблема. Энди Керк из visualisingdata.com &lt;b&gt;&lt;a href="http://www.visualisingdata.com/2015/02/poll-results-lets-think-blue-pink/"&gt;провел опрос&lt;/a&gt;&lt;/b&gt; среди 126 дизайнеров по визуализации данных (76 мужчин, 50 женщин) в 2015 году, на тему того, какие цвета они бы назначили для обозначения каждого пола. Среди мужчин, 41% использовали бы розовый и голубой. Среди женщин, такую комбинацию выбрали лишь 14%.&lt;/p&gt;
&lt;p&gt;В чем же проблема с розовым и голубым? В западной культуре, эти цвета идут в комплекте с целым чемоданом гендерных стереотипов. Розовый рисует в воображении слабых, стеснительных девочек, которые играют с куклами и не заслуживают тех же привилегий, что и мальчики. Голубой соотносится с мальчиками, которым нужно быть сильными и грубоватыми. &lt;b&gt;Когда мы создаем график с розовым и голубым, мы поддерживаем гендерные стереотипы.&lt;/b&gt; [1] Но когда мы изображаем неравенство полов (в оплате труда, например), это именно того, чего бы нам хотелось избежать.&lt;/p&gt;
&lt;h2&gt;Многие новостные редакции воздерживаются от использования розового и голубого&lt;/h2&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/pink-and-blue2.png"  border="0" width="100%" height="1000%"&gt;&lt;/p&gt;
&lt;p&gt;А теперь к хорошим новостям: &lt;b&gt;в то время как некоторые продолжают использовать эту комбинацию, розовый + голубой перестали быть золотым стандартом, по крайней мере в крупных новостных компаниях&lt;/b&gt;. Когда в этом году в Великобритании вышла статистика с данными о неравенстве в оплате труда мужчин и женщин, дизайнеры визуализации использовали очень разнообразную цветовую палитру. До этого я предполагала, что они будут использовать голубой для мужчин и какой-то теплый цвет для женщин. Но меня ждал сюрприз: The Economist, Guardian, The Telegraph, Washington Post и другие &lt;b&gt;использовали более холодный цвет для женщин, чем для мужчин&lt;/b&gt;. Вау! Более сильное отступление от шаблона и представить нельзя. Вот несколько примеров (не только из статистики этого года о неравенстве в оплате труда мужчин и женщин).&lt;/p&gt;
&lt;p&gt;И снова, слева — оттенки для обозначения мужчин, справа — женщин.&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://www.economist.com/leaders/2018/04/07/how-to-narrow-britains-gender-pay-gap"&gt;Economist, 2018&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://interaktiv.morgenpost.de/gender-pay-gap/"&gt;Morgenpost, 2017&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://www.theguardian.com/news/ng-interactive/2018/apr/05/women-are-paid-less-than-men-heres-how-to-fix-it"&gt;Guardian, 2018&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://www.ft.com/content/cfa7a81a-38df-11e8-8b98-2f31af407cc8"&gt;Financial Times, 2018&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://www.telegraph.co.uk/women/business/women-mean-business-interactive/"&gt;The Telegraph, 2018&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://informationisbeautiful.net/visualizations/gender-pay-gap-us/"&gt;Information Is Beautiful, 2014&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://www.washingtonpost.com/graphics/2017/business/women-pay-gap/?utm_term=.17891138f587"&gt;Washington Post, 2017&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt; &lt;b&gt;&lt;a href="https://www.bloomberg.com/graphics/2018-uk-gender-pay-gap/uk-gender-pay-gap/"&gt;Bloomberg, 2018&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://www.bbc.com/news/uk-england-41805053"&gt;BBC, 2017&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://www.bbc.com/news/business-43632763"&gt;BBC, 2018&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/pink-and-blue-table02.png"  border="0" width="60%" height="100%"&gt;&lt;/p&gt;
&lt;h2&gt;В новостных редакциях нет единого подхода&lt;/h2&gt;
&lt;p&gt;Мне очень нравится разнообразие цветов, которые используются для визуализации гендерно-значимых данных в различных изданиях. Но в визуализации данных существует такая мантра: в статье каждый отдельный цвет должен символизировать только одно явление. Если ваш первый график использует голубой цвет для данных о мужчинах, этот цвет не может отображать данные о женщинах во втором графике. Стоит ли придерживаться этого же подхода для всех графиков в рамках одного выпуска?&lt;/p&gt;
&lt;p&gt;По-видимому, дизайнеры визуализации не видят в этом необходимости. Если мы посмотрим на цвета, которые дизайнеры используют в отдельном новостном издании, мы не увидим постоянства. &lt;b&gt;Для каждой отдельной статьи, решение об используемых цветах для гендерно-значимых данных принимается индивидуально.&lt;/b&gt; Внизу вы можете увидеть примеры цветов, которые использует Bloomberg, но я могла бы взять любое другое издание — The New York Times, Guardian, ZEIT Online и так далее.&lt;/p&gt;
&lt;p&gt;Левая колонка — цвета для обозначения мужчин, правая — женщин.&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://www.bloomberg.com/graphics/2018-women-professional-inequality-college/"&gt;Bloomberg, 2018&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://www.bloomberg.com/news/features/2018-03-29/the-gender-pay-gap-is-real-say-new-numbers-from-the-u-k"&gt;Bloomberg, 2018&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://www.bloomberg.com/graphics/2018-uk-gender-pay-gap/uk-gender-pay-gap/"&gt;Bloomberg, 2018&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://www.bloomberg.com/graphics/2018-female-billionaires/"&gt;Bloomberg, 2018&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/pink-and-blue-table03.png"  border="0" width="60%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Но даже в изданиях, где цвета обычно используются предсказуемо, подходы к гендерно-значимой графике не такие однозначные. Основные цвета, которые использует издание Quartz в своих графиках — это &lt;a href="https://www.theatlas.com/charts/popular"&gt;розовый и голубой&lt;/a&gt;, поэтому мне стало любопытно, какие цвета они применяют при сопоставлении данных о мужчинах и женщинах. Выяснилось, что они и сами в неведении. Почти все авторы в Quartz согласны в использовании розового и голубого. Вот только они не решили, какой цвет соответствует какому полу.&lt;/p&gt;
&lt;p&gt;Cлева — цвета, которые использовались для мужчин, справа — для женщин.&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://qz.com/490332/omg-the-social-media-gender-gap-is-narrowing/"&gt;Quartz, 2015&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://qz.com/564354/the-state-of-internet-connectivity-in-the-world-in-9-charts/"&gt;Quartz, 2015&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://qz.com/458138/where-indians-are-committing-suicide-and-why/"&gt;Quartz, 2015&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://qz.com/775470/facebook-fb-has-a-massive-gender-divide-in-india/"&gt;Quartz, 2016&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://qz.com/692985/youre-a-girl-and-other-obstacles-to-becoming-an-entrepreneur-in-south-africa/"&gt;Quartz, 2016&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://qz.com/905643/one-of-nigerias-foremost-islamic-leaders-wants-to-turn-northern-mosques-into-schools/"&gt;Quartz, 2017&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://qz.com/1104106/bollywood-has-a-very-real-gender-problem-an-analysis-of-4000-films-reveal/"&gt;Quartz, 2017&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://qz.com/1149300/why-starting-a-business-as-a-woman-is-so-hard-in-five-charts/"&gt;Quartz, 2017&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://qz.com/1259463/women-should-manage-their-savings-like-theyll-live-to-100/"&gt;Quartz, 2018&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://qz.com/1187410/women-are-underrepresented-in-economics-textbooks-a-study-finds/"&gt;Quartz, 2018&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/pink-and-blue-table4.png"  border="0" width="60%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Мне такая практика кажется опасной. &lt;b&gt;Графики, которые меняют роли классического значения цветов, могут затруднять восприятие информации.&lt;/b&gt; Когда читатели видят розовый и голубой в графиках о гендерных данных, они почти наверняка не станут консультироваться с легендой.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/pink-and-blue3.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Использование розового для мужчин и голубого для женщин в графиках о неравенстве полов может иметь &lt;b&gt;негативные последствия&lt;/b&gt;: вместо того, чтобы поставить стереотипы под сомнение, читатель может ошибочно подумать, «О, а женщины-то себя здесь лучше ощущают, чем мужчины». Это в частности создает проблемы, когда читатели привыкли к тому, что то же самое издание использует комбинацию розового и голубого в «правильной», классической интерпретации.&lt;/p&gt;
&lt;h2&gt;Как выбирать цвета: пример The Telegraph&lt;/h2&gt;
&lt;p&gt;Следуя зову любопытства о том, как &lt;a href="https://www.telegraph.co.uk/women/business/women-mean-business-interactive/"&gt;The Telegraph&lt;/a&gt; решили использовать &lt;strong&gt;&lt;span style="color:#8624f5" class="has-inline-color"&gt;сиреневый для женщин&lt;/span&gt;&lt;/strong&gt; и &lt;strong&gt;&lt;span style="color:#1fc3aa" class="has-inline-color"&gt;зеленый для мужчин&lt;/span&gt;&lt;/strong&gt;, я спросила &lt;a href="https://twitter.com/fraserlyness?lang=en"&gt;Фрейзера Лайнесса&lt;/a&gt;, директора графического репортажа в The Telegraph. Его ответ меня впечатлил:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/pink-and-blue4.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Такой выбор цвета был вдохновлен кампанией «Votes for Women», которая была частью движения суфражисток в Великобритании в начале XX века. В сердце этого движения стояли «&lt;b&gt;&lt;a href="https://en.wikipedia.org/wiki/Sylvia_Pankhurst"&gt;Сильвия Панкхерст&lt;/a&gt;&lt;/b&gt; и &lt;b&gt;&lt;a href="https://en.wikipedia.org/wiki/Emmeline_Pethick-Lawrence,_Baroness_Pethick-Lawrence"&gt;Эммелина Петик-Лоуренс&lt;/a&gt;&lt;/b&gt;. А цвета, в сущности, были выбраны ими самими (символизируя: сиреневый — свободу и честь, белый — чистоту, зеленый — надежду)».&lt;/p&gt;
&lt;p&gt;Фрейзер и его команда ввели в использование эти цвета в свои графики о неравенстве полов, тонко, но при этом недвусмысленно, намекая о необходимости равенства.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/pink-and-blue5.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;В последних графиках (например, &lt;b&gt;&lt;a href="https://www.telegraph.co.uk/news/2018/03/22/large-companies-publishing-gender-pay-gap-figures-need-know/"&gt;здесь в платной версии&lt;/a&gt;&lt;/b&gt; или в этой статье &lt;b&gt;&lt;a href="https://www.telegraph.co.uk/women/business/women-mean-business-interactive/" class="nu"&gt;«&lt;u&gt;Рожденные равными. Неравные по жизни&lt;/u&gt;»&lt;/a&gt;&lt;/b&gt;), женщины представлены сиреневым, а мужчины — зеленым. Но как было принято такое решение?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;«Когда мы думали над тем, каким цветом обозначить каждый пол, это была скорее попытка дать фору женщинам в этой гендерной эстафете. &lt;b&gt;На контрасте с белым, сиреневый выделяется гораздо ярче и привлекает больше внимания по сравнению с зеленым.&lt;/b&gt; Не значительно больше, но достаточно, чтобы склонить чашу весов. Во многих графиках, мужчины часто превышают по количеству женщин, поэтому нам хотелось иметь простой способ вернуть их в центр внимания».&lt;br /&gt;
&lt;i&gt;Фрейзер Лайнесс&lt;/i&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Можно легко заметить подобный эффект в графиках, похожий на этот:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/pink-and-blue6.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;При грамотной подготовке проект принесет плоды. Фрейзер смог не только отобрать два красивых цвета, но и обосновать такой выбор весомой причиной. «Имея в качестве отсылки движение суфражисток, мне было проще принять окончательное решение», — сказал он мне.&lt;/p&gt;
&lt;p&gt;Жаловались ли его коллеги, что более привычные для глаза розовый и голубой не были использованы в проекте? Видимо, нет:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;«В целом, никто не возражал против идеи о том, что эти цвета не будут так же легко идентифицироваться, как и комбинация розовый + голубой. Скорее наоборот, им казалось, что привычная цветовая пара может навредить тому посланию, которое мы надеялись передать».&lt;br /&gt;
&lt;i&gt;Фрейзер Лайнесс&lt;/i&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Новая жизнь розового и голубого&lt;/h2&gt;
&lt;p&gt;Попытка уйти от розового и голубого в гендерно-ориентированных данных — это задача с двумя фронтами. С одной стороны, данные о мужчинах и женщинах необходимо отображать с помощью других цветов. С другой стороны, комбинация розовый + голубой также нуждается в переосмыслении. И я рада поделиться с вами примерами того, как визуализация данных меняется в этом направлении. Первые три примера выиграли награды &lt;b&gt;&lt;a href="https://www.informationisbeautifulawards.com/"&gt;Kantar Information Is Beautiful Awards 2017&lt;/a&gt;&lt;/b&gt;, а последний из них — это работа &lt;b&gt;&lt;a href="https://twitter.com/jburnmurdoch"&gt;Джона Берн-Мердока&lt;/a&gt;&lt;/b&gt;, которому награда Information Is Beautiful Award в целом-то и не нужна. Все примеры переосмысляют розовый и голубой. И ни один не отображает гендерно-значимые данные:&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://adventuresinmapping.com/2017/04/18/lights-on-lights-out/" class="nu"&gt;«&lt;u&gt;Включен &amp; выключен&lt;/u&gt;»&lt;/a&gt;&lt;/b&gt; Джона Нельсона&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/pink-and-blue7.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="http://sciencepaths.kimalbrecht.com/" class="nu"&gt;«&lt;u&gt;Пути науки&lt;/u&gt;»&lt;/a&gt;&lt;/b&gt; Кима Албрехт&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/pink-and-blue8.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="http://projects.christianlaesser.com/travel-visa-inequality/" class="nu"&gt;«&lt;u&gt;Неравенство туристической визы&lt;/u&gt;»&lt;/a&gt;&lt;/b&gt; Кристиана Лэссера&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/pink-and-blue9.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p class="note"&gt;&lt;b&gt;&lt;a href="https://twitter.com/jburnmurdoch/status/932200041305071621" class="nu"&gt;«&lt;u&gt;Регионы Англии наиболее уязвимые для будущего экономического давления&lt;/u&gt;»&lt;/a&gt;&lt;/b&gt; Джона Берн-Мердока&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/pink-and-blue10.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;p&gt;Мне приятно видеть, что многие новостные издания отказываются от розового и голубого. &lt;b&gt;Спасибо каждому, кто пересматривает свои цвета перед тем, как опубликовать что-либо.&lt;/b&gt; Спасибо каждому, кто экспериментирует с цветами для гендерно-значимых данных, даже если это по началу кажется плохой идеей. Вы заслуживаете похвалы. Также спасибо Джону Берн-Мердоку за вдохновение и Фрейзеру Лайнессу за ответы на мои вопросы о цветах, которые использует The Telegraph.&lt;/p&gt;
</description>
<pubDate>Tue, 20 Sep 2022 13:08:40 +0300</pubDate>
</item>


</channel>
</rss>