Pandas EssentialsPandas: основы

The single-machine workhorse for tabular data: Series/DataFrame, loc vs iloc, boolean filtering, groupby, merge, missing values, dtypes, read/write. Code-first. When comfortable, switch to Performance. Рабочая лошадка для табличных данных на одной машине: Series/DataFrame, loc vs iloc, булева фильтрация, groupby, merge, пропуски, dtypes, чтение/запись. Сначала код. Когда станет комфортно, переключайся на Performance.
🟢 EssentialsОсновы 🔴 PerformanceПроизводительность Scale out: PySparkМасштаб: PySpark

1. What is Pandas (and when NOT to use it)?1. Что такое Pandas (и когда его НЕ брать)?

Pandas is the standard Python library for in-memory tabular data: load, clean, reshape, aggregate. It runs on one machine and holds everything in RAM, built on NumPy arrays.

Pandas — стандартная Python-библиотека для табличных данных в памяти: загрузка, очистка, преобразование, агрегация. Работает на одной машине и держит всё в RAM, поверх массивов NumPy.

AnalogyАналогия Pandas is a spreadsheet you drive with code — fast, scriptable, repeatable. But the whole sheet must fit on your desk (RAM). PySpark is the warehouse with 100 workers for when it doesn't. Pandas — это таблица, которой управляешь кодом: быстро, скриптуемо, воспроизводимо. Но весь лист должен поместиться на твоём столе (RAM). PySpark — склад со 100 рабочими на случай, когда не помещается.
Rule of thumb (a classic interview question)Эмпирика (классический вопрос на интервью) Data fits comfortably in RAM (rough guide: < a few GB, since pandas needs several× the data size to work) → pandas. Bigger than memory / needs a cluster → PySpark, Polars, or DuckDB. Данные комфортно влезают в RAM (грубо: < нескольких GB, т.к. pandas нужно в несколько раз больше размера данных) → pandas. Больше памяти / нужен кластер → PySpark, Polars или DuckDB.

2. Series & DataFrame2. Series и DataFrame

A Series is a 1-D labeled array (one column). A DataFrame is a 2-D table — a dict of Series sharing one index (the row labels). The index is what makes pandas special: alignment happens by label, not position.

Series — одномерный помеченный массив (одна колонка). DataFrame — двумерная таблица — словарь Series с общим индексом (метки строк). Индекс — то, что делает pandas особенным: выравнивание идёт по метке, а не по позиции.

import pandas as pd

df = pd.DataFrame({
    "user_id": [1, 2, 3],
    "country": ["DE", "UK", "DE"],
    "amount": [10.0, 25.5, 7.0],
})

df.head()       # first rows
df.info()       # dtypes + memory + non-null counts
df.describe()   # summary stats of numeric cols
df.shape       # (rows, cols)
df.dtypes      # type per column
Index alignmentВыравнивание по индексу Operations between two Series align on the index first. Add two Series with different labels and you get NaN where labels don't match — surprises beginners, but it's the core feature. Операции между двумя Series сначала выравниваются по индексу. Сложи два Series с разными метками — получишь NaN там, где метки не совпали — удивляет новичков, но это ключевая фича.

3. Selecting & filtering: loc vs iloc3. Выбор и фильтрация: loc vs iloc

GoalCode
One column → Seriesdf["amount"]
Several columns → DataFramedf[["user_id","amount"]]
By label (rows, cols)df.loc[row_label, "amount"]
By position (integer)df.iloc[0, 2]
Boolean filterdf[df["amount"] > 10]
ЦельКод
Одна колонка → Seriesdf["amount"]
Несколько колонок → DataFramedf[["user_id","amount"]]
По метке (строки, колонки)df.loc[row_label, "amount"]
По позиции (целое число)df.iloc[0, 2]
Булев фильтрdf[df["amount"] > 10]
# combine conditions: & | ~  and wrap each in parentheses!
df[(df["country"] == "DE") & (df["amount"] > 5)]

