На собеседовании аналитика данных Python проверяют не как у разработчика — а как инструмент обработки данных. Ядро — pandas: от вас ждут, что вы умеете группировать (groupby), соединять таблицы (merge), чистить пропуски и дубликаты, считать оконные метрики (rolling, shift) и делать это векторизованно, а не через циклы for. Типовые задачи — парсинг логов, RFM-сегментация, сессионизация событий и скользящие средние. Отвечать нужно кодом и объяснением сложности, а не заученной теорией.
Python-секция на собесе аналитика редко бывает алгоритмической олимпиадой. Работодателю важно, сможете ли вы завтра взять сырой CSV или выгрузку из БД и превратить её в отчёт. Поэтому вопросы крутятся вокруг pandas и numpy, а «чистый» Python (генераторы, мутабельные дефолты, list comprehension) спрашивают точечно — чтобы отличить того, кто понимает язык, от того, кто копирует сниппеты. Ниже — карта того, что реально дают, с формулировками как на живых интервью в российских компаниях.
Почему pandas — главный навык аналитика, а не Python вообще?
Аналитик проводит большую часть времени в трансформации табличных данных: агрегации, джойны, фильтры, витрины. Ровно для этого создан pandas — библиотека с двумя структурами: Series (одна колонка с индексом) и DataFrame (таблица). Всё остальное в Python вы используете как обвязку вокруг неё.
Почему не только SQL? SQL заканчивается там, где начинается итеративная работа: быстрый EDA, нестандартная логика очистки, интеграция с моделями, графики. pandas даёт то, чего в SQL нет из коробки — например, df.describe() одной строкой или сложную оконную логику без переписывания запроса. Но важно понимать симметрию: почти всё, что вы делаете в pandas, имеет прямой аналог в SQL, и хороший интервьюер этого касается.
| Операция | SQL | pandas |
|---|---|---|
| Фильтр строк | WHERE amount > 100 | df[df.amount > 100] |
| Агрегация по группе | GROUP BY user_id | df.groupby('user_id') |
| Соединение таблиц | JOIN ... ON | df.merge(other, on=...) |
| Оконная функция | SUM() OVER (PARTITION BY ...) | df.groupby(...).transform('sum') |
| Дедупликация | SELECT DISTINCT | df.drop_duplicates() |
Если вы понимаете эту таблицу «в обе стороны», половина вопросов закрыта. Отработать связку можно в тренажёре Python, где задачи идут от простого фильтра к оконным метрикам, а проверка кода — сразу в браузере.
Какие вопросы по groupby задают чаще всего?
groupby — самый частый вопрос, потому что через него проверяют понимание модели «split-apply-combine»: данные разбиваются на группы, к каждой применяется функция, результаты собираются обратно. Типичная формулировка: «Посчитай средний чек и число заказов по каждому пользователю».
df.groupby('user_id').agg(
avg_check=('amount', 'mean'),
orders=('order_id', 'count'),
).reset_index()
Что здесь проверяют и о чём стоит сказать вслух:
aggс именованными агрегатами (синтаксисновое_имя=('колонка', 'функция')) — это признак того, что вы работаете с современным pandas, а не пишете кашу из вложенных словарей.reset_index()— послеgroupbyключ уходит в индекс; если нужен плоский DataFrame, индекс надо вернуть в колонку. Забыть это — классическая ошибка джуна.transformvsagg:aggсжимает группу до одной строки,transformвозвращает результат той же длины, что исходная таблица. «Добавь колонку с долей заказа юзера в его общей сумме» — этоtransform, а неagg:
df['share'] = df['amount'] / df.groupby('user_id')['amount'].transform('sum')
Углубиться в нюансы groupby (несколько ключей, apply против встроенных агрегатов, as_index=False) можно в разборе pandas groupby.
Чем merge отличается от join и где ловушки с дубликатами?
merge в pandas — это SQL-JOIN: соединение двух таблиц по ключу. Главный вопрос интервьюера — понимаете ли вы типы соединения и что происходит с числом строк.
df.merge(orders, on='user_id', how='left')
Ключевые тезисы для ответа:
howопределяет тип:inner(пересечение, по умолчанию),left(все строки слева),right,outer(объединение). Аналитику чаще всего нуженleft— сохранить всех пользователей и подтянуть к ним заказы, не потеряв тех, у кого заказов нет.- Ловушка размножения строк: если ключ неожиданно не уникален,
mergeдаёт декартово произведение внутри группы, и число строк раздувается. Фиксируйте ожидаемую кардинальность через параметрvalidate:
df.merge(orders, on='user_id', how='left', validate='one_to_many')
one_to_many требует, чтобы user_id был уникален в левой таблице (один пользователь — много заказов). Если это не так, pandas бросит ошибку — и это спасёт от молчаливого раздувания данных. Умение назвать validate сильно выделяет кандидата.
mergevsjoinvsconcat:joinсоединяет по индексу (короткий синтаксис),merge— по колонкам (гибче),concat— просто склеивает таблицы по вертикали или горизонтали без логики ключей.- Индикатор источника:
indicator=Trueдобавляет колонку_mergeсо значениямиleft_only/right_only/both— удобно для анти-джойна (найти строки без пары). Подробный разбор — в pandas merge.
Почему векторизация быстрее apply и цикла for?
Это вопрос-водораздел между джуном и мидлом. Правильный ответ: pandas и numpy хранят данные в непрерывных C-массивах, и векторизованная операция выполняется единым проходом на уровне C, без интерпретатора Python на каждой строке. Цикл for и apply по строкам гоняют Python-интерпретатор на каждой итерации — отсюда разница в скорости на порядки.
Иерархия скорости, от быстрого к медленному:
- Векторизация —
df['a'] + df['b'],np.where(...), булева индексация. Самый быстрый вариант. applyпо колонке (axis=0) — иногда неизбежен, но медленнее векторизации.applyпо строкам (axis=1) — почти всегда красный флаг, скрытый цикл.- Явный цикл
forпоiterrows— самое медленное, на собесе лучше не показывать.
Классическая задача: «Проставь категорию заказа: до 1000 — small, до 5000 — medium, иначе large». Плохой ответ — apply с функцией. Хороший — векторизация:
import numpy as np
df['bucket'] = np.select(
[df.amount < 1000, df.amount < 5000],
['small', 'medium'],
default='large',
)
np.select и np.where — то, что интервьюер хочет услышать вместо apply(lambda row: ...). Проговорите вслух: «Здесь я избегаю построчного apply, чтобы операция шла векторизованно». Это ровно тот сигнал, который отделяет вас от кандидата, пишущего медленный код.
Как правильно обрабатывать пропуски и дубликаты?
Очистка данных — то, чем аналитик занимается каждый день, поэтому вопросы про NaN и дубли почти гарантированы.
Пропуски. Сначала диагностика, потом стратегия:
df.isna().sum() # сколько пропусков в каждой колонке
df.isna().mean().round(3) # доля пропусков
Стратегии и когда их называть:
dropna()— удалить строки/колонки. Уместно, если пропусков мало и они случайны.fillna(значение)— заполнить константой, медианой или средним. Важно: медиана устойчивее к выбросам, чем среднее, — это стоит проговорить.fillna(method='ffill')— протянуть последнее известное значение вперёд. Незаменимо для временных рядов (цены, остатки).- Флаг пропуска — иногда сам факт отсутствия информативен, и создают бинарную колонку
was_missing.
Главная ловушка, которую любят на собесе: NaN != NaN. Сравнение через == не найдёт пропуски — только isna(). И помните, что операции над NaN (например NaN + 5) дают NaN.
Дубликаты. Различайте полные и по подмножеству колонок:
df.duplicated().sum() # полные дубли строк
df.drop_duplicates(subset=['user_id', 'date'], keep='last')
Параметр keep (first/last/False) — частый уточняющий вопрос: keep='last' оставит последнюю запись, keep=False выкинет все повторы вообще. Скажите, что перед дедупликацией всегда стоит проверить, какие строки считаются дублями по бизнес-логике, а не механически.
Какие вопросы по оконным функциям в pandas задают?
Оконные операции — то, где аналитик обгоняет junior-разработчика. Основа — shift, rolling, expanding и cumsum, часто в связке с groupby.
shift(n)— сдвиг значения на n строк. «Посчитай прирост выручки день к дню» →df['revenue'].pct_change()или черезshift:
df['dod_growth'] = df['revenue'] / df['revenue'].shift(1) - 1
rolling(window)— скользящее окно. «Сглади метрику 7-дневным средним»:
df['ma7'] = df['revenue'].rolling(window=7, min_periods=1).mean()
Параметр min_periods — частый вопрос: без него первые 6 значений будут NaN.
cumsum/cummax— накопительные суммы и максимумы, полезны для накопленной выручки или отслеживания пика.
- Окно внутри группы: комбинация с
groupby— это аналогOVER (PARTITION BY ...)в SQL:
df['user_cummean'] = df.groupby('user_id')['amount'].expanding().mean().reset_index(level=0, drop=True)
Развёрнутый разбор десяти оконных функций (rolling, shift, resample, rank, pct_change, diff и других) с примерами кода — в отдельной статье про окна и временные ряды в pandas.
Какие вопросы по «чистому» Python спрашивают у аналитика?
Помимо pandas, интервьюер задаёт 2-3 вопроса на понимание самого языка. Не глубокий CS, а типичные грабли.
Мутабельные аргументы по умолчанию. Классика, которую спрашивают почти всегда:
def add_item(item, box=[]): # ОПАСНО
box.append(item)
return box
Список [] создаётся один раз при определении функции, а не при каждом вызове, — поэтому состояние «протекает» между вызовами. Правильно:
def add_item(item, box=None):
if box is None:
box = []
box.append(item)
return box
Проговорите причину: значения по умолчанию вычисляются один раз в момент объявления функции. Это точно тот ответ, что от вас хотят.
Генераторы vs списки. «Чем [x for x in data] отличается от (x for x in data)?» Список создаётся целиком в памяти сразу, генератор отдаёт элементы по одному лениво. Для большого файла логов генератор экономит память — читаете построчно, не грузя весь файл. Ключевые слова для ответа: yield, ленивость, O(1) по памяти против O(n).
Прочее, что всплывает точечно:
isvs==:==сравнивает значения,is— идентичность объектов в памяти.Noneпроверяют черезis None.- Поверхностная vs глубокая копия:
df.copy()против присваивания-ссылки; отсюда предупреждениеSettingWithCopyWarning— оно означает, что вы правите срез, а не самостоятельную копию. - **
*args/kwargs— распаковка позиционных и именованных аргументов.
Эти вопросы разбирают в блоке подготовки к собеседованиям вместе с типовыми SQL и продуктовыми задачами.
Как решать типовые практические задачи: логи, RFM, сессии?
Практическая часть — это 1-2 задачи «дано вот такой датафрейм, посчитай вот это». Разберём четыре самых частых сценария.
1. Парсинг логов. Дан текстовый лог веб-сервера, нужно вытащить структуру. Ключ — векторизованные строковые методы через аксессор .str и регулярки:
pattern = r'(?P<ip>\d+\.\d+\.\d+\.\d+).*?"(?P<method>GET|POST) (?P<url>\S+)'
parsed = df['raw'].str.extract(pattern)
str.extract с именованными группами сразу даёт готовый DataFrame — это сильнее, чем apply с re.match по строкам.
2. RFM-сегментация. Классика e-commerce: Recency (как давно покупал), Frequency (как часто), Monetary (на сколько). Считается через groupby + qcut для разбивки на квантили:
snapshot = df['date'].max()
rfm = df.groupby('user_id').agg(
recency=('date', lambda x: (snapshot - x.max()).days),
frequency=('order_id', 'nunique'),
monetary=('amount', 'sum'),
)
rfm['R'] = pd.qcut(rfm['recency'], 4, labels=[4, 3, 2, 1])
Тонкость, о которой стоит сказать: для Recency меньшее значение — лучше (недавняя покупка), поэтому метки квантилей разворачивают.
3. Сессионизация. Разбить поток событий пользователя на сессии: новая сессия начинается, если между событиями прошло больше 30 минут. Шаблон — сортировка, diff внутри группы, cumsum булева флага:
df = df.sort_values(['user_id', 'ts'])
gap = df.groupby('user_id')['ts'].diff() > pd.Timedelta('30min')
df['session_id'] = gap.groupby(df['user_id']).cumsum()
Идея cumsum по булевой маске (каждый разрыв увеличивает счётчик сессии) — то, что интервьюер хочет увидеть. Это же паттерн «gaps and islands» из SQL.
4. Скользящее среднее без утечки будущего. «Посчитай 7-дневное среднее по каждому магазину». Здесь важно не забыть сортировку по времени и группировку:
df = df.sort_values('date')
df['ma7'] = df.groupby('store_id')['sales'].transform(
lambda s: s.rolling(7, min_periods=1).mean()
)
Все четыре задачи можно прогнать вживую в тренажёре Python: условие, редактор, автопроверка результата и разбор сложности — как на реальном собесе.
Как готовиться к Python-секции, чтобы не завалить?
Стратегия подготовки, которая работает лучше зубрёжки синтаксиса:
- Решайте руками, а не читайте. Понимание
groupbyприходит только от десятков написанных агрегаций. Начните с базового курса pandas с нуля, затем закрепляйте задачами. - Проговаривайте вслух сложность и альтернативы. На собесе ценят не только правильный ответ, но и «здесь я векторизую, потому что apply по строкам был бы медленным». Это показывает инженерное мышление.
- Держите в голове мост между pandas и SQL. Половина Python-вопросов — это переформулированные SQL-задачи. Умение сказать «это то же самое, что
SUM() OVER (PARTITION BY)» экономит время и впечатляет. - Отрабатывайте на реальных датасетах, а не на
[1, 2, 3]. Логи, заказы, события — именно на них строятся задачи. Синтетика из туториалов не готовит к грязным данным. - Тренируйте формат «вопрос — код — объяснение» на мок-собеседованиях, чтобы не терять мысль под давлением.
Чек-лист тем, которые нужно закрыть перед интервью: groupby/agg/transform, все типы merge и ловушка дубликатов, векторизация против apply, обработка NaN и дублей, оконные shift/rolling/cumsum, мутабельные дефолты и генераторы, и четыре практических паттерна выше (логи, RFM, сессии, скользящее среднее).
Где потренироваться?
Теория без практики на собесе не спасает — код нужно писать до автоматизма. Вот с чего начать:
- Тренажёр Python — задачи с автопроверкой прямо в браузере: pandas, numpy, парсинг логов, RFM, сессионизация, оконные метрики. От простого фильтра до боевых кейсов аналитики. Первые задачи открыты бесплатно.
- AI мок-собеседование — прогон Python-секции в формате живого интервью: вопрос, ваш ответ, обратная связь по слабым местам. Тренирует именно связку «вопрос — код — объяснение».
- Курс pandas с нуля — если база проседает:
Series,DataFrame, индексация,groupby,mergeпо шагам, с закреплением на задачах.
Начните с одной задачи на groupby сегодня — и через две недели практики Python-секция перестанет быть узким местом на собеседовании.