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

Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.

Зачем это знать

Pandas — де-факто стандарт для data analysis в Python. Но на 10M+ rows легко напороться на медленные операции, OOM. Знать, как оптимизировать — разница между 30-секундным и 10-минутным скриптом.

На собесах middle+ часто: «как бы вы ускорили этот pandas-код?».

Топ приёмов

1. Vectorize (избегать for loops)

Плохо:

result = []
for i in range(len(df)):
    result.append(df['a'][i] + df['b'][i])

Хорошо:

df['sum'] = df['a'] + df['b']

Векторизованные операции — C-optimized, в 100x быстрее.

2. apply vs map vs vectorize

Skip lambdas для простых операций:

# Slow
df['upper'] = df['name'].apply(lambda x: x.upper())

# Fast (vectorized string methods)
df['upper'] = df['name'].str.upper()

3. Categorical dtype

Для strings с low cardinality:

df['country'] = df['country'].astype('category')

Memory ↓ 10x, operations быстрее.

4. Правильный dtype

# Default int64 → 8 bytes
# Часто достаточно int32 или int16
df['age'] = df['age'].astype('int16')

5. Избегать chained indexing

# Плохо (warning, может не сработать)
df['col'][0] = 5

# Хорошо
df.loc[0, 'col'] = 5

6. iloc vs loc

  • iloc — positional (integer)
  • loc — label-based

Для performance — iloc обычно быстрее.

7. Query vs boolean indexing

# Боlee читаемый + часто быстрее на big data
df.query('age > 30 and income > 50000')

# vs
df[(df['age'] > 30) & (df['income'] > 50000)]

8. In-place operations

# Не in-place (создаёт copy)
df = df.fillna(0)

# In-place (быстрее, меньше memory)
df.fillna(0, inplace=True)

Но осторожно: inplace deprecated, использование меняется.

9. Read только нужные columns

df = pd.read_csv('big.csv', usecols=['col1', 'col2'])

Не загружать всё.

10. Chunking для huge files

for chunk in pd.read_csv('huge.csv', chunksize=100000):
    process(chunk)

Memory optimization

Check

df.memory_usage(deep=True).sum() / 1024**2  # MB

Downcast

df = df.apply(pd.to_numeric, errors='ignore', downcast='integer')

Auto-detect minimal dtype.

Object to category

for col in df.select_dtypes(include='object'):
    if df[col].nunique() / len(df) < 0.5:
        df[col] = df[col].astype('category')

Альтернативы

polars

Rust-based, faster на large data:

import polars as pl
df = pl.read_csv('file.csv')
df = df.filter(pl.col('x') > 100).group_by('y').sum()

10-100x быстрее pandas на many operations.

DuckDB

In-process SQL database:

import duckdb
result = duckdb.sql("SELECT * FROM 'file.parquet' WHERE x > 100").df()

Fast, SQL syntax, lazy.

Dask

Pandas API, distributed:

import dask.dataframe as dd
df = dd.read_csv('*.csv')
df.groupby('col').sum().compute()

Для datasets > memory.

Spark

Для huge data + cluster.

Profiling

%timeit

%timeit df['col'].sum()

Measure single operation.

%prun

%prun slow_function()

Function-level profiling.

Memory profiler

from memory_profiler import profile

@profile
def func():
    ...

Specific tips

merge vs join

# Merge for complex
df.merge(other, on='key')

# Join for index-based
df.join(other)  # on index

Index matters

Set index для частых lookups:

df.set_index('user_id', inplace=True)
df.loc[user_id]  # O(1)

groupby + agg

# Slow
df.groupby('col').apply(custom_function)

# Fast
df.groupby('col').agg({'a': 'sum', 'b': 'mean'})

Parallel

modin

Drop-in pandas replacement:

import modin.pandas as pd  # вместо pandas

Auto-parallelizes.

swifter

Auto-detect vectorization:

df['new'] = df['col'].swifter.apply(my_func)

На собесе

«Pandas медленно. Как ускорить?»

  1. Vectorize (no loops)
  2. Правильный dtype (category, downcast)
  3. Read только нужные columns
  4. Profile для identifying bottleneck

«10M rows — pandas ok?»

В memory — да. Но polars / DuckDB обычно быстрее.

«100M+ rows?»

Уже не pandas. Spark, DuckDB, warehouse.

Частые ошибки

Loop over rows

Always slow. Use vectorization or .apply (better still — no).

Object dtype

String columns — explicit str or category.

Inplace mix

Use consistent style. Modern — assignment: df = df.method().

Связанные темы

FAQ

Pandas или polars для новых?

Polars faster, pandas standard. Learn both.

Parallel pandas — modin worth?

Для simple replacement yes. Для complex — Spark / polars.

Typing autodetect?

df.infer_objects() или downcast='integer'.


Тренируйте Python — откройте тренажёр с 1500+ вопросами для собесов.