Когда мы рассказывали о том, как устроено бесплатное обучение в «Яндекс Практикуме», то на одном из скриншотов тренажёра показали тепловую карту — она показывала распределение возвратов товара по неделям среди разных компаний. Сегодня мы сделаем нечто подобное сами: используем тепловые карты для анализа постов Байдена и Трампа, чтобы посмотреть, есть ли там что-то интересное.
В чём идея
Стивен Вуд в 2020 году спарсил твиты кандидатов в президенты США — Джозефа Байдена и Дональда Трампа. Спарсил — то есть запустил программу, которая забирает какие-то данные из веб-страницы и укладывает их в понятном нам формате.
В подборку попали твиты с 1 февраля 2017 года по 11 сентября 2020 года в таком формате:
- id твита,
- дата и время поста,
- количество ретвитов.
Всего получилось около 17 700 твитов, что уже можно считать бигдатой и на чём уже можно проводить математический анализ. Всем стало интересно, а можно ли в этих данных найти какие-то закономерности, и если да — то какие?
В нашем проекте мы проанализируем распределение твитов каждого кандидата по часам и минутам, а результат выведем в виде тепловой карты.
Что такое тепловая карта
Тепловая карта — это график с распределением значений по обеим осям, где каждому квадрату на карте соответствует какое-то значение.
Вспомним пример из Практикума: здесь тепловая карта показывает распределение возвратов товара по неделям для разных компаний. Красные и белые квадраты — много возвратов, чёрные — мало. Видно, что меньше всего отказов у Яндекс Муркета, а больше — у Робокотчи и Мурдроида. А ещё по какой-то причине в 14-ю неделю возвратов нет ни у кого, и это странно — нужно разбираться, действительно ли это так.
Тепловая карта хороша тем, что, глядя на неё, легко найти закономерности в данных, которые сложно заметить, рассматривая просто таблицы с числами.
Загружаем данные в программу
Мы будем использовать тот же датасет, что собрал Стивен Вуд, — если будете повторять за нами, то скачайте его себе на компьютер и поменяйте в программе путь к файлу. Писать код будем на Python — он лучше всего подходит для аналитики и работы с бигдатой.
Заведём программу на Python и подключим сразу все библиотеки, которые нам понадобятся:
# подключаем модуль для обработки данных
import pandas as pd
# # модули для работы с тепловыми картами
import seaborn as sns
import matplotlib.pyplot as plt
Если какой-то библиотеки ещё нет на компьютере, её можно установить командой:
pip install seaborn
где seaborn — это название библиотеки.
Проверить, все ли библиотеки на месте, можно командой
pip list
Теперь загружаем датасет в программу и сразу выбираем оттуда данные только о часах и минутах публикаций и имя пользователя, отбрасывая всё остальное — для анализа нам нужно только время.
Датасет — это специально отобранный массив данных, который используется для дальнейшей обработки и анализа. В нашем случае, датасет — это 17 тысяч строк с записями о том, какой пользователь, когда и во сколько сделал какой твит.
# путь к файлу с датасетом
data_url = "/Users/mike/Downloads/biden_trump_tweets.csv"
# считываем данные
df = pd.read_csv(data_url,
parse_dates=['date_utc'],
dtype={'hour_utc':int,'minute_utc':int,'id':str}
)
# проверяем, что данные считались правильно
# для этого выводим первые 5 строк нашего датасета
print(df.head())
Считаем количество твитов с разбивкой по часам и минутам
Чтобы вывести тепловую карту, данные для неё нужно подготовить: сгруппировать данные по какому-то признаку и добавить нули туда, где значений нет. Сначала сгруппируем все записи по часам и минутам — так мы потом сможем посмотреть, кто из кандидатов в какое время активнее всего:
# объединяем события, которые произошли у каждого пользователя в одно и то же время
g = df.groupby(['hour_utc','minute_utc','username'])
# считаем количество твитов в каждой получившейся группе
tweet_cnt = g.id.nunique()
# для проверки выводим первые 5 строк обработанных данных
print(tweet_cnt.head())
Видно, что Байден в 0 часов 0 минут всего сделал 26 твитов, а Трамп — только 6. В 0:01 у Байдена всего 16 твитов, а у Трампа — 11. Кажется, что Байден активнее Трампа, но это только вывод первых 5 значений нашего нового датасета, и делать выводы пока ещё рано.
Формируем данные для тепловой карты
Теперь нам надо превратить предыдущую таблицу в формат, подходящий для тепловой карты. Для этого используем метод loc() — он с помощью внутренней магии превратит это в таблицу с разбивкой только по часам и минутам.
Начнём с Джозефа Байдена:
# начинаем обработку данных с Джо Байдена
# получаем доступ к группе строк и столбцов,
# чтобы сформировать новый датасет для тепловой карты
jb_tweet_cnt = tweet_cnt.loc[:,:,'JoeBiden'].reset_index().pivot(index='hour_utc', columns='minute_utc', values='id')
# выводим фрагмент нового датасета для проверки
print(jb_tweet_cnt.iloc[:10,:9])
В некоторых полях таблицы стоит NaN — неопределённое значение, потому что этих данных не было в исходном датасете. Но для тепловой карты это не подходит — ей нужны точные значения. Заменим их на нули:
# меняем NaN на 0, чтобы у нас в данных остались только численные значения
jb_tweet_cnt.fillna(0, inplace=True)
# убеждаемся, что это сработало и вместо NaN стоят нули
print(jb_tweet_cnt.iloc[:10,:9])
Нули появились, но нужно решить ещё одну проблему: у нас нет минут 7, 8 и 9, а ещё, может быть, нет минут, которые не вошли в тестовый вывод. Всё потому, что в исходном датасете не было записи о том, что в это время публиковались твиты. Это может исказить тепловую карту, поэтому добавим недостающие минуты и часы, а также приведём всё к целым числам:
# Добавляем отсутствующие часы, если их нет в нашем датасете
jb_tweet_cnt = jb_tweet_cnt.reindex(range(0,24), axis=0, fill_value=0)
# Добавляем отсутствующие минуты и приводим всё к целым числам
jb_tweet_cnt = jb_tweet_cnt.reindex(range(0,60), axis=1, fill_value=0).astype(int)
# проверяем, что у нас появились 7, 8 и 9-я минуты
print(jb_tweet_cnt.iloc[:10,:9])
Сделаем всё то же самое для Дональда Трампа:
# делаем то же самое для данных Дональда Трампа
dt_tweet_cnt = tweet_cnt.loc[:,:,'realDonaldTrump'].reset_index().pivot(index='hour_utc', columns='minute_utc', values='id')
dt_tweet_cnt.fillna(0, inplace=True)
dt_tweet_cnt = dt_tweet_cnt.reindex(range(0,24), axis=0, fill_value=0)
dt_tweet_cnt = dt_tweet_cnt.reindex(range(0,60), axis=1, fill_value=0).astype(int)
Рисуем тепловую карту
У нас всё готово к тому, чтобы вывести тепловую карту твитов. Логика формирования карты будет такая:
- Обе карты сделаем для наглядности в одном файле.
- В цикле переберём все элементы из нашего подготовленного датасета и отправим данные из них в массив тепловой карты.
- Сразу зададим минимальное и максимальное значение на шкале (мы заранее запустили и выяснили, что максимум было 40 твитов в одно и то же время).
- Подпишем оси и их условные обозначения.
- Сохраним результат в файл.
Читайте комментарии в коде, чтобы лучше разобраться в логике построения тепловой карты — добавили их к каждой строчке.
# готовимся показать несколько графиков в одном фрейме
fig, ax = plt.subplots(2, 1, figsize=(24,12))
# перебираем оба датасета по одному элементу
for i,d in enumerate([jb_tweet_cnt,dt_tweet_cnt]):
# получаем значения минут и часов для разметки осей
labels = d.applymap(lambda v: str(v) if v == d.values.max() else '')
# формируем тепловую карту
sns.heatmap(d,
cmap="viridis", # тема оформления
annot=labels, # разметку осей берём из переменной labels
annot_kws={'fontsize':11}, # размер шрифта для подписей
fmt='', # говорим, что с метками надо работать как со строками
square=True, # квадратные ячейки
vmax=40, # максимальное
vmin=0, # и минимальное значение твитов в ячейке
linewidth=0.01, # добавляем разлиновку сеткой
linecolor="#222",# цвет сетки
ax=ax[i], # значение каждой клетки в тепловой карте берём в соответствии с датасетом
)
# подписываем оси
ax[0].set_title('@JoeBiden')
ax[1].set_title('@realDonaldTrump')
ax[0].set_ylabel('Распределение по часам')
ax[1].set_ylabel('Распределение по часам')
ax[0].set_xlabel('')
ax[1].set_xlabel('Распределение по минутам')
# сохраняем результат в файл final.png
plt.tight_layout()
plt.savefig('final.png', dpi=120)
# подключаем модуль для обработки данных
import pandas as pd
# модули для работы с тепловыми картами
import seaborn as sns
import matplotlib.pyplot as plt
# путь к файлу с датасетом
data_url = "/Users/mike/Downloads/biden_trump_tweets.csv"
# считываем данные
df = pd.read_csv(data_url,
parse_dates=['date_utc'],
dtype={'hour_utc':int,'minute_utc':int,'id':str}
)
# проверяем, что данные считались правильно
# для этого выводим первые 5 строк нашего датасета
print(df.head())
# объединяем события, которые произошли у каждого пользователя в одно и то же время
g = df.groupby(['hour_utc','minute_utc','username'])
# считаем количество твитов в каждой получившейся группе
tweet_cnt = g.id.nunique()
# для проверки выводим первые 5 строк обработанных данных
print(tweet_cnt.head())
# начинаем обработку данных с Джо Байдена
# получаем доступ к группе строк и столбцов,
# чтобы сформировать новый датасет для тепловой карты
jb_tweet_cnt = tweet_cnt.loc[:,:,'JoeBiden'].reset_index().pivot(index='hour_utc', columns='minute_utc', values='id')
# выводим фрагмент нового датасета для проверки
print(jb_tweet_cnt.iloc[:10,:9])
# меняем NaN на 0, чтобы у нас в данных остались только численные значения
jb_tweet_cnt.fillna(0, inplace=True)
# убеждаемся, что это сработало и вместо NaN стоят нули
print(jb_tweet_cnt.iloc[:10,:9])
# Добавляем отсуствующие часы, если их нет в нашем датасете
jb_tweet_cnt = jb_tweet_cnt.reindex(range(0,24), axis=0, fill_value=0)
# Добавляем отсутствующие минуты и приводим всё к целым числам
jb_tweet_cnt = jb_tweet_cnt.reindex(range(0,60), axis=1, fill_value=0).astype(int)
# проверяем, что у нас появились 7, 8 и 9-я минуты
print(jb_tweet_cnt.iloc[:10,:9])
# делаем то же самое для данных Дональда Трампа
dt_tweet_cnt = tweet_cnt.loc[:,:,'realDonaldTrump'].reset_index().pivot(index='hour_utc', columns='minute_utc', values='id')
dt_tweet_cnt.fillna(0, inplace=True)
dt_tweet_cnt = dt_tweet_cnt.reindex(range(0,24), axis=0, fill_value=0)
dt_tweet_cnt = dt_tweet_cnt.reindex(range(0,60), axis=1, fill_value=0).astype(int)
# готовимся показать несколько графиков в одном фрейме
fig, ax = plt.subplots(2, 1, figsize=(24,12))
# перебираем оба датасета по одному элементу
for i,d in enumerate([jb_tweet_cnt,dt_tweet_cnt]):
# получаем значения минут и часов для разметки осей
labels = d.applymap(lambda v: str(v) if v == d.values.max() else '')
# формируем тепловую карту
sns.heatmap(d,
cmap="viridis", # тема оформления
annot=labels, # разметку осей берём из переменной labels
annot_kws={'fontsize':11}, # размер шрифта для подписей
fmt='', # говорим, что с метками надо работать как со строками
square=True, # квадратные ячейки
vmax=40, # максимальное
vmin=0, # и минимальное значение твитов в ячейке
linewidth=0.01, # добавляем разлиновку сеткой
linecolor="#222",# цвет сетки
ax=ax[i], # значение каждой клетки в тепловой карте берём в соответствии с датасетом
)
# подписываем оси
ax[0].set_title('@JoeBiden')
ax[1].set_title('@realDonaldTrump')
ax[0].set_ylabel('Распределение по часам')
ax[1].set_ylabel('Распределение по часам')
ax[0].set_xlabel('')
ax[1].set_xlabel('Распределение по минутам')
# сохраняем результат в файл final.png
plt.tight_layout()
plt.savefig('final.png', dpi=120)
Смотрим на результат
Картинка с картами лежит в той же папке, что и наша программа. Чем светлее — тем больше твитов было в это время. Вот какие выводы можно сделать, глядя на эти две карты:
- У Байдена явно видно, что большинство твитов делается в интервалах по 15 и 5 минут, причём в 30 и 45 минут твитов больше всего. Скорее всего, это значит, что за его твиттером следит команда, которая по расписанию выкладывает нужные записи (или ставит их на отложенную публикацию).
- Трамп не привязывался ко времени и публиковал новые посты когда захочет. Судя по карте, чаще всего он это делал с утра на свежую голову, а потом в течение дня добавлял новые записи, как только возникали новые мысли.
- Байден (или его команда) отдыхает явно больше Трампа — чёрный интервал, когда твитов почти нет, у Байдена в два раза больше.
Общий вывод напрашивается такой: Трамп вёл свой твиттер сам, а за Байдена это делает команда с чётким графиком публикаций.
Всё это можно понять, просто посмотрев на две картинки и подумав несколько минут. В этом мощь тепловых карт — представлять наглядно большие данные так, чтобы в них было легко разобраться с одного взгляда.