Продуктовый взгляд | Аналитика данных – Telegram
Продуктовый взгляд | Аналитика данных
2.04K subscribers
29 photos
1 video
5 files
34 links
Этот канал посвящён аналитике данных, продуктовой аналитике и стажировкам.
Download Telegram
Задача из отбора на стажировку Мини-CEO в Т-банк


Условие

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

Решение
0) Чтобы получить таблицу где будут кредиты и соответствующие им даты платежей делаем JOIN, по умолчанию функция JOIN работает как INNER JOIN
1) Для каждого кредита находим даты первого и последнего платежа — это можно сделать функциями MAX и MIN. Затем считаем их разницу, предварительно приведя p.payment_date из формата timestamp в date.
2) С помощью AVG усредняем эти разницы по всем закрытым кредитам и округляем до целого.

Когда я в первый раз решал задачу, система выдала вердикт Presentation Error — Ошибка неправильного формата. Первая версия моего кода выглядела так:
SELECT ROUND(AVG(last_date - first_date))
FROM (
    SELECT MIN(p.payment_date) AS first_date, MAX(p.payment_date) AS last_date
    …
) AS diffs;


Ключевой момент в том, что столбец p.payment_date имеет формат timestamp, разность таймстемпов даст интервал "XX days HH:MM:SS" (timestamp – timestamp = interval). AVG(interval) возвращает опять interval. Вызов ROUND(interval) не существует, значит результат будет в формате вида "42 days 11:23:58", которую система считывает, но она не совпадает с ожидаемым форматом (PE).

Если мы приведем payment_date к date, то разность дат даст целое число дней (date – date = integer). AVG(integer) вернет в этом случае numeric. Округляя среднее число дней ROUND(numeric) получим, то что от нас требовали.

SELECT ROUND(AVG(day_diff))
FROM (
    SELECT cr.credit_id, MAX(p.payment_date)::date - MIN(p.payment_date)::date AS day_diff
    FROM credits AS cr
    JOIN payments AS p ON cr.credit_id = p.credit_id
    WHERE cr.status = 'closed'
    GROUP BY cr.credit_id
) AS diffs;


@ProdAnalysis
👍11❤‍🔥22
Задача с финала Яндекса на Middle Аналитика
Эту задачу также часто дают и в других компаниях. Давайте разберём этот баян 😎

Условие

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

Решение
Будем решать задачу "с конца"
1) Если сейчас третий бросок, выбора бросать дальше или остановиться нет

Матожидание числа, которое выпадет на третьем броске E3 = (1 + 2 + 3 + 4 + 5 + 6)/6 = 3.5, так как каждое значение на кубике выпадает с равной вероятностью, равной 1/6

2) Если сейчас второй бросок

Допустим выпало число X, тогда есть 2 варианта:
- остановиться на X
- продолжить и на третьем броске получить матожидание 3.5

Значит нам выгодно остановиться на втором броске, если значение X ≥ 3.5. Для кубика это X = 4, 5, 6.

Посчитаем матожидание для двух последних бросков, с учётом нашего правила (останавливаемся на втором броске, если значение X ≥ 3.5)
- Вероятность того, что выпадут значения 4, 5, 6 равна 1/2 → их среднее (4 + 5 + 6)/3 = 5
- Вероятность того, что выпадут значения 1, 2, 3 равна 1/2 → в этом случае бросаем игральный кубик в третий раз. В среднем на третьем броске нам выпадает 3.5

E2 = (4 + 5 + 6)/3 * 1/2 + E3 * 1/2 = 5 * 1/2 + 3.5 * 1/2 = 4.25

3) Если сейчас первый бросок

Допустим выпало число Y, тогда есть 2 варианта:
- остановиться на Y
- продолжить и на втором броске получить матожидание 4.25

Значит нам выгодно остановиться на первом броске, если значение Y ≥ 4.25. Для кубика это Y = 5, 6

Наконец-то, посчитаем матожидание для числа, выпавшего в последнем броске, с учётом наших правил (останавливаемся на первом броске, если значение Y ≥ 4.25; останавливаемся на втором броске, если значение X ≥ 3.5)
- Вероятность того, что выпадут значения 5, 6 равна 1/3 → их среднее (5 + 6)/2 = 5.5
- Вероятность того, что выпадут значения 1, 2, 3, 4 равна 2/3 → в этом случае бросаем игральный кубик во второй раз. В среднем для двух последних бросков мы получаем значение 4.25

