43 заметки с тегом

python

Анализируем речь с помощью Python: Сколько раз в минуту матерятся на интервью YouTube-канала «вДудь»?

Время чтения текста – 19 минут

Выход практически каждого ролика на канале «вДудь» считается событием, а некоторые из этих релизов даже сопровождаются скандалами из-за неосторожных высказываний его гостей.
Сегодня при помощи статистических подходов и алгоритмов ML мы будем анализировать прямую речь. В качестве данных используем интервью, которые журналист Юрий Дудь (признан иностранным агентом на территории РФ) берет для своего YouTube-канала. Посмотрим с помощью Python, о чем таком интересном говорили в интервью на канале «вДудь».

Сбор данных

C помощью YouTube API мы получили список всех видео с канала Юрия Дудя, а также их метаинформацию. О том, как это сделать, вы можете узнать, например, из статьи нашего блога.
Если вы уже слышали знаменитое “Юрий будет дуть, дуть будет Юрий”, то наверняка знаете, что на этом канале есть документальные фильмы, а также интервью, в которых участвуют сразу несколько гостей. Нас заинтересовали только те выпуски, в которых преимущественно говорит только один гость. Поэтому нам пришлось провести фильтрацию всех видео вручную.
Для дальнейшего анализа нам необходимо было получить длительности роликов. Это мы сделали с помощью GET-запросов к YouTube API. Результаты приходили в специфическом формате (для примера: “PT1H49M35S”), поэтому их нам пришлось распарсить и перевести в секунды.
Итак, мы получили датафрейм, состоящий из 122 записей:

На основе метаинформации по лайкам, комментариям и просмотрам мы построили следующий Bubble Chart:

Так как наша цель — проанализировать речь в интервью, нам необходимо было получить текстовые составляющие роликов. В этом нам помог API-интерфейс youtube_transcript_api, который скачивает субтитры из видео на YouTube. Для каких-то роликов субтитры были прописаны вручную, но для большинства они были сгенерированы автоматически. К сожалению, для 10 видео субтитров не оказалось: беседы с L’one, Шнуром, Ресторатором, Амираном, Ильичом, Ильей Найшуллером, Соболевым, Иваном Дорном, Навальным, Noize MC. Причину их отсутствия мы, к сожалению, понять не смогли.

А гости кто?

Спектр рода деятельности гостей канала «вДудь» достаточно обширен, поэтому было решено пополнить исходные данные информацией о том, чем же в основном занимается приглашенный участник каждого интервью. К сожалению, ролики не сопровождаются четкими метками профессиональной принадлежности гостя, поэтому мы прописали эту информацию сами. На момент выгрузки данных последним видео на канале был разговор с комиком Дмитрием Романовым.
Если с идентификацией профессии каждого гостя мы не ошиблись, то вот такое распределение в итоге получается:

Музыканты, рэперы и актеры — самые частые гости Юрия, скорее всего, они являются самыми интересными для автора и аудитории. Представителей научного сообщества (астрофизик, историк, экономист и т.д) наоборот, гораздо меньше, ведь научно-популярные интервью — прерогатива других интервьюеров.

Обработка текста

Анализ текстовой информации сложен в той степени, в какой сложен язык, на котором написан текст. Подробно о подготовке текста к анализу мы рассказывали в материале «Python и тексты нового альбома Земфиры». Тут была проведена идентичная работа.
Как и раньше, для решения аналитической задачи мы решили использовать такой подход как лемматизация, т. е. приведение слова к его словарной форме. Проведя лемматизацию текстовых данных по правилам русского языка, мы получим существительные в именительном падеже единственного числа (кошками — кошка), прилагательные в именительном падеже мужского рода (пушистая — пушистый), а глаголы в инфинитиве (бежит — бежать). В этом проекте мы опять воспользовались библиотекой Pymorphy, представляющую собой морфологический анализатор.
Помимо приведения к словарной форме нам потребовалось убрать из текстов часто встречающиеся слова, которые не несут ценности для анализа. Это было необходимо, потому что так называемые стоп-слова могут повлиять на работу используемой модели машинного обучения. Список таких слов мы взяли из пакета ntlk.corpus, а после расширили его, изучив тексты интервью. Конечно, мы также убрали все знаки пунктуации.

Анализ словарного запаса

После обработки текста мы посчитали для каждого интервью количество всех слов, а также абсолютное и относительное количество уникальных слов. Конечно, полученные значения неидеальны, так как, во-первых, для большинства интервью были получены автоматически сгенерированные субтитры, которые являются неточными, а во-вторых, тексты были очищены от лишней информации.
Сперва мы решили наглядно представить основной массив лексики, которая звучит в интервью. После группировки интервью по роду деятельности гостя нам удалось это сделать и в этом нам помогла библиотека wordcloud. У нас получились такие облака слов:

Лейтмотивом всех интервью Юрия являются обсуждение России (политики, социальной жизни и других особенностей), уровня заработка гостей, а также непосредственно профессиональной деятельности гостя (это особенно заметно у представителей индустрии кинопроизводства).
Далее мы решили построить боксплот для количества слов для каждого рода деятельности (профессии, которые были представлены единственным гостем, мы не стали учитывать):

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

Что касается количества уникальных слов, то тут ситуация аналогичная. И рэперы опять в аутсайдерах…

Если говорить об отношении уникальных слов к общему количеству, то тут можно увидеть совершенно иную картину. Теперь впереди оказываются, рэперы, музыканты и бизнесмены. Предыдущие же лидеры, наоборот, становятся самыми последними.
Конечно, стоит отметить, что такие сравнения могут быть несправедливыми, так как длительность интервью у каждого гостя Дудя разная, а потому кто-то просто мог успеть наговорить больше слов, чем остальные. Наглядно в этом можно убедиться, взглянув на распределение длительности интервью по роду деятельности (для построения использовался тот же пул гостей, что и для боксплотов выше):

К тому же, разные роды деятельности представляет разное количество человек, это тоже могло сказаться на результатах.
Далее мы составили список слов, появление которых в интервью было бы интересно отследить, и посмотрели как часто они упоминаются для каждого рода деятельности. Также мы решили учесть дисбаланс среди представителей разных профессиональных категорий и разделили полученные частоты на соответствующее количество гостей.