# membership & query syntax
df[df["country"].isin(["DE", "UK"])]
df.query("country == 'DE' and amount > 5")
Top beginner trapsГлавные ловушки новичков 1) Use &/| (not and/or) for element-wise filters, and parenthesize each condition. 2) loc = labels, iloc = integer positions — mixing them is a frequent bug. 1) Используй &/| (не and/or) для поэлементных фильтров и заключай каждое условие в скобки. 2) loc = метки, iloc = целочисленные позиции — их путаница частый баг.

4. Transforming columns4. Преобразование колонок

# vectorized math — fast, preferred
df["amount_eur"] = df["amount"] * 1.08

# conditional column without a loop
import numpy as np
df["tier"] = np.where(df["amount"] > 20, "hi", "lo")

# map a Series through a dict
df["region"] = df["country"].map({"DE": "EU", "UK": "non-EU"})

# string & datetime accessors
df["country"].str.lower()
pd.to_datetime(df["ts"]).dt.year

# assign() for chaining (returns a new df)
df = df.assign(net=lambda d: d.amount - d.fee)
apply() is a fallback, not a defaultapply() — запасной вариант, не дефолт df.apply(func, axis=1) loops row-by-row in Python — slow. Use it only when there's no vectorized form. iterrows() is even slower; avoid it. (More in Performance.) df.apply(func, axis=1) идёт построчно в Python — медленно. Используй, только если нет векторизованной формы. iterrows() ещё медленнее; избегай. (Подробнее в Performance.)

5. groupby & merge5. groupby и merge

groupby follows split → apply → combine: split rows by key, apply an aggregation, combine into a result.

groupby работает по схеме split → apply → combine: разбей строки по ключу, примени агрегацию, собери результат.

(df.groupby("country")
   .agg(n=("user_id", "count"),
        total=("amount", "sum"),
        avg=("amount", "mean"))
   .reset_index())   # turn the group key back into a column

# transform keeps original shape (like a SQL window)
df["grp_avg"] = df.groupby("country")["amount"].transform("mean")

merge = SQL join. Know how and watch the row count after.

merge = SQL join. Знай how и следи за числом строк после.

pd.merge(orders, users, on="user_id", how="left")
# how: inner | left | right | outer ;  validate guards dup keys:
pd.merge(orders, users, on="user_id", how="left", validate="many_to_one")

# concat = stack rows (UNION) or columns
pd.concat([jan, feb], axis=0, ignore_index=True)
merge vs join vs concatmerge vs join vs concat merge joins on columns (most common). df.join joins on the index. concat stacks frames. validate= catches accidental fan-out from duplicate keys — a real interview-grade habit. merge джойнит по колонкам (чаще всего). df.join — по индексу. concat складывает фреймы. validate= ловит случайное размножение строк из-за дублей ключей — привычка уровня интервью.

6. Missing data & dtypes6. Пропуски и типы (dtypes)

df.isna().sum()                    # nulls per column
df.dropna(subset=["user_id"])      # drop rows missing a key
df["amount"].fillna(0)              # fill
df.fillna({"amount": 0, "country": "NA"})

# types: shrink memory & fix parsing
df["user_id"] = df["user_id"].astype("int32")
df["country"] = df["country"].astype("category")  # big win for repeats
NaN gotchasПодвохи NaN Classic NaN is a float, so an int column with a null becomes float64. NaN != NaN, so never compare with == — use .isna(). Modern pandas has nullable Int64/boolean dtypes that keep ints int. Классический NaN — это float, поэтому int-колонка с пропуском становится float64. NaN != NaN, поэтому никогда не сравнивай через == — используй .isna(). В современном pandas есть nullable-типы Int64/boolean, сохраняющие int.

7. Reading & writing7. Чтение и запись

