Pandas и DataFrame: вопросы для собеседования (часть 4)
DataFrame — основная структура данных в pandas и главный инструмент аналитика для работы с табличными данными в Python. На собеседовании проверяют фильтрацию, группировку, merge, работу с пропусками и типами данных. Часто дают датасет и просят посчитать метрику прямо в коде.
Вопросы 16–20 из 20
16Что вернёт выражение `df.groupby("city").agg(total=("revenue", "sum"), avg=("revenue", "mean"))`?
A`Series` с двумя значениями: total и avg
B`DataFrame`, где строки — это исходные строки `df`, а `total` и `avg` добавлены как новые столбцы
C`DataFrame`, где индекс — `city`, а столбцы — `total` и `avg` (агрегаты по городу)
D`Series`, где индекс — пары (`city`, `total`)
Ответ: `.agg()` с несколькими агрегатами формирует итоговую таблицу по группам.
После `.groupby("city")` данные разделяются по ключу `city`. Метод `.agg(...)` вычисляет агрегаты по каждой группе и возвращает итоговый `DataFrame`: по одной строке на город. В этом примере будут два столбца: сумма `revenue` (`total`) и среднее `revenue` (`avg`). По умолчанию ключ группировки становится индексом результата.
17Зачем добавляют `validate="one_to_one"` в `pd.merge(a, b, on="id", how="left", validate="one_to_one")`?
AЧтобы автоматически привести типы столбцов ключа `id` к одинаковым
BЧтобы проверить, что ключ `id` уникален в обеих таблицах, и получить ошибку, если это не так
CЧтобы удалить строки с `NaN` после объединения
DЧтобы гарантировать сортировку результата по `id`
Ответ: Параметр `validate` помогает ловить ошибки кардинальности соединения (one-to-one, one-to-many).
`validate="one_to_one"` сообщает pandas, что вы ожидаете уникальный ключ `id` и в `a`, и в `b`. Если на практике обнаружатся дубликаты ключа хотя бы с одной стороны, `.merge()` выбросит исключение. Это полезно, когда вы строите аналитические витрины и хотите заранее защититься от неожиданного размножения строк.
18Как корректно проставить 1 в столбец `flag` для строк, где `x > 0`, избегая chained indexing и `SettingWithCopyWarning`?
A`df[df["x"] > 0]["flag"] = 1`
B`df.loc[df["x"] > 0, "flag"] = 1`
C`for i in range(len(df)): df.loc[i, "flag"] = int(df.loc[i, "x"] > 0)`
D`df["flag"] = 1; df = df[df["x"] > 0]`
Ответ: Для безопасного присваивания по условию используйте `.loc[условие, столбец] = ...`.
Выражение вида `df[условие]["col"] = ...` создает промежуточный объект и может менять не исходный `DataFrame`, что приводит к `SettingWithCopyWarning` и потенциально к ошибкам в аналитике. Идиоматичный и надежный способ — `df.loc[условие, "col"] = значение`: в одном шаге выбираются нужные строки и столбец, и присваивание выполняется на оригинальном `DataFrame`.
19Нужно посчитать долю каждой строки в сумме продаж своей группы по `store`: `share = sales / sum(sales)` внутри `store`. Какой вариант самый идиоматичный в pandas?
A`df["share"] = df["sales"] / df.groupby("store")["sales"].transform("sum")`
B`df["share"] = df.groupby("store")["sales"].sum() / df["sales"]`
C`df["share"] = df["sales"] / df.groupby("store").sum()`
D`df["share"] = df["sales"].apply(lambda x: x / df["sales"].sum())`
Ответ: Для расчёта значений на уровне строк внутри групп чаще всего используют `.transform()`.
`.transform("sum")` возвращает `Series` той же длины, что и исходный `DataFrame`, где каждой строке сопоставлена сумма ее группы. Это позволяет делать поэлементные вычисления без циклов: `df["sales"] / ...`. Варианты с `.sum()` без `transform()` обычно дают агрегированный результат по группам и не совпадают по длине с исходными строками.
20Есть `events` со столбцами `user_id`, `event`, `ts` и таблица `users` со столбцом `user_id`. Нужно посчитать число покупок (`event == "purchase"`) в 2025 году на пользователя и присоединить к `users`, сохранив всех пользователей. Какой вариант корректен?
A`p = events[events["event"] == "purchase"].groupby("user_id").size(); result = pd.merge(users, p, on="user_id", how="inner")`
B`p = events[(events["event"] == "purchase") & (events["ts"].dt.year == 2025)].groupby("user_id").count(); result = users.merge(p, on="user_id", how="left")`
C`p = events.groupby("user_id").size().reset_index(name="purchase_cnt"); result = users.merge(p, on="user_id", how="left")`
D`p = events[(events["event"] == "purchase") & (events["ts"].dt.year == 2025)].groupby("user_id").size().reset_index(name="purchase_cnt"); result = users.merge(p, on="user_id", how="left")`
Ответ: Типичный пайплайн: фильтрация → `.groupby().size()` → `reset_index` → `.merge(..., how="left")`.
Сначала нужно отфильтровать события по типу и периоду через булевую индексацию. Затем агрегировать по `user_id`, удобный счетчик покупок — `.size()`, который считает строки в группе. Чтобы результат был табличным и легко объединялся, делают `reset_index(name="purchase_cnt")`. Наконец, `.merge(..., how="left")` сохраняет всех пользователей из `users`, добавляя им счетчик покупок (у пользователей без покупок значение станет `NaN`, которое при необходимости можно заменить на 0).
Хотите тренировать интерактивно?
В приложении — таймер, прогресс, стрики и 1700+ вопросов по всем темам.
Тренировать в Telegram