Первое место по упоминаниям очевидно занимает Россия. Что касается Запада, то про США гости говорили в 2,5 раза меньше. Что касается лидера РФ, то про него речь заходила достаточно часто. Его оппонент, Алексей Навальный, в этой словесной “баталии” потерпел поражение. Интересно, что политики далеко не в топе по упоминаниям Путина. Впереди оказался экономист Сергей Гуриев, после него ведущий Александр Гордон, а тройку замкнули журналисты.
Глагол “любить” чаще использовали люди, имеющие отношение к искусству, творчеству и гуманитарным наукам — кинокритик Антон Долин, мультипликатор Олег Куваев, историк Тамара Эйдельман, актеры, рэперы, художник Федор Павлов-Андреевич, комики, музыканты, режиссеры. Про страхи (если судить по глаголу “бояться”) гости говорили реже, чем о любви. В топ вошли историк Эйдельман, дизайнер Артемий Лебедев, кинокритик Долин и политики. Может быть в этом кроется ответ на вопрос, почему же политики не так охотно произносили имя президента России.
Что касается денег, то о них говорили все. Ну, за исключением человека науки, астрофизика Константина Батыгина. С церковью же имеем совершенно обратную ситуацию. О ней по большей части говорили только писатели и художник Павлов-Андреевич.

Анализ мата

Далее мы решили проанализировать то, как часто гости Юрия Дудя ругались матом. С помощью регулярных выражений мы составили словарь матерных слов со всех интервью. После этого, для каждого ролика было подсчитано суммарное количество вхождений элементов составленного словаря.
Мы построили диаграммы, отражающие топ-10 любителей нецензурно выражаться по количеству “запрещенных” слов в минуту.

Как видим, рэперы и музыканты почти полностью захватили топ. Помимо них очень часто ругались такие гости как блогер Данила Поперечный и комики Иван Усович и Алексей Щербаков. Первое место в рейтинге с большим отрывом от остальных держит Morgenstern (признан иностранным агентом на территории РФ), а вот Олег Тиньков в своем последнем интервью матерился не так много, чтобы попасть в Топ-10.
Зато, как искрометно!

После персонального анализа мы решили узнать, насколько насыщена нецензурными словами речь представителей разных профессиональных групп. Нулевые показатели при этом были опущены.

Ожидаемо, что больше всех матерились рэперы. На втором месте оказались блогеры (по большей части за счет Поперечного). За ними следует Артемий Лебедев, единственный дизайнер в нашей выборке, благодаря разнообразия речи которого, представители этой профессии и попали в топ-3 этого распределения. Кстати, если вы еще не знакомы с нашим анализом телеграм-канала Лебедева, то мы не понимаем, чего же вы ждете! Несмотря на то что генератор постов Артемия Лебедева сейчас выключен, исследование его телеграм-канала все равно заслуживает вашего внимания.

Ограничения анализа

Стоит отметить, что в нашем небольшом исследовании есть два недостатка:

  1. Как уже говорилось ранее, мы не смогли отделить слова гостей Дудя от речи Юрия, который и сам зачастую не брезгует использовать нецензурные выражения. Однако, задача интервьюера — подстроиться под стиль речи гостя, поэтому, скорее всего, результаты бы не сильно изменились.
  2. В автосгенерированных субтитрах нам встретилось некое подобие цензуры — некоторые слова были заменены на ‘[ __ ]’. Тут можно выделить несколько интересных моментов:
    • действительно некоторые матерные слова были зацензурены (по большей части слово “бл**ь”);
    • остальные матерные слова остались нетронутыми;
    • под чистку попали некоторые другие грубые слова, при этом не являющиеся матерными (“мудак”, “гавно”).

Продемонстрируем наглядно на примере следующего диалога:
Дудь: Почему твои треки такое гавно?
Гнойный: Мои треки ох**тельные, Юра, просто ты любишь гавно.

Такие замены встречались в субтитрах роликов с людьми, которые не употребляли нецензурные выражения в своей речи (по крайней мере на протяжении интервью). Однозначное решение, что же делать с ‘[ __ ]’, мы не смогли принять, поэтому для некоторых гостей какая-то часть матерных слов была, увы, не подсчитана.

Работа с Word2vec

После статистического анализа интервью мы перешли к определению их контекста. Для этого мы, как и раньше, воспользовались моделью Word2vec. Она основана на нейронной сети и позволяет представлять слова в виде векторов с учетом семантической составляющей. Косинусная мера семантически схожих слов будет стремиться к 1, а у двух слов, не имеющих ничего общего по смыслу, она близка к 0. Модель можно обучать самостоятельно на подготовленном корпусе текстов, но мы решили взять готовую — от RusVectores. Для ее использования нам понадобилась библиотека gensim.
Мы рассчитали векторы-представления для каждой профессиональной группы. Наверное, можно ожидать, что режиссёры обсуждали кино и все, что с ним связано, а музыканты — музыку. Поэтому для каждого рода деятельности мы получили список слов, описывающих тематику текстов соответствующих роликов. Также мы раскрасили ячейки в зависимости от того, насколько каждое полученное слово было близко к текстам соответствующей категории гостей.

Можно сказать, что в целом каждая профессиональная категория описывается вполне соответствующими терминами. Конечно, некоторые слова могут показаться спорными. К примеру, на первом месте для рэперов стоит слово “джазовый”, хотя ни с 1 представителем хип-хоп течения речь о джазе не заходила. Тем не менее модель посчитала, что это слово достаточно близко к общему смыслу интервью людей, относящихся к этой категории (видимо, за счет непосредственного отношения рэперов к музыке).

P.S. Мистическое число 25.000000

Как мы уже говорили, среди скачанных субтитров некоторые были написаны вручную. Интересно, что все они начинаются с числа 25.000000, причем оно нигде не озвучивается.

Что же это за мистическое число? Если уйти в конспирологию, то можно вспомнить про 25-й кадр. К сожалению, нам об этом ничего неизвестно, мы просто оставим это как пищу для размышлений…

 Нет комментариев    1043   2022   api   Data Analytics   nltk   python   word cloud   YouTube

Где поесть? Куда сходить? Ищем ответ на вопрос с помощью пары рекомендаций и скрипта Python

Время чтения текста – 12 минут

Поскольку наш блог придерживается технологий, аналитики и IT-тематики, то обсуждение политики мы здесь, естественно опустим. Однако, сложно не заметить, что многие сейчас целенаправленно или волей случая оказываются в незнакомых городах и странах. Или планируют переезд, или просто путешествуют. В любом случае, где бы вы ни оказались, всегда хочется выстроить быт, сходить на завтрак или выпить вкусный кофе. Если вам интересно, чем в этих вопросах может помочь наш скрипт и проверить его в действии — продолжайте читать.

Что мы придумали?

Итак, предположим, что вы оказались в незнакомом городе. У вас есть несколько рекомендаций от друзей или просто несколько проверенных мест, где вам вкусно и красиво.
Наш алгоритм может быстро увеличить этот список в несколько раз, дополнив его 5-10 рекомендациями того же качества или уровня. Звучит здорово, да?

