Datalytics

Блог Алексея Макарова о веб-аналитике, анализе данных и не только.
Группа в Фейсбук
Канал в Телеграм

«Взлом» интервью на продакт-менеджера: взгляд через противоречия

Введение

Между строгой системностью и творческим хаосом, между аналитикой и интуицией, между цифрами и эмоциями — в этих пространствах напряжения рождается то, что мы называем продуктовым мышлением. Книга «Cracking the PM Interview» Гейл Лакманн Макдауэлл и Джеки Баваро, на первый взгляд, просто набор техник для прохождения собеседований. Но если смотреть глубже, перед нами — исследование особого типа познания реальности, где «взлом» системы найма становится метафорой постижения сложных социотехнических систем.

Честно говоря, мы часто упускаем из виду, что успешные продуктовые решения возникают не в пространстве безупречной логики или чистого вдохновения, а именно на их границе. Вопрос «Спроектируйте будильник для слепых людей» — это не просто задачка на собеседовании. Это приглашение перейти от абстрактного к конкретному, от теоретической возможности к воплощению, от понимания технического к постижению человеческого.

В этой статье мы не просто пересказываем содержание книги. Мы исследуем саму природу продуктового мышления — этого удивительного сплава эмпатии и системного анализа. Через разбор конкретных кейсов — от дизайна инклюзивных устройств до создания социальных продуктов, от оптимизации конверсии до планирования A/B-тестирования — мы попытаемся деконструировать те ментальные операции, которые делают продуктовое мышление таким эффективным инструментом решения сложных проблем.

Наиболее продуктивный парадокс, который мы будем исследовать: как в продуктовом мышлении сочетаются методологическая строгость и эмоциональная чуткость? Как продакт-менеджеры удерживают в фокусе одновременно запросы бизнеса и потребности живых людей? И наконец, что нам может рассказать о природе мышления сама практика его проверки через интервью?

Взгляд на «Cracking the PM Interview»: между системой и творчеством

Книга «Cracking the PM Interview» — это не просто справочник для собеседований, а исследование особого типа мышления на пересечении логики и интуиции.

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

Один из парадоксов профессии: чтобы получить работу PM, нужно уже мыслить как PM, но чтобы так мыслить, нужен опыт работы PM. Книга разрывает этот замкнутый круг.

Собеседования на PM — это своеобразный перформанс. Когда тебя просят «спроектировать будильник для слепых», это приглашение к определенному типу присутствия в мире — творческому, эмпатичному, структурированному.

Разные типы вопросов высвечивают разные грани мышления:

  • Поведенческие: между личным опытом и общими уроками
  • Оценочные: между точностью и скоростью
  • Продуктовые: между инновацией и реализуемостью
  • Кейсы: между аналитикой и интуицией

Хотя книга вышла в 2013-м, в её частичной устарелости скрыта особая ценность — она показывает, что меняется, а что остаётся неизменным в продуктовом мышлении. Гугл уже не торт, но принципы структурированного мышления и эмпатии к пользователю всё ещё актуальны.

Где заканчивается применимость книги? Возможно, там, где начинается настоящее творчество. Но эта граница подвижна — и в этом её красота. Чтобы исследовать эту пограничную территорию между формулами и живым мышлением, между структурой и спонтанностью, дальше мы рассмотрим конкретные примеры продуктовых вопросов и кейсов. В алгоритмах их решения обнаруживается то самое продуктивное противоречие: структурированный подход как каркас, внутри которого рождается нечто большее, чем просто механическое следование шагам. Эти примеры — не столько готовые шаблоны, сколько приглашение к собственному мыслительному эксперименту.

Какие вопросы/кейсы рассмотрим?

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

Примеры продуктовых вопросов

Дизайн нового продукта:

  • «Спроектируйте будильник для слепых людей» — этот вопрос встречается в самой книге и стал уже классикой
  • «Как бы вы создали приложение для совместного просмотра видео с друзьями на расстоянии?»
  • «Разработайте умную систему для снижения пробок в большом городе»
  • «Как бы вы спроектировали цифровой продукт для помощи пожилым людям в приёме лекарств?»

Улучшение существующего продукта:

  • «Какие три вещи вы бы изменили в Google Maps?»
  • «Как бы вы улучшили процесс заказа еды в Uber Eats?»
  • «Что вы бы изменили в формате историй Instagram?»
  • «Как бы вы переработали систему комментариев на YouTube?»

Анализ любимого продукта:

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

Примеры кейс-вопросов

Тут интересное напряжение между консалтинговым подходом (ориентация на бизнес-метрики) и продуктовым мышлением (ориентация на пользователя). Вот примеры:

  • «Вы PM в стриминговом сервисе и замечаете, что средняя длительность сеанса снизилась на 15% за последний месяц. Как вы будете исследовать проблему?»
  • «Ваша компания думает о запуске продукта в Бразилии. Как вы оцените, стоит ли это делать?»
  • «Вы заметили, что новая функция в вашем мобильном приложении имеет низкий показатель использования. Как вы определите, в чём проблема и что с этим делать?»
  • «Генеральный директор хочет увеличить доходы от рекламы в вашем бесплатном приложении. Как вы подойдёте к этой задаче, не ухудшая пользовательский опыт?»
  • «Представьте, что вы PM в платёжной системе. Конверсия на этапе регистрации всего 45%. Что бы вы сделали для её увеличения?»

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

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

★ Спроектируйте будильник для слепых людей

Вот задача, которая на первый взгляд кажется простой, но разворачивается в целый мир противоречий и возможностей — «Спроектируйте будильник для слепых людей». Честно говоря, этот кейс идеально высвечивает саму суть продуктового мышления. Здесь сталкиваются тактильное и звуковое, технологичное и доступное, универсальное и персонализированное. В пространстве между этими полюсами и рождается настоящее решение. Ты не можешь просто опираться на визуальные паттерны дизайна, привычные для обычных продуктов. Тебе приходится переосмыслить саму идею взаимодействия человека с технологией, когда один из основных каналов восприятия недоступен. И тут возникает потрясающая возможность — отказавшись от привычного, увидеть нечто, что было всегда скрыто за доминированием визуального. Как же решать такую задачу?

1️⃣ Понимание пользователя и проблемы

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

Основные проблемы, которые предстоит решить:

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

2️⃣ Исследование возможностей

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

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

3️⃣ Ключевые функции и решения

1. Методы пробуждения

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

2. Интерфейс настройки

  • Голосовое управление: «Установить будильник на 7 утра»
  • Тактильные кнопки с маркировкой Брайля: для настройки времени без голоса
  • Обратная связь: голосовое подтверждение установленного времени
  • Интеграция со смартфоном: настройка через доступное приложение с поддержкой VoiceOver/TalkBack

3. Проверка текущего времени и настроек

  • Голосовые подсказки по запросу: «Сейчас 10:30 вечера, будильник установлен на 7 утра»
  • Тактильный дисплей: возможность «прочитать» время пальцами

4. Выключение будильника

  • Жестовое управление: двойное касание, встряхивание
  • Голосовая команда: «Стоп» или «Выключить»
  • Большая кнопка отключения: легко нащупываемая на устройстве

5️⃣ Дополнительные возможности

  • Несколько будильников с разной маркировкой
  • Умная интеграция с домом: включение света, кофеварки
  • Функция «не беспокоить»: для совместного проживания с другими людьми
  • Автоматическая синхронизация времени через интернет

6️⃣ Проблемы, требующие решения

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

Еще одно противоречие: между автономностью (работа без интернета, долгое время без подзарядки) и смарт-функциями (интеграция, голосовое управление).

7️⃣ Метрики успеха

  • Надежность пробуждения (процент успешных пробуждений в заданное время)
  • Удобство использования (время, затрачиваемое на настройку)
  • Точность установки (насколько точно пользователи могут установить нужное время)
  • Удовлетворенность пользователей

8️⃣ Прототип для тестирования

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

★ Проектирование приложения для совместного просмотра видео с друзьями на расстоянии

1️⃣ Понимание проблемы и пользовательских потребностей

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

Основные боли пользователей:

  • Невозможность синхронизировать воспроизведение (один смотрит быстрее, другой медленнее)
  • Отсутствие ощущения совместного присутствия
  • Неудобство параллельного использования мессенджера для обсуждения
  • Сложность выбора контента для совместного просмотра

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

2️⃣ Ключевые функции

1. Синхронизация воспроизведения

  • Автоматическая синхронизация временной шкалы для всех участников
  • «Демократическое» управление (любой может поставить на паузу) или назначение «хозяина сеанса»
  • Индикация состояния воспроизведения для каждого участника

2. Социальные взаимодействия

  • Встроенный видео/аудио чат (возможность видеть и слышать реакции друзей)
  • Текстовый чат для комментариев без прерывания просмотра
  • Эмоциональные реакции (аналог реакций в соцсетях) в реальном времени
  • Возможность «показать» на экране интересный момент другим

3. Интеграция контента

  • Доступ к существующим стриминговым сервисам (YouTube, Netflix, и др.)
  • Возможность загрузки локальных файлов для совместного просмотра
  • Рекомендации контента для групп на основе общих интересов

4. Организация просмотров

  • Планирование сеансов с календарной интеграцией
  • Приглашение друзей через разные платформы
  • История просмотров с возможностью продолжить с места остановки

3️⃣ Технические аспекты и ограничения

Здесь возникает несколько серьезных технических вызовов:

  1. Задержки сети: разная скорость интернета у пользователей
  • Решение: буферизация и предсказательные алгоритмы для синхронизации
  1. Авторские права и интеграция с сервисами
  • Решение: разработка API-партнерства с контент-провайдерами или модель «расширения браузера»
  1. Различия в устройствах пользователей
  • Решение: кросс-платформенная разработка (мобильные устройства, ПК, смарт-ТВ)
  1. Качество звука и видео
  • Решение: умное управление ресурсами, понижение качества видеочата при необходимости для сохранения качества основного контента

4️⃣ Бизнес-модель и монетизация

Варианты монетизации:

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

5️⃣ Метрики успеха

  1. Вовлеченность
  • Длительность совместных сеансов
  • Количество сообщений и реакций за сеанс
  • Регулярность использования (DAU/MAU)
  1. Рост
  • Количество новых пользователей
  • Коэффициент вирального распространения (k-фактор)
  • Показатель возврата к продукту
  1. Удовлетворенность
  • NPS (индекс потребительской лояльности)
  • Отзывы и рейтинги
  • Количество и характер поддержки

6️⃣ MVP и дальнейшее развитие

Для MVP я бы сосредоточился на:

  1. Базовой синхронизации воспроизведения
  2. Простом текстовом чате
  3. Интеграции с 1-2 популярными сервисами (например, YouTube)

В дальнейшем добавил бы:

  • Видео/аудио чат
  • Расширенные социальные функции
  • Интеграцию с большим количеством сервисов
  • Мобильную версию

7️⃣ Потенциальные проблемы и риски

  • Технические сбои: проблемы с синхронизацией могут серьезно подорвать доверие
  • Конкуренция: крупные платформы могут внедрить аналогичную функциональность
  • Правовые вопросы: потенциальные проблемы с авторскими правами
  • Поведенческие риски: возможность токсичного поведения пользователей

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

★ Проектирование умной системы для снижения пробок в большом городе

1️⃣ Понимание проблемы

Для начала, стоит обозначить — транспортные заторы в городах это многогранная проблема, а не просто «много машин». Пробки возникают из-за:

  • Несбалансированного спроса на дорожную инфраструктуру (пиковые часы)
  • Неоптимального использования существующих дорог
  • Недостаточной информированности водителей о ситуации
  • Аварий и дорожных работ, создающих узкие места
  • Неэффективной системы общественного транспорта
  • Плохой координации светофоров

Тут сразу возникает интересное противоречие: мы хотим сделать поездки на личном транспорте удобнее (что привлечет больше машин на дороги), но это может усугубить проблему пробок в долгосрочной перспективе.

2️⃣ Заинтересованные стороны

  • Водители и пассажиры
  • Городская администрация
  • Местный бизнес
  • Экстренные службы
  • Общественный транспорт
  • Экологические службы
  • Разработчики городской инфраструктуры

3️⃣ Компоненты решения

1. Система сбора и анализа данных

  • Сенсорная сеть: камеры, датчики на дорогах, GPS-данные с мобильных устройств
  • Интеграция данных: объединение информации от навигационных приложений, общественного транспорта, служб такси
  • Прогнозная аналитика: машинное обучение для предсказания загруженности на основе исторических данных, погоды, событий в городе

2. Умное управление трафиком

  • Адаптивные светофоры: динамическое изменение циклов в зависимости от текущей ситуации
  • Координированные светофорные коридоры: «зеленая волна» для основных направлений движения
  • Динамическое управление полосами: меняющееся направление полос в зависимости от пиковой нагрузки (утром — в центр, вечером — из центра)

3. Информирование и перераспределение

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

4. Стимулирование поведенческих изменений

  • Динамическое ценообразование для платных дорог и парковок
  • Программа вознаграждений за поездки вне часов пик или использование общественного транспорта
  • Интеграция с приложениями карпулинга (совместного использования автомобилей)
  • Поощрение удаленной работы и гибкого графика среди городских работодателей

4️⃣ Технологический стек

  • Сенсоры и IoT: для сбора данных в реальном времени
  • Облачная платформа: для обработки больших объемов данных
  • AI и машинное обучение: для прогнозирования и оптимизации
  • Мобильные приложения: для взаимодействия с пользователями
  • Системы управления светофорами: с поддержкой API для интеграции
  • Блокчейн (опционально): для системы вознаграждений и прозрачности

5️⃣ Метрики успеха

  • Время в пути: среднее время перемещения из точки А в точку Б
  • Плотность трафика: количество машин на единицу дорожного пространства
  • Предсказуемость: разница между ожидаемым и фактическим временем поездки
  • Выбросы CO2: снижение вследствие уменьшения простоя в пробках
  • Экономический эффект: сокращение потерь рабочего времени и расхода топлива
  • Удовлетворенность граждан: опросы до/после внедрения системы

6️⃣ Пилотирование и масштабирование

  1. Выбор пилотного района: область с хроническими пробками, но управляемого размера
  2. Базовые измерения: сбор данных до внедрения для сравнения
  3. Поэтапное внедрение: начиная с системы сбора данных и умных светофоров
  4. Анализ результатов и корректировка: на основе реальных данных
  5. Масштабирование: расширение на другие районы города

