Продолжение первой части: маркетинг ждёт, сколько клиентов в каждом городе и сколько бонусных баллов у них в среднем. Текстовые столбцы мы уже почистили, остались даты, числа и дубликаты.
Подготовка
Ячейка ниже повторяет первую часть: загружает выгрузку и чистит коды, имена, города, сегменты и почту — это решения упражнений первой части. Выполните её (или сразу первое упражнение — она выполнится сама). Последняя строка напоминает, сколько полных повторов было в сырой выгрузке: их понадобится сравнить.
import pandas as pd
customers = pd.read_csv("data/customers_raw.csv")
raw_duplicates = customers.duplicated().sum()
short = {"Спб": "Санкт-Петербург", "С.-Петербург": "Санкт-Петербург", "Екб": "Екатеринбург"}
customers["customer_id"] = customers["customer_id"].str.strip().str.upper()
customers["name"] = customers["name"].str.strip().str.replace(" ", " ").str.title()
customers["city"] = customers["city"].str.strip().str.title().replace(short)
customers["segment"] = customers["segment"].str.strip().str.lower()
customers["email"] = customers["email"].str.lower()
print("полных повторов в сырой выгрузке:", raw_duplicates)
customers.head(5)полных повторов в сырой выгрузке: 10
| customer_id | name | city | signup_date | segment | bonus | ||
|---|---|---|---|---|---|---|---|
| 0 | C001 | Сергей Ю. | Екатеринбург | 2025-06-12 | постоянный | client001@lavka.example | 120 |
| 1 | C002 | Сергей В. | Екатеринбург | 2025-01-14 | новый | client002@lavka.example | 2 400 |
| 2 | C003 | Максим С. | Екатеринбург | 2025-05-19 | новый | client003@lavka.example | 50 |
| 3 | C004 | Андрей Д. | Москва | 2023-09-15 | новый | client004@lavka.example | 1900 |
| 4 | C005 | Наталья Е. | Екатеринбург | 2024-08-24 | постоянный | NaN | NaN |
Шаг 1. Дата регистрации
В столбце signup_date два формата: 2025-06-12 и 24.08.2024.
Добавьте в customers столбец signup — дату регистрации настоящей датой. В no_date сохраните число строк,
где даты нет.
errors="coerce": "%Y-%m-%d" и "%d.%m.%Y".iso.fillna(dotted); пропуски — customers["signup"].isna().sum().Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
# добавьте в customers столбец signup — дату регистрации
no_date = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
iso = pd.to_datetime(customers["signup_date"], format="%Y-%m-%d", errors="coerce")
dotted = pd.to_datetime(customers["signup_date"], format="%d.%m.%Y", errors="coerce")
customers["signup"] = iso.fillna(dotted)
no_date = customers["signup"].isna().sum()Пропуски остались только там, где даты не было в самой выгрузке. Восстановить её неоткуда, и заполнять её чем-то «средним» нельзя: оставляем пропуск.
Шаг 2. Бонусные баллы
print("пустых полей:", customers["bonus"].isna().sum())
customers["bonus"].value_counts().head(8)пустых полей: 11
bonus 0 38 50 23 450 21 800 21 300 21 нет 20 120 18 2 400 14 Name: count, dtype: int64
Числа, числа с пробелом и слово «нет». Есть и пустые поля — value_counts их не показывает, поэтому
последняя строка считает их отдельно: 11 пропусков. Маркетологи подтвердили: «нет» и пустое поле значат, что
баллов нет, — то есть ноль.
Добавьте в customers столбец points — баллы целым числом: пробелы между тысячами убраны, «нет» и пропуск
превращены в 0. В total_points сохраните сумму баллов по всем строкам.
.str.replace(" ", ""), потом pd.to_numeric(..., errors="coerce")..fillna(0).astype("int64").Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
# добавьте в customers столбец points — бонусные баллы целым числом
total_points = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
points = pd.to_numeric(customers["bonus"].str.replace(" ", ""), errors="coerce")
customers["points"] = points.fillna(0).astype("int64")
total_points = customers["points"].sum()Шаг 3. Дубликаты
Теперь, когда текст чистый, посмотрим на повторы ещё раз:
print(customers.duplicated().sum())
print(customers.duplicated(subset=["customer_id"]).sum())12 17
В сырой выгрузке полных повторов было 10 (последняя строка подготовки) — стало 12, а по коду клиента их 17.
Семь записей прятались за пробелами и регистром. Пять из них и сейчас не считаются полными повторами: они
отличаются только записью даты в исходном столбце signup_date (2023-07-21 и 21.07.2023), который мы
не меняли, а заменили столбцом signup. В итоговую таблицу возьмём только чистые столбцы.
Сохраните в clean столбцы из списка columns таблицы customers без дубликатов, отсортированные по
customer_id, с индексом с нуля.
customers[columns].drop_duplicates() — исходные столбцы signup_date и bonus в сравнение уже не попадут..sort_values("customer_id").reset_index(drop=True).Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
columns = ["customer_id", "name", "city", "signup", "segment", "email", "points"]
clean = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
columns = ["customer_id", "name", "city", "signup", "segment", "email", "points"]
clean = customers[columns].drop_duplicates().sort_values("customer_id").reset_index(drop=True)Шаг 4. Проверка и ответ
У магазина есть эталонный справочник клиентов — data/customers.csv. Сверим с ним результат:
reference = pd.read_csv("data/customers.csv")
print((clean["customer_id"] == reference["customer_id"]).all())
print((clean["name"] == reference["name"]).all())
print((clean["city"] == reference["city"]).all())
print((clean["segment"] == reference["segment"]).all())True True True True
Все четыре столбца совпали. В жизни эталона обычно нет — тогда проверяют по-другому: число строк, список значений в каждом текстовом столбце, минимум и максимум в числах и датах.
По таблице clean сохраните: в by_city — число клиентов в каждом городе, в wholesale — строки оптовых
клиентов (сегмент «оптовый»), в avg_points — средний бонус.
clean["city"].value_counts(), clean[clean["segment"] == "оптовый"], clean["points"].mean().Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
by_city = ...
wholesale = ...
avg_points = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
by_city = clean["city"].value_counts()
wholesale = clean[clean["segment"] == "оптовый"]
avg_points = clean["points"].mean()print("строк в выгрузке:", len(customers), "→ клиентов:", len(clean))
print("без даты регистрации:", clean["signup"].isna().sum(), "· без email:", clean["email"].isna().sum())
print("оптовых:", len(wholesale), "· средний бонус:", round(avg_points))
print(by_city)строк в выгрузке: 257 → клиентов: 240 без даты регистрации: 5 · без email: 43 оптовых: 26 · средний бонус: 862 city Москва 89 Санкт-Петербург 60 Екатеринбург 39 Казань 32 Новосибирск 20 Name: count, dtype: int64
Выводы
Из 257 строк выгрузки получилось 240 клиентов. Если бы мы посчитали города по сырым данным, получили бы 22 «города» и завышенное число клиентов; если бы суммировали баллы без очистки — получили бы ошибку или потеряли треть баллов.
Порядок, который стоит запомнить:
- Осмотреть:
info,isna().sum(),value_countsпо текстовым столбцам. - Текст: пробелы, регистр, варианты написания.
- Типы: даты и числа — с проверкой, сколько значений не распозналось.
- Пропуски: решить для каждого столбца отдельно — оставить, заполнить или удалить.
- Дубликаты — после всего остального.
- Проверить результат.