Как мы это реализовали?

Мы все еще не умеем колдовать, поэтому решили прибегнуть к более простому способу — написать Python-скрипт для решения этой задачи.
Начинаем, как всегда, с подготовки. Самая важная шестеренка в нашем скрипте — Instagram API (деятельность социальной сети признана экстремистской и запрещена в Российской Федерации).

from instagrapi import Client
import time
import pandas as pd

Затем, подключаемся к API, чтобы приступить к обработке данных.

cl = Client()
cl.login("username", "password")

Наша задача реализуется двумя небольшими скриптами. Первый собирает и обрабатывает геометки из Instagram (деятельность этой социальной сети признана экстремистской и запрещена в Российской Федерации), а также находит людей, которые отмечали эти геометки на фотографиях. Мы знаем, что эти места наверняка нравятся не только нам и предполагаем, что мы нашли тех людей, которые разделяют наши вкусы и ценности. Поэтому, мы собираем все недавние геометки в их профиле и так получаем список рекомендаций. Затем, с помощью второго скрипта мы узнаем точные адреса этих мест в Яндекс.Картах, чтобы определиться с выбором.

Сбор и обработка данных

Начинаем работу. Для того чтобы получить список рекомендаций нам нужны три подходящих примера, например кофейни Санкт-Петербурга: Смена, ТЧК, Civil. Наш скрипт принимает на вход идентификаторы геоточек заведений.
Как их получить?

  1. Переходим по ссылке в профиль заведения, например:
    https://www.instagram.com/smenacafe/
  2. Анализируем геометки в постах (считаем, что официальный профиль содержит правильные геометки)
  3. Находим ссылку на геометку, например:
    https://www.instagram.com/explore/locations/727911037416015/smenacafe/
  4. Цифры в ссылке и есть идентификатор геоточки
    727911037416015
    После того как мы нашли геометки первичных рекомендаций, нам нужно найти тех пользователей, кто ходит в это заведение (= отмечает его на своих фотографиях). Для этого мы берем последние 150 отметок заведения.
    Конечно, это должны быть реальные пользователи, а не бизнес-аккаунты, потому что там могут присутствовать рекламные интеграции, а мы в них не заинтересованы.
pk_place_ids = [541835746291313, 2103750586526340, 100059475]
 
print('Getting users started')
 
# получаем пользователей, которые отметили заведение на фото
users = []
for i in pk_place_ids:
    # получаем 150 последних постов с выбранной геометкой
    medias = cl.location_medias_recent(i, amount=150)
    
    for m in medias:
        user_id = m.dict()['user']['pk']
        if not user_id in users:
            users.append(user_id)
    
count_users = len(users)
 
print(f'Getting {count_users} users finished')
 
print('Getting not business users started')
 
# отбираем тех пользователей, чьи аккаунты не являются комерческими
users_not_business = []
for u in users:
    try:        
        u_info = cl.user_info(u).dict()
        
        if not u_info['is_business']:
            users_not_business.append(u)
    except:
        next
 
count_nb_users = len(users_not_business)
 
print(f'Getting {count_nb_users} not business users finished')
 
print('Getting location started')

Мы нашли людей, которые отмечали эти места у себя в профиле. Теперь мы собираем все места, которые отмечены в профилях этих людей и выводим эти места списком, сортируя по числу упоминаний.

# получаем места, которые посетил пользователь
locations = {}
 
end_cursor = None
for u in users_not_business:
    # скрипт работает довольно медленно, поэтому анализируем только 100 последних постов пользователя
    # посты получаем порциями 20 раз по 5 с сохранением курсора
    for page in range(20):
        u_medias, end_cursor = cl.user_medias_paginated(u, 5, end_cursor=end_cursor)
        for m in u_medias:
            # обернул в обработку исключений, т.к. иногда парсер падает
            try:
                # задержка для снижения чатсоты запросов в инстаграм
                time.sleep(1)
                # по идентификатору поста получаем данные поста (важно, что есть имя места, но нет его координат)
                info = cl.media_info(m.dict()['pk']).dict()
                if 'location' in info:               
                    loc_key = info['location']['pk']
                    
                    # вывод имени отмеченного места (для лога)
                    #print(info['location']['name'])
                    
                    # если место встретилось первый раз, то узнаем его координаты
                    if loc_key not in locations:
                        
                        # для того, чтобы узнать координаты, берем последний пост с такой геометкой
                        loc_data = cl.location_medias_recent(loc_key, amount=1)[0].dict()
                        
                        lng=''
                        lat=''
                        
                        if 'location' in loc_data:
                            lng=loc_data['location']['lng']
                            lat=loc_data['location']['lat']
                        
                        locations[info['location']['pk']] = [info['location']['name'],1,lng,lat] 
                    else:
                        locations[info['location']['pk']][1] = locations[info['location']['pk']][1] + 1
                    
                    # сохраняем результат в csv файл
                    ids = [i for i in locations]
                    names = [locations[i][0] for i in locations]
                    vizits = [locations[i][1] for i in locations]
                    lngs = [locations[i][2] for i in locations]
                    lats = [locations[i][3] for i in locations]
 
                    df = pd.DataFrame(
                        {'id': ids,
                        'name': names,
                        'vizit': vizits,     
                        'lng': lngs,
                        'lat': lats     
                        })
 
                    df.sort_values('vizit', ascending=False).to_csv('places.csv', index=False)                  
                    
            except:
                next
                
count_locations = len(locations)
 
print(f'Getting {count_locations} location finished')

Второй скрипт должен находить координаты новых рекомендаций в Яндекс.Картах.

# получим категории из справочника организаций сервиса Яндекс.Карты
import requests
 
# ключ API можно найти в кабинете разработчика
api_key = "---"
 
addrs = []
urls = []
cat1s = []
cat2s = []
cat3s = []
 
df = pd.read_csv("places.csv")
 