7️⃣ Ограничения и вызовы

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

Другие вызовы:

  • Бюджетные ограничения городской администрации
  • Техническая инфраструктура: возможно, потребуется модернизация
  • Сопротивление изменениям со стороны населения
  • Координация между различными службами города
  • Киберзащита: система становится критически важной инфраструктурой

8️⃣ Бизнес-модель

  • Государственно-частное партнерство: город + технологическая компания
  • Экономия на инфраструктурных затратах: эффективнее использовать существующие дороги
  • Платный доступ к премиум-функциям приложения
  • Данные как ценность: анонимизированная аналитика для городского планирования и бизнеса
  • Снижение экологического ущерба: возможность получения «зеленых» грантов

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

★ Проектирование цифрового продукта для помощи пожилым людям в приёме лекарств

1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣

1️⃣ Понимание пользователей и проблемы

Пожилые люди часто сталкиваются с серьёзными трудностями при приёме лекарств:

  • Сложные схемы приёма: множество разных препаратов в разное время дня
  • Когнитивные ограничения: проблемы с памятью, забывчивость
  • Физические ограничения: ухудшение зрения, слуха, моторики
  • Низкая технологическая грамотность: сложности с использованием цифровых устройств
  • Зависимость от помощников: часто требуется участие родственников или опекунов

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

2️⃣ Ключевые функции

1. Умная система напоминаний

  • Мультимодальные оповещения: звук, вибрация, визуальные сигналы
  • Настраиваемая частота: повторение напоминаний до подтверждения
  • Контекстные подсказки: «Примите красную таблетку с едой» с изображением
  • Голосовые напоминания: естественная речь вместо обезличенных уведомлений

2. Упрощённый учёт приёма

  • Одно касание для подтверждения: максимально простой способ отметить приём
  • Визуальный журнал: наглядное отображение принятых и пропущенных доз
  • Автоматическое отслеживание: опциональная интеграция с умными таблетницами
  • Обратная связь: поощрение регулярного приёма («Вы принимаете лекарства уже 7 дней подряд!»)

3. Управление запасами

  • Мониторинг остатков: автоматический расчёт, когда закончатся лекарства
  • Напоминания о покупке: заблаговременные уведомления
  • Интеграция с аптеками: возможность заказа доставки прямо из приложения
  • Сканирование упаковок: быстрое добавление препаратов с помощью камеры

4. Семейная поддержка

  • Доступ для опекунов: возможность удалённого мониторинга приёма
  • Уведомления о пропусках: оповещение родственников при критичных нарушениях режима
  • Совместное управление лекарствами: распределение ответственности

5. Медицинская информация

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

3️⃣ UX-дизайн, адаптированный для пожилых

  • Увеличенные элементы интерфейса: крупные кнопки, значки и шрифты
  • Высококонтрастная цветовая схема: легко различимые элементы
  • Минимум текста: приоритет визуальным элементам и инфографике
  • Голосовое управление: возможность взаимодействия без касаний экрана
  • Снисходительный интерфейс: прощение ошибок, понятные пути возврата
  • Физические компоненты: интеграция с простыми специализированными устройствами (кнопки подтверждения, умные таблетницы)

4️⃣ Технические решения

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

  • «Невидимые» технологии: сложные алгоритмы под капотом, но предельно простой интерфейс
  • Многоуровневый доступ: базовый интерфейс для пользователя, расширенный для опекунов
  • Локальная обработка данных: минимизация зависимости от интернета
  • Надёжное сохранение данных: предотвращение потери информации при сбоях

5️⃣ MVP и путь к полному продукту

Для начала я бы сосредоточился на трёх ключевых функциях:

  1. Базовая система напоминаний с простым подтверждением
  2. Визуальный журнал приёма
  3. Оповещения для родственников о пропусках

После тестирования добавил бы:

  • Управление запасами
  • Интеграцию с аптеками
  • Расширенную медицинскую информацию
  • Интеграцию с умными устройствами

6️⃣ Метрики успеха

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

7️⃣ Потенциальные проблемы и риски

  • Технологический барьер: некоторые пожилые люди могут сопротивляться использованию цифровых устройств
  • Зависимость от устройства: риск полной зависимости от электроники
  • Конфиденциальность: хранение чувствительных медицинских данных
  • Чрезмерная опека: возможность излишнего контроля со стороны родственников

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

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

★ Три вещи, которые я бы изменил в Google Maps

Честно говоря, я часто пользуюсь Google Maps и считаю его отличным продуктом, но всегда есть что улучшить. Я начну с понимания базовых сценариев использования и ключевых пользовательских групп.

Google Maps сегодня используют для:

  • Ежедневной навигации (поездки на работу, поездки в новые места)
  • Поиска и исследования мест (рестораны, магазины, достопримечательности)
  • Планирования маршрутов (особенно в путешествиях)

Вот три вещи, которые я бы изменил:

1️⃣ Расширенный офлайн-режим с предиктивной загрузкой данных

Проблема: Сегодня офлайн-режим в Google Maps ограничен. Нужно заранее скачивать карты, многие функции недоступны без интернета, а батарея быстро садится.

Решение: Создать умную систему предиктивной загрузки и кэширования данных.

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

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

2️⃣ Контекстные маршруты и навигация с учетом активности

Проблема: Текущая навигация сконцентрирована на «быстрее» или «короче», но не учитывает _цель_ поездки или прогулки.

Решение: Маршруты, адаптированные под конкретное намерение пользователя.

  • Для туристов: «Живописный маршрут» с интересными видами и достопримечательностями
  • Для бегунов/велосипедистов: «Спортивный маршрут» с учетом рельефа (холмы для тренировки или ровные участки)
  • Для людей с ограниченной мобильностью: Маршруты с гарантированной доступностью
  • Для родителей с детьми: Маршруты с местами для остановок и отдыха

Реализация: Наложение дополнительных слоев данных (рельеф, зелень, инфраструктура) и машинное обучение на основе отзывов пользователей и данных из Google Street View.

Метрики успеха: Рост использования пешеходной/велосипедной навигации, повышение коэффициента завершения маршрутов, позитивные отзывы.

3️⃣ Социальный слой и коллективное планирование

Проблема: Google Maps слабо интегрирован с социальными взаимодействиями, хотя перемещения и исследование мест — часто коллективная активность.

Решение: Добавить социальный слой, позволяющий совместно планировать и координировать активности.

  • Совместное планирование: Создание маршрутов с друзьями с возможностью совместного редактирования и обсуждения
  • Временные локации друзей: Возможность временного шеринга местоположения для встреч, с контролем приватности
  • Групповые рекомендации: Предложения мест, учитывающие интересы всех участников группы
  • Разделение расходов: Интеграция с платежными сервисами для разделения расходов на топливо или такси

Почему это важно: Укрепляет социальную связанность продукта, увеличивает вовлеченность, открывает новые сценарии использования.

4️⃣ Соображения и компромиссы

Существует интересное напряжение между:

  • Простотой и функциональностью: Добавление новых функций без перегрузки интерфейса
  • Приватностью и полезностью: Больше данных = лучшие рекомендации, но выше риски для приватности
  • Монетизацией и пользовательским опытом: Как интегрировать бизнес-модель Google (реклама) в новые функции

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

★ Как улучшить процесс заказа еды в Uber Eats

1️⃣ Анализ текущей ситуации и выявление болевых точек

Для начала проанализирую текущий процесс заказа в Uber Eats и выявлю потенциальные болевые точки пользователей:

  1. Открытие приложения и поиск
  • Долгая загрузка
  • Перегруженный интерфейс
  • Не всегда релевантные рекомендации
  1. Выбор ресторана
  • Информационная асимметрия (сложно оценить соотношение цена/качество)
  • Недостаточно детальная информация о времени доставки
  • Ограниченная возможность сравнения вариантов
  1. Выбор блюд
  • Не всегда понятные описания
  • Отсутствие персонализированных рекомендаций
  • Сложность кастомизации заказа
  1. Оформление и оплата
  • Неожиданные наценки на последнем этапе
  • Перегруженный чекаут
  • Ограниченная гибкость в выборе времени доставки
  1. Отслеживание и получение
  • Неточные оценки времени
  • Недостаточная коммуникация с курьером
  • Проблемы с инструкциями по доставке

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

2️⃣ Предложения по улучшению

1. Персонализация и машинное обучение

  • Смарт-рекомендации: не просто по популярности, а на основе истории заказов и предпочтений
  • Предиктивный заказ: «Заказать как обычно» с одной кнопки для регулярных заказов
  • Персонализированные фильтры: автоматически выделять рестораны с блюдами, соответствующими вашим предпочтениям

2. Прозрачность и честность

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

3. Социальные функции

  • Групповые заказы: упрощение заказа для компании с отдельной оплатой
  • Рекомендации от друзей: что заказывают ваши друзья в этом ресторане
  • Совместные заказы с соседями: опция разделения стоимости доставки

4. Улучшения UX/UI

  • Упрощенный чекаут: сохранение всех предпочтений для быстрого оформления
  • Визуальный конструктор блюд: интерактивное добавление/удаление ингредиентов
  • Голосовой заказ: возможность заказать через голосовой интерфейс

5. Экологичность и социальная ответственность

  • Опция экологичной упаковки: выбор более экологичной тары
  • Функция пожертвования: возможность округлить сумму заказа и пожертвовать разницу на благотворительность
  • Углеродный след: информация об экологическом воздействии заказа

4️⃣ Приоритизация изменений

Я бы расставил приоритеты следующим образом:

Высокий приоритет (quick wins):

  • Единая прозрачная цена с самого начала
  • Предиктивный заказ и быстрое повторение
  • Улучшенная оценка времени доставки

Средний приоритет:

  • Групповые заказы
  • Визуальный конструктор блюд
  • Персонализированные рекомендации

Долгосрочные инициативы:

  • Социальные функции
  • Экологичные опции
  • Голосовое управление

5️⃣ Метрики успеха

Я бы отслеживал следующие метрики для оценки эффективности изменений:

  • Конверсия: процент пользователей, завершающих заказ
  • Время оформления: среднее время от открытия приложения до подтверждения заказа
  • Частота возврата: как часто пользователи возвращаются
  • Средний чек: увеличение среднего размера заказа
  • NPS: удовлетворенность пользователей
  • Процент отмен: сокращение количества отмененных заказов

6️⃣ Тестирование и внедрение

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

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

★ Что вы бы изменили в формате историй Instagram?

1️⃣ Анализ текущего состояния

Прежде чем предлагать изменения, давайте посмотрим на истории Instagram критическим взглядом.

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

Сильные стороны:

  • Простота создания и потребления
  • Быстрое скроллинг и перелистывание
  • Интеграция интерактивных элементов (опросы, вопросы)
  • Широкое распространение среди пользователей

Слабые стороны:

  • Ограниченность по длительности (15 секунд на фрагмент)
  • Отсутствие хорошей категоризации и поиска
  • Быстрое исчезновение (24 часа) ценного контента
  • Ограниченность в коллаборации между пользователями

2️⃣ Предлагаемые изменения

1. Тематические коллекции и улучшенная организация

Instagram позволяет сохранять истории в «Актуальное», но этого недостаточно. Я бы ввел:

  • Смарт-коллекции: автоматическая группировка историй по темам с помощью AI
  • Тематические хаштеги для историй: возможность искать истории по темам, не только по аккаунтам
  • Календарный вид: возможность просматривать истории в хронологическом порядке, как дневник

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

2. Расширенные возможности коллаборации

  • Совместные истории: возможность нескольким пользователям добавлять контент в одну историю (как совместные альбомы в Google Photos)
  • Реакции-дополнения: позволить подписчикам дополнять истории своим контентом (с модерацией автора)
  • Цепочки историй: легкий способ создавать тематические продолжения историй друг друга

Это решает проблему социальной изоляции в существующем формате — сейчас истории это в основном монолог.

3. Контекстное обогащение

  • Смарт-контекст: автоматическое добавление информации о местах, событиях или музыке
  • Углубленные ссылки: не просто свайп вверх, но контекстные действия в зависимости от контента
  • Интеграция с внешними сервисами: например, упомянул ресторан — появляется возможность забронировать столик

4. Адаптивная длительность

Сейчас все истории имеют фиксированный таймер. Я бы предложил:

  • Умный таймер: алгоритм, определяющий оптимальную длительность показа на основе сложности контента
  • Настройка индивидуальных предпочтений: пользователи могут выбрать, как долго они хотят смотреть истории
  • Замедление при взаимодействии: автоматическое увеличение времени, если пользователь начал печатать ответ

3️⃣ Тестирование и внедрение

Я понимаю, что нельзя сразу внедрять все эти изменения. Поэтому предложил бы:

  1. Сперва провести исследование предпочтений пользователей через опросы и фокус-группы
  2. Выбрать 1-2 самых перспективных изменения для A/B тестирования
  3. Измерять ключевые метрики:
    • Время, проведенное в просмотре историй
    • Количество взаимодействий
    • Частота создания историй
    • Уровень спада интереса (dropout rate)

4️⃣ Сложные компромиссы

В этих изменениях есть несколько интересных противоречий:

  1. Простота vs. функциональность: добавление функций может усложнить интерфейс
  2. Быстротечность vs. сохранность: эфемерность делает истории особенными, но ценный контент хочется сохранить
  3. Алгоритмическая лента vs. хронология: персонализированный контент против естественной последовательности

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

★ Переработка системы комментариев на YouTube

1️⃣ Анализ текущего состояния и выявление проблем

Прежде всего, нужно понять текущие проблемы с комментариями на YouTube:

  • Низкое качество дискуссий: часто встречаются поверхностные или токсичные комментарии
  • Система ранжирования: комментарии с наибольшим количеством лайков попадают наверх, что не всегда выявляет наиболее полезный контент
  • Ограниченные возможности для создателей: недостаточно инструментов для модерации и взаимодействия с аудиторией
  • Плохая организация при большом количестве комментариев: трудно следить за темами и находить интересные обсуждения
  • Отсутствие контекста: комментарии не привязаны к конкретным моментам видео
  • Проблемы с модерацией: автоматическая модерация часто дает ложные срабатывания или пропускает токсичный контент

Здесь возникает интересное противоречие между свободой самовыражения и качеством дискуссии. Чрезмерная модерация убивает спонтанность, но её недостаток приводит к хаосу.