E1 = (5 + 6)/2 * 1/3 + E2 * 2/3 = 5.5 * 1/3 + 4.25 * 2/3 = 4.67

Оптимальная стратегия такая:
– после первого броска оставляем только 5 или 6
– если решили бросить второй раз, то теперь оставляем 4, 5, 6
– если дошёл до третьего раза, берем что выпало
При такой тактике матожидание финального значения 4.67, что выше 3.5 у одиночного броска


@ProdAnalysis
133👍3
В честь начала учебного года и обновления контекста на сатижировку в Яндекс, мы объявляем финальную скидку на все наши курсы 50% до 5 сентября включительно.

Там же на курсах уже выложен разбор нового контекста в Яндекс, а ещё вернут потраченные деньги на курс, если первым сдадите все дз.

Задания Яндекс контекста здесь, а разбор только на наших курсах:

Для записи и всех вопросов:
➡️ алгоритмы старт
➡️ аналитика старт
➡️ машинное обучение старт
➡️ бэкенд разработка старт

➡️дата сайнс (глубокое обучение)
➡️фронтенд
➡️дата инженер
➡️математика для карьеры

➡️ машинное обучение хард
➡️ бэкенд хард
➡️ аналитика хард
➡️ алгоритмы хард

Для записи и всех вопросов: @menshe_treh
Please open Telegram to view this post
VIEW IN TELEGRAM
🍌1
Задача с собеседования на Junior аналитика в Т-банк

При отборе на позиции аналитика (продуктового, бизнес, риск-аналитика) у вас всегда будет 2 секции:
1. Математика — дают 3-4 задачки, проверяют знания теорвера, матстата и логики
2. Технические кейсы, связанные с вашим напрявлением

Чтобы не завалить первую секцию, советуем вам прочитать этот пост!

Условие

Монетку подбрасывают до тех пор пока не выпадет одна из двух последовательностей. Для первого игрока — орёл орёл (ОО), для второго —решка орёл (РО). За кого выгоднее болеть и какова вероятность победы каждого из игроков?

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

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

Найдём вероятность того, что победит 1 игрок, то есть выпадет ОО

Состояния:
1) В последнем броске выпала решка

Если в следующем броске выпадет орёл у нас появится пара РО, и первый игрок проиграет. Если же снова выпадет решка, ситуация не изменится — мы остаёмся в состоянии "выпала решка".

Найдем вероятность победы первого игрока (выпадет ОО), если выпала решка:
P(победит ОО | выпала Р) = P(выпал О) * P(победит ОО | выпал О) + P(выпала Р) * P(победит ОО | выпала Р) = 1/2 * 0 + 1/2 * P(победит ОО | выпала Р) = 1/2 * P(победит ОО | выпала Р)

P(победит ОО | выпала Р) = 0

2) В последнем броске выпал орёл

Если в следующем броске выпадет орёл у нас появится пара ОО, и первый игрок выиграет. Если же выпадет решка — мы перейдём в состояние "выпала решка", вероятность этого исхода мы нашли в предыдущем шаге P(победит ОО | выпала Р) = 0.

Найдем вероятность победы первого игрока (выпадет ОО), если выпал орёл:
P(победит ОО | выпал О) = P(выпал О) * P(победит ОО | выпал О) + P(выпала Р) * P(победит ОО | выпала Р) = 1/2 * 1 + 1/2 * 0 = 1/2

P(победит ОО | выпал О) = 1/2

3) Старт
В первом броске с вероятностью 1/2 мы получаем орла и с вероятностью 1/2 мы получаем решку
P = 1/2 * P(победит ОО | выпал О) + 1/2 * P(победит ОО | выпала Р) = 1/2 * 1/2 + 1/2 * 0 = 1/4

Вероятность того, что победит 1 игрок, выпадет ОО: 1/4
Вероятность того, что победит 2 игрок, выпадет РО: 3/4


@ProdAnalysis
17
Задача с собеседования на Junior аналитика в Т-банк

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

Условие

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

Решение
1) Матожидание количества девочек: по условию задачи в каждой семье ровно одна девочка
E[числа девочек]  = 1

2) Матожидание количества мальчиков:
Вероятность того, что родится девочка p = 1/2

Посчитаем вероятность того, что родится k мальчиков:
P(число мальчиков = k) = (1 - p)^k * p, k = 0, 1, 2, ....

Тогда матожидание числа мальчиков по определению:
E[числа мальчиков] = (от k = 0 до ∞) Σk*P(число мальчиков = k) = (от k = 0 до ∞) Σk * (1 - p)^k * p