for i, row in df.iterrows():
    
    time.sleep(1)
    
    lng = row['lng']
    lat = row['lat']
    name = row['name']
    print(name)
        
    req = f"https://search-maps.yandex.ru/v1/?text={name}&results=1&type=biz&lang=ru_RU&ll={lng},{lat}&spn=0.01,0.01&apikey={api_key}"
 
    response = requests.get(req)
 
    addr = ''
    url = ''
    cat1 = ''
    cat2 = ''
    cat3 = ''
 
    if response.status_code == 200:
        # обернули в обработку исключений, т.к. иногда падает
        try:
            company_data = response.json()['features'][0]['properties']['CompanyMetaData']
                        
            addr = company_data['address']
            url = company_data['url']
            
            count_categories = len(company_data['Categories'])
            
            # у организации может быть до 3 категорий, сохраняем их все
            if count_categories > 0:
                if count_categories == 1:
                    cat1 = company_data['Categories'][0]['name']
                elif count_categories == 2:
                    cat1 = company_data['Categories'][0]['name']
                    cat2 = company_data['Categories'][1]['name']
                elif count_categories == 3:
                    cat1 = company_data['Categories'][0]['name']
                    cat2 = company_data['Categories'][1]['name']
                    cat3 = company_data['Categories'][2]['name']             
            
        except:
            pass
        
    addrs.append(addr)
    urls.append(url)
    cat1s.append(cat1)
    cat2s.append(cat2)
    cat3s.append(cat3)
    
df['address'] = addrs
df['url'] = urls
df['cat1'] = cat1s
df['cat2'] = cat2s
df['cat3'] = cat3s
df.to_csv('places_24.csv', index=False)

Результаты

Выпить кофе в Петербурге
Мы решили проверить, как работает скрипт на примере трех наших любимых кофеен в Петербурге. Получилось следующее:

Поужинать в Петербурге
Также мы протестировали наш скрипт, задав три рекомендации классных ресторанов Питера: Chang, Tiger Lilly, Jack and Chan.

Ограничения скрипта

  1. Скрипт сбора данных работает недостаточно быстро (примерно 100 геоточек в час).
  2. У геоточек может быть несколько дублей. Бывают геоточки с одинаковыми названиями, но отличающимися по координатам. Если честно, то по сути в геоточках просто хаос. Поэтому остается много ручной работы на этапе обработки и получения результатов.
  3. Яндекс отдает данные хорошо: база организаций очень большая, но для большинства объектов заполнена не одна, а целых три категории. У кафе может быть первая категория бар, затем кафе, затем ресторан. В общем, приходится опять же проверять многое вручную.
  4. Сделать единую процедуру пока не очень получается.
    Пока со скриптом можно работать в два этапа:
    — Первый этап — получение данных из инстаграма (название, частота посещений, координаты) и затем полуавтоматическая чистка от ненужных объектов + объединение дублей (это важно, так как бесплатная квота Яндекс.Карт — 500 запросов в день),
    — Второй этап — получение из Яндекс.Карт категорий, адреса, сайта и затем ручная сверка категорий и выбор наиболее точной категории из трех.
 Нет комментариев    282   2022   python   recommendation

Решение головоломок Wordle с помощью Basic Python

Время чтения текста – 21 минута

Перевод статьи “Solving Wordle Puzzles with Basic Python” автора Mickey Petersen

Вы наверняка слышали о Wordle? Это словесная головоломка не так проста, как кажется на первый взгляд. Вас просят угадать английское «слово дня», которое состоит из пяти букв. Если вы ошибетесь, вам дадут несколько подсказок: буква в слове будет зеленой, если вы правильно угадали нужную букву в нужном месте; желтой, если эта буква присутствует в слове, но не на этом месте; и серой, если буквы вообще нет в слове.

На самом деле, решать эту головоломку довольно сложно! Вот как вы можете написать Wordle Solver на Python, с использованием множеств, представления списков (list comprehension) и капелькой удачи!

Суть головоломки

Каждый день Wordle генерирует новое слово, которое нужно угадать. Поскольку у нас есть только шесть попыток — сайт использует файлы cookie для отслеживания вашего прогресса — попытки нужно использовать аккуратно!

На первый взгляд, есть несколько подсказок, которые упрощают решение:

  1. Слово состоит ровно из пяти букв.
  1. Слово из английского языка и использовать можно только алфавит — никаких знаков препинания, цифр или других символов.
  1. Любая попытка дает подсказки:
  • Зеленая буква, если буква и её место в слове правильные.
  • Желтая буква, если буква присутствует в слове, но было выбрано не то место.
  • Серая буква, если буквы вообще нет в слове.
  1. Существует конечное число слов, и их количество дополнительно ограничено словарем, используемым Wordle.

Поскольку я не хочу пытаться извлечь тот же словарь, что использует Wordle (это слишком просто), вместо этого я буду использовать свободно доступный словарь, который устанавливается через Linux в директорию /usr/share/dict/american-english. Словарь — это текстовый файл с одним словом в каждой строке.

Загрузка и генерация слов

Для начала нам понадобится словарь — вы можете установить любой удобный вам или использовать уже установленный, если такой есть.

Далее нам нужно закодировать правила игры:

import string

DICT = "/usr/share/dict/american-english"

ALLOWABLE_CHARACTERS = set(string.ascii_letters)
ALLOWED_ATTEMPTS = 6
WORD_LENGTH = 5

У нас есть всего шесть попыток; длина слова равна пяти, и мы можем использовать все доступные буквы английского алфавита.

Я преобразовываю допустимые символы в Python set(), чтобы использовать функции, которые доступны для работы с множествами, для проверки наличия букв в слове — но об этом чуть позже.

Теперь я могу сгенерировать множество тех слов, которые соответствуют правилам:

from pathlib import Path

WORDS = {
  word.lower()
  for word in Path(DICT).read_text().splitlines()
  if len(word) == WORD_LENGTH and set(word) < ALLOWABLE_CHARACTERS
}

Здесь я использую представление списков (list comprehension) для создания множества допустимых слов. Я использую отличный класс Path для чтения непосредственно из файла. Если вы еще не знакомы с Path, я рекомендую вам узнать о нем, поскольку это очень удобный инструмент.

Так, я фильтрую слова из словаря, которые имеют правильную длину и в которых набор символов в слове является подмножеством ALLOWABLE_CHARACTERS. Другими словами, выбираются только слова, которые составлены из набора допустимых символов.

Алфавитно-частотный анализ на английского языка

Особенность английского языка заключается в неравномерном распределении букв, используемых в словах. Например, буква E используется гораздо чаще, чем X. Поэтому, если мы сможем генерировать слова с наиболее распространенными буквами, у нас больше шансов угадать некоторые или даже все буквы в слове. Таким образом, выигрышная стратегия состоит в том, чтобы придумать систему, которая вычислит наиболее популярные буквы английского языка.

К счастью, у нас есть словарь английских слов!

from collections import Counter
from itertools import chain

LETTER_COUNTER = Counter(chain.from_iterable(WORDS))

