Поставщик прислал «Кофейной лавке» прайс-лист на 2026 год. Файл набирали вручную — и это видно.
import pandas as pd
supplier = pd.read_csv("data/supplier_prices.csv")
supplier.head(6)| product_id | name | category | price | cost | stock | updated | |
|---|---|---|---|---|---|---|---|
| 0 | P01 | эспрессо-смесь 1 кг | Кофе | 1 560 ₽ | 920 | 120.0 | 15.01.2026 |
| 1 | P02 | Колумбия 250 г | Кофе | 760 ₽ | 400 | 64.0 | 13.01.2026 |
| 2 | P03 | эфиопия 250 г | Кофе | 830 ₽ | 460 | NaN | 12.01.2026 |
| 3 | P04 | БРАЗИЛИЯ 1 КГ | Кофе | 1 370 ₽ | 850 | 64.0 | 16.01.2026 |
| 4 | P05 | Кофе без кофеина 250 г | Кофе | 760 ₽ | нет данных | 59.0 | 13.01.2026 |
| 5 | P06 | Чёрный чай 100 г | Чай | 370 ₽ | 160 | 104.0 | 14.01.2026 |
Названия написаны как попало, в цене стоит знак рубля, в себестоимости вместо числа встречается «нет данных». В этом и следующих уроках мы приведём таблицу в порядок. Начнём с типов.
Числа, которые стали текстом
supplier.dtypesproduct_id str name str category str price str cost str stock float64 updated str dtype: object
price и cost — это цены, но их тип — str, текст. Причина: тип у столбца один, и если хотя бы одно
значение не число («нет данных», 1 560 ₽), pandas читает текстом весь столбец. stock (остаток
на складе) — числа, но дробные: в нём есть пропуски.
Текстовый столбец с цифрами опасен тем, что не всегда даёт ошибку. Сложение для строк — это склейка, умножение на число — повтор:
print(supplier["cost"].head(3).sum())
print((supplier["cost"].head(3) * 2).tolist())920400460 ['920920', '400400', '460460']
А вот среднее по тексту посчитать нельзя:
supplier["cost"].mean()TypeError: Cannot perform reduction 'mean' with string dtype
astype и to_numeric
Метод astype из прошлого модуля переводит столбец в указанный тип. Но он требует, чтобы подошли все
значения:
supplier["cost"].astype(int)ValueError: invalid literal for int() with base 10: 'нет данных'
Функция pd.to_numeric с параметром errors="coerce" («принудить») поступает мягче: всё, что не получилось
превратить в число, заменяет пропуском NaN:
answers = pd.Series(["12", "7", "нет", "3.5", "?"])
pd.to_numeric(answers, errors="coerce")0 12.0 1 7.0 2 NaN 3 3.5 4 NaN dtype: float64
Сохраните в cost_num столбец cost таблицы supplier, переведённый в числа (нечисловые значения —
пропуски). В n_bad — сколько значений не распозналось, в cost_total — сумму распознанных.
pd.to_numeric(supplier["cost"], errors="coerce").cost_num.isna().sum(). Сумма пропускает NaN сама.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
cost_num = ...
n_bad = ...
cost_total = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
cost_num = pd.to_numeric(supplier["cost"], errors="coerce")
n_bad = cost_num.isna().sum()
cost_total = cost_num.sum()Нечисловые значения теперь легко найти — по маске пропусков:
supplier.loc[cost_num.isna(), ["product_id", "name", "cost"]]| product_id | name | cost | |
|---|---|---|---|
| 4 | P05 | Кофе без кофеина 250 г | нет данных |
| 12 | P13 | Зефир | нет данных |
| 16 | P17 | Чайник заварочный | — |
Посмотреть на них обязательно: вдруг там не «нет данных», а опечатка, которую можно исправить. Убедившись, что это действительно отсутствующие значения, записываем числовой столбец в таблицу:
supplier["cost"] = cost_num
print(supplier["cost"].dtype)
print(supplier["cost"].mean())float64 538.2352941176471
У мягкости есть обратная сторона. Попробуем так же перевести цены:
price = pd.to_numeric(supplier["price"], errors="coerce")
print(price.isna().sum(), "из", len(price))20 из 20
Все двадцать значений стали пропусками: 1 560 ₽ — не число, и to_numeric молча превратил столбец
в пустой. Здесь сначала нужно убрать из текста пробелы и знак рубля — этим займёмся в следующем уроке.
Целые числа с пропусками: Int64
Остаток на складе — целое число штук, но pandas показывает 120.0:
supplier["stock"].head(4)0 120.0 1 64.0 2 NaN 3 64.0 Name: stock, dtype: float64
Виноват NaN: это дробное значение, и обычный целый тип int64 хранить его не умеет. Перевести столбец
в int не получится — pandas сам подсказывает, что делать:
supplier["stock"].astype(int)IntCastingNaNError: Cannot convert non-finite values (NA or inf) to integer.Replace or remove non-finite values or cast to an integer typethat supports these values (e.g. 'Int64')
Для таких столбцов в pandas есть отдельный тип — Int64, с заглавной буквы: целые числа, которые умеют
хранить пропуск. Название пишут строкой в кавычках:
stock = supplier["stock"].astype("Int64")
stock.head(4)0 120 1 64 2 <NA> 3 64 Name: stock, dtype: Int64
Пропуск в таком столбце показан как <NA> («not available»). Это тот же пропуск: isna() его находит,
а sum и max — пропускают.
print(stock.isna().sum())
print(stock.sum())
print(stock.max())
print((stock > 100).head(4).tolist())3 1253 138 [True, False, <NA>, False]
Одно отличие от NaN видно в последней строке: сравнение с <NA> даёт не False, а снова <NA> —
«неизвестно». Методы sum и mean такие значения маски пропускают, поэтому доля по маске получается уже среди
известных значений, а не среди всех строк.
В таблице delivery столбцы rating и days дробные из-за пропусков. Переведите оба в целый тип
с пропусками. В fives сохраните число оценок «5».
delivery["rating"] = delivery["rating"].astype("Int64") — и так же для days.delivery["rating"] == 5.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
delivery = pd.read_csv("data/delivery_h1.csv")
# переведите столбцы rating и days в целый тип с пропусками
fives = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
delivery = pd.read_csv("data/delivery_h1.csv")
delivery["rating"] = delivery["rating"].astype("Int64")
delivery["days"] = delivery["days"].astype("Int64")
fives = (delivery["rating"] == 5).sum()Дробные в целые и другие переводы
astype(int) для дробных чисел отбрасывает дробную часть, а не округляет:
weights = pd.Series([0.25, 0.9, 1.5, 2.99])
print(weights.astype(int).tolist())
print(weights.round().astype(int).tolist())[0, 0, 1, 2] [0, 1, 2, 3]
Что выведет print(pd.Series([1.7, 2.2]).astype(int).tolist())?
astype(int) отбрасывает дробную часть: 1.7 → 1. Чтобы получить 2, сначала округлите: .round().astype(int).Так же числа переводят в текст, а логические значения — в 1 и 0:
codes = pd.Series([7, 42, 105])
print(codes.astype(str).tolist())
print(pd.Series([True, False, True]).astype(int).tolist())['7', '42', '105'] [1, 0, 1]
Поставщик объяснил: пустое поле в stock значит, что товара на складе нет. Сохраните в stock_int столбец
stock таблицы supplier, где пропуски заменены нулём, а тип — обычный целый (не дробный и не Int64).
В total_stock сохраните общий остаток.
supplier["stock"].fillna(0).astype(int).Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
stock_int = ...
total_stock = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
stock_int = supplier["stock"].fillna(0).astype(int)
total_stock = stock_int.sum()Здесь заполнение нулём оправдано: пропуск и правда значит «ноль штук». Сравните с оценками из прошлого урока, где ноль исказил бы среднее.
Итоги
dtypes— первое, что стоит проверить: числа могли прочитаться какstr.astype(тип)переводит, если подходят все значения;pd.to_numeric(столбец, errors="coerce")превращает неподходящие вNaN.- После
coerceвсегда считайте пропуски и смотрите на нераспознанные значения. Int64— целые числа с пропусками; пропуск в них выглядит как<NA>.astype(int)отбрасывает дробную часть — округляйте заранее.