2️⃣ Цели переработки

  1. Повысить качество дискуссий
  2. Улучшить возможности для создателей контента
  3. Сделать навигацию по комментариям более интуитивной
  4. Создать более персонализированный опыт
  5. Балансировать между свободой выражения и безопасной средой

3️⃣ Предлагаемые решения

1. Структурирование комментариев

  • Тематические теги: возможность создавать обсуждения по определенным аспектам видео
  • Привязка к временной шкале: комментарии, привязанные к конкретным моментам в видео
  • Визуализация дискуссий: графическое представление связей между комментариями для лучшего понимания структуры обсуждения

2. Улучшенная система ранжирования

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

3. Инструменты для создателей контента

  • Расширенные инструменты взаимодействия: возможность создавать опросы в комментариях, выделять «комментарий дня»
  • Улучшенная модерация: более гибкие настройки автоматической модерации
  • Аналитика комментариев: понимание тем, которые вызывают наибольший отклик

4. Социальные функции

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

5. Умная модерация

  • Контекстуальный AI: более точное определение токсичного контента с учетом контекста
  • Образовательный подход: предупреждения перед публикацией потенциально проблемного комментария
  • Самомодерация сообщества: расширенные возможности для зрителей помечать неприемлемый контент

4️⃣ Метрики успеха

  • Вовлеченность: количество и качество комментариев
  • Время взаимодействия: как долго пользователи проводят в разделе комментариев
  • Удовлетворенность: отзывы создателей и зрителей
  • Снижение токсичности: процент неприемлемых комментариев
  • Конверсия из зрителей в комментаторы: увеличение доли зрителей, оставляющих комментарии

5️⃣ Потенциальные риски и решения

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

6️⃣ План внедрения

  1. Исследовательская фаза: интервью с создателями контента и зрителями
  2. Прототипирование: создание и тестирование прототипов с фокус-группами
  3. Бета-версия: запуск обновленной системы для ограниченной группы каналов
  4. Глобальный запуск: поэтапное внедрение с тщательным мониторингом

В этой задаче интересна диалектика между стремлением к качественному обсуждению и массовой доступностью комментариев. YouTube изначально был создан как демократичная платформа «для всех», но при этом качество обсуждений страдает именно из-за этой массовости. Как создать систему, которая будет поощрять качество, оставаясь при этом доступной для всех — вот действительно интригующий вызов.

★ Исследование снижения длительности сеансов в стриминговом сервисе

1️⃣ Прояснение контекста и масштаба проблемы

Прежде всего, нужно более детально понять характер снижения:

  • Распространение проблемы: снижение наблюдается у всех пользователей или у определенных сегментов?
  • Географическая корреляция: затронуты все регионы или конкретные территории?
  • Привязка к устройствам: одинаково ли снижение на всех платформах (мобильные, Smart TV, десктоп)?
  • Временной паттерн: когда именно начался спад и был ли он резким или постепенным?

15% — это существенное падение, которое точно требует внимания. Давайте рассмотрим структурированный подход к решению проблемы.

2️⃣ Формулирование гипотез

Можно выделить несколько групп возможных причин:

Внутренние факторы (под нашим контролем)

  • Технические проблемы:
  • Снижение качества стриминга или частые буферизации
  • Задержки при загрузке контента
  • Баги в последнем обновлении приложения
  • Контентные изменения:
  • Запуск нового контента, не удерживающего внимание
  • Окончание популярных сериалов без достойной замены
  • Изменения в алгоритмах рекомендаций
  • Интерфейсные изменения:
  • Недавние изменения UX/UI, затрудняющие навигацию
  • Новые функции, отвлекающие от просмотра

Внешние факторы (вне нашего прямого контроля)

  • Конкуренция:
  • Запуск привлекательных сервисов-конкурентов
  • Эксклюзивный контент у конкурентов
  • Сезонность:
  • Изменение поведения пользователей из-за сезона или праздников
  • Возврат к «офлайн-активностям» после пандемии
  • Рыночные изменения:
  • Экономические факторы, влияющие на свободное время пользователей
  • Изменения в привычках медиапотребления

3️⃣ План исследования

Для исследования внутренних факторов:

Технический анализ
  • Проверить логи ошибок и времени загрузки
  • Сравнить показатели производительности до и после снижения
  • Оценить корреляцию между техническими сбоями и прерываниями сеансов
Анализ контента и взаимодействия
  • Исследовать данные о просмотрах по типам контента
  • Проанализировать, на каком этапе просмотра происходит завершение сеанса
  • Оценить эффективность системы рекомендаций
  • Проверить, как изменилась активность пользователей (поиск, пролистывание)
Пользовательские исследования
  • Провести A/B-тестирование проблемных зон
  • Организовать опросы пользователей о причинах сокращения времени просмотра
  • Провести углубленные интервью с сегментами, показавшими наибольший спад

Для исследования внешних факторов:

  • Проанализировать активность конкурентов и их недавние запуски
  • Сравнить тренды использования с предыдущими годами (сезонность)
  • Оценить макроэкономические тренды и их влияние на медиапотребление

4️⃣ Приоритизация действий

Я бы двигался по следующему плану:

  1. Быстрый анализ технических метрик — часто это самые очевидные и исправимые проблемы.
  2. Сегментация данных — определить, какие группы пользователей наиболее затронуты.
  3. Анализ точек выхода — понять, в какой момент просмотра пользователи прекращают сеанс.
  4. Качественные исследования — поговорить с пользователями напрямую.
  5. Анализ конкурентов — изучить, что происходит на рынке.

5️⃣ Возможные решения (в зависимости от выявленных причин)

Если проблема в:

  • Технических аспектах: оптимизация буферизации, улучшение CDN, исправление багов
  • Контенте: корректировка контентной стратегии, запуск новых шоу
  • Рекомендациях: настройка алгоритмов для увеличения релевантности
  • UX/UI: возврат к предыдущим версиям интерфейса или новые улучшения
  • Конкуренции: усиление дифференциации, новые ценностные предложения

6️⃣ Измерение результатов

Я бы отслеживал:

  • Возврат средней длительности сеанса к прежним значениям
  • Показатели вовлеченности (частота посещений, взаимодействие с контентом)
  • Удержание пользователей (churn rate)
  • NPS и удовлетворенность пользователей

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

★ Оценка запуска продукта в Бразилии

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

1️⃣ Структура анализа

Я бы оценивал это по нескольким ключевым направлениям:

  1. Анализ рынка и конкуренции
  2. Соответствие продукта местным потребностям
  3. Экономическое обоснование
  4. Операционные возможности и риски
  5. Стратегический контекст

2️⃣ Анализ рынка и конкуренции

Размер и потенциал рынка:

  • Численность населения Бразилии (более 210 миллионов человек) — огромный потенциальный рынок
  • Размер целевой аудитории для нашего продукта в Бразилии
  • Динамика роста этого сегмента рынка

Конкурентная среда:

  • Кто основные конкуренты на бразильском рынке?
  • Их рыночные доли, сильные и слабые стороны
  • Наше потенциальное конкурентное преимущество на этом рынке

Каналы дистрибуции:

  • Какие каналы работают эффективно в Бразилии
  • Затраты на привлечение клиентов через эти каналы

Тут возникает интересное противоречие между глобальной стандартизацией и локальной адаптацией — насколько наш продукт должен быть изменен для локального рынка?

3️⃣ Соответствие продукта местным потребностям

Культурные особенности:

  • Насколько наш продукт соответствует местным ценностям и привычкам
  • Потребность в локализации (язык, контент, дизайн)

Технологические факторы:

  • Уровень проникновения интернета и смартфонов в Бразилии
  • Особенности использования технологий (мобильные vs. десктоп)
  • Скорость интернета и технологическая инфраструктура

Потребительские предпочтения:

  • Чем бразильский пользователь отличается от пользователей на наших текущих рынках?
  • Какие адаптации продукта необходимы?

4️⃣ Экономическое обоснование

Прогноз доходов:

  • Оценка потенциального объема продаж
  • Ценовая стратегия с учетом покупательной способности
  • Особенности платежных систем в Бразилии

Затраты на вход и операционное присутствие:

  • Стоимость локализации продукта
  • Маркетинговые расходы для выхода на рынок
  • Операционные расходы (персонал, офисы, налоги)

Расчет ROI и точки безубыточности:

  • Через какой период мы ожидаем достичь прибыльности
  • Какие ключевые метрики будут свидетельствовать об успехе

5️⃣ Операционные возможности и риски

Юридические аспекты:

  • Особенности бразильского законодательства (защита данных, налоги, требования к бизнесу)
  • Защита интеллектуальной собственности

Логистика и инфраструктура:

  • Если наш продукт имеет физический компонент, каковы логистические возможности?
  • Если это цифровой продукт, каковы особенности облачной инфраструктуры в регионе?

Кадровые ресурсы:

  • Наличие квалифицированного персонала
  • Стратегия найма: локальная команда или удаленное управление

Политические и экономические риски:

  • Стабильность валюты (реал)
  • Политические риски, регуляторная среда

6️⃣ Стратегический контекст

Соответствие долгосрочной стратегии компании:

  • Как выход на бразильский рынок соотносится с нашей глобальной стратегией?
  • Может ли это быть плацдармом для расширения в других странах Латинской Америки?

Альтернативные возможности:

  • Сравнение с другими потенциальными рынками для экспансии
  • Возможность партнерства с местными компаниями вместо самостоятельного входа

7️⃣ Сбор данных и принятие решения

Для принятия обоснованного решения я бы предложил:

  1. Полевое исследование рынка:
  • Интервью с потенциальными пользователями в Бразилии
  • Фокус-группы для тестирования продукта
  1. Анализ данных:
  • Изучение рыночных отчетов и статистики
  • Анализ стратегий конкурентов
  1. Пилотный запуск:
  • Небольшое тестирование продукта для сбора данных о принятии и адаптации
  • A/B тестирование различных маркетинговых подходов
  1. Консультации с экспертами:
  • Привлечение местных экспертов для оценки рисков и возможностей
  • Встречи с потенциальными партнерами

8️⃣ Итоговая оценка

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

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

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

Что ты думаешь об этом подходе? Какие критические факторы кажутся тебе наиболее важными при рассмотрении выхода на бразильский рынок?

★ Анализ низкого показателя использования новой функции

1️⃣ Определение и измерение проблемы

Для начала я бы убедился, что правильно понимаю масштаб и характер проблемы:

  • Насколько «низким» является показатель использования? Есть ли у нас бенчмарки для сравнения?
  • Какие именно метрики мы отслеживаем — частоту использования, количество пользователей, попробовавших функцию, время взаимодействия?
  • Меняется ли показатель со временем или стабильно низкий?
  • Есть ли различия в использовании между сегментами пользователей (новые/старые, регионы, устройства)?

Я бы создал дашборд для мониторинга воронки использования функции — от обнаружения до завершения.

2️⃣ Формирование гипотез

Рассмотрел бы несколько возможных причин проблемы:

  1. Проблемы с обнаружением — пользователи не знают о функции или не могут её найти
  2. Проблемы с пониманием — непонятно, как использовать или какую ценность получат
  3. Проблемы с ценностью — функция не решает реальную потребность пользователей
  4. Проблемы с UX — интерфейс сложный, запутанный или неинтуитивный
  5. Технические проблемы — баги, медленная работа, сбои
  6. Контекстные ограничения — функция требует определённых условий, которые редко возникают

Тут возникает интересное напряжение между «быстрым» исправлением симптомов (улучшить UI, сделать более заметным) и решением возможных глубинных проблем (функция может не соответствовать реальным потребностям).

3️⃣ Исследование и проверка гипотез

Я бы использовал сочетание количественных и качественных методов:

Количественные исследования:

  • Анализ данных воронки использования — где именно пользователи «отваливаются»
  • A/B тестирование разных вариантов представления функции
  • Анализ корреляций между использованием функции и другими активностями в приложении
  • Сравнение показателей между разными когортами пользователей

Качественные исследования:

  • Юзабилити-тестирование с наблюдением за использованием функции
  • Интервью с пользователями (включая тех, кто попробовал и не продолжил использование)
  • Опросы для выяснения осведомлённости и восприятия функции
  • Анализ обратной связи из отзывов и обращений в поддержку

4️⃣ Разработка решений

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

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

5️⃣ План действий

Я бы предложил поэтапный план:

  1. Быстрые победы — изменения, которые можно внедрить немедленно (улучшение видимости, исправление очевидных UX-проблем)
  2. Средние по сложности изменения — улучшение обучающих материалов, корректировка ценностного предложения
  3. Стратегические изменения — если необходим существенный пересмотр функции

Каждый этап сопровождался бы тестированием и измерением результатов.

6️⃣ Измерение успеха

Ключевые метрики для оценки эффективности принятых мер:

  • Процент пользователей, знающих о функции
  • Процент пользователей, попробовавших функцию
  • Процент пользователей, продолжающих использовать функцию регулярно
  • Глубина использования (сколько раз / как долго)
  • Влияние на ключевые бизнес-показатели (удержание, конверсия, монетизация)

7️⃣ Вывод

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

Главное — не принимать поспешных решений, основанных только на одной метрике, а исследовать проблему всесторонне, опираясь как на данные, так и на глубокое понимание пользователей.

★ Как увеличить доходы от рекламы без ухудшения пользовательского опыта

1️⃣ Прояснение ситуации и целей

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

  • Какова текущая реклама в приложении (форматы, частота показов)
  • Что именно значит «увеличить доходы» — на какой процент и в какие сроки
  • Какие у нас есть данные о взаимодействии пользователей с существующей рекламой

2️⃣ Подход к решению задачи

1. Аналитический этап

  • Провести сегментацию пользователей по взаимодействию с рекламой
  • Изучить моменты, когда пользователи наиболее и наименее толерантны к рекламе
  • Проанализировать показатели вовлеченности и ухода пользователей в связи с рекламой

2. Стратегии увеличения доходов без ухудшения UX

А. Улучшение релевантности рекламы

  • Внедрение более точного таргетинга на основе контекста и поведения
  • Создание рекламных профилей для разных сегментов пользователей
  • Партнерство с рекламодателями, чей контент соответствует интересам пользователей

Б. Оптимизация размещения рекламы

  • Показ рекламы в естественных паузах пользовательского пути
  • Интеграция рекламы в моменты достижений или переходов между экранами
  • A/B тестирование различных мест размещения для выявления наименее раздражающих