Класс Counter — полезное изобретение! Это модифицированный словарь, который считает количество повторений каждого элемента. Когда вы передаете ему список элементов, они становятся ключами, а затем он сохраняет количество появлений каждого ключа в его значение. Это как раз то, что нам нужно, чтобы посчитать популярность каждой буквы среди всех английских слов из 5 букв.

Для этого я использую малоизвестную функцию chain из модуля itertools. Эта функция имеет один скрытый метод from_iterable, который берет один элемент и итерирует его:

Я думаю, что лучше всего объяснить на конкретном примере:

>>> list(chain.from_iterable(["inspired", "python"]))
['i', 'n', 's', 'p', 'i', 'r', 'e', 'd', 'p', 'y', 't', 'h', 'o', 'n']

Поскольку строки также можно итерировать, а WORDS — это множество строк, то мы можем разбить это множество (или список и т. д.) на составные элементы. Это очень полезное свойство строк; вы можете прогнать строку через set() и получить все уникальные символы строки:

>>> set("hello")
{'e', 'h', 'l', 'o'}

Множества созданы по образцу своих математических тезок.

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

Множества обладают многими полезными функциями, такими как проверка, содержится ли одно множество полностью в другом множестве (подмножестве); получение элементов, содержащихся в обоих множествах (пересечение); совмещение элементов двух множеств (объединение) и так далее.

Итак, мы посчитали количество букв во всем словаре, и вот что получилось:

>>> LETTER_COUNTER
Counter({'h': 828,
         'o': 1888,
         'n': 1484,
         'e': 3106,
         's': 2954,
         'v': 338,
         # ...etc...
        })

Так, мы получили только абсолютное количество букв в словаре. Теперь нужно разделить его на общее количество букв, чтобы перейти к относительным величинам. К счастью, в классе Counter есть удобный метод total, который может посчитать общее количество букв всех слов словаря.

Затем составляем таблицу частот:

LETTER_FREQUENCY = {
    character: value / LETTER_COUNTER.total()
    for character, value in LETTER_COUNTER.items()
}

Метод Counter.total() был добавлен в Python 3.10, поэтому, если вы используете более старую версию Python, вы можете заменить его на sum(LETTER_COUNTER.values()), который делает то же самое.

Здесь я использую представление словарей (dictionary comprehension) для обработки каждого ключа и значения LETTER_COUNTER (это модифицированный словарь) и деления каждого значения на общее количество символов:

>>> LETTER_FREQUENCY
{ 'h': 0,02804403048264183,
  'o': 0,06394580863674852,
  'n': 0,050262489415749366,
  'e': 0,10519898391193903,
  's': 0.10005080440304827,
  # ...etc...
  }

И теперь у нас есть идеальный счетчик букв в подмножестве словаря, которые мы считаем существующими словами Wordle. Обратите внимание, что я не делал эти операции для всего словаря — я обработал только те части, которые нам интересны. Маловероятно, что это сильно повлияло бы на ранжирование популярности букв, но в конечном итоге мы руководствуемся именно этим набором слов.

Теперь нам нужен способ оценки каждого слова, чтобы мы могли предположить, какие слова встретятся с наибольшей вероятностью. Итак, нам нужно взять нашу таблицу частоты и создать функцию подсчета популярности слов, которая оценивает, насколько «популярны» буквы в этом слове:

def calculate_word_commonality(word):
    score = 0.0
    for char in word:
        score += LETTER_FREQUENCY[char]
    return score / (WORD_LENGTH - len(set(word)) + 1)

Снова вспоминаем, что строка является итерируемой, и перебираем каждую букву в слове. Затем получаем популярность каждой буквы и суммируем их. Потом общее количество делится на длину слова минус количество уникальных символов (плюс один, чтобы предотвратить деление на ноль).

Это не то что бы удивительная функция оценки слов, но она проста и взвешивает слова таким образом, что более уникальным символам придается больший вес. В идеале нам нужно как можно больше уникальных, часто встречающихся символов, чтобы максимизировать вероятность получения зеленых или желтых совпадений в Wordle.

Быстрый тест подтверждает, что слова с редкими и повторяющимися символами имеют более низкий вес, чем слова с частыми и более уникальными символами.

>>> calculate_word_commonality("fuzzy")
0.04604572396274344
>>> calculate_word_commonality("arose")
0.42692633361558

Все, что нам сейчас нужно — придумать способ сортировки и отображения этих слов, чтобы игрок мог выбирать из них:

import operator

def sort_by_word_commonality(words):
    sort_by = operator.itemgetter(1)
    return sorted(
        [(word, calculate_word_commonality(word)) for word in words],
        key=sort_by,
        reverse=True,
    )

def display_word_table(word_commonalities):
    for (word, freq) in word_commonalities:
        print(f"{word:<10} | {freq:<5.2}")

С помощью sort_by_word_commonality я создаю отсортированный (от большего к меньшему) список кортежей, где каждый кортеж содержит слово и рассчитанный балл для этого слова. Ключ, по которому я сортирую, — это относительная популярность слова в словаре.

Я не использую лямбду для получения первого элемента; для таких простых вещей я предпочитаю operator.itemgetter, который делает то же самое.

Также, я добавил функцию быстрого отображения для форматирования слов и их оценки в простую таблицу.

Теперь поговорим о самом решении головоломки.

Решение головоломки Wordle

Поскольку я создаю его как простое консольное приложение, я собираюсь использовать input() и print().

def input_word():
    while True:
        word = input("Input the word you entered> ")
        if len(word) == WORD_LENGTH and word.lower() in WORDS:
            break
    return word.lower()


def input_response():
    print("Type the color-coded reply from Wordle:")
    print("  G for Green")
    print("  Y for Yellow")
    print("  ? for Gray")
    while True:
        response = input("Response from Wordle> ")
        if len(response) == WORD_LENGTH and set(response) <= {"G", "Y", "?"}:
            break
        else:
            print(f"Error - invalid answer {response}")
    return response

Функционал приложения очень прост. Нужно узнать у пользователя слово WORD_LENGTH, которое он ввел в игре Wordle, и записать ответ от Wordle. Поскольку есть только три возможных цвета для буквы (зеленый, желтый и серый), ответ закодирован в простую строку из трех символов: G, Y и ?.

Я также добавил обработку ошибок на тот случай, если пользователь ошибается при вводе данных, повторяя цикл до тех пор, пока не будет задана правильная последовательность. Я делаю это, снова преобразовывая полученную информацию в множество, а затем проверяя, является ли это множество подмножеством допустимых ответов.

Фильтрация зеленых, желтых и серых букв с помощью вектора слова

Зеленая буква указывает на правильность буквы и ее места в слове. Желтый означает, что место неправильное, но буква в слове присутствует; а серый что буквы нигде нет.

