Pandas
Задачи на группировку, объединение, фильтрацию и условную агрегацию DataFrame.
20 вопросов
JuniorТеорияОчень частоgroupby ленив — чем различаются agg, transform и apply?
groupby ленив — чем различаются agg, transform и apply?
groupby ленив — ничего не считает, пока не запущена операция. agg сводит группу к одной строке на ключ; transform возвращает ту же форму, что и вход, транслируя значение группы на каждую строку; apply — гибкий, но медленный запасной путь. Только transform кладёт агрегат группы на строки без merge.
Типичные ошибки
- ✗Считать, что
transformсхлопывает фрейм, какagg - ✗Хвататься за
apply, где быстрее векторныйaggилиtransform - ✗Приклеивать агрегат группы вручную через merge вместо
transform
Уточняющие вопросы
- →Как добавить столбец с долей каждой строки в итоге её группы?
- →Почему
applyнад groupby часто медленнееagg?
JuniorКодОчень частоСуммарные продажи на сотрудника объединением двух DataFrame
Суммарные продажи на сотрудника объединением двух DataFrame
Агрегируют продажи на сотрудника, затем подтягивают имя. Группируют продажи по emp_id и суммируют num_sales, затем объединяют с именами по emp_id: df_sales.groupby('emp_id')['num_sales'].sum().reset_index().merge(df_names, on='emp_id')[['emp_name','num_sales']].
Типичные ошибки
- ✗Использовать concat вместо объединения по ключу
- ✗Возвращать строки по дате без суммирования num_sales
- ✗Считать строки имён вместо суммирования продаж
Уточняющие вопросы
- →Важно ли, группировать до или после объединения?
- →Как появится сотрудник без продаж?
JuniorТеорияОчень частоSeries против DataFrame, и когда .loc и .iloc расходятся?
Series против DataFrame, и когда .loc и .iloc расходятся?
Series — подписанный 1-D столбец; DataFrame — 2-D таблица из выровненных Series с общим индексом. .loc выбирает по метке, .iloc — по позиции. На RangeIndex совпадают, но расходятся при фильтрации или своём индексе — .loc[0] ищет метку 0, её может не быть, а .iloc[0] берёт первую строку.
Типичные ошибки
- ✗Думать, что
.locпозиционный, а.ilocпо меткам (они наоборот) - ✗Считать, что индексаторы всегда совпадают, ведь индекс по умолчанию похож на позиции
- ✗Забывать, что срез
.locвключает конечную метку, а.iloc— нет
Уточняющие вопросы
- →Почему после
df = df[df.x > 0]вызовdf.loc[0]может дать KeyError? - →Чем срез
.locотличается от.ilocна конечной границе?
JuniorКодЧастоУбрать дубликаты заказов, оставив последнюю строку на order_id
Убрать дубликаты заказов, оставив последнюю строку на order_id
Сортируют по времени, затем убирают дубликаты, оставляя последнюю строку на ключ: df.sort_values('updated_at').drop_duplicates('order_id', keep='last'). Сортировка делает keep='last' синонимом «самой свежей»; без неё останется строка, оказавшаяся последней в файле, а не свежайшее обновление.
Типичные ошибки
- ✗Звать drop_duplicates без сортировки, из-за чего keep='last' произволен
- ✗Сортировать по order_id вместо updated_at
- ✗По ошибке брать keep='first' после сортировки по убыванию
Уточняющие вопросы
- →Как оставить последнюю строку через groupby и idxmax?
- →Что будет с ничьими, где две строки делят один updated_at?
JuniorТеорияЧастоfillna нулём, средним или forward-fill — когда каждый вариант лжёт?
fillna нулём, средним или forward-fill — когда каждый вариант лжёт?
fillna(0) годится для настоящих нулей, но занижает измерение, делая «неизвестно» нулём. Среднее сохраняет среднее, но сжимает дисперсию и может течь между train/test. Forward-fill годится для упорядоченных рядов, но на неупорядоченных тянет чужое значение. Каждый лжёт, когда механизм пропусков не тот, что предполагает.
Типичные ошибки
- ✗Заполнять измерение нулём и занижать его сумму или среднее
- ✗Заполнять средним до разбиения train/test, допуская утечку
- ✗Применять forward-fill к строкам без хронологического порядка
Уточняющие вопросы
- →Чем поможет столбец-индикатор пропуска дальше по пайплайну?
- →Когда удалить строки лучше, чем импутировать их?
JuniorКодЧастоПомесячная выручка по странам, считая только оплаченные заказы
Помесячная выручка по странам, считая только оплаченные заказы
Обнуляют неоплаченное через where, затем один groupby. Строят paid = df['amount'].where(df['status'].eq('paid'), 0), берут месяц из order_ts и суммируют: df.assign(paid=paid, m=df['order_ts'].dt.to_period('M')).groupby(['country','m'])['paid'].sum(). where хранит все строки, покрывая все месяцы.
Типичные ошибки
- ✗Фильтровать оплаченные строки, из-за чего месяцы без оплат исчезают из вывода
- ✗Забыть вывести период месяца из order_ts перед группировкой
- ✗Считать строки вместо суммирования столбца amount
Уточняющие вопросы
- →Как оставить в результате месяцы с нулевой оплаченной выручкой?
- →Чем маскирование через where лучше фильтрации строк до groupby?
MiddleКодЧастоСлить orders с customers так, чтобы дубль ключа не размножил строки и не раздул сумму — как?
Слить orders с customers так, чтобы дубль ключа не размножил строки и не раздул сумму — как?
Передайте validate='many_to_one': pandas проверяет уникальность правого ключа и на fan-out кидает MergeError, а не молча размножает заказы и раздувает SUM(amount). Добавьте indicator=True и фильтр _merge == 'left_only' для несопоставленных — оба ловят это в момент merge.
Типичные ошибки
- ✗Считать, что один left join уже спасает от fan-out по дублям правого ключа
- ✗Путать направление validate (здесь берут
one_to_many) - ✗Искать несопоставленные заказы в
right_only, а не вleft_only
Уточняющие вопросы
- →Что именно проверяет
validate='one_to_one'в отличие отmany_to_one? - →Как дедуплицировать
customers, если fan-out ожидаем, но нежелателен?
MiddleКодЧастоРазвернуть длинную таблицу событий в широкую матрицу когорт — pivot или melt?
Развернуть длинную таблицу событий в широкую матрицу когорт — pivot или melt?
pivot разворачивает длинное в широкое: df.pivot(index='cohort_month', columns='activity_month', values='users'). melt — обратное, широкое в длинное. pivot годится, только если пара индекс/столбец уникальна; если ячейка может повторяться, pivot_table(..., aggfunc='sum') агрегирует коллизии.
Типичные ошибки
- ✗Путать pivot и melt — брать melt для расширения фрейма
- ✗Звать pivot при повторах пар индекс/столбец и ловить ValueError
- ✗Считать, что pivot агрегирует дубликаты, как pivot_table
Уточняющие вопросы
- →Когда обязательно переходить с pivot на pivot_table?
- →Как melt называет свои столбцы value и variable?
MiddleКодЧастоСвести дневные события в недельные суммы и добавить 4-недельное скользящее среднее — как?
Свести дневные события в недельные суммы и добавить 4-недельное скользящее среднее — как?
resample требует DatetimeIndex, поэтому сначала set_index('date'), затем resample('W')['events'].sum() даёт недельные суммы (неделя до воскресенья). Далее .rolling(4, min_periods=1).mean() — 4-недельное среднее. В отличие от groupby по номеру недели, resample заполняет пустые недели.
Типичные ошибки
- ✗Звать
resampleбез DatetimeIndex и получать TypeError - ✗Группировать по номеру недели и терять недели без событий
- ✗Запускать
rollingпо дневным строкам, а не по недельной серии
Уточняющие вопросы
- →Как переключить конец недели с воскресенья на понедельник?
- →Что меняет
min_periodsв начале окна rolling?
MiddleДебаггингЧастоfillna через цепочечную индексацию оставил NaN и напечатал SettingWithCopyWarning — как исправить?
fillna через цепочечную индексацию оставил NaN и напечатал SettingWithCopyWarning — как исправить?
df[mask]['price'] = 0 — цепочечная индексация: df[mask] строит временную копию, запись идёт туда, а df не трогается — поэтому NaN остаются, и pandas предупреждает. Чинят одним индексатором: df.loc[df['price'].isna(), 'price'] = 0 (или .fillna). Правило: один .loc/.iloc на запись.
Типичные ошибки
- ✗Глушить предупреждение вместо починки потерянной записи
- ✗Винить
isna()или отсутствие.copy(), а не цепочечную индексацию - ✗Не знать, что
.loc[mask, col]принимает булеву маску по строкам
Уточняющие вопросы
- →Почему pandas не может знать,
df[mask]— это view или копия? - →Когда
df.fillnaпредпочтительнее присваивания через.loc?
MiddleПроизводительностьЧастоПострочный .apply() по 5 млн строк идёт 40 минут — как векторизовать и куда ушло время?
Построчный .apply() по 5 млн строк идёт 40 минут — как векторизовать и куда ушло время?
.apply(axis=1) крутит Python-цикл с боксингом объектов на строку и диспетчеризацией — доминируют эти накладные, а не арифметика. Заменяют столбцовыми C-операциями по массивам: булевы маски, np.where/np.select, аксессоры .str/.dt. Это превращает минуты в секунды.
Типичные ошибки
- ✗Винить арифметику вместо накладных расходов Python-цикла и боксинга
- ✗Считать
.iterrows()или.itertuples()векторным ускорением - ✗Думать, что любое ветвление требует
apply, а неnp.where/np.select
Уточняющие вопросы
- →Когда
np.selectуместнее цепочки вызововnp.where? - →Как профилировать, чтобы подтвердить, что цена — цикл, а не I/O?
JuniorКодИногдаСотрудники, зарабатывающие больше своего менеджера
Сотрудники, зарабатывающие больше своего менеджера
Самообъединяют таблицу по managerId == id, чтобы поставить каждого сотрудника рядом с его менеджером, затем фильтруют, где зарплата сотрудника больше менеджерской. Ключ объединения связывает менеджера строки с его собственной строкой, ставя две зарплаты рядом.
Типичные ошибки
- ✗Сравнивать со средним по команде или глобально вместо менеджера
- ✗Забыть самообъединение, чтобы поставить зарплату менеджера рядом
- ✗Считать, что больше менеджера может лишь один подчинённый
Уточняющие вопросы
- →Почему join использует left_on managerId и right_on id?
- →Что станет с сотрудниками, у которых managerId пустой?
MiddleПроизводительностьИногдаCSV на 6 ГБ не влезает в RAM — как обработать его в pandas и когда пора уходить?
CSV на 6 ГБ не влезает в RAM — как обработать его в pandas и когда пора уходить?
Читают по частям через read_csv(chunksize=...) и агрегируют каждый чанк (chunked groupby), так в памяти лишь один чанк. Режут вес явным dtype= — даункаст чисел, category для строк низкой кардинальности — и usecols. Когда chunked pandas всё равно буксует — DuckDB или Polars.
Типичные ошибки
- ✗Считать, что
chunksizeгрузит весь файл до нарезки - ✗Оставлять дефолтные
int64/object, раздувающие вес в памяти - ✗Не объединять агрегаты чанков и пересчитывать весь файл заново
Уточняющие вопросы
- →Как объединить результаты
groupbyпо чанкам в один верный итог? - →Какие признаки, что задача переросла pandas в пользу DuckDB или Polars?
MiddleКодИногдаПроцент немедленных заказов среди первых заказов клиентов
Процент немедленных заказов среди первых заказов клиентов
Берут первый заказ каждого клиента, затем долю немедленных. Индексы строк первых заказов получают через groupby('customer_id')['order_date'].idxmin(), выбирают эти строки и считают среднее булева order_date == customer_pref_delivery_date, умноженное на 100, округляя до 2.
Типичные ошибки
- ✗Считать процент по всем заказам, а не первым
- ✗Выбирать последний заказ вместо самого раннего
- ✗Делить на клиентов без выделения первых заказов
Уточняющие вопросы
- →Почему idxmin, а не сортировка и head(1)?
- →Как совпадения по order_date повлияют на выбор первого?
MiddleКодИногдаМенеджеры с минимум пятью прямыми подчинёнными
Менеджеры с минимум пятью прямыми подчинёнными
Считают, сколько сотрудников подчинено каждому менеджеру, затем оставляют менеджеров, прошедших порог, и находят их имена. Группируют по managerId через size(), берут индекс, где счёт >= 5, и возвращают имена сотрудников, чей id в этом множестве.
Типичные ошибки
- ✗Считать размер отдела вместо подчинённых на managerId
- ✗Считать вхождения id сотрудника вместо группировки подчинённых
- ✗Приравнивать верх иерархии к наличию пяти подчинённых
Уточняющие вопросы
- →Зачем группировать по managerId, а не по отделу?
- →Как заодно вернуть число подчинённых на менеджера?
MiddleКодИногдаЧисло заказов на покупателя и дни от регистрации до первого заказа
Число заказов на покупателя и дни от регистрации до первого заказа
Агрегируют заказы на покупателя, затем подтягивают дату регистрации. Группируют orders по buyer_id для даты первого заказа (min(order_date)) и числа заказов (size), объединяют это с users по buyer_id == user_id и считают delta_days = (first_order - join_date).dt.days.
Типичные ошибки
- ✗Использовать последний заказ вместо первого для дельты
- ✗Считать строки пользователей вместо строк заказов
- ✗Выдавать дельты по заказам вместо одной на покупателя
Уточняющие вопросы
- →Зачем агрегировать заказы до объединения с users?
- →Как покупатель без заказов появится в выводе?
MiddleКодИногдаПомесячная статистика транзакций по стране с условной агрегацией
Помесячная статистика транзакций по стране с условной агрегацией
Строят ключ месяца и вспомогательные столбцы для approved, затем группируют. Задают month = trans_date.dt.strftime('%Y-%m'), добавляют is_approved = (state == 'approved') и approved_amount = amount.where(state == 'approved', 0).
Типичные ошибки
- ✗Удалять declined-строки, ломая итоги по всем транзакциям
- ✗Считать approved-показатели равными итогам
- ✗Группировать по неверным измерениям
Уточняющие вопросы
- →Зачем amount.where(...) вместо фильтрации фрейма?
- →Как добавить declined_count без второго прохода?
MiddleТеорияИногдаКогда MultiIndex оправдан и как вернуть плоские столбцы после агрегации по нескольким ключам?
Когда MultiIndex оправдан и как вернуть плоские столбцы после агрегации по нескольким ключам?
MultiIndex оправдан на иерархических данных — groupby по нескольким ключам — давая срез .xs/.loc с выравниванием по уровням без суррогатного ключа. Многостолбцовый .agg даёт MultiIndex-столбцы; .reset_index() и склейка кортежей уплощают их.
Типичные ошибки
- ✗Считать MultiIndex мусором и уплощать его до среза по уровню
- ✗Забывать, что многостолбцовый agg даёт MultiIndex-, а не плоские столбцы
- ✗Строить суррогатный ключ вместо среза
.xs/.locпо уровням
Уточняющие вопросы
- →Чем
.xsотличается от.locпри выборе одного уровня? - →Что меняет в результате
.groupby(..., as_index=False)?
MiddleКодИногдаЦена продукта на заданную дату со значением 10 до изменений
Цена продукта на заданную дату со значением 10 до изменений
Это поиск на момент времени (point-in-time). Оставляют изменения не позднее целевой даты, берут последнее такое изменение каждого продукта (max change_date, например через idxmax после сортировки или groupby) и читают его new_price.
Типичные ошибки
- ✗Брать последнее изменение, игнорируя целевую дату
- ✗Дефолтить только продукты без строк, а не без изменений до даты
- ✗Усреднять цены вместо as-of поиска
Уточняющие вопросы
- →Зачем фильтровать по дате до взятия последнего изменения?
- →Как расширить это на произвольный столбец дат запроса?
SeniorКодИногдаДневная доля отмен без забаненных клиентов и водителей
Дневная доля отмен без забаненных клиентов и водителей
Фильтруют до поездок, где обе стороны не забанены, затем берут дневное среднее флага отмены. Объединяют users дважды — по client_id и по driver_id — чтобы привязать бан-статус каждой стороны, оставляют строки, где ни один не забанен, ограничивают диапазоном дат и группируют по дню.
Типичные ошибки
- ✗Проверять бан только клиента, но не водителя
- ✗Пропускать фильтр банов вовсе
- ✗Считать отменой только один тип отмены
Уточняющие вопросы
- →Зачем здесь два отдельных объединения с users?
- →Как str.startswith обрабатывает оба типа отмены сразу?