Привет, Хабр!

На связи Сергей и Григорий - Data Scientist'ы.

Сегодня расскажем, как заняли 2 место в общем зачете AI Generative Product Hackathon, инициированного Napoleon IT,  и 1 место в кейсе по анализу рекламных креативов для крупной российской фармацевтической компании.

Проблематика

Компания, занимающаяся промышленным производством и продажей медицинских препаратов, представила на хакатон кейс ”Медиа корреляции”, в котором требовалось проводить анализ рекламных креативов.

Даже самые лучшие креативы перестают приносить конверсии, если крутятся слишком долго. Отслеживание “выгорания” креатива позволяет маркетологу грамотно распределить бюджет и заблаговременно подготовить вариант на смену.

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

Постановка задачи

Кейс включал в себя две разные задачи:

  • Проанализировать ход рекламной кампании и предоставить рекомендации для маркетолога.

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

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

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

Исходные данные

Исходные данные состояли из csv-файла с данными о предыдущих рекламных кампаниях различных препаратов. Данные были разделены по дням, и для каждого было известно общее количество показа данного типа рекламы (“impressions”) и количество кликов (“clicks”).

Дополнительную информацию о препарате, ЦА и типе рекламы мы получали из столбца “campaign_name”. 

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

def pretty_dataframe(dataframe):

    dataframe.columns = dataframe.iloc[0]

    dataframe.drop(dataframe.index[0], inplace=True)

    dataframe.drop('campaign_id', axis=1, inplace=True)

    dataframe.drop('platform', axis=1, inplace=True)

    dataframe = type_dataframe(dataframe)

    dataframe = clear_zeros(dataframe)

    dataframe = add_click_rate_to_dataframe(dataframe)

    dataframe = add_drug_name_to_dataframe(dataframe)

    dataframe = add_medic_group_to_dataframe(dataframe)

    dataframe = add_adv_format_to_dataframe(dataframe)

    dataframe = add_campaign_numbers_to_dataframe(dataframe)

    dataframe = add_rolling_mean_to_dataframe(dataframe, window=3)

    dataframe = add_rolling_mean_to_dataframe(dataframe, window=5)

    dataframe = add_rolling_mean_to_dataframe(dataframe, window=7)

    dataframe = add_trend_flag_to_dataframe(dataframe)

    dataframe = add_moving_average_change_to_dataframe(dataframe)

    dataframe = add_day_of_campaign(dataframe)

    return dataframe

Финальный набор новых признаков выглядел примерно так:

Техническое решение

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

Начало взаимодействия с ботом. Как видно, бот умеет парсить Google Sheets и для каждого юзера записывать их в небольшую бд-шку.

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