Другой способ интерпретации этой информации заключается в том, что пока Wordle не сообщит нам, какие буквы зеленые, желтые или серые, существуют все варианты.

word_vector = [set(string.ascii_lowercase) for _ in range(WORD_LENGTH)]

Я создаю список из пяти множеств, так как нам нужно определить 5 букв слова. Каждый элемент списка — это множество всех строчных английских букв. Проходясь по каждому множеству, я могу удалять буквы, в соответствии с тем, как окрашены буквы после попытки:

  • Зеленая буква дает нам информацию только по текущему множеству
    Это означает, что если я встречу зеленую букву на втором месте, то я могу изменить второе множество и оставить только эту букву.
  • Желтые буквы исключают возможность использовать эту букву на этом месте
    Таким образом, все буквы, кроме этой, технически могут оказаться на этом месте. Удаление буквы из набора в этой позиции гарантирует, что мы не сможем выбрать слова, в которых эта буква стоит на этом месте.
  • Серые буквы подразумевают исключение буквы из всего вектора.
    Следовательно, эта буква должна быть удалена из всех множеств в векторе слова.

Теперь нам нужна функция, которая выбирает слова, которые соответствуют текущему вектору слова. Есть несколько способов сделать это, но я предлагаю вот такой красивый и простой вариант:

def match_word_vector(word, word_vector):
    assert len(word) == len(word_vector)
    for letter, v_letter in zip(word, word_vector):
        if letter not in v_letter:
            return False
    return True

Этот подход использует метод zip для попарного сопоставления каждого символа в слове и каждого символа в векторе слова.

Проходимся по каждому множеству, и, если буквы на определенном месте нет в соответствующем множестве, то цикл прерывается и слово исключается. Если все в порядке и цикл отрабатывает до последней буквы, то мы получаем ответ True и выходим из цикла, отмечая совпадение слова с вектором.

Проверяем слова на соответствие

Принимая во внимание все правила игры, теперь можем написать функцию поиска, которая фильтрует список слов с учетом ответов, которые мы получили от Wordle.

def match(word_vector, possible_words):
    return [word for word in possible_words if match_word_vector(word, word_vector)]

Эта функция объединяет в себе все правила, которые мы обсудили выше, и ищет с помощью представления списков (list comprehension). Каждое слово проверяется на соответствие с word_vector с помощью match_word_vector.

Сортировка результатов

Наконец, нам нужно создать небольшой UI, который может многократно запрашивать нужный нам ответ.

def solve():
    possible_words = WORDS.copy()
    word_vector = [set(string.ascii_lowercase) for _ in range(WORD_LENGTH)]
    for attempt in range(1, ALLOWED_ATTEMPTS + 1):
        print(f"Attempt {attempt} with {len(possible_words)} possible words")
        display_word_table(sort_by_word_commonality(possible_words)[:15])
        word = input_word()
        response = input_response()
        for idx, letter in enumerate(response):
            if letter == "G":
                word_vector[idx] = {word[idx]}
            elif letter == "Y":
                try:
                    word_vector[idx].remove(word[idx])
                except KeyError:
                    pass
            elif letter == "?":
                for vector in word_vector:
                    try:
                        vector.remove(word[idx])
                    except KeyError:
                        pass
        possible_words = match(word_vector, possible_words)

Функция solve() включает в себя некоторые элементы, которые мы уже обсудили. Затем попадаем в цикл от 1 до ALLOWED_ATTEMPTS + 1, и после каждой попытки мы отображаем номер текущей попытки и количество оставшихся возможных слов. Затем мы вызываем функцию display_word_table, которая выводит красивую таблицу из 15 совпадений с наибольшим рейтингом популярности слова. Затем функции нужно узнать слово, которое пользователь в итоге ввел и ответ Wordle.

После этого мы проходимся по ответу Wordle, отмечая какой букве соответствует ответ (последовательность цветов). Код прост: мы сопоставляем каждый из трех возможных цветов с соответствующим контейнером (зеленый — с word_vector и т. д.) и применяем правила, которые мы обсудили выше.

Наконец, мы заново берем possible_words и проверяем совпадение с новым вектором слова с помощью match, сокращая множество потенциальных вариантов.

Давайте проверим, как это работает

Все начинается с запуска функции solve() (для краткости часть вывода опущена):

>>> Attempt 1 with 5905 possible words
arose      | 0.43
raise      | 0.42

   ... etc ...

Input the word you entered> arose
Type the color-coded reply from Wordle:
  G for Green
  Y for Yellow
  ? for Gray
Response from Wordle> ?Y??Y
Attempt 2 with 829 possible words
liter      | 0.34
liner      | 0.34

   ... etc ...

Input the word you entered> liter
Response from Wordle> ???YY
Attempt 3 with 108 possible words
nerdy      | 0.29
nehru      | 0.28

   ... etc ...

Input the word you entered> nerdy
Response from Wordle> ?YY?G
Attempt 4 with 25 possible words
query      | 0.24
chewy      | 0.21

   ... etc ...

Input the word you entered> query
Response from Wordle> GGGGG
Attempt 5 with 1 possible words
query      | 0.24

Резюме

  • Представления (Comprehensions) — мощный инструмент Python
    Они могут совмещать итерацию с фильтрацией, но если вы злоупотребите этой функцией, добавляя слишком много циклов for или слишком много условных операторов, код может стать нечитаемым. Избегайте сильной вложенности этих операторов, если это возможно.
  • Множества — полезный тип объекта в Python
    Множества их их верное использование делают код более стабильным, более математически правильным и более кратким. Главное — знать, когда и как их использовать. В нашем решении множества сыграли существенную роль — не пренебрегайте ими!
  • Регулярные выражения могут помочь описать все требования к поиску
    Хотя это не было использовано в коде, совпадение (или несовпадение) шаблона и слова — это то, что регулярные выражения делают круче всего. Подумайте, как можно переписать мэтчинг и векторизацию слов с помощью регулярных выражений.
  • Модули itertools и collections содержат очень полезные инструменты
    Вы можете многого добиться с базовым знанием Python, если знаете, как именно можно использовать встроенные модули. Модуль itertools особенно полезен, если вы вам нужно провернуть итеративные вычисления.
 Нет комментариев    214   2022   python   solver   wordle

How to: YouTube API

Время чтения текста – 9 минут

