1. Vectorize — never loop rows1. Векторизуй — не перебирай строки
The #1 pandas performance rule, and the most common interview answer. Pandas is fast because operations run on whole columns in compiled C/NumPy. A Python-level loop throws that away.
Правило производительности №1 и самый частый ответ на интервью. Pandas быстр, потому что операции идут над целыми колонками в скомпилированном C/NumPy. Python-цикл это рушит.
| Approach | Relative speed | Use |
|---|---|---|
Vectorized (df["a"]*2, np.where) | ⚡ fastest (1×) | Always try first |
Built-in methods (.str, .dt, groupby) | ⚡ fast | String/date/group work |
.apply(axis=1) | 🐢 ~10–100× slower | Only if no vectorized form |
.iterrows() / Python for | 🐌 slowest | Avoid |
| Подход | Относит. скорость | Когда |
|---|---|---|
Векторизация (df["a"]*2, np.where) | ⚡ быстрее всего (1×) | Всегда пробуй первым |
Встроенные методы (.str, .dt, groupby) | ⚡ быстро | Строки/даты/группы |
.apply(axis=1) | 🐢 в ~10–100× медленнее | Только если нет векторной формы |
.iterrows() / Python for | 🐌 медленнее всего | Избегай |
# SLOW — Python loop per row df["net"] = df.apply(lambda r: r["amount"] - r["fee"], axis=1) # FAST — one vectorized C operation df["net"] = df["amount"] - df["fee"]
2. Memory & dtypes2. Память и типы (dtypes)
Pandas often uses far more RAM than the file on disk. Right-sizing dtypes is the cheapest big win — and a great interview talking point.
Pandas часто ест намного больше RAM, чем файл на диске. Правильный подбор dtypes — самая дешёвая большая победа и отличная тема на интервью.
- Downcast numbers:
int64→int32/int16,float64→float32when range allows. categoryfor repeated strings (country, status): stores codes once → often 10×+ smaller and faster groupby.- Avoid
objectdtype for strings where you can; it's pointers to Python objects. Usecategoryor the newerstring[pyarrow]dtype. - Measure:
df.memory_usage(deep=True)shows true per-column bytes.
- Понижай числа:
int64→int32/int16,float64→float32, если диапазон позволяет. categoryдля повторяющихся строк (country, status): хранит коды один раз → часто в 10×+ меньше и быстрее groupby.- Избегай
objectдля строк, где можно; это указатели на Python-объекты. Используйcategoryили новыйstring[pyarrow]. - Измеряй:
df.memory_usage(deep=True)показывает реальные байты по колонкам.
df["country"] = df["country"].astype("category") df["qty"] = pd.to_numeric(df["qty"], downcast="integer") df.memory_usage(deep=True).sum() / 1e6 # MB
3. Copy vs view & SettingWithCopyWarning3. Copy vs view и SettingWithCopyWarning
A near-universal interview/debug topic. Some slices return a view (shares memory), others a copy. Assigning into a chained slice may silently hit a temporary copy and not change your data — that's what the warning means.
Почти универсальная тема интервью/отладки. Одни срезы возвращают view (общая память), другие copy. Присвоение в цепочечный срез может тихо попасть во временную копию и не изменить твои данные — об этом и предупреждение.
# RISKY — chained indexing, may not write back df[df.amount > 0]["flag"] = 1 # SettingWithCopyWarning # CORRECT — single .loc call does the assignment df.loc[df.amount > 0, "flag"] = 1 # Want an independent frame? be explicit: sub = df[df.amount > 0].copy()
[] twice for assignment. Use one .loc[rows, cols] = value. If you subset then modify, add .copy(). (Pandas 3.0 / Copy-on-Write makes this stricter and clearer.)
Никогда не цепляй [] дважды для присваивания. Используй один .loc[строки, колонки] = значение. Если делаешь подвыборку и потом меняешь — добавь .copy(). (Pandas 3.0 / Copy-on-Write делает это строже и яснее.)
4. Data bigger than comfort4. Данные больше комфортного
Still want pandas but it's straining memory? Reduce what you load and process in pieces:
Всё ещё хочешь pandas, но память на пределе? Сократи объём загрузки и обрабатывай по частям:
- Load less:
usecols=,dtype=,parse_dates=at read time; filter rows ASAP. - Columnar source: Parquet reads only needed columns & is pre-typed — much lighter than CSV.
- Chunking:
read_csv(..., chunksize=N)yields frames; aggregate incrementally. - Drop intermediates: reassign /
delbig temporaries; avoid keeping every step.
- Грузи меньше:
usecols=,dtype=,parse_dates=при чтении; фильтруй строки как можно раньше. - Колоночный источник: Parquet читает только нужные колонки и уже типизирован — намного легче CSV.
- Чанкинг:
read_csv(..., chunksize=N)отдаёт фреймы; агрегируй инкрементально. - Убирай промежуточное: переприсваивай /
delбольших временных; не держи каждый шаг.
total = 0 for chunk in pd.read_csv("big.csv", chunksize=1_000_000, usecols=["amount"]): total += chunk["amount"].sum()
5. Faster joins & groupby5. Быстрее join и groupby
- Watch the row count after a merge. Duplicate keys fan out rows (a many-to-many blow-up). Use
validate="one_to_many"to assert your assumption. - Join on an index when repeating joins on the same key:
set_indexonce, thendf.joinis faster than re-merging on a column. - category keys make groupby and merges faster and lighter.
- Pick the right agg: built-in named aggregations (
("amount","sum")) beatgroupby().apply(custom). - Sort once if you do many ordered ops; avoid repeated implicit sorts.
- Следи за числом строк после merge. Дубли ключей размножают строки (взрыв many-to-many). Используй
validate="one_to_many", чтобы зафиксировать допущение. - Join по индексу, если повторяешь join по тому же ключу: один раз
set_index, потомdf.joinбыстрее повторного merge по колонке. - category-ключи делают groupby и merge быстрее и легче.
- Бери правильную агрегацию: встроенные именованные агрегации (
("amount","sum")) быстрееgroupby().apply(custom). - Сортируй один раз, если много упорядоченных операций; избегай повторных неявных сортировок.
df["key"].is_unique and use validate=.
Баг «неверные числа после join» почти всегда — дубли ключей на стороне, которую ты считал уникальной. Проверь df["key"].is_unique и используй validate=.
6. When to leave pandas6. Когда уходить из pandas
| Tool | Reach for it when |
|---|---|
| Polars | Want pandas-like single-node but much faster: Rust, multi-threaded, lazy, Arrow-native. |
| DuckDB | You'd rather write SQL over Parquet/larger-than-RAM on one machine; great with pandas. |
| PySpark / Dask | Data truly exceeds one machine; need a cluster & distributed shuffle. |
| pandas API on Spark | Keep pandas syntax but run it on Spark (pyspark.pandas). |
| Инструмент | Брать, когда |
|---|---|
| Polars | Хочешь как pandas на одном узле, но намного быстрее: Rust, многопоточность, ленивость, Arrow. |
| DuckDB | Удобнее писать SQL поверх Parquet / больше-чем-RAM на одной машине; дружит с pandas. |
| PySpark / Dask | Данные реально превышают одну машину; нужен кластер и распределённый shuffle. |
| pandas API на Spark | Сохранить синтаксис pandas, но выполнять на Spark (pyspark.pandas). |
7. Quick self-check7. Быстрая самопроверка
Answer in your head, then tap to flip.Ответь про себя, потом нажми, чтобы перевернуть.
.str/.dt/groupby methods.Они идут циклом в Python, теряя скорость скомпилированного C/NumPy (часто в 10–100× медленнее). Предпочитай векторные операции и встроенные .str/.dt/groupby.category dtype: stores each distinct value once + integer codes. Often 10×+ smaller and speeds up groupby/merge. Plus downcast numeric dtypes.Перевести в category: хранит каждое уникальное значение один раз + целые коды. Часто в 10×+ меньше и ускоряет groupby/merge. Плюс понижай числовые типы.df.loc[rows, cols] = val, or .copy() when intentionally subsetting.Ты присвоил в цепочечный срез, который может быть временной копией, поэтому запись может не примениться. Решение: один df.loc[строки, колонки] = знач или .copy() при намеренной подвыборке..is_unique and pass validate="many_to_one" to fail loudly.Дубли ключей на «справочной» стороне размножают строки (many-to-many). Проверь .is_unique и передай validate="many_to_one", чтобы падало явно.usecols+dtype to load less, switch to Parquet, or stream with chunksize and aggregate incrementally. Or move to DuckDB/Polars/PySpark.usecols+dtype чтобы грузить меньше, перейти на Parquet, или читать с chunksize и агрегировать инкрементально. Либо DuckDB/Polars/PySpark.