Распишем чему равен ряд: S = (от k = 0 до ∞) Σk * q^k
Первый член нулевой, значит S = (от k = 1 до ∞) Σk * q^k

qS = (от k = 1 до ∞) Σk * q^{k + 1} = (от k = 2 до ∞) Σ (k - 1) * q^k

S - qS = (от k = 1 до ∞) Σk * q^k - (от k = 2 до ∞) Σ (k - 1) * q^k = 1 * q + (от k = 2 до ∞) Σ [k - (k - 1)] * q^k = q + (от k = 2 до ∞) Σ q^k = (от k = 1 до ∞) Σ q^k = q / (1 - q)

S = q / (1 - q)^2

E[числа мальчиков] = p * (1 - p)/p^2 = (1 - p)/p = 1

————————————————————————
В среднем на каждую семью 1 девочка и 1 мальчик. Если в королевстве тысячи семей, то по закону больших чисел средние значения складываются, поэтому во всем королевстве остаётся такое же соотношение:
мальчики : девочкам = 1 : 1


@ProdAnalysis
102❤‍🔥1👍1
Отбор на стажировку в Яндекс в самом разгаре. Специально для наших подписчиков выкладываем разбор одной из задач из контеста на позицию аналитика

Условие

Артемий занимается регламентными работами на кластере, ему дали данные о последних проведенных работах в виде таблицы nodebase, вот несколько строк:

node  parent  diagnostics
7 5 2025-07-01
5 … 2025-06-30
1 … 2025-06-10
10 7 2024-12-08
11 7 2024-12-04
… … 2025-07-06
2 11 2025-03-26
4 3 2024-11-11
6 3 2025-02-19
3 … 2025-02-17
8 3 2024-12-31


где node — id ноды, parent — id родительской ноды, diagnostics — дата диагностики ноды (если нода диагностировалась несколько раз за последние 5 лет, в таблице будет несколько строк с одинаковыми node).

Помогите Артемию подготовить отчет, в котором необходимо вывести номер ноды, ее положение в иерархии (начальная — root, внутренняя — inner или конечная — leaf) и время ее последней диагностики. Важно сначала вывести начальные ноды, у которых прошло наибольшее время с дня последней диагностики, затем ноды inner и в конце leaf. Необходимо написать запрос на sqlite, который поможет собрать нужную информацию в правильной сортировке.

Решение
Структура итоговой таблицы будет такой:
- node номер ноды
- position её роль в иерархии: root (нет родителя), inner (есть родитель и есть дети), leaf (есть родитель, но детей нет)
- last_date дата последней диагностики

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

with last_date as (
select node, max(diagnostics) as last_date
from nodebase
group by node
),


2) Чтобы определить позицию в таблице для каждой ноды, нужно понять есть ли у неё родитель или нет. То есть можно узнать id её родителя или вывести NULL, если его нет. Сделаем также как в прошлом пункте, с помощью функции max(..).

parent_node as (
select node, max(parent) as parent
from nodebase
group by node
),


P. S. Можно объединить cte из 1 и 2 пункта, наше решение в таком виде написано для наглядности

3) Чтобы определить позицию в таблице, для каждой ноды нужно понять есть ли у неё дети или нет. Опять напишем cte, будем группировать по колонке parent, предварительно отбросив все строки где parent = NULL. Узнаем сколько детей у каждого родителя с помощью count(*) (либо можно сделать также как в прошлом пункте, с помощью функции max(node) — ведь нам нужно просто знать есть ли они или нет).

child_node as (
select parent as node, count(*) as child
from nodebase
where parent is not null
group by parent
),


4) Определяем положение каждой ноды
Соединим 2 таблицы из предыдущих 2 пунктов child_node и parent_node с помощью left join, а затем каждой ноде присвоим один из типов (root, inner, leaf)

position as (
select p.node,
case
when p.parent is null then 'root'
when c.child is not null then 'inner'
else 'leaf'
end as position
from parent_node as p
left join child_node as c on c.node = p.node
)


5) Соединяем информацию о позиции и последней дате и сортируем сначала по позиции (для этого расставим приоритеты в сортировке root — 0, inner — 1, leaf — 2), а потом по самой старой диагностике

select p.node, p.position, dt.last_date
from position as p
left join last_date as dt on p.node = dt.node
order by
case p.position
when 'root' then 0
when 'inner' then 1
else 2
end,
dt.last_date asc


