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

Изменить значения

Записать значение в ячейку и в строки по условию через loc; почему запись в два шага df[маска][столбец] = … ничего не меняет; правило Copy-on-Write и явная копия copy().

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

Новый столбец меняет всю таблицу сразу. Но часто нужно поправить только часть значений: исправить опечатку в одной ячейке, поднять цены одной категории, пометить дорогие товары. Для этого есть один надёжный способ — 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:

[ ]Весь чай — по 400 ₽
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 ₽:

[ ]Сладости дорожают на 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.

[ ]Упражнение 1Исправить опечатку решено

В справочнике ошибка: у товара в строке с меткой 12 (зефир) цена должна быть 250, а себестоимость — 120. Исправьте оба значения в таблице fixed.

fixed = pd.read_csv("data/products.csv")
# исправьте цену и себестоимость в строке с меткой 12

Так же меняют текст. Отметим дорогие товары отдельной категорией:

[ ]Товары дороже 2000 ₽ — в «Премиум»
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"] = 0
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
ChainedAssignmentError: 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 запись не доходит.

[ ]Упражнение 2Чай дорожает решено

В таблице sale поднимите цену всех товаров категории «Чай» на 30 ₽. Остальные цены не меняйте.

sale = pd.read_csv("data/products.csv")
# поднимите цену всего чая на 30 ₽

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

Что выведет этот код?

Второе имя — не копия

Один случай выглядит как исключение, но он не про 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
[ ]Упражнение 3Черновик решено

Сохраните в draft независимую копию таблицы original и обнулите в draft весь столбец price. Таблица original должна остаться прежней.

original = pd.read_csv("data/products.csv")
draft = ...
# обнулите столбец price в draft

Дробное число в целый столбец

У столбца один тип. В столбец целых чисел (int64) pandas 3 откажется записать дробное:

[ ]699.9 в столбец целыхошибка: TypeError
fresh.loc[1, "price"] = 699.9
Ошибкасохранённый · нажмите «Выполнить», чтобы получить свой
TypeError: 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 в первой строке — след примера с двумя именами выше.)

[ ]Упражнение 4Кофе дорожает на 7.5 % решено

В таблице raised поднимите цену всех товаров категории «Кофе» на 7.5 %: новая цена — прежняя, умноженная на 1.075. Остальные цены не меняйте.

raised = pd.read_csv("data/products.csv")
# поднимите цену кофе на 7.5 %

Итоги

  • Изменить часть значений: таблица.loc[строки, "столбец"] = значение — строки задаёт метка или маска.
  • таблица[маска]["столбец"] = … — запись в два шага: меняется временная копия, исходная таблица — нет.
  • Copy-on-Write: всё, что получено из таблицы, ведёт себя как независимая копия.
  • b = a — второе имя той же таблицы; независимая копия — a.copy().
  • В целый столбец нельзя записать дробное число — сначала astype(float).

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

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