В. Новые форматы с лучшим пользовательским опытом

  • Нативная реклама, стилистически соответствующая дизайну приложения
  • Reward ads (реклама за вознаграждение) — пользователь получает бонусы за просмотр
  • Спонсорский контент, создающий ценность для пользователя

Г. Премиальная реклама и партнерства

  • Прямые сделки с рекламодателями для более качественной и дорогой рекламы
  • Эксклюзивные партнерства с брендами, которые могут интегрироваться в функциональность
  • Создание премиальных рекламных слотов с лимитированным количеством показов

3. Тестирование и измерение воздействия

Здесь возникает интересное противоречие между измерением успеха в краткосрочной и долгосрочной перспективе.

Я предлагаю отслеживать следующие метрики:

  • Бизнес-метрики: доход на пользователя (ARPU), eCPM, CTR
  • Метрики пользовательского опыта: удержание, время в приложении, NPS
  • Комплексные метрики: LTV с учетом и рекламного дохода, и удержания

4. Поэтапное внедрение

Критически важно внедрять изменения постепенно:

  1. Начать с малых изменений на ограниченной выборке пользователей
  2. Тщательно измерять влияние на ключевые метрики
  3. Масштабировать успешные эксперименты
  4. Быть готовым быстро откатить изменения, если они негативно влияют на пользовательский опыт

4️⃣ Конкретные идеи для реализации

  1. Интеллектуальная ротация рекламы — показывать разные форматы в зависимости от контекста и активности пользователя
  2. Опциональная премиум-модель — дать возможность отключить рекламу за небольшую плату
  3. Геймификация рекламы — интегрировать просмотр рекламы в игровые механики приложения
  4. Пользовательский выбор — дать пользователям возможность выбрать предпочтительные категории рекламы

Основной принцип — реклама должна либо быть настолько ненавязчивой, чтобы не мешать, либо настолько ценной, чтобы пользователи были готовы с ней взаимодействовать.

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

★ Анализ и повышение конверсии на этапе регистрации в платёжной системе

1️⃣ Уточнение проблемы

Прежде чем предлагать решения, я бы уточнил детали и контекст:

  • 45% от какого общего числа? Насколько это ниже ожиданий?
  • Какова структура процесса регистрации? (количество шагов, запрашиваемая информация)
  • На каком конкретно этапе регистрации происходит основной отток?
  • Кто целевая аудитория и для чего они используют платёжную систему?

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

2️⃣ Анализ данных и диагностика

Я бы начал с детального анализа данных:

  1. Воронка конверсии по шагам:
  • Отслеживание на каком именно этапе пользователи прекращают регистрацию
  • Анализ времени, затрачиваемого на каждом шаге
  1. Сегментация пользователей:
  • Различия в конверсии между мобильными и десктоп-пользователями
  • Географические различия
  • Демографические данные
  • Источники трафика
  1. Качественные исследования:
  • Проведение интервью с пользователями, прекративших регистрацию
  • Анализ обратной связи от службы поддержки
  • Тестирование юзабилити с записью экрана

3️⃣ Вероятные причины низкой конверсии

На основе опыта с платёжными системами, могу предположить несколько вероятных причин:

  1. Проблемы доверия и безопасности
  • Недостаточные гарантии безопасности
  • Отсутствие понятной информации о защите данных
  • Недостаточная репутация бренда
  1. Сложность процесса
  • Слишком много полей для заполнения
  • Неясные требования к паролю или другим данным
  • Технические ошибки при заполнении форм
  1. Недостаточные стимулы
  • Неочевидная ценность регистрации
  • Отсутствие немедленной выгоды от завершения процесса
  1. Юридические ограничения
  • Сложные условия использования
  • Пугающие запросы на согласие с политиками

Тут интересное противоречие между потребностью в безопасности (требует больше шагов верификации) и удобством (требует минимум действий). Это ключевое напряжение в дизайне платёжных систем.

4️⃣ Решения для повышения конверсии

Немедленные тактические улучшения:

  1. Оптимизация форм:
  • Сокращение количества полей до минимально необходимого
  • Автозаполнение где возможно
  • Мгновенная валидация полей
  • Сохранение прогресса при переходе между шагами
  1. Усиление элементов доверия:
  • Добавление знаков доверия (SSL, PCI DSS, отзывы)
  • Прозрачная информация о безопасности
  • Добавление опции «Зачем нам нужны эти данные?» рядом с чувствительными полями
  1. Улучшение потока:
  • Внедрение индикатора прогресса
  • Разделение процесса на логические блоки
  • Возможность отложить проверку email/телефона
  1. Стимулы для завершения:
  • Предложение бонуса за регистрацию
  • Временная акция для новых пользователей
  • Подчеркивание ценности на каждом шаге

Стратегические изменения:

  1. Альтернативные пути регистрации:
  • Интеграция с Apple/Google/соцсетями для быстрой регистрации
  • Возможность начать использование с минимальной информацией и дозаполнять профиль позже
  1. Персонализация процесса:
  • Адаптация шагов в зависимости от сегмента пользователя
  • A/B тестирование разных потоков для разных аудиторий
  1. Редизайн ценностного предложения:
  • Переосмысление того, как мы коммуникируем ценность платёжной системы
  • Усиление ключевых преимуществ перед конкурентами

5️⃣ Метрики для отслеживания

  1. Основные метрики:
  • Общая конверсия регистрации (цель: с 45% до 65+%)
  • Конверсия по шагам
  • Время завершения регистрации
  1. Вторичные метрики:
  • Процент возвратов к незавершенной регистрации
  • NPS/CSAT после регистрации
  • Активация после регистрации (действительно ли пользователи используют сервис)

6️⃣ План реализации

  1. Быстрые победы (1-2 недели):
  • Упрощение формы
  • Добавление элементов доверия
  • Улучшение обработки ошибок
  1. Среднесрочные улучшения (1-2 месяца):
  • A/B тестирование различных стимулов
  • Внедрение альтернативных способов регистрации
  • Улучшение мобильного опыта
  1. Долгосрочные изменения (3+ месяцев):
  • Полный редизайн потока при необходимости
  • Интеграция с большим количеством сервисов
  • Пересмотр бизнес-модели если необходимо

Всё это время я продолжал бы сбор данных, проведение интервью с пользователями и регулярные A/B тесты, чтобы постоянно улучшать поток регистрации.

★ A/B-тестирование адаптивного процесса регистрации

Отличный вопрос! Адаптация шагов регистрации под разные сегменты — многообещающий подход, но требует структурированного экспериментирования. Сейчас расскажу, как бы я организовал A/B-тестирование этой функции.

1️⃣ Сегментация пользователей

Для начала определим значимые сегменты. Для платёжной системы это могут быть:

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

Тут возникает первое противоречие — между глубиной сегментации и статистической значимостью. Слишком много сегментов даст более персонализированный опыт, но затруднит получение статистически значимых результатов из-за малых размеров выборок.

2️⃣ Формулировка гипотез

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

  • Для мобильных пользователей: минимальная форма с возможностью дозаполнения увеличит конверсию на 15%
  • Для бизнес-пользователей: дополнительные элементы безопасности и верификации повысят доверие и конверсию на 10%
  • Для пользователей из органического поиска: акцент на преимуществах перед конкурентами увеличит конверсию на 20%

Важно, чтобы гипотезы были:

  • Измеримыми
  • Проверяемыми
  • Связанными с основной метрикой успеха (конверсия)
  • Основанными на предыдущих данных или качественных исследованиях

3️⃣ Дизайн эксперимента

Подготовка вариантов потока:

  1. Контрольная группа: стандартный процесс регистрации (одинаковый для всех)
  2. Вариант A: адаптированный поток для мобильных пользователей (2-3 шага, крупные кнопки)
  3. Вариант B: адаптированный поток для бизнес-пользователей (акцент на безопасность и бизнес-функции)
  4. Вариант C: адаптированный поток для пользователей из органики (акцент на конкурентные преимущества)

Техническая реализация:

  1. Настройка системы распределения трафика (например, через Google Optimize, Optimizely или собственную)
  2. Определение правил распределения пользователей по вариантам
  3. Настройка отслеживания поведения (event tracking) для каждого шага

Здесь важный нюанс — нам нужно сохранять консистентный опыт для одного пользователя. Если он покинул регистрацию и вернулся, он должен попасть в тот же вариант.

4️⃣ Определение метрик

Основные метрики:

  • Конверсия на финальном шаге (завершение регистрации)
  • Отток на каждом промежуточном шаге
  • Время заполнения формы

Дополнительные метрики:

  • Показатель возврата к незавершенной регистрации
  • Процент ошибок при заполнении полей
  • Активация пользователя после регистрации (совершил ли он транзакцию)

Интересное противоречие здесь — оптимизируя под краткосрочную метрику (конверсия регистрации), мы можем упустить из виду долгосрочную ценность (привлечение «правильных» пользователей, которые будут активно использовать систему).

5️⃣ Расчёт размера выборки и продолжительности

Используя инструменты статистического расчёта:

  1. Определим минимальное количество пользователей для каждого варианта
  2. Рассчитаем предполагаемую продолжительность теста

Например:

  • Текущая конверсия: 45%
  • Минимальное обнаруживаемое изменение: 5%
  • Уровень значимости: 95%
  • Статистическая мощность: 80%

Это даст нам необходимый размер выборки для каждого варианта и определит сроки тестирования.

6️⃣ Запуск и мониторинг

  1. Постепенный запуск эксперимента (5-10% трафика)
  2. Проверка технической корректности (трекинг работает, сегментация корректна)
  3. Расширение до полного масштаба (100% подходящего трафика)
  4. Регулярный мониторинг с помощью дашборда:
    • Проверка баланса групп (равномерное распределение)
    • Отслеживание аномалий
    • Промежуточный анализ

Важно не прекращать тест преждевременно при первых признаках улучшения — это может привести к ложным выводам.

7️⃣ Анализ результатов

После достижения статистической значимости:

  1. Комплексный анализ всех метрик по сегментам
  2. Сравнение вариантов между собой и с контрольной группой
  3. Проверка внешних факторов, которые могли повлиять (сезонность, маркетинговые кампании)
  4. Подготовка выводов и рекомендаций

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

8️⃣ Реализация и итерация

  1. Внедрение наиболее эффективных вариантов для соответствующих сегментов
  2. Документирование выученных уроков
  3. Планирование следующих итераций тестирования

Тут рождается ещё одно интересное противоречие — между стандартизацией пользовательского опыта и его персонализацией. Слишком разные пути для разных сегментов могут создать проблемы с поддержкой и развитием продукта.

9️⃣ Возможные подводные камни

  • Проблема пересечения сегментов: пользователь может попадать в несколько сегментов одновременно
  • Эффект новизны: временное улучшение показателей из-за новизны интерфейса
  • Риск переоптимизации: создание слишком специфичных путей для узких сегментов
  • Технические сложности при масштабировании адаптивного подхода

Я бы рекомендовал начать с 2-3 наиболее четких сегментов и постепенно расширять этот подход по мере получения положительных результатов.

Заключение

Завершая наше путешествие по ландшафту продуктового мышления, мы обнаруживаем себя в точке, где пересекаются диалектика и прагматика, теория и практика, системное и интуитивное. Книга «Cracking the PM Interview» предлагает нам не просто инструментарий для получения работы, но и окно в особый способ познания и преобразования реальности.

Разбирая кейсы — от будильника для слепых до платёжной системы с проблемной конверсией — мы видим, как продуктовое мышление постоянно преодолевает дихотомии. Дизайн инклюзивного продукта требует одновременно эмпатического вчувствования и строгого инженерного расчёта. Анализ метрик конверсии нуждается как в холодной аналитике, так и в понимании эмоциональных барьеров пользователей. A/B-тестирование демонстрирует нам границу между научным методом и творческим поиском.

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

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

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

Этот вопрос остаётся открытым — и в этой открытости, в этом продуктивном напряжении между структурой и свободой, формой и содержанием, системой и её преодолением и заключается вечное движение мысли, которое никогда не останавливается на достигнутом.

Углубленное изучение Pandas: структуры данных

Есть у меня определенное мнение, что понимание базовых структур данных в Pandas крайне важно, потому что это дает нам представление о фундаментальной основе тех операций, которые мы выполняем с данными. Pandas очень прост и предоставляет простой способ организации данных и манипулирования ими. Но часто бывает так, что для того, чтобы сделать что-то, выходящее за рамки мануалов из сети, мы должны сначала понять основные принципы, на которых построена технология. Структуры данных Series и DataFrame — это строительные блоки, из которых построена работа с данными в Pandas. И их понимание может помочь нам лучаше понять Pandas как продвинутый инструмент. Благодаря лучшему пониманию структур данных нам будет легче создавать эффективные пайплайны обработки данных, легче читать и поддерживать код, делать его более переиспользуемым. В конечном счете, понимая как Pandas работает на более глубоком уровне, позволит раскрыть потенциал Pandas на полную.

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


Введение

Pandas — одна из наиболее важных и широко используемых библиотек анализа данных в Python. Чтобы эффективно использовать Pandas, важно понимать две основные структуры данных, которые он предлагает: Series и DataFrame.

Series и DataFrames являются важными структурами данных, которые используются для хранения, обработки и анализа данных в Pandas. В этом уроке мы дадим всестороннее представление об этих структурах данных, включая их создание и индексация внутри этих структур данных. Также поделимся особенностями работы этих структур.

Этот пост будет посвящен следующим темам:

  • Объяснение разницы между Series и DataFrame;
  • Объяснение структуры данных Series в Pandas;
  • Обсуждение разницы между списком и Series;
  • Обсуждение того, как библиотека Pandas автоматически присваивает тип данных элементам в серии;
  • Объяснение структуры и свойств DataFrame, включая его столбцы, индекс и взаимосвязь между столбцами и строками как объектами Series.

II. Разница между Series и DataFrame

Представьте, что у вас есть последовательность точек данных, представляющих одну переменную, такую как рост, вес или возраст. Эта последовательность (мы её ещё можем назвать серией точек) точек данных может быть представлена с помощью объекта Series в Pandas. Series (можно перевести как «ряд») — это одномерная структура данных, которая содержит последовательность значений и похожа на список или массив.

Вот пример Series:


0    78
1    53
2    76
3    67
4    60
Name: age, dtype: int64

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

Вот пример DataFrame:


       height     weight  age