@ProdAnalysis
8❤‍🔥2
Поступашки открывают набор на новую линейку математических курсов 🎓

Хочешь поступить в ШАД, Ai Masters, или ААА? А может ты мечтаешь тащить собесы и поступить в крутую магу, но тебе не хватает фундамента? Узнал себя? Тогда записывайся у администратора на любой из курсов:

➡️ алгоритмы
➡️ теория вероятностей
➡️ линейная алгебра
➡️ математический анализ

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

Помимо кучи авторских задач мы даем доступ к уникальной закрытой базе заданий ШАДа, разбор реального контеста в ШАД, разбор ВСЕХ задач с собеседований в ШАД, Ai Masters, ААА! Более того, вы получите эксклюзивные материалы для проверяющих с собесов, пробный экзамен, инсайды, персональные рекомендации, собес с подробной консультацией и дальнейшим сопровождением вплоть до поступления в место мечты! А если вы выполните все рекомендации, но не достигнете поставленной цели, то вам вернут все потраченные деньги!

Для вопросов и покупок пишем администратору и не тянем с этим: на каждом курсе количество мест ограничено!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
Продолжаем разбирать отбор на стажировку в Яндекс. Специально для наших подписчиков выкладываем разбор одной из задач из контеста на позицию аналитика

Условие

Два древних шамана сошлись в ритуальном состязании! Перед ними лежит священный сосуд с 16 магическими камнями четырёх стихий:
- 4 камня духа земли
- 4 камня духа огня
- 4 камня духа воды
- 4 камня духа ветра

Правила испытания:
1) Шаманы по очереди берут по одному камню случайной стихии (старший шаман начинает).
2) Когда у одного из шаманов окажется 3 камня одной стихии — его дух пробуждается, и он объявляется победителем!
3) Камни после вытягивания не возвращаются в священный сосуд.

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

Решение
Давайте построим вероятностную модель:
множество всех исходов — это случайная перестановка всех 16 камней, то есть упорядоченный список вида (камень 1, камень 2, …, камень 16).

Перед началом ритуала порядок неизвестен и равновероятен, то есть любая из 16! перестановок возможна

1) Как раздаются камни
По ходу игры шаманы "открывают" этот список поочередно:
старший берёт 1-й, 3-й, 5-й, ..., 15-й камни (всего 8)
младший берёт 2-й, 4-й, 6-й, ..., 16-й (всего 8)
следовательно, множество камней старшего — это равновероятное 8 элементное подмножество из 16

таких подмножеств C из 16 по 8 = 12 870

2) Посчитаем вероятность ничьей
Ритуал завершится пробуждением духа, когда у одного из шаманов соберётся 3 камня одной стихии. Значит ничья будет в том случае, когда у обоих шаманов будет не больше двух камней каждой стихии.
Камней каждой стихии по 4 штуки и шаманы берут камни пока они не закончатся. Значит ничья возможна только при таком раскладе:

у младшего: 2 земли, 2 огня, 2 воды, 2 ветра
у старшего: 2 земли, 2 огня, 2 воды, 2 ветра

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

3) Посчитаем количество способов получить 2 земли, 2 огня, 2 воды, 2 ветра

У нас все камни внутри одной стихии разные. Мы выбираем 2 из 4 камней огня, 2 из 4 камней земли, 2 из 4 камней воды, 2 из 4 камней ветра

(C из 4 по 2)^4 = 6^4 = 1 296

4) Итог
Вероятность ничьей: P = 1 296/12 870= 72/715
Вероятность пробуждения духа: 1 - P = 643/715 = 0.9


@ProdAnalysis
8🥰1
Как определять NSM метрику в кейсах на собесах?

North Star Metric (NSM) — это один ключевой показатель, который помогает компании измерять прогресс в создании устойчивой ценности для пользователей и рост бизнеса в долгосрочной перспективе. Хорошо подобранная NSM становится своеобразной «северной звездой» для всей команды: она упрощает приоритизацию, синхронизирует цели и помогает не теряться в множестве второстепенных метрик. Частая ошибка при выборе NSM — использовать показатели вроде выручки, GMV или среднего времени на пользователя. Эти метрики важны, но чаще отражают интересы бизнеса, а не реальную ценность для пользователей, из-за чего плохо работают как ориентир для продуктовой команды.

Хорошая NSM должна соответствовать четырем критериям:
1) это должна быть одна и только одна метрика.
2) она должна отражать ценность для пользователей, а не внутренние бизнес-показатели.
3) NSM должна быть количественно измеримой и показывать прогресс в достижении стратегической цели.
4) она должна быть простой в понимании и расчете, чтобы каждый сотрудник мог объяснить, почему именно она важна.

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