Современным аналитикам необходимо обладать навыком сбора информации из социальных сетей, ведь сейчас контент социальных сетей очень точно отражает реальную ситуацию в мире, помогает быстро распространить новости и позволяет анализировать аудиторию — подписчиков. В предыдущих постах мы уже описывали кейсы с использованием различных API: Vkontakte API, Facebook API, GitHub API. Сегодня мы расскажем вам о том, что представляет из себя YouTube API, как получить ключ API, а также наглядно покажем, какую информацию можно собрать с его помощью. В двух словах, с помощью YouTube API можно находить каналы по ключевым словам, выгружать данные канала, а также статистику по видео, опубликованным на этих каналах.

Подготовительный этап для работы с YouTube API

Для начала, нужно разобраться в том, как получить доступ к API. Этот процесс подробно изложен на сайте для разработчиков, на который вы можете перейти по ссылке. Если коротко, то необходимо иметь или завести аккаунт Google, войти в профиль для разработчиков, создать проект, получить ключ API и подключить к нему API YouTube Data API v3. Далее, с использованием этого ключа вам будет доступен весь необходимый функционал.
После того, как вы успешно получили ключ, можно открывать любой удобный ноутбук (Jupyter Notebook, Collab и т. д.), устанавливать и подключать нужные для работы библиотеки.

# установка библиотек
	pip install --upgrade google-api-python-client
	pip install --upgrade google-auth-oauthlib google-auth-httplib2
	# импорт необходимых библиотек
import googleapiclient.discovery
import time

Квоты

Один важный момент, который важно знать при использовании Youtube API — это наличие дневных квот на использование функций YouTube API в бесплатном режиме. На день дается квота 10000 юнитов, вызов функции поиска стоит 100 юнитов, вызов информации по объекту — 1 юнит, загрузка видео на YouTube стоит 1600 юнитов. Если вам недостаточно дневной квоты, то вы можете подать запрос в Google на её увеличение, в котором нужно подробно указать цели вашей деятельности c YouTube API.

Поиск YouTube-каналов по ключевым словам

Для начала заведем несколько переменных, которые понадобятся нам в процессе сбора информации.

channels_data = {}
channels_data_full = {}
video_data = {}

Дальше написан скрипт, который можно использовать для поиска перечня каналов по ключевым словам. Мы искали каналы, в названии или описании которых используются следующие слова: s_query = ’аналитика данных data’. Сначала выводятся каналы, в названии или описании которых присутствуют все три слова, затем хотя бы любые два, затем хотя бы одно. Чем больше ключевых слов по теме мы укажем, тем точнее будет результат.

api_service_name = "youtube"
api_version = "v3"
DEVELOPER_KEY = "" #тут нужно указать ключ, который вы получите при подключении YouTube API
 
youtube = googleapiclient.discovery.build(
   api_service_name, api_version, developerKey = DEVELOPER_KEY)
#строка поиска
s_query = 'аналитика данных data'
next_token = ''
 
while(True): 
 time.sleep(0.2)
 request = youtube.search().list(
     part="snippet",
     q=s_query,
     relevanceLanguage="ru",
     type="channel",
     maxResults=25,
     access_token=DEVELOPER_KEY,
     pageToken = next_token
 )
 response = request.execute()
 for item in response['items']:
   channels_data[item['snippet']['channelId']] = [item['snippet']['title'], item['snippet']['description']
   ]
 #берем только первые 25 результатов
 break

Добавим пару важных пояснений относительно скрипта. В начале цикла в этом скрипте (как и в двух последующих) мы вызываем функцию time.sleep(), чтобы инициировать двухсекундную задержку между вызовом функций. Это нужно для того, чтобы запросы к YouTube не были чересчур частыми (и вообще, это считается правилом хорошего тона в программировании, так что советуем взять на заметку).
Для простоты нашего примера мы сохранили только 25 первых каналов из всех подходящих под условия поиска. Если вам хочется найти все каналы, в которых упоминается хотя бы одно из ключевых слов, то нужно использовать следующее свойство:

try:
    next_token = response["nextPageToken"]
  except:
    break

Сбор полной информации по всем выбранным каналам

Теперь, когда названия и описания выбранных каналов собраны, можно переходить к следующему этапу, а именно — выгрузке всей информации об этих каналах, в том числе: количество подписчиков канала, количество видео, общее количество просмотров всех видео канала и страна в которой живет, автор канала.

scount = ''
for channel in channels_data:
   #получаем данные по каждому каналу
   time.sleep(0.2)
   r = youtube.channels().list(
         part="snippet, statistics",
         id=channel,
         access_token=DEVELOPER_KEY
   )
   resp = r.execute()
        
   try:
     if resp['items'][0]['statistics']['hiddenSubscriberCount']:
       scount = 'hidden'
     else:
       scount = resp['items'][0]['statistics']['subscriberCount']
  
     channels_data_full[channel] = [resp['items'][0]['snippet']['title'],
                                  resp['items'][0]['snippet']['description'],
                                  scount,
                                  resp['items'][0]['statistics']['videoCount'],
                                  resp['items'][0]['statistics']['viewCount'],
                                  resp['items'][0]['snippet']['country']
     ]
      
   except:
     pass

Теперь вся нужная информация о канале хранится в переменнной channels_data_full.

Получение информации о видео

Если у вас есть необходимость получить статистику по видео из выбранных каналов, то ниже приведен скрипт на этот случай. В итоге, вы получите словарь video_data с подробной информацией о каждом видео из плейлиста (список всех видео каждого канала): название канала, дата публикации, название и описание видео, количество просмотров, лайков/дизлайков и комментариев.

# получаем информацию по всем видео ранее найденных каналов
for channel in channels_data:
   #анализируем каналы
   time.sleep(0.2)
   r = youtube.channels().list(
           part="contentDetails",
           id=channel,
           access_token=DEVELOPER_KEY
     )
   resp = r.execute()           
   try:
     #получаем плейлист из видео для одного канала из списка
     id_playlist = resp['items'][0]['contentDetails']['relatedPlaylists']['uploads']     
     #получаем набор элементов плейлиста (видео)
     next_token = ''
     while(True):     
       time.sleep(0.2)
       r = youtube.playlistItems().list(
             part="contentDetails",
             playlistId=id_playlist,
             access_token=DEVELOPER_KEY,
             pageToken = next_token
       )
       resp = r.execute()
       for i in resp['items']:
         id_videos = i['contentDetails']['videoId']
         r = youtube.videos().list(
               part="snippet, statistics",
               id=id_videos,               
               access_token=DEVELOPER_KEY
         )
         resp1 = r.execute()       
         video_data[id_videos] = [channel,
                                   resp1['items'][0]['snippet']['publishedAt'],
                                   resp1['items'][0]['snippet']['title'],
                                   resp1['items'][0]['snippet']['description'],
                                   resp1['items'][0]['statistics']['viewCount'],
                                   resp1['items'][0]['statistics']['likeCount'],
                                   resp1['items'][0]['statistics']['dislikeCount'],
                                   resp1['items'][0]['statistics']['commentCount']
          ]
       break

