Нашли ошибку?
Скачать чистый
урок 4 из 5≈ 20 минупражнения: 0 из 4

Замены и правила

Значение по словарю — map, замена отдельных значений — replace, столбец по условию — np.where, своё правило — apply. И почему сначала стоит поискать готовую операцию.

Python запустится при первом выполнении

Арифметикой можно получить столбец из других столбцов. Но бывают правила, которые формулой не запишешь: «категории „Кофе“ — полка 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 из первого примера — нет.

[ ]Упражнение 1Скидка по категории решено

В словаре rates — скидка для каждой категории. Добавьте в таблицу goods столбец discount — скидку товара по его категории — и столбец sale_price: цену со скидкой, округлённую до целых рублей.

goods = pd.read_csv("data/products.csv")
rates = {"Кофе": 0.04, "Чай": 0.06, "Сладости": 0.12, "Посуда": 0.18, "Аксессуары": 0.18}
# добавьте в goods столбцы discount и sale_price

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())?

[ ]Упражнение 2Напитки решено

Добавьте в goods столбец group: категории «Кофе» и «Чай» заменены на «Напитки», остальные — без изменений. Столбец category не меняйте. В n_drinks сохраните число товаров-напитков.

# добавьте в goods столбец group
n_drinks = ...

np.where: одно из двух по условию

Столбец «если условие выполнено — одно значение, иначе — другое» строит функция where из библиотеки NumPy. pandas построен на NumPy, и некоторые её функции удобно вызывать напрямую; подключают её под именем np. У np.where три аргумента: маска, значение для True, значение для False.

[ ]Дороже 1000 ₽ или нет
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
[ ]Упражнение 3Высокая и обычная маржа решено

В share — доля маржи в цене каждого товара. Добавьте в goods столбец margin: «высокая», если доля не меньше 0.5, и «обычная» в остальных случаях. В n_high сохраните число товаров с высокой маржой.

share = (goods["price"] - goods["cost"]) / goods["price"]
# добавьте в goods столбец margin
n_high = ...

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.

[ ]Упражнение 4Стоимость доставки решено

Доставка товара стоит: бесплатно — при цене от 1000 ₽, 150 ₽ — при цене от 500 до 999 ₽, 250 ₽ — если товар дешевле 500 ₽. Допишите функцию shipping(price), добавьте в goods столбец shipping с её результатами и сохраните в free число товаров с бесплатной доставкой.

def shipping(price):
    return ...


# добавьте в goods столбец shipping
free = ...

Итоги

  • столбец.map(словарь) — перевести все значения по словарю; нет ключа — NaN.
  • столбец.replace(словарь) — заменить только указанные значения.
  • np.where(маска, a, b) — a там, где True, и b там, где False.
  • столбец.apply(функция) — своё правило для каждого значения; медленно, поэтому в последнюю очередь.

Подробнее в справочнике

Нашли ошибку?