Например, для Кинопоиска логичной NSM будет общее время просмотров, так как это продукт внимания. Для Yandex Taxi - общее количество поездок, так как это транзакционный сервис. Для Fitbit - общее количество шагов, потому что это продукт продуктивности. Для YouTube хорошей NSM будет общее время просмотра видео, для Tinder - количество взаимных матчей или свайпов, для Google Drive - объем загруженных и используемых файлов, для Ozon - количество завершенных заказов, для таймера Pomodoro - число завершенных рабочих сессий, а для Yandex Music - минуты прослушивания треков.

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

Ошибки при выборе метрики

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

@ProdAnalysis
19
Поступашки продолжают набор на новую линейку математических курсов. Стартуем уже в это воскресение 🎓

Хочешь поступить в ШАД, Ai Masters, или ААА? А может ты мечтаешь тащить собесы и поступить в крутую магу, но тебе не хватает фундамента? Узнал себя? Тогда записывайся у администратора на любой из курсов:

➡️ алгоритмы
➡️ теория вероятностей
➡️ линейная алгебра
➡️ математический анализ

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

Помимо кучи авторских задач мы даем доступ к уникальной закрытой базе заданий ШАДа, разбор реального контеста в ШАД, разбор ВСЕХ задач с собеседований в ШАД, Ai Masters, ААА! Более того, вы получите эксклюзивные материалы для проверяющих с собесов, пробный экзамен, инсайды, персональные рекомендации, собес с подробной консультацией и дальнейшим сопровождением вплоть до поступления в место мечты! А если вы выполните все рекомендации, но не достигнете поставленной цели, то вам вернут все потраченные деньги!

Для вопросов и покупок пишем администратору и не тянем с этим: на каждом курсе количество мест ограничено!
Please open Telegram to view this post
VIEW IN TELEGRAM
Продолжаем разбирать отбор на стажировку в Яндекс. Специально для наших подписчиков выкладываем разбор одной из задач из контеста на позицию аналитика

Условие

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

Вам предоставляется доступ к базе данных игры в формате tsv (данные разделены знаком табуляции).

Выведите наибольшее количество активных игроков, которое присутствовало в игре в некоторый день.

Формат ввода
Считывается текстовый файл input.txt
Формат вывода
Выведите наибольшее количество активных участников целым числом.
Примечания
Активные клиенты текущего дня определяются по последним 30 дням, включая текущий Первый тест для пробы.


Решение
1) Переформулируем задачу
Активным игроком на момент текущей даты мы называем игрока, который за последние 30 дней заходил в игру ≥ 5 раз.
Нужно пройти по всем датам, которые есть в базе данных, и посчитать для каждой даты сколько было уникальных активных игроков, а затем взять максимум из этих чисел.

2) Читаем входные данные
Входной файл input.txt содержит много не нужных колонок, нам понадобятся только client_id и timestamp.
Так как timestamp изначально дано как "целое число" (то есть время в секундах) нужно его преобразовать в формат "YYYY-MM-DD". При преобразовании нужно указать, что время в секундах unit='s' и округляем до начала дня .floor('D') (к примеру 1696439123 -> 2023-10-04 13:25:23 -> floor('D') = 2023-10-04 00:00:00)

df = pd.read_csv('input.txt', sep='\t')
df['date'] = pd.to_datetime(df['timestamp'], unit='s').dt.floor('D')


3) Посчитаем сколько визитов сделал каждый игрок в каждую дату

Это можно сделать с помощью метода .size(). У нас получится таблица с мультииндектом, поэтому с помощью .reset_index() преобразуем в таблицу с обычными индексами 1, ..., n - 1

date_visits = (df.groupby(['date', 'client_id']).size().rename('counts_visits').reset_index())


4) Для каждого игрока и каждой даты посчитаем, сколько суммарно визитов он успел сделать за последние 30 дней

Для этого поставим в качестве индексов даты и сгруппируем по игрокам. С помощью метода .rolling('30D', closed='both') для каждого игрока мы можем просуммировать в окне длины 30 дней количество визитов

date_visits = date_visits.set_index('date')
rolling = (date_visits.groupby('client_id')['counts_visits'].rolling('30D', closed='both').sum().reset_index())


5) Помечаем активных игроков (то есть у которых ≥ 5 визитов)