0  187.640523  83.831507   78
1  174.001572  51.522409   53
2  179.787380  52.295150   76
3  192.408932  74.693967   67
4  188.675580  53.268766   60

Разницу между Series и DataFrame можно сравнить с разницей между линией и квадратом. Линия является одномерной и может содержать только одну последовательность точек данных, в то время как квадрат является двумерным и может содержать несколько последовательностей точек. Таким же образом, Series содержит одну последовательность точек данных, в то время как DataFrame содержит несколько последовательностей точек данных в разных столбцах.

Итак, при работе с данными в Pandas важно понимать разницу между Series и DataFrame и выбирать соответствующую структуру данных на основе размерности ваших данных.


III. Структура данных Series

Series — это одномерный индексированный массив, способный содержать любой тип данных.

Создать Series в Pandas довольно просто. Чтобы создать Series, вы просто передаете список значений в виде списка в функцию `pd.Series()` и при желании указываете индекс. Вот пример:

import pandas as pd import numpy as np s = pd.Series([1, 3, 5, np.nan, 6, 8]) print(s)

0    1.0
1    3.0
2    5.0
3    NaN
4    6.0
5    8.0
dtype: float64

А вот пример создания Series с указанием индекса:

s = pd.Series([1, 3, 5, np.nan, 6, 8], index = [1,2,3,5,8,11]) print(s)

1     1.0
2     3.0
3     5.0
5     NaN
8     6.0
11    8.0
dtype: float64


Автоматическое определение типов

Когда вы передаете список в объект Series, Pandas автоматически определяет тип данных элементов в списке и присваивает серии соответствующий тип данных. Это означает, что вам не нужно вручную указывать тип данных, и Pandas позаботится об этом за вас.

Например, если вы передадите список целых чисел объекту Series, Pandas автоматически присвоит серии тип данных integer. Если вы передадите список строк, Pandas присвоит серии строковый тип данных. Это делает работу с данными в Pandas удобной и легкой, так как вам не нужно беспокоиться о ручном указании типа данных для каждой серии.

Вот пример, иллюстрирующий эту мысль:


# Создаем Series из списка целых чисел
integer_series = pd.Series([1, 2, 3, 4, 5])
print("Data type of integer series:", integer_series.dtype)

# Создаем Series из списка строк
string_series = pd.Series(["apple", "banana", "cherry", "date"])
print("Data type of string series:", string_series.dtype)


Data type of integer series: int64
Data type of string series: object

Как вы можете видеть, Pandas автоматически определил тип данных элементов в списке и присвоил Series соответствующий тип данных. В первом случае список целых чисел передается объекту Series, поэтому Pandas присваивает серии целочисленный тип данных `int64`. Во втором случае список строк передается объекту Series, поэтому Pandas присваивает Series тип данных `object`, поскольку строки хранятся в Pandas как объекты.


Автоматическое определение типов при смешанных типах

Если в списке есть хотя бы один элемент с типом данных `str`, то Pandas присвоит объектный тип данных всей серии, независимо от типов данных других элементов в списке.

Вот пример, иллюстрирующий это:


# Создаем Series, содержащий как целые числа, так и строки
mixed_series = pd.Series([1, 2, "apple", 4, "banana"])
print("Data type of mixed series:", mixed_series.dtype)


Data type of mixed series: object

Как вы можете видеть, несмотря на то, что список, переданный конструктору pd.Series(), содержит как целые числа, так и строки, результирующий Series имеет тип данных `object`, поскольку в списке есть по крайней мере один элемент с типом данных `str`. Это означает, что все элементы в Series будут рассматриваться как объекты, независимо от их фактического типа данных.


Индексирование в Series

Индексирование и построение срезов (slicing) в Series работают аналогично индексации в списке или массиве. Вы можете получить доступ к отдельным элементам серии, используя квадратные скобки со значением индекса. Вот пример:


print(s[0])


1.0


print(s[:3])


0    1.0
1    3.0
2    5.0
dtype: float64


Атрибут index

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

Вот несколько примеров того, как использовать атрибут index в Series:


# Создание Series со стандартными индексами
default_index_series = pd.Series([1, 2, 3, 4, 5])

# Вызов индексов
print(default_index_series.index)


RangeIndex(start=0, stop=5, step=1)


# Создание Series с пользовательскими индексами
custom_index_series = pd.Series([1, 2, 3, 4, 5], index=["a", "b", "c", "d", "e"])

# Вызов индексов
print(custom_index_series.index)


Index(['a', 'b', 'c', 'd', 'e'], dtype='object')


# Изменение индексов
custom_index_series.index = ["x", "y", "z", "w", "v"]
print(custom_index_series)


x    1
y    2
z    3
w    4
v    5
dtype: int64

Как вы можете видеть, вы можете получить атрибут `index` из Series, просто вызвав `series.index`. Кроме того, вы можете изменить атрибут `index`, присвоив ему новое значение, как показано в примере выше. Это может быть полезно, когда вы хотите изменить метки для элементов в Series.


Разница и сходство Series и списков

Series и обычные списки в Python похожи: они одномерные, хранят элементы, у элементов есть индексы. Но в отличие от обычного списка, индексы в Series могут быть изменяемыми и не обязательно начинаться с нуля или располагаться в возрастающем порядке. Кроме того, индексы в серии Pandas могут быть как целыми числами, так и строками.

Вот пример, иллюстрирующий это:


# Создаем серию с кастомным индексом
custom_index_series = pd.Series([1, 2, 3, 4, 5], index=["a", "b", "c", "d", "e"])
print(custom_index_series)

# Создаем серию с неинкрементальным индексом
non_incremental_index_series = pd.Series([1, 2, 3, 4, 5], index=[1, 5, 2, 6, 3])
print(non_incremental_index_series)


a    1
b    2
c    3
d    4
e    5
dtype: int64
1    1
5    2
2    3
6    4
3    5
dtype: int64

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


Вы также можете использовать вместе с Series различные методы Pandas, такие как `.head()`, `.tail()`, `.describe()` и т. д. Вот пример:


print(s.head())


0    1.0
1    3.0
2    5.0
3    NaN
4    6.0
dtype: float64


print(s.describe())


count    5.000000
mean     4.600000
std      2.701851
min      1.000000
25%      3.000000
50%      5.000000
75%      6.000000
max      8.000000
dtype: float64

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


IV. Структура данных DataFrame

DataFrame (датафрейм) — это двумерная структура данных, которая может содержать несколько типов данных в разных столбцах. Другими словами, DataFrame — это, по сути, таблица со строками и столбцами.

Создание DataFrame в Pandas выполняется с помощью конструктора `pd.DataFrame()`. Вы можете создать DataFrame из списка, словаря или массива NumPy. Вот пример использования словаря для создания DataFrame:


import pandas as pd

data = {
    'Name': ['John', 'Jane', 'Jim', 'Joan'],
    'Age': [32, 28, 41, 37],
    'City': ['New York', 'London', 'Paris', 'Berlin']
}
df = pd.DataFrame(data)
print(df)


   Name  Age      City
0  John   32  New York
1  Jane   28    London
2   Jim   41     Paris
3  Joan   37    Berlin

А вот пример создания DataFrame из списка списков:


# Создаем список списков
data = [['John', 25, 70, 180],
        ['Jane', 30, 65, 170],
        ['Jim', 35, 72, 185]]

# Создаем датафрейм из списка списков с колонками 'Name', 'Age', 'Weight', 'Height'
df = pd.DataFrame(data, columns=['Name', 'Age', 'Weight', 'Height'])

Этот код создает список списков под названием data. Каждый список внутри data представляет собой строку в DataFrame и содержит данные об имени, возрасте, весе и росте человека.

Затем мы создаем DataFrame из списка списков, передавая данные конструктору DataFrame. Мы также предоставляем список имен столбцов с аргументом columns.

Результирующий DataFrame будет выглядеть следующим образом:


   Name  Age  Weight  Height
0  John   25      70    180
1  Jane   30      65    170
2   Jim   35      72    185

Индексирование и срезы в DataFrame работают аналогично тому, как и в Series. Вы можете получить доступ к отдельным элементам, строкам или столбцам, используя квадратные скобки со значениями индекса. Вот пример:


print(df['Name'])


0    John
1    Jane
2     Jim
Name: Name, dtype: object


print(df[0:2])


   Name  Age  Weight  Height
0  John   25      70     180
1  Jane   30      65     170


Колонки — это Series

Когда вы вызываете колонки DataFrame, вы фактически получаете доступ к отдельным объектам Series, по одному для каждой колонки. Индекс внутри каждой Series совпадает с индексом DataFrame, что означает, что он содержит индексы строк внутри DataFrame.

Вот пример, иллюстрирующий это:


import pandas as pd

# Создаем DataFrame
df = pd.DataFrame({"A": [1, 2, 3, 4, 5],
                   "B": [6, 7, 8, 9, 10]})

# Получаем колонки
col_a = df["A"]
col_b = df["B"]

# Получаем индексы Series (колонок)
print(col_a.index)
print(col_b.index)

# Сравниваем индексы между собой
print(col_a.index == df.index)
print(col_b.index == df.index)


RangeIndex(start=0, stop=5, step=1)
RangeIndex(start=0, stop=5, step=1)
[ True  True  True  True  True]
[ True  True  True  True  True]

Как вы можете видеть, индекс как `col_a`, так и `col_b` совпадает с индексом DataFrame `df`, и сравнение между ними возвращает массив истинных значений, указывающий на то, что они действительно равны. Это означает, что вы можете использовать индекс DataFrame для доступа и манипулирования значениями в отдельных колонках, а также DataFrame в целом.


Атрибут name у Series

Атрибут `name` у Series позволяет получить название, присвоенное Series. Название используется для идентификации Series и может быть использовано как справочные данные, когда мы работаем с одинарной Series. В случае Series, полученного из DataFrame через доступ к его столбцам, значение атрибута `name` соответствует имени столбца, из которого был получен ряд.


np.random.seed(0)

data = {'height': np.random.normal(170, 10, 100),
        'weight': np.random.normal(65, 10, 100),
        'age': np.random.randint(18, 80, 100)}

df = pd.DataFrame(data)

height_series = df['height']

print(height_series.name)


height

Этот код выведет «height». «height» — название столбца исходного DataFrame, из которого была получена Series.


Строки — тоже Series

Когда вы извлекаете строку из DataFrame, вы тоже получаете объект Series. Индекс Series в этом случае выступает в качестве метки для каждого элемента в Series, что позволяет получать нужные данные. А ещё индекс Series эквивалентен именам колонок в DataFrame.


import pandas as pd
import numpy as np

np.random.seed(0)

data = {'height': np.random.normal(170, 10, 100),
        'weight': np.random.normal(65, 10, 100),
        'age': np.random.randint(18, 80, 100)}

df = pd.DataFrame(data)

first_row = df.loc[0]

print(first_row.index)

print(first_row['height'])


Index(['height', 'weight', 'age'], dtype='object')
187.64052345967664

Этот код выведет: Index([’height’, ’weight’, ’age’], dtype=’object’). Сначала мы получили первую строку из DataFrame (df.loc[0]) в виде Series. А затем вывели индекс этого Series. Видно, что он совпадает с именами колонок в DataFrame.

Когда вы извлекаете строку из DataFrame, вы получаете Series, и индекс серии действует как метка для каждого элемента в Series, позволяя вам ссылаться на данные при необходимости (например, таким образом выведено отдельное значение из first_row — first_row[’height’]). В этом случае индекс ряда эквивалентен именам столбцов в DataFrame.


Транспонирование

Сходство между строками и колонками в DataFrame приводит нас к операции транспонирования.

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

На уровне соединения столбцов и строк DataFrame состоит из нескольких Series, где каждый столбец представляет Series, а индекс каждой Series представляет индексы строк. Транспонирование DataFrame означает, что каждая Series-колонка становится новой строкой в транспонированном DataFrame, а метки строк каждой Series становятся новым индексом для транспонированного DataFrame.

Например, рассмотрим следующий код:


np.random.seed(0)

data = {'height': np.random.normal(170, 10, 100),
        'weight': np.random.normal(65, 10, 100),
        'age': np.random.randint(18, 80, 100)}

df = pd.DataFrame(data)

transposed_df = df.T

print(transposed_df)


                0           1          2           3           4           5   \
height  187.640523  174.001572  179.78738  192.408932  188.675580  160.227221   
weight   83.831507   51.522409   52.29515   74.693967   53.268766   84.436212   
age      78.000000   53.000000   76.00000   67.000000   60.000000   27.000000   

                6           7           8           9   ...          90  \
height  179.500884  168.486428  168.967811  174.105985  ...  165.968231   
weight   60.863810   57.525452   84.229420   79.805148  ...   52.071431   
age      62.000000   31.000000   75.000000   24.000000  ...   27.000000   

                91          92          93          94          95  \
height  182.224451  172.082750  179.766390  173.563664  177.065732   
weight   67.670509   64.607172   53.319065   70.232767   63.284537   
age      78.000000   45.000000   65.000000   53.000000   37.000000   

                96          97          98          99  
height  170.105000  187.858705  171.269121  174.019894  
weight   72.717906   73.235042   86.632359   78.365279  
age      30.000000   36.000000   67.000000   28.000000  

[3 rows x 100 columns]

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


V. Заключение

Библиотека Pandas для Python предоставляет эффективные структуры данных для хранения данных и манипулирования ими. Структуры Series и DataFrame являются основными объектами в Pandas. Series — это одномерный индексированный массив, а DataFrame — это двумерная структура данных с индексированными строками и столбцами.

DataFrame создаются из объектов Series, и каждая колонка в DataFrame представляет собой Series со своей собственной меткой. Таким же образом, когда мы обращаемся к строке в DataFrame, мы получаем объект Series, и индекс внутри этого объекта совпадает с индексом всего DataFrame.

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

Кого читать по теме аналитики данных

«Мы видим больше и дальше, чем они, не потому, что взгляд у нас острее и сами мы выше, но потому, что они подняли нас вверх и воздвигли на свою гигантскую высоту»

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

Наплывают воспоминания о том как в 2012 я изучал веб-аналитику с помощью блога web-analytic.ru, который вёл Даниил Азовских. С тех пор много воды утекло: сейчас Даниил уже не ведёт свой блог, а ещё я уже давно перестал читать блог Авинаша Кошика, но я с теплотой вспоминаю эти радостные моменты жадного поглощения новых знаний. Помню как статьи Олега Якубенкова помогли мне сделать тестовое задание на должность аналитика в Ингейт; статьи в блоге ohmystats Лёши Куличевского познакомили с когортным анализом и уже через несколько недель я хвастался перед коллегами тем, что умею строить когорты в Excel (в 2014 это был серьёзный повод похвастаться).

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