def generate_report_text(self):  # Текст отчёта

        if (self.user_timeseries.trend.tail(1).values[0] == 'Increase') & (

                self.user_timeseries.moving_average_change.tail(1).values[0] > 0):

            trend = f'''\nПоказатель CTR за последние дни вырос на {self.user_timeseries.moving_average_change.tail(1).values[0]:.2f}%!\nДля получения дополнительно информации, можете ознакомиться с графиками.  

            '''

        elif (self.user_timeseries.trend.tail(1).values[0] == 'Decrease') & (

                self.user_timeseries.moving_average_change.tail(1).values[0] < 0):

            trend = f'''\nПоказатель CTR за последние дни упал на {self.user_timeseries.moving_average_change.tail(1).values[0]:.2f}%.\nРекомендуется ознакомиться с графиком общей тенденции CTR, чтобы принять решение.\n

            '''

        else:

            trend = f'''\nПоказатель CTR колеблется, либо остаётся на одном уровне.\nОзнакомьтесь с другими графиками, чтобы принять взвешенное решение.

            '''

        impressions = self.user_timeseries.impressions.sum()

        ctr = self.user_timeseries.clicks.sum() / self.user_timeseries.impressions.sum()

        return (

            f"Отчёт по креативу:\n"

            f"Название препарата: {self.user_timeseries.drug_name[1]}\n"

            f"Целевая группа: {self.user_timeseries.medic_group[1]}\n"

            f"Тип креатива: {self.user_timeseries.adv_format[1]}\n"

            f"{trend}\n"

            f"Общее количество показов: {impressions}, это больше чем у "

            f"{(self.history_timeseries_for_this.groupby(['campaign_name']).impressions.sum() < impressions).sum() * 100 / (self.history_timeseries_for_this.groupby(['campaign_name']).impressions.sum() < impressions).count():.1f}% "

            f"кампаний для данных препарата/ца/формата креатива, а так же больше чем у "

            f"{(self.history_timeseries.groupby(['campaign_name']).impressions.sum() < impressions).sum() * 100 / (self.history_timeseries.groupby(['campaign_name']).impressions.sum() < impressions).count():.1f}% "

            f"всех предыдущих рекламных кампаний.\n\n"

            f"СTR кампании за всё время составил {ctr * 100:.3f}%. Для данных препарата/ца/формата креатива средний показатель CTR составляет "

            f"{(self.history_timeseries_for_this.groupby(['campaign_name']).clicks.sum() * 100 / self.history_timeseries_for_this.groupby(['campaign_name']).impressions.sum()).median():.3f}% "

            f"Средний показатель CTR для всех предыдущих рекламных кампаний составляет "

            f"{(self.history_timeseries.groupby(['campaign_name']).clicks.sum() * 100 / self.history_timeseries.groupby(['campaign_name']).impressions.sum()).median():.3f}%\n\n"

            f"На данный момент кампания длится {self.user_timeseries.day.iloc[-1]} дней."

            f"Обычно кампания для данных препарата/ца/формата креатива длится "

            f"{self.history_timeseries_for_this.groupby(['campaign_name', 'campaign_number'])['day'].max().median():.0f} дня/дней, а "

            f"средняя длительность всех рекламных кампаний составляет {self.history_timeseries.groupby(['campaign_name', 'campaign_number'])['day'].max().median():.0f} дня/дней.\n\n"

            f"В ходе этой кампании значение CTR росло {(self.user_timeseries.trend == 'Increase').sum()} дня/дней, падало {(self.user_timeseries.trend == 'Decrease').sum()} дня/дней и стабилизровалось, либо было стабильно {(self.user_timeseries.trend == 'Plateau').sum()} дня/дней."

        )

    def generate_report_images(self):

        report_images = []

        buffer = BytesIO()

        fig2, ax = plt.subplots(figsize=(15, 9))

        image2 = sns.lineplot(data=self.user_timeseries[self.user_timeseries['day'] > 5], x='day', y='rolled_5', ax=ax,

                              label="Текущая кампания")

        history = select_campaign_metrics(drug_name=None, medic_group=self.user_timeseries.medic_group.iloc[0],

                                          adv_format=None)

        history = history[history['click_rate'] < 0.015]

        history = history[(history['day'] > 5) & (history['day'] < self.user_timeseries.day.max() + 5)]

        image2 = sns.lineplot(data=history, x='day', y='rolled_5', ax=ax, label="Усредненная история кампаний")

        image2.set_ylabel("Среднее CTR")

        image2.set_xlabel("День кампании")

        image2.set_title("Сравнение среднего CTR для данной ГРУППЫ ВРАЧЕЙ в ходе прошлых кампаниях и нынешней")

        ax.legend()

        ax.grid(True)

        sns.set_style("whitegrid")

        image2.get_figure().savefig(buffer, format='png')

        buffer.seek(0)

        report_images.append(buffer)

        buffer.flush()

        buffer2 = BytesIO()

        fig2, ax2 = plt.subplots(figsize=(15, 9))

        image2 = sns.lineplot(data=self.user_timeseries[self.user_timeseries['day'] > 5], x='day', y='rolled_5', ax=ax2,

                              label="Текущая кампания")

        history = select_campaign_metrics(drug_name=None, medic_group=None, adv_format=None)

        history = history[history['click_rate'] < 0.015]

        history = history[(history['day'] > 5) & (history['day'] < self.user_timeseries.day.max() + 5)]

        image2 = sns.lineplot(data=history, x='day', y='rolled_5', ax=ax2, label="Усредненная история кампаний")

        image2.set_ylabel("Среднее CTR")

        image2.set_xlabel("День кампании")

        image2.set_title("Сравнение среднего CTR для данного ТИПА КРЕАТИВА в ходе прошлых кампаниях и нынешней")

        ax2.legend()

        ax2.grid(True)

        sns.set_style("whitegrid")

        image2.get_figure().savefig(buffer2, format='png')

        buffer2.seek(0)

        report_images.append(buffer2)

        buffer2.flush()

        buffer3 = BytesIO()

        fig3, ax3 = plt.subplots(figsize=(15, 9))

        image3 = sns.lineplot(data=self.user_timeseries[self.user_timeseries['day'] > 5], x='day', y='rolled_5', ax=ax3,

                              label="Текущая кампания")

        history = select_campaign_metrics(drug_name=self.user_timeseries.drug_name.iloc[0], medic_group=None,

                                          adv_format=None)

        history = history[history['click_rate'] < 0.015]

        history = history[(history['day'] > 5) & (history['day'] < self.user_timeseries.day.max() + 5)]

        image3 = sns.lineplot(data=history, x='day', y='rolled_5', ax=ax3, label="Усредненная история кампаний")

        image3.set_ylabel("Среднее CTR")

        image3.set_ylabel("День кампании")

        image2.set_title("Информация об изменении CTR в ходе кампании")

        image3.set_title("Сравнение среднего CTR данного ПРЕПАРАТА в ходе прошлых кампаниях и нынешней")

        ax3.legend()

        ax3.grid(True)

        sns.set_style("whitegrid")

        image3.get_figure().savefig(buffer3, format='png')

        buffer3.seek(0)

        report_images.append(buffer3)

        buffer3.flush()

        return report_images