rolling['active'] = rolling['counts_visits'] >= 5


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

count_active_users =rolling.groupby('date')['active'].sum()


7) Ответ

print(count_active_users.max())


@ProdAnalysis
5👍3
Чек-лист: 6 обязательных пунктов перед сдачей дашборда.

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

1. Проверка навигации
Информация расположена в логическом порядке? Взгляд сам находит главный KPI, а затем следует к деталям?

2. Проверка заголовков
Название каждого графика однозначно объясняет, что на нем изображено? Из заголовка ясна суть?

3. Проверка осей
Все оси подписаны? Единицы измерения указаны? Без этого контекста график теряет смысл.

4. Проверка логики
Данные связаны в повествование? Дашборд отвечает на конкретный бизнес-вопрос, а не просто демонстрирует факты?

5. Проверка на шум
Вы убрали всё лишнее: яркую сетку, 3D-эффекты, декоративные элементы? Осталось только то, что работает на смысл?

6. Проверка цветов
Используется не более 3-5 основных цветов? Цвета не конфликтуют и помогают акцентировать внимание?

@ProdAnalysis
4🔥1
Это должен знать каждый аналитик, чтобы пройти собеседование в Big Tech. Сегодня разберём несколько вопросов по SQL с реальных собеседований в топовые IT компании, если вы с чем-то не справились это знак пойти повторять SQL

1. В чём разница между WHERE и HAVING в SELECT-запросе?

Ответ: WHERE фильтрует строки до группировки, HAVING фильтрует уже сгруппированные данные.
Например есть задача "Какой запрос SQL вернёт максимальную зарплату сотрудников для каждого отдела, где средняя з/п по отделу превышает 5000?". Сначала нужно сгруппировать по отделам, а затем в HAVING отфильтровать по средней з/п, если это сделать в WHERE будет ошибка.

2. Допустим в таблице есть 3 столбца user_id, salary, employees. В чём разница между count(*) и count(employees)?

Ответ: count(*) считает все строки таблицы, а count(employees) считает только те строки, где в столбце employees значение не NULL


3. Как в PostgreSQL работать с NaN для числовых типов: чем он отличается от NULL и что вернут COALESCE(NaN::numeric, 0) и NULLIF(NaN::numeric, NaN::numeric)?

Ответ: NaN в PostgreSQL — это реальное числовое значение (Not-a-Number), а не отсутствие данных: оно хранится в ячейке, участвует в арифметике и сравнениях. NULL значит, что значения нет вовсе
Поэтому count(..), avg(..) не посчитают ячейки с NULL, но учтут ячейки с NaN

COALESCE(value [, ...]) возвращает первый ненулевой аргумент. Поэтому COALESCE(NaN::numeric, 0) вернёт сам NaN (первый аргумент не NULL)
NULLIF(value1, value2) возвращает NULL, если value1 = value2, иначе вернёт value1. Поэтому NULLIF(NaN::numeric, NaN::numeric) вернёт NULL

4. Допустим в двух разных таблицах есть колонки user_id и employees_id. В каком случае JOIN выполнится быстрее, если обе колонки будут типа numeric(11) или varchar(10)?

Ответ: numeric(11) быстрее, потому что значения хранятся как упорядоченные числа фиксированной длины и сравнение двух чисел — это одна бинарная операция. varchar(10) каждый ключ хранит длину и символы, соответственно для сравнения нужно пройтись посимвольно, что медленнее, а сами индексы больше (нужно читать больше страниц и в рабочем кэше помещается меньше записей).
Если БД очень важна использование числового тип данных может существенно ускорить работу запросов.

5. Какой компонент отвечает за оптимизацию и преобразование SQL-запроса в план выполнения?

Ответ: Query Processor (оптимизатор запросов)
Оптимизатор учитывает такие факторы, как количество записей, наличие индексов, партиций и т.д. На основе этого он строит план EXPLAIN — последовательность операций, которые
выполняет БД для получения требуемого в запросе результата.

6. Какой компонент восстанавливает базу после сбоя?

Ответ: Recovery Manager
Вместе с изменением данных ведется ещё и журнал этих изменений. Recovery manager читает журнал предзаписи WAL, повторно выполняет все зафиксированные изменения после контрольной точки (redo) и откатывает изменения незавершённых транзакций (undo)

7. Что такое партиции и где они хранятся?

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


@ProdAnalysis
👍94
Ratio - метрики