Check it out:

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

Головоломка про рандомный сэмпл

Введение

Иногда чтение чужого кода бывает крайне интересным и увлекательным, будто читаешь отличный нон-фикшн. Чаще бывает, что читаешь и понимаешь, что автор вообще не думал о том, что кто-то будет читать его творение. Будто намеренно обфусцировал. Еще бы переменные называл a,b,c,d и тогда можно было бы даже не пробовать разбираться.

И вот читая один ноутбук, я наткнулся на конструкцию, которая по началу ввела в ступор:

df.loc[df.index.isin(df.sample(int(len(df)*0.1)).index), 'some_column'] = 1

Немного подумав, я сообразил что таким образом делает автор и это решение показалось достаточно интересным, хотя и не достаточно прозрачным. Задача этой строчки кода — задать значение 1 в колонке some_column для 10% случайных строк в датафрейм.

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

В этой статье мы детально разберем как работает эта отдельно взятая строчка кода (да, целая статья ради строчки кода, так она меня вдохновила). На самом деле, в этой строчке заложен целый алгоритм. Мы поэтапно рассмотрим каждый этап работы этого алгоритма. Это может быть полезно для начинающих изучать Pandas (и Python в целом) в качестве примера того, что даже странные вещи при декомпозиции кажутся простыми и понятными.

Создание тестового датафрейма

Первое, что мы сделаем — это создадим тестовый датафрейм.

Сначала создаем массив numpy (numpy.array) с помощью функции np.random.randint. Эта функция позволяет создать массив numpy заданной размерности (size) и заполнить его случайными целыми числами из определенного диапазона. Нижняя граница диапазона задается в параметре low, а верхняя — в high. Создадим двумерный массив размерностью 100 на 5 (или 100 массивов по 5 элементов в каждом) со случайными числами от 0 до 9 включительно и запишем его в переменную random_array:

import numpy as np
import pandas as pd
random_array = np.random.randint(low=0,high=10,size=(100,5))
display(random_array)

Out:

Из получившегося numpy-массива random_array сделаем датафрейм df:

df = pd.DataFrame(random_array,columns=['a','b','c','d','e'])
display(df.head())

Out:

Декомпозируй это

А теперь приступим по кусочкам разбирать исходную строку кода. Немного изменим её и вместо имени колонки some_column зададим имя колонки, которая уже присутствует в нашем датафрейме df, например, колонку e:

df.loc[df.index.isin(df.sample(int(len(df)*0.1)).index), 'e'] = 1

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

1. Определение длины датафрейма

Начнем с конструкции len(df):

Тут всё просто — стандартная питоновская функция len при передаче в неё датафрейма возвращает его длину. Запишем результат в переменную count_of_rows. Ожидаемо, count_of_rows у нас будет равняться 100:

count_of_rows = len(df)
print(count_of_rows)

Out:

2. Определение размера случайной выборки

Посмотрим что происходит на следующем этапе:

Тут полученное количество строк умножается на 0.1 и затем приводится к целому числу. То есть мы получаем 1/10 от длины датафрейма df. Запишем результат в переменную sample_size. В нашем случае это будет число 10:

sample_size = int(count_of_rows*0.1)
print (sample_size)

Out:

3. Формирование случайной выборки

Дальше нас встречает функция sample:

Эта функция нужна для получения случайной выборки из датафрейма. Мы передаем в функцию sample значение sample_size, тем самым указывая, что нам нужна выборка заданного размера (10 строк) и записываем выборку в переменную sample_from_df:

sample_from_df = df.sample(sample_size)
display(sample_from_df)

Out:

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

4. Получение индекса из случайной выборки

Следующий шаг — получение индекса строк из образовавшейся выборки:

Полученные индексы запишем в переменную index_of_sample_from_df:

index_of_sample_from_df = sample_from_df.index
display(index_of_sample_from_df)

Out:

5. Создание маски для выборки

Дальше нас встречает конструкция с оператором //isin:

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

Итак, что же делает функция isin? Когда она применяется к индексу, то она возвращает массив булевых (True/False) значений, где True будет соответствовать тем строкам датафрейма df, индексы которых находятся в массиве index_of_sample_from_df, а False — всем остальным строкам. Запишем такой массив в переменную binary_mask:

binary_mask = df.index.isin(index_of_sample_from_df)
display(binary_mask)

Out:

6. Присвоение значения в соответствии с маской

И последний этап нашего алгоритма — присвоить значение 1 в колонке e строкам датафрейма df, которые попали в случайную 10ти-процентную выборку:

Такие строки как раз можно выделить из датафрейма df, передав битовую маску binary_mask в функцию loc.

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

df.loc[binary_mask, 'e'] = 1
display(df.loc[binary_mask])

Out:

Обратите внимание на индекс этой выборки. Он совпадает с индексом случайной выборки index_of_sample_from_df. Так и задумывалось! :)

df.loc[binary_mask].index == sorted(sample_from_df.index)

Out:

Заключение

Таким образом, рассмотрев одну строчку кода мы рассмотрели небольшой алгоритм преобразования данных и углубились внутрь работы множества функций Pandas: sample, index, isin, loc.

Можно ли было проще?

В завершение рассмотрим как можно было бы сделать этот код чуть более легким. На самом деле, нет нужды в использовании конструкции df.index.isin. Хотя в некоторых случаях, от этой функции есть польза, например, при работе с мультииндексами, но об этом как-нибудь в другой раз.

Переменная index_of_sample_from_df уже содержит индексы строк, передав которые в loc вместо binary_mask мы получим выборку строк в соответствии с индексами:

df.loc[index_of_sample_from_df, 'e'] = 1
display(df.loc[index_of_sample_from_df])

Out:

Учитывая это, наш код можно было бы сократить:

df.loc[df.sample(int(len(df)*0.1)).index, 'e'] = 1

Или в более читабельном, но более длинном, виде:

count_of_rows = len(df)
sample_size = int(count_of_rows*0.1)
sample_from_df = df.sample(sample_size)
index_of_sample_from_df = sample_from_df.index
df.loc[index_of_sample_from_df, 'e'] = 1

Я уверен, что у этой задачи есть и другие решения. Если вам придёт в голову своё решение — не стесняйтесь писать в комментариях.

На этом всё. Спасибо, что читаете!

Успехов!

Работаем с API Google Drive с помощью Python

Решил написать достаточно подробную инструкцию о том как работать с API Google Drive v3 с помощью клиентской библиотеки Google API для Python. Статья будет полезна тем, кому приходится часто работать с документами в Google Drive: скачивать и загружать новые документы, удалять файлы, создавать папки.

Также я покажу пример того как можно с помощью API скачивать файлы Google Sheets в формате Excel, или наоборот: заливать в Google Drive файл Excel в виде документа Google Sheets.

Использование API Google Drive может быть полезным для автоматизации различной рутины, связанной с отчетностью. Например, я использую его для того, чтобы по расписанию загружать заранее подготовленные отчеты в папку Google Drive, к которой есть доступ у конечных потребителей отчетов.

Все примеры на Python 3.

Создание сервисного аккаунта и получение ключа

Прежде всего создаем сервисный аккаунт в консоли Google Cloud и для email сервисного аккаунта открываем доступ на редактирование необходимых папок. Не забудьте добавить в папку файлы, если их там нет, потому что файл нам понадобится, когда мы будем выполнять первый пример — скачивание файлов из Google Drive.

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

Установка клиентской библиотеки Google API и получение доступа к API

Сначала устанавливаем клиентскую библиотеку Google API для Python

pip install --upgrade google-api-python-client

Дальше импортируем нужные модули или отдельные функции из библиотек.

Ниже будет небольшое описание импортируемых модулей. Это для тех кто хочет понимать, что импортирует, но большинство просто может скопировать импорты и вставить в ноутбук :)

  • Модуль service_account из google.oauth2 понадобится нам для авторизации с помощью сервисного аккаунта.
  • Классы MediaIoBaseDownload и MediaFileUpload, как ясно из названий, пригодятся, чтобы скачать или загрузить файлы. Эти классы импортируются из googleapiclient.http
  • Функция build из googleapiclient.discovery позволяет создать ресурс для обращения к API, то есть это некая абстракция над REST API Drive, чтобы удобнее обращаться к методам API.
from google.oauth2 import service_account
from googleapiclient.http import MediaIoBaseDownload,MediaFileUpload
from googleapiclient.discovery import build
import pprint
import io

pp = pprint.PrettyPrinter(indent=4)

Указываем Scopes. Scopes — это перечень возможностей, которыми будет обладать сервис, созданный в скрипте. Ниже приведены Scopes, которые относятся к API Google Drive (из официальной документации):

Как видно, разные Scope предоставляют разный уровень доступа к данным. Нас интересует Scope «https://www.googleapis.com/auth/drive», который позволяет просматривать, редактировать, удалять или создавать файлы на Google Диске.

Также указываем в переменной SERVICE_ACCOUNT_FILE путь к файлу с ключами сервисного аккаунта.

SCOPES = ['https://www.googleapis.com/auth/drive']
SERVICE_ACCOUNT_FILE = '/home/makarov/Google Drive Test-fc4f3aea4d98.json'

Создаем Credentials (учетные данные), указав путь к сервисному аккаунту, а также заданные Scopes. А затем создаем сервис, который будет использовать 3ю версию REST API Google Drive, отправляя запросы из-под учетных данных credentials.

credentials = service_account.Credentials.from_service_account_file(
        SERVICE_ACCOUNT_FILE, scopes=SCOPES)
service = build('drive', 'v3', credentials=credentials)