В окончательный отчет входят:

  • Шаблонная часть с текущими показателями рекламной кампании.

  • Гипотезы и рекомендации, сгенерированные языковой моделью.

  • Визуализация динамики показателей на фоне средней динамики по соответствующей группе.

Ниже приведен пример отчета, полученного от бота в автоматическом режиме:

Отчет по креативу:

Название препарата: -

Целевая группа: pharmacy

Тип креатива: banner

Показатель CTR за последние дни упал на -3.45%.

Рекомендуется ознакомиться с графиком общей тенденции CTR, чтобы принять решение.

Общее количество показов: 355187, это больше чем у 84.6% кампаний для данных препарата/ца/формата креатива, а также больше чем у 84.9% всех предыдущих рекламных кампаний.

СTR кампании за всё время составил 0.365%. Для данных препарата/ца/формата креатива средний показатель CTR составляет 0.441% Средний показатель CTR для всех предыдущих рекламных кампаний составляет 0.372%

На данный момент кампания длится 38 дней.Обычно кампания для данных препарата/ца/формата креатива длится 126 дня/дней, а средняя длительность всех рекламных кампаний составляет 47 дня/дней.

В ходе этой кампании значение CTR росло 11 дня/дней, падало 19 дня/дней и стабилизировалось, либо было стабильно 8 дня/дней.

Гипотеза 1: Кампания находится на этапе спада.

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

Против: Длительность кампании для данного препарата/формата обычно составляет около 126 дней, кампания длится только 38 дней, что говорит о том, что она еще не достигла своего среднего срока жизни и есть потенциал для роста.

Гипотеза 2: Кампания имеет перспективы на улучшение показателей.

За: CTR кампании уже превышает средний показатель по всем проведенным кампаниям. Большое количество показов говорит о хорошей достигаемости и масштабируемости кампании.

Против: В течение длительного периода (19 дней) значение CTR падало и текущий показатель является относительно низким. Это может говорить о неэффективности кампании в текущих рамках.

Целостный вывод: В целом, кампания показывает средний уровень показателя CTR по сравнению со всеми проведенными кампаниями, однако относительно данного препарата/формата креатива показатель ниже среднего. Это, вместе с тем, что кампания находится в стадии спада, говорит о том, что возможно стоит пересмотреть стратегию кампании или принять решение о ее сворачивании. Малое количество дней роста CTR кампании (11 дней) по сравнению с днями падения (19 дней) также свидетельствует о возможности пересмотра выбранной стратегии.

Полный код решения посмотреть здесь.

Итог

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

Наша команда постаралась представить максимально подходящее кейсодержателю решение, поэтому в конечном продукте для хакатона не было обучено какой-либо предсказательной модели, весь упор делался на расчёт понятных и хорошо интерпретируемых метрик, которые будут полезны в анализе любому маркетологу. Думаем, что  именно длительное и основательное выявление желаний и требований кейсодателя и принесло нам в результате 1 место по кейсу и 2 по всему хакатону).

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