Ratio-метрики вроде CTR, ARPU или среднего чека часто ломают привычный подход к A/B-тестам, потому что в них числитель и знаменатель зависимы: у пользователя с большими "views" обычно больше и "clicks", у крупного покупателя выше и "revenue", и "orders". Если применить стандартный t-test как к обычной метрике, дисперсия получается оценена неверно и итоговое p-value начинает врать. Delta-method как раз помогает аккуратно оценить дисперсию метрики вида num / denom с учётом ковариации между числителем и знаменателем и дальше уже работать с этой метрикой как с обычной: считать размер выборки, uplift, доверительные интервалы и значимость. Удобная реализация для данных на уровне пользователя:


def est_ratio_var(num, denom):
mean_num, mean_denom = np.mean(num), np.mean(denom)
var_num, var_denom = np.var(num), np.var(denom)

cov = np.cov(num, denom)[0, 1]

ratio_var = (
(var_num / mean_denom ** 2)
- (2 * (mean_num / mean_denom ** 3) * cov)
+ ((mean_num ** 2 / mean_denom ** 4) * var_denom)
)

return ratio_var


Дальше посмотрим на CTR, где по каждому "user_id" есть "clicks" и "views". Для расчёта размера выборки можно сначала оценить дисперсию CTR в текущих данных, задать относительный MDE и перевести его в "cohen_d", а затем воспользоваться стандартной функцией мощности, например так:


rel_mde = 0.1

var = est_ratio_var(df["clicks"], df["views"])
control_std = var**0.5

control_mean = df["clicks"].sum() / df["views"].sum()
test_mean = control_mean * (1 + rel_mde)

cohen_d = (test_mean - control_mean) / control_std

n1 = tt_ind_solve_power(
effect_size=cohen_d,
alpha=0.05,
power=0.8,
ratio=9,
alternative="two-sided"
)

print(
f"Размер выборки delta-method: "
f"n1 = {round(n1)}, "
f"n2 = {round(9 * n1)}"
)


Когда эксперимент уже прошёл, по тем же группам можно оценить статистическую значимость uplift CTR. Для этого считаем итоговые "clicks", "views" по группам, через delta-method получаем дисперсии и дальше строим нормальную статистику и p-value:


def delta_method(group_A, group_B):
ratio_A = group_A["clicks"].sum() / group_A["views"].sum()
ratio_B = group_B["clicks"].sum() / group_B["views"].sum()

var_A = est_ratio_var(group_A["clicks"], group_A["views"])
var_B = est_ratio_var(group_B["clicks"], group_B["views"])

uplift = ratio_B - ratio_A
se = np.sqrt(var_B / len(group_B) + var_A / len(group_A))

t = uplift / se
p_val = (1 - stats.norm.cdf(abs(t))) * 2

return p_val


Delta-method особенно удобен тем, что оставляет привычную для бизнеса картинку: "uplift", стандартная ошибка, доверительный интервал и p-value, но при этом корректно учитывает зависимость в ratio-метриках. Для надёжности в реальных задачах полезно иногда сверяться с бутстрепом или линеаризацией, но как базовый рабочий инструмент delta-method уже даёт аккуратные и довольно устойчивые оценки. Если формат такого кода и пояснений заходит, можно отдельно разобрать "линеаризацию" или "бутстреп" для тех же CTR и ARPU.

@ProdAnalysis
26😁3
Свершилось! Поступашки открывают набор на новую линейку карьерных курсов 🎉

Мечтаешь стать крутым специалистом и с легкость тащить собесы, но не хватает фундамента? Хочешь овладеть знаниями и навыками для работы в крупной компании как Яндекс, Тинькофф или ВК? Узнал себя? Тогда записывайся у администратора на любой из курсов (если андроид - смотрим через яндекс браузер):

➡️ SQL и базы данных
➡️ анализ временных рядов
➡️ обучение с подкреплением

Все курсы стартует 29.11. Курсы заточены на практику, вся теория будет разобрана на конкретных задачах и кейсах, с которыми сталкиваются на работе и на собесах. Ничего нудного и скучного! Изучаем только то, что тебе реально понадобится и залетаем на первую работу! Хочешь подробностей? На нашем сайте можно найти программу и отзывы на каждый курс.

Помимо этого на курсах тебя ждут:
- пет проекты и мини проекты, которые пойдут в портфолио;
- разбор реальных тестовых заданий бигтехов;
- разбор актуального контеста на стажировку в Яндекс и Тинькофф;
- банк реальных технических вопрос с собесов;
- разбор всех задач с алгособесов Яндекса!