Получение списка файлов

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

  • pageSize — количество результатов выдачи. Можете смело ставить максимальное значение 1000. У меня стоит 10 результатов, чтобы показать как быть, когда нужно получить результаты по следующей страницы результатов
  • параметр files() в fields — параметр, указывающий, что нужно возвращать список файлов, где в скобках указан список полей для файлов, которые нужно показывать в результатах выдачи. Со всеми возможными полями можно познакомиться в документации (https://developers.google.com/drive/api/v3/reference/files) в разделе «Valid fields for files.list». У меня указаны поля для файлов: id (идентификатор файла в Drive), name (имя) и mimeType (тип файла). Чуть дальше мы рассмотрим пример запроса с большим количеством полей
  • nextPageToken в fields — это токен следующей страницы, если все результаты не помещаются в один ответ
results = service.files().list(pageSize=10,
                               fields="nextPageToken, files(id, name, mimeType)").execute()

Получили вот такие результаты:

pp.pprint(results)
print(len(results.get('files')))

10

Получив из результатов nextPageToken мы можем передать его в следущий запрос в параметре pageToken, чтобы получить результаты следующей страницы. Если в результатах будет nextPageToken, это значит, что есть ещё одна или несколько страниц с результатами

nextPageToken = results.get('nextPageToken')
results_for_next_page = service.files().list(pageSize=10,
                               fields="nextPageToken, files(id, name, mimeType)",
                               pageToken=nextPageToken).execute()
print (results_for_next_page.get('nextPageToken'))

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

results = service.files().list(pageSize=10,
                               fields="nextPageToken, files(id, name, mimeType)").execute()
nextPageToken = results.get('nextPageToken')
while nextPageToken:
        nextPage = service.files().list(pageSize=10,
                                        fields="nextPageToken, files(id, name, mimeType, parents)",
                                        pageToken=nextPageToken).execute()
        nextPageToken = nextPage.get('nextPageToken')
        results['files'] = results['files'] + nextPage['files']
print(len(results.get('files')))

24

Дальше давайте рассмотрим какие ещё поля можно использовать для списка возвращаемых файлов. Как я уже писал выше, со всеми полями можно ознакомиться по ссылке. Давайте рассмотрим самые полезные из них:

  • parents — ID папки, в которой расположен файл/подпапка
  • createdTime — дата создания файла/папки
  • permissions — перечень прав доступа к файлу
  • quotaBytesUsed — сколько места от квоты хранилища занимает файл (в байтах)
results = service.files().list(
        pageSize=10, fields="nextPageToken, files(id, name, mimeType, parents, createdTime, permissions, quotaBytesUsed)").execute()

Отобразим один файл из результатов с расширенным списком полей. Как видно permissions содержит информацию о двух юзерах, один из которых имеет role = owner, то есть владелец файла, а другой с role = writer, то есть имеет право записи.

pp.pprint(results.get('files')[0])

Очень удобная штука, позволяющая сократить количество результатов в запросе, чтобы получать только то, что действительно нужно — это возможность задать параметры поиска для файлов. Например, мы можем задать в какой папке искать файлы, зная её id:

results = service.files().list(
    pageSize=5, 
    fields="nextPageToken, files(id, name, mimeType, parents, createdTime)",
    q="'1mCCK9QGQxLDED8_pgq2dyvkmGRXhWEtJ' in parents").execute()
pp.pprint(results['files'])

С синтаксисом поисковых запросов можно ознакомиться в документации. Ещё один удобный способ поиска нужных файлов — по имени. Вот пример запроса, где мы ищем все файлы, содержащие в названии «data»:

results = service.files().list(
    pageSize=10, 
    fields="nextPageToken, files(id, name, mimeType, parents, createdTime)",
    q="name contains 'data'").execute()
pp.pprint(results['files'])

Условия поиска можно комбинировать. Возьмем условие поиска в папке и совместим с условием поиска по названию:

results = service.files().list(
    pageSize=10, 
    fields="nextPageToken, files(id, name, mimeType, parents, createdTime)",
    q="'1uuecd6ndiZlj3d9dSVeZeKyEmEkC7qyr' in parents and name contains 'data'").execute()
pp.pprint(results['files'])

Скачивание файлов из Google Drive

Теперь рассмотрим как скачивать файлы из Google Drive. Для этого нам понадобится создать запрос request для получения файла. После этого задаем интерфейс fh для записи в файл с помощью библиотеки io, указав в filename название файла (таким образом, можно сохранять файлы из Google Drive сразу с другим названием). Затем создаем экземпляр класса MediaIoBaseDownload, передав наш интерфейс для записи файла fh и запрос для скачивания файла request. Следующим шагом скачиваем файл по небольшим кусочкам (чанкам) с помощью метода next_chunk.

Если из предыдущего описания вам мало что понятно, не запаривайтесь, просто укажите свой file_id и filename, и всё у вас будет в порядке.

file_id = '1HKC4U1BMJTsonlYJhUKzM-ygrIVGzdBr'
request = service.files().get_media(fileId=file_id)
filename = '/home/makarov/File.csv'
fh = io.FileIO(filename, 'wb')
downloader = MediaIoBaseDownload(fh, request)
done = False
while done is False:
    status, done = downloader.next_chunk()
    print ("Download %d%%." % int(status.progress() * 100))

Файлы Google Sheets или Google Docs можно конвертировать в другие форматы, указав параметр mimeType в функции export_media (обратите внимание, что в предыдущем примере скачивания файла мы использоали другую функцию get_media). Например, файл Google Sheets можно конвертировать и скачать в виде файла Excel.

file_id = '10MM2f3V98wTu7GsoZSxzr9hkTGYvq_Jfb2HACvB9KjE'
request = service.files().export_media(fileId=file_id,
                                             mimeType='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
filename = '/home/makarov/Sheet.xlsx'
fh = io.FileIO(filename, 'wb')
downloader = MediaIoBaseDownload(fh, request)
done = False
while done is False:
    status, done = downloader.next_chunk()
    print ("Download %d%%." % int(status.progress() * 100))

Затем скачанный файл можно загнать в датафрейм. Это достаточно простой способ получить данные из Google Sheet в pandas-dataframe, но есть и другие способы, например, воспользоваться библиотекой gspread.

import pandas as pd
df = pd.read_excel('/home/makarov/Sheet.xlsx')
df.head(5)

Загрузка файлов и удаление в Google Drive

Рассмотрим простой пример загрузки файла в папку. Во-первых, нужно указать folder_id — id папки (его можно получить в адресной строке браузера, зайдя в папку, либо получив все файлы и папки методом list). Также нужно указать название name, с которым файл загрузится на Google Drive. Это название может быть отличным от исходного названия файла. Параметры folder_id и name передаем в словарь file_metadata, в котором задаются метаданные загружаемого файла. В переменной file_path указываем путь к файлу. Создаем объект media, в котором будет указание по какому пути находится загружаемый файл, а также указание, что мы будем использовать возобновляемую загрузку, что позволит нам загружать большие файлы. Google рекомендует использовать этот тип загрузки для файлов больше 5 мегабайт. Затем выполняем функцию create, которая позволит загрузить файл на Google Drive.

folder_id = '1mCCK9QGQxLDED8_pgq2dyvkmGRXhWEtJ'
name = 'Script_2.py'
file_path = '/home/makarov/Script.py'
file_metadata = {
                'name': name,
                'parents': [folder_id]
            }
media = MediaFileUpload(file_path, resumable=True)
r = service.files().create(body=file_metadata, media_body=media, fields='id').execute()
pp.pprint(r)

Как видно выше, при вызове функции create возвращается id созданного файла. Можно удалить файл, вызвав функцию delete. Но мы этого делать не будет так как файл понадобится в следующем примере

service.files().delete(fileId='18Wwvuye8dOjCZfJzGf45yQvB87Lazbzu').execute()

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

print (credentials.signer_email)
results = service.files().list(
    pageSize=10, 
    fields="nextPageToken, files(id, name, mimeType, parents, createdTime)",
    q="'namby-pamby@tensile-verve-232214.iam.gserviceaccount.com' in owners").execute()
pp.pprint(results['files'][0:3])

Дальше — больше. С помощью API Google Drive мы можем загрузить файл с определенным mimeType, чтобы Drive понял к какому типу относится файл и предложил соответствующее приложение для его открытия.

folder_id = '1mCCK9QGQxLDED8_pgq2dyvkmGRXhWEtJ'
name = 'Sample data.csv'
file_path = '/home/makarov/sample_data_1.csv'
file_metadata = {
                'name': name,
                'mimeType': 'text/csv',
                'parents': [folder_id]
            }
media = MediaFileUpload(file_path, mimetype='text/csv', resumable=True)
r = service.files().create(body=file_metadata, media_body=media, fields='id').execute()
pp.pprint(r)

Но ещё более классная возможность — это загрузить файл одного типа с конвертацией в другой тип. Таким образом, мы можем залить csv файл из примера выше, указав для него тип Google Sheets. Это позволит сразу же конвертировать файл для открытия в Гугл Таблицах. Для этого надо в словаре file_metadata указать mimeType «application/vnd.google-apps.spreadsheet».

folder_id = '1mCCK9QGQxLDED8_pgq2dyvkmGRXhWEtJ'
name = 'Sheet from csv'
file_path = '/home/makarov/notebooks/sample_data_1.csv'
file_metadata = {
                'name': name,
                'mimeType': 'application/vnd.google-apps.spreadsheet',
                'parents': [folder_id]
            }
media = MediaFileUpload(file_path, mimetype='text/csv', resumable=True)
r = service.files().create(body=file_metadata, media_body=media, fields='id').execute()
pp.pprint(r)

Таким образом, загруженный нами CSV-файл будет доступен как Гугл Таблица:

Ещё одна часто необходимая функция — это создание папок. Тут всё просто, создание папки также делается с помощью метода create, надо только в file_metadata указать mimeType «application/vnd.google-apps.folder»

folder_id = '1uuecd6ndiZlj3d9dSVeZeKyEmEkC7qyr'
name = 'New Folder'
file_metadata = {
    'name': name,
    'mimeType': 'application/vnd.google-apps.folder',
    'parents': [folder_id]
}
r = service.files().create(body=file_metadata,
                                    fields='id').execute()
pp.pprint(r)

Заключение
Все содержимое этой статьи также представлено в виде ноутбука для Jupyter Notebook.

В этой статье мы рассмотрели лишь немногие возможности API Google Drive, но одни из самых необходимых:

  • Просмотр списка файлов
  • Скачивание документов из Google Drive (в том числе, скачивание с конвертацией, например, документов Google Sheets в формате Excel)
  • Загрузка документов в Google Drive (также как и в случае со скачиванием, с возможностью конвертации в нативные форматы Google Drive)
  • Удаление файлов
  • Создание папок

Вступайте в группу на Facebook и подписывайтесь на мой канал в Telegram, там публикуются интересные статьи про анализ данных и не только.

Успехов!

Большая подборка полезных ссылок про Pandas

Я достаточно долго веду группу в Facebook и канал в Telegram, посвященные анализу данных на Python и за это время накопилось множество полезных ссылок на материалы о библиотеке Pandas. Решил, что будет здорово, если все они будут в одном месте, а не разбросаны по каналу/группе. Поэтому завёл табличку в Notion, где все ссылки протегированы и разбиты по языку (ru, eng). Также в документе прикреплена ссылка, с помощью которой можно порекомендовать материал, будь то ваша авторская статья или просто понравившийся материал с просторов интернета. Материал по pandas появится в подборке и с высокой вероятностью может быть опубликован в канале и группе.

Встречайте: подборка полезных материалов про библиотеку Pandas

Лайк, шер, пускай больше людей приобщится к использованию Pandas!

Как в Pandas разбить одну колонку на несколько

Введение в задачу

Решил начать рассматривать нетривиальные кейсы в Pandas, с которыми иногда сталкиваюсь при работе с данными. Опять же, нетривиальные они только на мой взгляд, потому что какие-то вещи я решаю впервые и они заставляют немного подумать :) Возможно, такие небольшие кейсы помогут аналитикам, если они увидят в своих «затыках» что-то похожее. Также, я не претендую на абсолютную правильность или универсальность решения. Таким образом, у подобной задачи может быть несколько правильных решений. Я буду рад, если в комментариях вы будете предлагать свои решения. Касательно универсальности решения, тут я имею в виду, что решение может быть применимо к конкретному датасету, но при этом может не работать, если датасет будет иметь какие-то существенные видоизменения.

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

Итак, у меня есть вот такой dataframe:

Скачать данные для датафрейма в csv можно тут.

Выведем первое значение из колонки new_values, чтобы лучше понять что же нам надо сделать:

Как видно, значения разделены знаком переноса строки (\n), а также каждое значение представлено в виде ключ=значение (например, ключом выступает date_start, а значением 2018-12-04).

Задача состоит в том, чтобы привести датафрейм к вот такому виду:

Решение №1

Итак, первое решение будет простым и достаточно коротким.

Сначала, нам понадобится пандосовская функция str.split (https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.str.split.html). Она просто разбивает столбец на список на основании разделителя

df['new_values'].str.split('\n')

Вот как выглядит отдельное значение:

df['new_values'].str.split('\n')[0]

Но у функции str.split есть замечательный параметр expand=True, позволяющий сразу сделать разбиение на колонки и получить датафрейм

new_df = df['new_values'].str.split('\n',expand=True)
new_df

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

new_df.columns=['date_start','component','customer']
new_df

Затем нам нужно избавиться в колонках от названия ключа и знака «равно». Сделаем это простым циклом с функцией str.replace (https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.str.replace.html):

for column in new_df.columns:
    new_df[column] = new_df[column].str.replace(column+'=','')
new_df

Осталось только соединить два датафрейма (исходный df и new_df) с помощью функции pd.concat (https://pandas.pydata.org/pandas-docs/version/0.23.4/generated/pandas.concat.html). Обратите внимание на параметр axis=1, который позволяет соединить датафреймы по столбцам, а не по строкам

final_df = pd.concat([df,new_df],axis=1)
final_df

Ну и выкинем из получившегося датафрейма ненужный нам теперь столбец new_values с помощью drop (https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.drop.html):

final_df = final_df.drop('new_values',axis=1)
final_df

Итоговый код для решения задачи выглядит так:

df = pd.read_csv('sample_data_1.csv')
new_df = df['new_values'].str.split('\n',expand=True)
new_df.columns=['date_start','component','customer']
for column in new_df.columns:
    new_df[column] = new_df[column].str.replace(column+'=','')
final_df = pd.concat([df,new_df],axis=1).drop('new_values',axis=1)

Решение №2

Другой способ, который я покажу, является более универсальным.

Представим, что исходный датафрейм отличается от того, что я показывал выше.

Скачать данные для датафрейма в csv можно тут.

Этот датафрейм отличается тем, что в некоторых строках колонки new_values отсутствует параметр date_start. Из-за этого при попытке сделать str.split с параметром expand=True мы получим вот такой датафрейм:

df['new_values'].str.split('\n',expand=True)

В этом случае, мы не сможем просто пройтись циклом по колонкам и получить нужные значения, так как значения по ключу не всегда однозначно находятся в одной колонке, а могут быть разбросаны по нескольким. Такая же ситуация могла бы быть, если бы перечисление значений шло не в одном порядке, а по-разному. Например в одной строке date_start=2018-12-28\ncomponent=abc\ncustomer=59352, а в другой component=abc\ncustomer=22080\ndate_start=2018-12-18

Чтобы обойти эту проблему нам нужно преобразовать эти данные в такую структуру, которую было бы удобно запихнуть в датафрейм и pandas сам бы смог разделить данные по нужным колонкам, опираясь на структуру данных. Одной из таких структур может быть список словарей (list of dicts). В виде списка словарей данные должны выглядеть вот так:

Когда мы получим такую структуру, то потом сможем преобразовать её в датафрейм, а полученный датафрейм соединить с исходным (как мы уже это делали выше с помощью функции pd.concat).

Итак, первое что мы сделаем, это разобъем колонку с помощью уже знакомой функции str.split, но без параметра expand=True. Это позволит нам сделать отдельный series, содержащий списки:

s = df['new_values'].str.split('\n')
s

Затем каждый из списков нам нужно преобразовать к словарю. То есть совершить вот такое преобразование:

Чтобы лучше понять суть преобразования покажу на примере одного списка s[0], а затем сделаем функцию, которую применим к каждому элементу в series.

Сначала сделаем внутри списка s[0] вложенные списки, сделав split каждого из элемента списка по знаку «равно» через list comprehension (подробнее про list comprehension можно прочитать в статье, скажу только, что это очень удобно):

splited_items = [a.split('=') for a in s[0]]
splited_items

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

dictionary = {}
for item in splited_items:
    key = item[0]
    value = item[1]
    dictionary[key] = value
dictionary

Сделаем функцию, которая делает вышеописанное преобразование:

def convertToDict(x):
    splited_items = [a.split('=') for a in x]
    dictionary = {}
    for item in splited_items:
        key = item[0]
        value = item[1]
        dictionary[key] = value
    return dictionary

После этого можно применить функцию к серии s с помощью apply (https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.apply.html):

s = s.apply(lambda x: convertToDict(x))
s

Затем применим функцию pd.Series к каждому из словарей, таким образом преобразовав каждый словарь в series, а вместе последовательность series образует датафрейм:

new_df = s.apply(pd.Series)
new_df

Следующим шагом соединим датафрейм new_df с исходным df и уберем столбец с помощью функции drop:

final_df = pd.concat([df,new_df],axis=1).drop('new_values',axis=1)
final_df

Итоговый код для обработки данных выглядит вот так:

def convertToDict(x):
    splited_items = [a.split('=') for a in x]
    dictionary = {}
    for item in splited_items:
        key = item[0]
        value = item[1]
        dictionary[key] = value
    return dictionary

df = pd.read_csv('sample_data_2.csv')
s = df['new_values'].str.split('\n')
s = s.apply(lambda x: convertToDict(x))
new_df = s.apply(pd.Series)
final_df = pd.concat([df,new_df],axis=1).drop('new_values',axis=1)

И в качестве бонуса, можно сделать код более удобочитаемым с помощью method chaining:

df = pd.read_csv('sample_data_2.csv')
new_df = df['new_values'].str.split('\n').\
    apply(lambda x: convertToDict(x)).\
    apply(pd.Series)
final_df = pd.concat([df,new_df],axis=1).drop('new_values',axis=1)

Решение Романа Шапкова

UPD от 30.01.2019:
Читатель Роман решил эту задачу с помощью регулярных выражений

import pandas as pd
import re

df = pd.read_csv('sample_data_2.csv')

pattern_date = '(\d{4}-\d{2}-\d{2})'
pattern_ncomp = 'component=(\w*)'
pattern_ncust = 'customer=(\d*)'

def find_pattern(string, pattern):

    """
    аргументы: string - текстовая строка для поиска
    pattern - шаблон регулярного выражения

    функция осуществляет поиск шаблона "pattern" в строке "string" используя правила регулярных выражений(RegExp).
    Если шаблон найден - возвращает значение, иначе - возвращает None
    """
    if re.search(pattern, string):
        return re.search(pattern, string).group(1)

df['start_date'] = df['new_values'].apply(lambda x: find_pattern(x,pattern_date))
df['component'] = df['new_values'].apply(lambda x: find_pattern(x,pattern_ncomp))
df['customer'] = df['new_values'].apply(lambda x: find_pattern(x,pattern_ncust))

df

Заключение

На этом всё. Надеюсь, этот пример решения задачи кто-то найдет интересным и научится из него каким-то новым приёмам в своей работе. Это причина, по который я решил выкладывать такие примеры — не просто рассказывать про то, как работают функции в pandas или предлагать готовые решения, а показать последовательность действий и методологию решения задачи, чтобы читатели могли перенести части этого решения в свои проекты.

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

Вступайте в группу на Facebook и подписывайтесь на мой канал в Telegram, там публикуются интересные статьи про анализ данных и не только.

Как использовать Google BigQuery с помощью Python

Что такое Google BigQuery

Google BigQuery — это безсерверное масштабируемое хранилище данных. Использование безсерверного (облачного) решения — хорошая идея, если у вас нет серьезного бэкграунда в администрировании баз данных. Такой подход позволяет сосредоточиться только на анализе данных, и не думать об инфраструктуре хранения данных (шардировании, индексации, компрессии). BigQuery поддерживает стандартный диалект SQL, так что любой, кто когда-либо пользовался SQLными СУБД, с легкостью может начать им пользоваться.

Начало работы с Google BigQuery и создание ключа для сервисного аккаунта

Я не буду подробно объяснять о том как начать работу с Google Cloud Platform и завести первый проект, об этом хорошо написано в статье Алексея Селезнева в блоге Netpeak. Когда у нас уже есть проект в Cloud Platform с подключенным API BigQuery, следующим шагом нужно добавить учетные данные.

  1. Переходим в раздел «API и сервисы > Учетные данные»:
  1. Нажимаем «Создать учетные данные > Ключ сервисного аккаунта»
  1. Заполняем параметры: пишем название сервисного аккаунта; выбираем роль (как показано на скриншоте ниже, но роль может зависеть от уровня доступов, которые вы хотите предоставить сервисному аккаунту); выбираем тип ключа JSON; нажимаем «Создать»
  1. Переходим в раздел «IAM и администрирование > Сервисные аккаунты»
  1. В колонке «Действия» для созданного нами сервисного аккаунта выбираем «Создать ключ»
  1. Выбираем формат ключа «JSON» и нажимаем создать, после чего будет скачан JSON-файл, содержащий авторизационные данные для аккаунта

Полученный JSON с ключом нам понадобится в дальнейшем. Так что не теряем.

Использование pandas-gbq для импорта данных из Google BiqQuery

Первый способ, с помощью которого можно загружать данные из BigQuery в Pandas-датафрейм — библиотека pandas-gbq. Эта библиотека представляет собой обертку над API Google BigQuery, упрощающая работу с данными BigQuery через датафреймы.
Сначала нужно поставить библиотку pandas-gbq. Это можно сделать через pip или conda:

pip install pandas-gbq
conda install pandas-gbq -c conda-forge

Я решил рассмотреть основы работы с Google BigQuery с помощью Python на примере публичных датасетов. В качестве интересного примера возьмем датасет с данными о вопросах на сервисе Stackoverflow.

import pandas as pd
from google.oauth2 import service_account

# Прописываем адрес к файлу с данными по сервисному аккаунту и получаем credentials для доступа к данным
credentials = service_account.Credentials.from_service_account_file(
    'my-bq-project-225910-6e534ba48078.json')

# Формируем запрос и получаем количество вопросов с тегом "pandas", сгруппированные по дате создания
query = '''
SELECT DATE(creation_date) as date, COUNT(id) as questions
FROM
  [bigquery-public-data:stackoverflow.posts_questions]
WHERE tags LIKE '%pandas%'
GROUP BY
  date
'''

# Указываем идентификатор проекта
project_id = 'my-bq-project-225910'

# Выполняем запрос с помощью функции ((https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_gbq.html read_gbq)) в pandas, записывая результат в dataframe
df = pd.read_gbq(query, project_id=project_id, credentials=credentials)

display(df.head(5))

Важное примечание: По умолчанию функция read_gbq в pandas использует диалект legacy SQL. Для того, чтобы задать диалект standart SQL нужно воспользоваться параметром dialect:

df = pd.read_gbq(query, project_id=project_id, credentials=credentials, dialect='standard')

Дальше немного поиграем с обработкой данных. Выделим из даты месяц и год.

df['month'] =  df['date'].values.astype('datetime64[M]') # Создаем новый столбец с месяцем
df['year'] =  df['date'].values.astype('datetime64[Y]') # Создаем новый столбец с годом

# Отображаем один день с максимальным количеством вопросов
display(df.sort_values('questions',ascending=False).head(1))

Cгруппируем данные по годам и месяцам и запишем полученные данные в датафрейм stats

stats = df.groupby(['year','month'],as_index=False).agg({'questions':'sum'}) # Группируем данные по году и месяцу, используя в качестве агрегирующей функции сумму количества вопросов

display(stats.sort_values('questions',ascending=False).head(5))

Посчитаем суммарное количество вопросов в год, а также среднее количество запросов в месяц для каждого года, начиная с января 2013 и по август 2018 (последний полный месяц, который был в датасете на момент написания статьи). Запишем полученные данные в новый датафрейм year_stats

year_stats = stats[(stats.month >= '2013-01-01') & (stats.month < '2018-09-01')].groupby(['year'],as_index=False).agg({'questions':['mean','sum']})

display(year_stats)

Так как 2018 год в наших данных неполный, то мы можем посчитать оценочное количество вопросов, которое ожидается в 2018 году.

year_stats['estimate'] = year_stats[('questions','mean')]*12

display(year_stats)

На основе данных от StackOverflow, можно сказать, что популярность pandas из года в год растёт хорошими темпами :)

Запись данных из dataframe в Google BigQuery

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

В датафрейме year_stats получился multiindex из-за того, что мы применили две агрегирующие функции (mean и sum). Чтобы нормально записать такой датафрейм в BQ надо убрать multiindex. Для этого просто присвоим dataframe новые колонки.

year_stats.columns = ['year','mean_questions','sum_questions','estimate']

После этого применим к датафрейму year_stats функцию to_gbq. Параметр if_exists = ’fail’ означает, что при существовании таблицы с таким именем передача не выполнится. Также в значении этого параметра можно указать append и тогда к существующим данным в таблице будут добавлены новые. В параметре private_key указываем путь к ключу сервисного аккаунта.

year_stats.to_gbq('my_dataset.my_table', project_id=project_id, if_exists='fail', private_key='my-bq-project-225910-6e534ba48078.json')

После выполнения функции в BigQuery появятся наши данные:

Итак, мы рассмотрели импорт и экспорт данных в BiqQuery из Pandas’овского датафрейма с помощью pandas-gbq. Но pandas-gbq разрабатывается сообществом энтузиастов, в то время как существует официальная библиотека для работы с Google BigQuery с помощью Python. Основные сравнения pandas-gbq и официальной библиотеки можно посмотреть тут.

Использование официальной библиотеки для импорта данных из Google BiqQuery

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

from google.cloud import bigquery

client = bigquery.Client.from_service_account_json(
    '/home/makarov/notebooks/my-bq-project-225910-6e534ba48078.json')

sql = '''
    SELECT DATE(creation_date) as date, DATE_TRUNC(DATE(creation_date), MONTH) as month, DATE_TRUNC(DATE(creation_date), YEAR) as year, COUNT(id) as questions
FROM
  `bigquery-public-data.stackoverflow.posts_questions`
WHERE tags LIKE '%pandas%'
GROUP BY
  date, month, year
    '''

project_id = 'my-bq-project-225910'

df2 = client.query(sql, project=project_id).to_dataframe()

display(df2.head(5))

Как видно, по простоте синтаксиса, официальная библиотека мало чем отличается от использования pandas-gbq. При этом я заметил, что некоторые функции, (например, date_trunc), не работают через pandas-gbq. Так что я предпочитаю использовать официальное Python SDK для Google BigQuery.

По умолчанию в официальном SDK используется диалект Standard SQL, но можно использовать Legacy SQL. С примером использования Legacy SQL можно ознакомиться по ссылке.

Чтобы импортировать данные из датафрейма в BigQuery нужно установить pyarrow. Эта библиотека обеспечит унификацию данных в памяти, чтобы dataframe соответствовал структуре данных, нужных для загрузки в BigQuery.

# Создаем тестовый dataframe
df = pd.DataFrame(
    {
        'my_string': ['a', 'b', 'c'],
        'my_int64': [1, 2, 3],
        'my_float64': [4.0, 5.0, 6.0],
    }
)
dataset_ref = client.dataset('my_dataset_2') # Определяем датасет
dataset = bigquery.Dataset(dataset_ref)
dataset = client.create_dataset(dataset) # Создаем датасет

table_ref = dataset_ref.table('new_table') # Определяем таблицу (при этом не создавая её)

result = client.load_table_from_dataframe(df, table_ref).result() # Тут данные из датафрейма передаются в таблицу BQ, при этом таблица создается автоматически из определенной в предыдущей строке

Проверим, что наш датафрейм загрузился в BigQuery:

Прелесть использования нативного SDK вместо pandas_gbq в том, что можно управлять сущностями в BigQuery, например, создавать датасеты, редактировать таблицы (схемы, описания), создавать новые view и т. д. В общем, если pandas_gbq — это скорее про чтение и запись dataframe, то нативное SDK позволяет управлять всей внутренней кухней

Ниже привожу простой пример как можно изменить описание таблицы

table = client.get_table(table_ref) # Получаем данные о таблице

table.description = 'Моя таблица' # Задаем новый дескрипшн для таблицы

table = client.update_table(table, ['description'])  # Обновляем таблицу, передав новый дескрипшн через API

print(table.description)

Также с помощью нативного Python-SDK можно вывести все поля из схеме таблицы, отобразить количество строк в таблице

for schema_field in table.schema: # Для каждого поля в схеме
    print (schema_field) # Печатаем поле схемы

print(table.num_rows) # Отображаем количество строк в таблице

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

result = client.load_table_from_dataframe(df, table_ref).result() # Передаем данные из dataframe в BQ-table
table = client.get_table(table_ref) # Заново получаем данные о таблице
print(table.num_rows) # Отображаем новое количество строк

Заключение

Вот так, с помощью несложных скриптов, можно передавать и получать данные из Google BigQuery, а также управлять различными сущностями (датасетами, таблицами) внутри BigQuery.

Успехов!

Вступайте в группу на Facebook и подписывайтесь на мой канал в Telegram, там публикуются интересные статьи про анализ данных и не только.

С чего аналитику начать изучение Python

Многие аналитики задумываются об изучении Python, но не представляют себе первых шагов.

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

Следующий шаг — выбор среды разработки. Для анализа данных лучше всего подойдет Jupyter Notebook. Эта среда разработки устанавливается вместе с Anaconda. Вот простой туториал по работе с Jupyter Notebook.

Тем, кто вообще никогда не сталкивался с языками программирования (например, не писал на Паскале или Бейсике в школе), я бы посоветовал пройти любые курсы базового питона. Например, на Stepik или Codecademy.

Многие аналитики начинают учить Python, но быстро бросают. Чаще всего это происходит потому, что люди начинают изучение с синтаксиса и простых абстрактных примеров. Поначалу это может быть интересным, но потом надоедает. Лучше всего проходить основы языка (на курсах или с помощью учебника), но параллельно попробовать решать простые практические задачи, автоматизируя рутину и сразу же ощущая как Python улучшает вашу жизнь.

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

На мой взгляд, самая главная библиотека для аналитика — Pandas. Если вы хотите быстро очищать, трансформировать, агрегировать, объединять и вообще всячески манипулировать табличными данными, то Pandas будет в этом надежным помощником. Для аналитика эта библиотека покрывает 90% задач. Про Pandas есть хорошая статья в блоге khashtamov.com (и весь блог годный!). Также советую почитать более хардкорную статью ребят из ODS. Если вы решите выбрать образовательные курсы — это отлично, но не советую надеяться, что выбрав какую-то одну образовательную программу вы получите всеохватывающий спектр знаний, поэтому вашей надёжной подмогой станет постоянное изучение различных материалов: статей в блогах (пример), видео (пример), онлайн-учебников (пример). Не забывайте про документацию и вопросы на стаковерфлоу — почти как кофе и сигареты — это комбинация.

А ещё я подготовил большую подборку ссылок про Pandas.

Начните использовать Python с решения какой-то простой практической задачи, например, выгрузки данных через API Яндекс.Метрики и сохранения полученных данных в Excel. Узнать как начать работать с API Яндекс.Метрики можно из моей статьи.

Данные из Яндекс.Метрики в Python можно получить с помощью вот такого простого сниппета. Начните с получения токена для API Яндекс.Метрики и выполните этот код в Jupyter Notebook. Вы удивитесь как это просто!

Дальше можно усложнять скрипт, например, сделать несколько различных запросов и выгрузить данные на несколько вкладок в одном Excel-файле. Или выгрузить из Метрики данные с множеством dimensions и попробовать на их основе сделать в Pandas несколько таблиц с группировкой с помощью функции groupby, а также сводные таблицы с помощью функции pivot_table.

Успехов в автоматизированной борьбе с рутиной!

Вступайте в группу на Facebook и подписывайтесь на мой канал в Telegram, там публикуются интересные статьи про анализ данных и не только.

Группа в Facebook про анализ данных с помощью Python

Создал группу, посвященную анализу данных с помощью Python. Не столько про машинное обучение, сколько про подготовку/очистку/предобработку данных, использование Python для получения данных из API, парсинга веб-сайтов, автоматизации различной рутины. Группа предназначена для обмена опытом, взаимопомощи, поиска единомышленников для проектов. Приветствуются вопросы, обсуждения, ссылки на полезные материалы и инструменты. Постараюсь кидать максимум полезностей как для новичков, так и для проскилленных ребят.

https://www.facebook.com/groups/pydata/

Ранее Ctrl + ↓