df = pd.read_csv("data.csv", dtype={"user_id": "int32"},
                 parse_dates=["ts"], usecols=["user_id", "ts", "amount"])
df = pd.read_parquet("data.parquet")    # typed, compressed, fast

df.to_parquet("out.parquet", index=False)
df.to_csv("out.csv", index=False)
Read smartЧитай умно On read_csv, pass usecols (only needed columns), dtype (skip inference + save memory), and parse_dates. For big files, chunksize= streams it in pieces. Prefer Parquet when you control the format. В read_csv передавай usecols (только нужные колонки), dtype (без вывода типов + экономия памяти) и parse_dates. Для больших файлов chunksize= читает по частям. Предпочитай Parquet, когда контролируешь формат.

8. Mini-glossary8. Мини-словарь

TermPlain meaning
Series1-D labeled array (one column).
DataFrame2-D table; dict of Series sharing an index.
IndexThe row labels; drives alignment.
loc / ilocSelect by label / by integer position.
VectorizationWhole-column ops in C, no Python loop.
groupbysplit → apply → combine aggregation.
transformgroup op that keeps the original row count.
category dtypeCompact type for repeated string values.
ТерминПростой смысл
SeriesОдномерный помеченный массив (одна колонка).
DataFrameДвумерная таблица; словарь Series с общим индексом.
IndexМетки строк; управляет выравниванием.
loc / ilocВыбор по метке / по целочисленной позиции.
ВекторизацияОперации над всей колонкой в C, без Python-цикла.
groupbyАгрегация split → apply → combine.
transformГрупповая операция, сохраняющая число строк.
category dtypeКомпактный тип для повторяющихся строк.

9. Quick self-check9. Быстрая самопроверка

Answer in your head, then tap to flip.Ответь про себя, потом нажми, чтобы перевернуть.

Pandas vs PySpark — when each?Pandas vs PySpark — когда что?
tapнажми
Pandas: data fits in one machine's RAM (single-node, NumPy-backed). PySpark: bigger-than-memory / needs a cluster. Pandas needs several× the data size in RAM to operate.Pandas: данные влезают в RAM одной машины (один узел, на NumPy). PySpark: больше памяти / нужен кластер. Pandas требует в несколько раз больше размера данных в RAM.
loc vs iloc?loc vs iloc?
tapнажми
loc selects by label (index/column names); iloc by integer position. loc slices are inclusive of the end label; iloc is not.loc выбирает по метке (имена индекса/колонок); iloc по целой позиции. У loc срез включает конечную метку; у iloc — нет.
Why parenthesize filter conditions?Зачем скобки в условиях фильтра?
tapнажми
Element-wise filters use &/| (not and/or), and & binds tighter than ==, so each condition needs parentheses: (a==1)&(b>2).Поэлементные фильтры используют &/| (не and/or), и & связывает сильнее ==, поэтому каждое условие в скобках: (a==1)&(b>2).
agg vs transform in groupby?agg vs transform в groupby?
tapнажми
agg collapses each group to one row. transform returns a value per original row (broadcast back) — like a SQL window. Use transform to add a group stat as a new column.agg схлопывает каждую группу в одну строку. transform возвращает значение на каждую исходную строку — как SQL-окно. Используй transform, чтобы добавить групповую статистику новой колонкой.
Why does an int column become float?Почему int-колонка стала float?
tapнажми
A null appeared. Classic NaN is a float, so pandas upcasts the column to float64. Use nullable Int64 dtype to keep integers.Появился пропуск. Классический NaN — это float, поэтому pandas повышает тип колонки до float64. Используй nullable-тип Int64, чтобы сохранить целые.
How to check for nulls correctly?Как правильно проверять пропуски?
tapнажми
Use .isna() / .notna(), never == NaN (NaN ≠ NaN). df.isna().sum() counts nulls per column.Используй .isna() / .notna(), никогда == NaN (NaN ≠ NaN). df.isna().sum() считает пропуски по колонкам.