А после прохождения курса тебя ждет пробный собес с подробной консультацией и сопровождением, рефералкой в Яндекс или в другие топовые компании!

📊 Цена 10'000р! Хочешь купить несколько курсов сразу? Дадим хорошую скидку!

Для вопросов и покупок пишем администратору и не тянем с этим: на каждом курсе количество мест ограничено
Please open Telegram to view this post
VIEW IN TELEGRAM
🍌3
Оптимизации SQL

Оптимизация SQL в основном это уменьшение числа читаемых строк и корректная работа планировщика с индексами, а не сложный синтаксис запросов. Типичный кейс: большая таблица orders с полями user_id, created_at, status, amount и ежедневный отчёт по выручке за последние 30 дней. Наивный запрос:


SELECT
user_id,
SUM(amount) AS revenue
FROM orders
WHERE created_at >= now() - interval "30 days"
AND status IN ("paid", "completed")
GROUP BY user_id
ORDER BY revenue DESC
LIMIT 100;


По мере роста данных планировщик часто делает Seq Scan, потому что фильтр мало селективен и разрозненные индексы по created_at и status не помогают. Практичный подход — спроектировать один составной и покрывающий индекс под шаблон запроса:


CREATE INDEX CONCURRENTLY idx_orders_status_created_user
ON orders (status, created_at, user_id)
INCLUDE (amount);


Фильтры и JOIN должны быть sargable: столбцы используются напрямую, без обёртки в функции. Вариант WHERE date_trunc("day", created_at) >= current_date - interval "30 days" часто ломает использование индекса по created_at, и в таком случае либо переписывают условие на диапазон по created_at, либо заводят функциональный индекс:


CREATE INDEX CONCURRENTLY idx_orders_created_day
ON orders (date_trunc("day", created_at));


Во многих системах часть нагрузки создаёт ORM через шаблон N+1, когда вместо одного агрегирующего запроса выполняются десятки маленьких. Чаще всего их можно заменить одним запросом с JOIN и агрегацией:


SELECT
u.id,
u.name,
SUM(o.amount) AS revenue_30d
FROM users u
JOIN orders o
ON o.user_id = u.id
WHERE o.created_at >= now() - interval "30 days"
AND o.status IN ("paid", "completed")
GROUP BY u.id, u.name
ORDER BY revenue_30d DESC
LIMIT 100;


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


CREATE INDEX CONCURRENTLY idx_orders_paid_recent
ON orders (created_at, user_id)
WHERE status IN ("paid", "completed");


Ключевая идея при любой оптимизации - смотреть планы через EXPLAIN (ANALYZE, BUFFERS) и сравнивать их до и после изменений: тип скана, количество строк, время выполнения. Получается, что при написании эффективного кода мы выделяем важные запросы, делаем под них индексы с продуманным порядком колонок, формулируем условия в sargable виде и избегаем N+1 и тяжёлых агрегаций по сырым данным.

А также напоминаем, что уже скоро стартует наш курс по SQL, где вас научат писать сложные, эффективные запросы с нуля, а также покажут основы устройства и проектирования БД😎

@ProdAnalysis
2🔥2
Разбор на стажировку Яндекса

Финальные 6 часов скидок на наши курсы подходят к концу. В честь этого выкладываем разбор актуального контеста в Яндекс:
Бэкенд
Аналитика
ML & DS

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

@postypashki_old
3
Новогодние скидки на нашу линейку математических курсов 🎓

Хочешь поступить в ШАД, Ai Masters, или ААА? А может ты мечтаешь тащить собесы и поступить в крутую магу, но тебе не хватает фундамента? Узнал себя? Тогда записывайся у администратора на любой из курсов:

➡️ алгоритмы
➡️ теория вероятностей
➡️ линейная алгебра
➡️ математический анализ

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

Помимо кучи авторских задач мы даем доступ к уникальной закрытой базе заданий ШАДа, разбор реального контеста в ШАД, разбор ВСЕХ задач с собеседований в ШАД, Ai Masters, ААА! Более того, вы получите эксклюзивные материалы для проверяющих с собесов, пробный экзамен, инсайды, персональные рекомендации, собес с подробной консультацией и дальнейшим сопровождением вплоть до поступления в место мечты! А если вы выполните все рекомендации, но не достигнете поставленной цели, то вам вернут все потраченные деньги!

Для вопросов и покупок пишем администратору и не тянем с этим: на каждом курсе количество мест ограничено!
Please open Telegram to view this post
VIEW IN TELEGRAM