В конце мы ставим break, то есть обрабатываем только одну часть видео из плейлиста. Если вы хотите обработать все видео, то нужно использовать функцию nextpagetoken, которую мы предложили в конце первого скрипта.
В итоге, если трансформировать словарь в привычный датафрейм, мы получим таблицу, которая содержит подробную информацию про все обработанные видео.

d = {'id': [x for x in video_data],
      'channel_id': [video_data[x][0] for x in video_data],
       'published_at': [video_data[x][1] for x in video_data],
      'title': [video_data[x][2] for x in video_data],
      'description': [video_data[x][3] for x in video_data],
      'viewCount': [video_data[x][4] for x in video_data],
      'likeCount': [video_data[x][5] for x in video_data],
      'dislikeCount': [video_data[x][6] for x in video_data],
      'commentCount': [video_data[x][7] for x in video_data]
   }
df = pd.DataFrame(d)
df.head()

Выводы

Конечно, это не все способы работы с YouTube API, однако, мы надеемся, что вы получили представление о том, как сильно расширяются возможности аналитика для получения и обработки информации с помощью этого инструмента.

 1 комментарий    820   2021   api   python   аналитика

Как и для чего экспортировать красивые отчеты из Jupyter Notebook в PDF

Время чтения текста – 7 минут

Если вы специалист по анализу данных и вам нужно представить отчет для заказчика, если вы ищете работу и не знаете, как оформить тестовое задание так, чтобы на вас обратили внимание, если у вас много учебных проектов, связанных с аналитикой и визуализацией данных, то сегодняшний пост будет вам очень и очень полезен. Дело в том, что смотреть на чужой код в Jupyter Notebook бывает проблематично, ведь результат часто теряется между множеством строк кода с подготовкой данных, импортом нужных библиотек и серией попыток реализовать ту или иную идею. Именно поэтому такой метод, как экспорт результатов в PDF-файл в формате LaTeX — это отличный вариант для итоговой визуализации, который сэкономит время и будет выглядеть презентабельно. В научных кругах статьи и отчеты очень часто оформляются именно с использованием LaTeX, поскольку он имеет ряд преимуществ:

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

Сегодня мы подробно расскажем о том, как научиться экспортировать вот такие красивые отчеты из Jupyter Notebook в PDF с использованием LaTeX.

Установка LaTeX

Самый важный момент в формировании отчета из Jupyter Notebook на Python — это его экспорт в финальный файл. Для этого применяется одна библиотека — nbconvert — которая конвертирует ваш ноутбук в любой удобный формат документа: pdf (как в нашем случае), html, latex или другой. Эту библиотеку нужно не просто установить, а провести некоторую процедуру по предустановке нескольких других пакетов: Pandoc, TeX и Chromium. По ссылке на библиотеку весь процесс описан очень подробно для каждого программного обеспечения, поэтому подробно мы на нем останавливаться не будем.
Как только вы завершили все предварительные шаги, нужно установить и импортировать библиотеку в ваш Jupyter Notebook.

!pip install nbconvert
import nbconvert

Экспорт таблиц в Markdown формат

Обычно, таблицы не представляют в отчетах, поскольку их бывает трудно быстро прочесть, но иногда все-таки необходимо добавить небольшую таблицу в итоговый документ. Для того, чтобы таблица выглядела аккуратно, нужно представить ее в Markdown формате. Это можно сделать вручную, но если в таблице много данных, то лучше придумать более удобный метод. Мы предлагаем использовать следующую простую функцию pandas_df_to_markdown_table(), которая преобразует любой датафрейм в markdown-table. Единственный нюанс: после преобразования исчезают строчные индексы, потому, если они важны (как в нашем примере), то стоит записать их в переменную в первой колонке датафрейма.

data_g = px.data.gapminder()
summary = round(data_g.describe(),2)
summary.insert(0, 'metric', summary.index)

# Функция для преобразования dataframe в Markdown Table
def pandas_df_to_markdown_table(df):
    from IPython.display import Markdown, display
    fmt = ['---' for i in range(len(df.columns))]
    df_fmt = pd.DataFrame([fmt], columns=df.columns)
    df_formatted = pd.concat([df_fmt, df])
    display(Markdown(df_formatted.to_csv(sep="|", index=False)))

pandas_df_to_markdown_table(summary)

Экспорт изображения в отчет

В этом примере мы будем строить bubble-chart, про методику построения которых рассказывали в недавнем посте. В прошлый раз мы использовали пакет Seaborn, наглядно показывая, что отображение данных размером кругов на графике происходит корректно. Такие же графики можно построить и при помощи пакета Plotly.
Для того чтобы отобразить график, построенный в Plotly в отчете тоже нужно немного постараться. Дело в том, что plt.show() не поможет отобразить график при экспорте. Поэтому, нужно сохранить получившийся график в рабочей директории, а затем, используя библиотеку iPython.display, отобразить его с помощью функции Image().

from IPython.display import Image
import plotly.express as px
fig = px.scatter(data_g.query("year==2007"), x="gdpPercap", y="lifeExp",
                 size="pop", color="continent",
                 log_x=True, size_max=70)
fig.write_image('figure_1.jpg')
Image(data = 'figure_1.jpg', width = 1000)

Формирование и экспорт отчета

Когда все этапы анализа данных завершены, отчет можно экспортировать. Если вам нужны заголовки или текст в отчете, то пишите его в ячейках ноутбука, сменив формат Code на Markdown. Для экспорта можно использовать терминал, запуская там вторую строку без восклицательного знака, либо можно запустить код, написанный ниже, в ячейке ноутбука. Мы советуем не загружать отчет кодом, поэтому используем параметр TemplateExporter.exclude_input=True, чтобы ячейки с кодом не экспортировались. Также, при запуске этой ячейки код выдает стандартный поток (standard output) и, чтобы в отчете его не было видно, в начале ячейки нужно написать %%capture.

%%capture
!jupyter nbconvert --to pdf --TemplateExporter.exclude_input=True ~/Desktop/VALIOTTI/Reports/Sample\LaTeX\ Report.ipynb
!open ~/Desktop/VALIOTTI/Reports/Sample\ LaTeX\ Report.pdf

Если вы все сделали верно и методично, то в итоге получится вот такой отчет! Презентуйте данные красиво :)

 Нет комментариев    1912   2021   jupyter notebook   python
Ранее Ctrl + ↓