Арифметикой можно получить столбец из других столбцов. Но бывают правила, которые формулой не запишешь: «категории „Кофе“ — полка A, сладостям — полка B», «дороже 1000 ₽ — дорогой товар», «доставка зависит от цены ступеньками». Для них есть четыре инструмента — от самого простого к самому общему.
map: значение по словарю
Соответствие «было → стало» удобно записать словарём. Метод map заменяет каждое значение столбца тем, что
лежит в словаре по этому ключу:
import pandas as pd
products = pd.read_csv("data/products.csv")
shelf = {"Кофе": "A", "Чай": "A", "Сладости": "B", "Посуда": "C", "Аксессуары": "C"}
products["shelf"] = products["category"].map(shelf)
products[["name", "category", "shelf"]].head(7)| name | category | shelf | |
|---|---|---|---|
| 0 | Эспрессо-смесь 1 кг | Кофе | A |
| 1 | Колумбия 250 г | Кофе | A |
| 2 | Эфиопия 250 г | Кофе | A |
| 3 | Бразилия 1 кг | Кофе | A |
| 4 | Кофе без кофеина 250 г | Кофе | A |
| 5 | Чёрный чай 100 г | Чай | A |
| 6 | Зелёный чай 100 г | Чай | A |
map пропускает через словарь все значения. Если для значения нет ключа, на его месте окажется NaN —
так pandas обозначает пропуск, «нет данных»:
partial = products["category"].map({"Кофе": "A", "Чай": "A"})
print(partial.tail(3))
print(partial.notna().all())
print(products["shelf"].notna().all())17 NaN 18 NaN 19 NaN Name: category, dtype: str False True
Пропускам посвящён первый урок следующего модуля. Пока достаточно знать: NaN после map значит, что
в словаре не хватило ключа. Проверить столбец на пропуски помогает notna() — маска «значение есть».
столбец.notna().all() вернёт True, только если пропусков нет: в неполном переводе они есть, а в столбце
shelf из первого примера — нет.
В словаре rates — скидка для каждой категории. Добавьте в таблицу goods столбец discount — скидку
товара по его категории — и столбец sale_price: цену со скидкой, округлённую до целых рублей.
goods["discount"] = goods["category"].map(rates).goods["price"] * (1 - goods["discount"]); всё выражение в скобки и .round().Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
goods = pd.read_csv("data/products.csv")
rates = {"Кофе": 0.04, "Чай": 0.06, "Сладости": 0.12, "Посуда": 0.18, "Аксессуары": 0.18}
# добавьте в goods столбцы discount и sale_priceУпражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
goods = pd.read_csv("data/products.csv")
rates = {"Кофе": 0.04, "Чай": 0.06, "Сладости": 0.12, "Посуда": 0.18, "Аксессуары": 0.18}
goods["discount"] = goods["category"].map(rates)
goods["sale_price"] = (goods["price"] * (1 - goods["discount"])).round()replace: заменить только некоторые
Когда менять нужно лишь часть значений, а остальные оставить, подходит replace. Он тоже принимает словарь,
но значения, которых в словаре нет, не трогает:
products["category"].replace({"Посуда": "Для дома", "Аксессуары": "Для дома"}).value_counts()category Для дома 7 Кофе 5 Чай 4 Сладости 4 Name: count, dtype: int64
Запомнить разницу просто: map — «перевести всё по словарю», replace — «исправить указанное».
Сколько непустых значений останется:
print(pd.Series(["да", "нет", "да"]).map({"да": 1}).notna().sum())?
map поставит на его место NaN. Останется два значения. replace с тем же словарём оставил бы «нет» как есть.Добавьте в goods столбец group: категории «Кофе» и «Чай» заменены на «Напитки», остальные — без изменений.
Столбец category не меняйте. В n_drinks сохраните число товаров-напитков.
goods["category"].replace({"Кофе": "Напитки", "Чай": "Напитки"}).goods["group"] == "Напитки".Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
# добавьте в goods столбец group
n_drinks = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
goods["group"] = goods["category"].replace({"Кофе": "Напитки", "Чай": "Напитки"})
n_drinks = (goods["group"] == "Напитки").sum()np.where: одно из двух по условию
Столбец «если условие выполнено — одно значение, иначе — другое» строит функция where из библиотеки
NumPy. pandas построен на NumPy, и некоторые её функции удобно вызывать напрямую; подключают её под именем np.
У np.where три аргумента: маска, значение для True, значение для False.
import numpy as np
np.where(products["price"] >= 1000, "дорогой", "обычный")array(['дорогой', 'обычный', 'обычный', 'дорогой', 'обычный', 'обычный',
'обычный', 'обычный', 'обычный', 'обычный', 'обычный', 'обычный',
'обычный', 'обычный', 'дорогой', 'дорогой', 'дорогой', 'дорогой',
'обычный', 'дорогой'], dtype='<U7')Результат — массив NumPy (array): набор значений без индекса и названия. dtype='<U7' — так NumPy
обозначает текст длиной до семи символов. Массив можно сразу записать в столбец таблицы:
products["segment"] = np.where(products["price"] >= 1000, "дорогой", "обычный")
products["segment"].value_counts()segment обычный 13 дорогой 7 Name: count, dtype: int64
В share — доля маржи в цене каждого товара. Добавьте в goods столбец margin: «высокая», если доля
не меньше 0.5, и «обычная» в остальных случаях. В n_high сохраните число товаров с высокой маржой.
np.where(share >= 0.5, "высокая", "обычная") — сначала значение для True.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
share = (goods["price"] - goods["cost"]) / goods["price"]
# добавьте в goods столбец margin
n_high = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
share = (goods["price"] - goods["cost"]) / goods["price"]
goods["margin"] = np.where(share >= 0.5, "высокая", "обычная")
n_high = (goods["margin"] == "высокая").sum()apply: своё правило
Если правило сложнее — несколько ступенек, длинное условие, — его можно написать обычной функцией Python:
на вход одно значение, на выход результат. Метод apply вызовет функцию для каждого значения столбца:
def level(price):
if price < 300:
return "до 300"
if price < 1000:
return "300–999"
return "от 1000"
products["level"] = products["price"].apply(level)
products[["name", "price", "level"]].head(6)| name | price | level | |
|---|---|---|---|
| 0 | Эспрессо-смесь 1 кг | 1450 | от 1000 |
| 1 | Колумбия 250 г | 690 | 300–999 |
| 2 | Эфиопия 250 г | 790 | 300–999 |
| 3 | Бразилия 1 кг | 1290 | от 1000 |
| 4 | Кофе без кофеина 250 г | 720 | 300–999 |
| 5 | Чёрный чай 100 г | 350 | 300–999 |
Функцию передают без скобок — apply(level), а не apply(level()): мы отдаём саму функцию, а вызывать
её будет pandas.
apply умеет всё, но у этого есть цена: функция Python вызывается отдельно для каждой строки, и на больших
таблицах это в десятки и сотни раз медленнее готовых операций. Поэтому сначала стоит проверить, нет ли
операции, которая работает со столбцом целиком:
def plus_ten_percent(price):
return price * 1.1
slow = products["price"].apply(plus_ten_percent)
fast = products["price"] * 1.1
print((slow == fast).all())True
Порядок выбора: арифметика и сравнения → map, replace, np.where → и только если ничего не подошло,
apply.
Доставка товара стоит: бесплатно — при цене от 1000 ₽, 150 ₽ — при цене от 500 до 999 ₽, 250 ₽ — если товар
дешевле 500 ₽. Допишите функцию shipping(price), добавьте в goods столбец shipping с её результатами
и сохраните в free число товаров с бесплатной доставкой.
if с return и последний return 250, как в примере с уровнями цены.goods["shipping"] = goods["price"].apply(shipping); число — сумма маски goods["shipping"] == 0.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
def shipping(price):
return ...
# добавьте в goods столбец shipping
free = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
def shipping(price):
if price >= 1000:
return 0
if price >= 500:
return 150
return 250
goods["shipping"] = goods["price"].apply(shipping)
free = (goods["shipping"] == 0).sum()Итоги
столбец.map(словарь)— перевести все значения по словарю; нет ключа —NaN.столбец.replace(словарь)— заменить только указанные значения.np.where(маска, a, b)—aтам, гдеTrue, иbтам, гдеFalse.столбец.apply(функция)— своё правило для каждого значения; медленно, поэтому в последнюю очередь.