Нашли ошибку?
продвинутыйpandas 3.0.6

Производительность и память

Как уменьшить память таблицы и ускорить обработку — типы, векторизация, чтение только нужного.

Нужно знать:Типы данныхКатегориальный типФункции: map, apply, pipe

Содержание статьи

Коротко

  • Сколько занимает таблица — memory_usage(deep=True). Без deep у столбцов object считаются только ссылки на строки; у типа str результат одинаковый.
  • Повторяющиеся строки → category: память падает в разы.
  • Маленькие числа → меньший тип: pd.to_numeric(s, downcast="integer").
  • Главное ускорение — векторизация: операции над столбцами вместо apply(axis=1) и циклов. Разница — на порядки.
  • Читайте только нужное: read_csv(usecols=…, dtype=…); для промежуточных данных используйте Parquet.
  • Типы Apache Arrow (dtype_backend="pyarrow") есть для всех столбцов. Строки типа str при установленном pyarrow и так хранятся в Arrow.
  • eval и query — выражения строкой; на больших таблицах иногда быстрее, но главное — короче.

Синтаксис

df.memory_usage(deep=True)
df["col"].astype("category")
pd.to_numeric(s, downcast="integer")          # или "float"
pd.read_csv(path, usecols=[...], dtype={...}, dtype_backend="pyarrow")
df.convert_dtypes(dtype_backend="pyarrow")
df.eval("c = a * b"), df.query("a > 1")

Примеры

Данные для примеровиспользуются во всех примерах ниже
rng = np.random.default_rng(0)
n = 100_000
orders = pd.DataFrame({
    "city": rng.choice(["Москва", "Санкт-Петербург", "Казань", "Омск"], size=n),
    "qty": rng.integers(1, 20, size=n),
    "price": rng.uniform(100, 500, size=n).round(2),
})
Сколько памяти занимают столбцы
usage = orders.memory_usage(deep=True, index=False)   # байты по столбцам
(usage / 2**20).round(1)                               # в мегабайтах
Вывод
city     2.2
qty      0.8
price    0.8
dtype: float64
Когда нужен deep=True
text = orders["city"]                                  # тип str
obj = text.astype(object)                              # те же строки как объекты Python
print(text.memory_usage(index=False) == text.memory_usage(deep=True, index=False))
obj.memory_usage(index=False) < obj.memory_usage(deep=True, index=False)   # без deep — только ссылки
Вывод
True
True
category для повторяющихся строк
before = orders["city"].memory_usage(deep=True, index=False)
after = orders["city"].astype("category").memory_usage(deep=True, index=False)
before // after                                        # во сколько раз меньше: категории хранятся один раз
Вывод
23
Меньший целый тип
small = pd.to_numeric(orders["qty"], downcast="integer")
print(small.dtype)                                     # хватит int8: значения до 20
orders["qty"].nbytes // small.nbytes                   # 8 байт на число → 1 байт
Вывод
int8
8
Векторизация против apply
import timeit
t_apply = min(timeit.repeat(lambda: orders.apply(lambda r: r["qty"] * r["price"], axis=1), number=1, repeat=3))
t_vector = min(timeit.repeat(lambda: orders["qty"] * orders["price"], number=1, repeat=3))
print(f"apply: {t_apply * 1000:.0f} мс, столбцы: {t_vector * 1000:.2f} мс")
print(f"ускорение: ×{t_apply / t_vector:.0f}")         # разница — на порядки
Выводзамер на Apple M4 · macOS · Python 3.14, 29.09.2026
apply: 167 мс, столбцы: 0.07 мс
ускорение: ×2431

Время снято один раз и показано вместе с машиной и датой замера. На другом компьютере числа будут другими, но порядок разницы сохранится.

Читать только нужное
orders.to_csv("orders.csv", index=False)
pd.read_csv("orders.csv", usecols=["city", "qty"], dtype={"city": "category"}).dtypes
Вывод
city    category
qty        int64
dtype: object
Типы Arrow
orders.head(3).convert_dtypes(dtype_backend="pyarrow").dtypes   # типы на основе Apache Arrow
Вывод
city     string[pyarrow]
qty       int64[pyarrow]
price    double[pyarrow]
dtype: object
eval
orders.eval("revenue = qty * price").head(3)           # выражение строкой
Вывод
     city  qty   price  revenue
0    Омск    9  342.80  3085.20
1  Казань   11  185.02  2035.22
2  Казань    3  322.50   967.50

Подводные камни

Оптимизация до измерения

Сначала найдите, что действительно медленно или занимает память: memory_usage(deep=True), %timeit в Jupyter. Чаще всего проблема — apply, iterrows или цикл с concat, а не типы.

downcast и переполнение

После уменьшения типа (int8 до 127) дальнейшая арифметика может выйти за пределы и молча переполниться, как в NumPy. Уменьшайте типы для хранения, а считайте в обычных.

int8 + 100
small = pd.Series([100, 120], dtype="int8")
print(small + 100)                                     # 200 и 220 не помещаются в int8
small.astype("int64") + 100
Вывод
0   -56
1   -36
dtype: int8
0    200
1    220
dtype: int64