Новый столбец меняет всю таблицу сразу. Но часто нужно поправить только часть значений: исправить опечатку
в одной ячейке, поднять цены одной категории, пометить дорогие товары. Для этого есть один надёжный способ —
loc слева от знака =.
Одна ячейка
loc[метка строки, название столбца] не только читает ячейку, но и записывает в неё:
import pandas as pd
products = pd.read_csv("data/products.csv")
products.loc[9, "price"] = 190
products.loc[9]product_id P10 name Шоколад горький category Сладости price 190 cost 90 Name: 9, dtype: object
Строки по условию
На месте метки строки может стоять маска. Тогда значение запишется во все строки, где в маске True:
is_tea = products["category"] == "Чай"
products.loc[is_tea, "price"] = 400
products[is_tea]| product_id | name | category | price | cost | |
|---|---|---|---|---|---|
| 5 | P06 | Чёрный чай 100 г | Чай | 400 | 150 |
| 6 | P07 | Зелёный чай 100 г | Чай | 400 | 170 |
| 7 | P08 | Улун 100 г | Чай | 400 | 260 |
| 8 | P09 | Травяной сбор 50 г | Чай | 400 | 100 |
Справа может стоять не одно значение, а Series — например, прежние цены тех же строк плюс 20 ₽:
is_sweet = products["category"] == "Сладости"
products.loc[is_sweet, "price"] = products.loc[is_sweet, "price"] + 20
products[is_sweet]| product_id | name | category | price | cost | |
|---|---|---|---|---|---|
| 9 | P10 | Шоколад горький | Сладости | 210 | 90 |
| 10 | P11 | Печенье овсяное | Сладости | 170 | 70 |
| 11 | P12 | Миндаль в шоколаде | Сладости | 340 | 170 |
| 12 | P13 | Зефир | Сладости | 230 | 100 |
Читается так: «в строках-сладостях в столбец price записать прежние цены этих строк, увеличенные на 20».
То же самое короче: products.loc[is_sweet, "price"] += 20.
В справочнике ошибка: у товара в строке с меткой 12 (зефир) цена должна быть 250, а себестоимость — 120.
Исправьте оба значения в таблице fixed.
fixed.loc[12, "price"] = 250 — и так же для столбца cost.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
fixed = pd.read_csv("data/products.csv")
# исправьте цену и себестоимость в строке с меткой 12Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
fixed = pd.read_csv("data/products.csv")
fixed.loc[12, "price"] = 250
fixed.loc[12, "cost"] = 120Так же меняют текст. Отметим дорогие товары отдельной категорией:
products.loc[products["price"] > 2000, "category"] = "Премиум"
products["category"].value_counts()category Кофе 5 Чай 4 Сладости 4 Посуда 3 Премиум 2 Аксессуары 2 Name: count, dtype: int64
Запись в два шага не работает
Мы умеем фильтровать: таблица[маска]. И умеем брать столбец: таблица["price"]. Напрашивается соединить —
отфильтровать, взять столбец и присвоить. Попробуем обнулить цены чая в свежей таблице:
fresh = pd.read_csv("data/products.csv")
fresh[fresh["category"] == "Чай"]["price"] = 0ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment. Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write). Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step. See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignment
Несмотря на слово Error в названии, это предупреждение, а не ошибка: программа не остановилась,
следующие строки выполнились бы. pandas сообщает, что значение записано в копию. Проверим — цены чая прежние:
fresh[fresh["category"] == "Чай"]| product_id | name | category | price | cost | |
|---|---|---|---|---|---|
| 5 | P06 | Чёрный чай 100 г | Чай | 350 | 150 |
| 6 | P07 | Зелёный чай 100 г | Чай | 390 | 170 |
| 7 | P08 | Улун 100 г | Чай | 540 | 260 |
| 8 | P09 | Травяной сбор 50 г | Чай | 260 | 100 |
Почему так? Запись состоит из двух шагов. Первый — fresh[маска] — создаёт новую таблицу из отобранных
строк. Второй — ["price"] = 0 — записывает нули в эту новую таблицу. Но она не сохранена ни в какой
переменной и сразу исчезает. До fresh запись не доходит.
В таблице sale поднимите цену всех товаров категории «Чай» на 30 ₽. Остальные цены не меняйте.
sale["category"] == "Чай". Слева от = — sale.loc[маска, "price"].sale.loc[маска, "price"] + 30. Или короче — оператор +=.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
sale = pd.read_csv("data/products.csv")
# поднимите цену всего чая на 30 ₽Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
sale = pd.read_csv("data/products.csv")
sale.loc[sale["category"] == "Чай", "price"] = sale.loc[sale["category"] == "Чай", "price"] + 30Copy-on-Write: всё, что получено из таблицы, — отдельная таблица
За этим стоит простое правило pandas 3. Оно называется Copy-on-Write («копирование при записи»: настоящая копия данных создаётся не сразу, а в тот момент, когда в объект что-то записывают):
Любая таблица или Series, полученные из другой таблицы, ведут себя как независимая копия. Изменение одного объекта никогда не меняет другой.
Отобрали строки в отдельную переменную и изменили их — исходная таблица не заметит:
tea = fresh[fresh["category"] == "Чай"]
tea["price"] = 0
print(tea["price"].tolist())
print(fresh.loc[fresh["category"] == "Чай", "price"].tolist())[0, 0, 0, 0] [350, 390, 540, 260]
То же со столбцом: fresh["price"] — отдельный Series, запись в него таблицу не меняет.
prices = fresh["price"]
prices.loc[0] = 1
print(prices.loc[0])
print(fresh.loc[0, "price"])1 1450
Правило удобно тем, что у него нет исключений: чтобы изменить таблицу, нужно обратиться к ней самой —
таблица.loc[...] = ... или таблица["столбец"] = .... Случайно испортить её через выборку нельзя.
t = pd.DataFrame({"x": [1, 2, 3]})
part = t[t["x"] > 1]
part["x"] = 100
print(t["x"].sum())Что выведет этот код?
part получена из t фильтром — это независимая таблица. Запись в part не меняет t: в ней по-прежнему 1, 2, 3, сумма 6.Второе имя — не копия
Один случай выглядит как исключение, но он не про pandas, а про сам Python. Присваивание same = fresh ничего
не получает «из таблицы»: оно даёт той же таблице второе имя.
same = fresh
same.loc[0, "price"] = 1
print(fresh.loc[0, "price"])1
Так же ведут себя списки и словари Python. Когда нужна настоящая независимая копия — например, чтобы
поэкспериментировать и не испортить исходные данные, — её создаёт метод copy:
backup = fresh.copy()
backup.loc[0, "price"] = 5000
print(backup.loc[0, "price"])
print(fresh.loc[0, "price"])5000 1
Сохраните в draft независимую копию таблицы original и обнулите в draft весь столбец price.
Таблица original должна остаться прежней.
draft = original.copy().draft["price"] = 0.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
original = pd.read_csv("data/products.csv")
draft = ...
# обнулите столбец price в draftУпражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
original = pd.read_csv("data/products.csv")
draft = original.copy()
draft["price"] = 0Дробное число в целый столбец
У столбца один тип. В столбец целых чисел (int64) pandas 3 откажется записать дробное:
fresh.loc[1, "price"] = 699.9TypeError: Invalid value '699.9' for dtype 'int64'
Старые версии pandas в такой ситуации молча меняли тип всего столбца на дробный — и целые цены вдруг становились
1450.0. Теперь тип нужно сменить явно: сначала столбец переводят в дробный. Это делает метод astype — подробно о типах поговорим в модуле об очистке:
fresh["price"] = fresh["price"].astype(float)
fresh.loc[1, "price"] = 699.9
fresh.head(3)| product_id | name | category | price | cost | |
|---|---|---|---|---|---|
| 0 | P01 | Эспрессо-смесь 1 кг | Кофе | 1.0 | 870 |
| 1 | P02 | Колумбия 250 г | Кофе | 699.9 | 380 |
| 2 | P03 | Эфиопия 250 г | Кофе | 790.0 | 430 |
(Цена 1.0 в первой строке — след примера с двумя именами выше.)
В таблице raised поднимите цену всех товаров категории «Кофе» на 7.5 %: новая цена — прежняя, умноженная
на 1.075. Остальные цены не меняйте.
raised["price"] = raised["price"].astype(float).raised.loc[маска, "price"] = raised.loc[маска, "price"] * 1.075.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
raised = pd.read_csv("data/products.csv")
# поднимите цену кофе на 7.5 %Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
raised = pd.read_csv("data/products.csv")
raised["price"] = raised["price"].astype(float)
is_coffee = raised["category"] == "Кофе"
raised.loc[is_coffee, "price"] = raised.loc[is_coffee, "price"] * 1.075Итоги
- Изменить часть значений:
таблица.loc[строки, "столбец"] = значение— строки задаёт метка или маска. таблица[маска]["столбец"] = …— запись в два шага: меняется временная копия, исходная таблица — нет.- Copy-on-Write: всё, что получено из таблицы, ведёт себя как независимая копия.
b = a— второе имя той же таблицы; независимая копия —a.copy().- В целый столбец нельзя записать дробное число — сначала
astype(float).