pandas даёт две основные структуры: Series (одна колонка с метками) и DataFrame (таблица из колонок с общим индексом). Почти все сюрпризы pandas объясняются индексом, поэтому начнём с него.
Series
Series это массив значений одного типа плюс индекс, набор меток.
import pandas as pd
s = pd.Series([120, 80, 95], index=["чай", "сок", "кофе"])
s["кофе"] # 95, доступ по метке
s.iloc[0] # 120, доступ по позиции
s.index # Index(['чай', 'сок', 'кофе'])
Метки не обязаны быть уникальными. Если метка повторяется, s.loc["x"] вернёт не число, а Series из всех совпадений.
DataFrame
DataFrame удобно представлять как словарь Series с общим индексом строк. У каждой колонки свой dtype.
df = pd.DataFrame({
"city": ["Казань", "Пермь", "Казань"],
"revenue": [100, 250, 80],
})
df.shape # (3, 2)
len(df) # 3, число строк
df["revenue"] # Series
df[["revenue"]] # DataFrame с одной колонкой
Первые шаги с новыми данными: df.shape, df.head(), df.info() (типы и непустые значения), df.describe() (статистики), df["col"].value_counts().
Выравнивание по индексу
Арифметика между Series идёт по меткам, а не по позициям:
a = pd.Series([1, 2, 3], index=["x", "y", "z"])
b = pd.Series([10, 20], index=["y", "w"])
a + b
# w NaN
# x NaN
# y 12.0
# z NaN
Результат получает объединение меток, пары нет у трёх, там NaN, и тип стал float. Если отсутствующее нужно считать нулём, используйте a.add(b, fill_value=0). Если нужна именно позиционная операция, сравните .to_numpy().
То же самое происходит при присваивании колонки: df["new"] = other_series выровняет по индексу, и строки без пары получат NaN. Это частая причина «откуда пропуски после присваивания».
Типы колонок
int64,float64,bool: обычные числовые типы NumPy.datetime64[ns],timedelta64[ns]: дата и интервал.category: повторяющиеся строки, хранятся как коды плюс словарь.object: произвольные объекты Python, медленно.- Строки: в pandas 3.0 по умолчанию строковый dtype
str, в pandas 2.x былobject. - Nullable-типы
Int64,boolean,Float64: умеют хранить пропускpd.NA, не меняя тип.
Важное следствие: обычная целочисленная колонка с пропуском превращается во float, отсюда «1.0, 2.0» в выгрузках.
pandas 3.0: Copy-on-Write
С pandas 3.0 включён Copy-on-Write. Любой объект, полученный индексацией или методом, ведёт себя как независимая копия, а реальное копирование откладывается до первой записи.
d2 = df[df["revenue"] > 90]
d2["revenue"] = 0 # меняет только d2, df не тронут
Следствие: цепочное присваивание вида df["a"][mask] = 0 больше никогда не меняет df. Менять данные нужно одним шагом через df.loc[mask, "a"] = 0. Старое предупреждение SettingWithCopyWarning ушло вместе с неоднозначностью.
Индекс: ставить или нет
set_index("user_id") полезен, когда вы часто обращаетесь по ключу, делаете join по индексу или работаете с временным рядом (DatetimeIndex даёт срезы по датам и resample). После groupby ключи сами уходят в индекс, reset_index() возвращает их в колонки, а старый индекс без имени превращается в колонку index, если не указать drop=True.
inplace
Методы с inplace=True возвращают None. Ошибка df = df.sort_values("a", inplace=True) превращает df в None. Современный стиль: без inplace, с присваиванием результата, это удобно для цепочек методов. Экономии памяти inplace обычно не даёт.
Частые ошибки
- Ожидать сложение по позициям, а получить NaN от выравнивания.
- Путать
df["a"](Series) иdf[["a"]](DataFrame). drop("col")безcolumns=: ищет строку, а не колонку.- Присваивать результат метода с
inplace=True. - Не проверить
df.dtypesпосле чтения: числа оказались строками.
Что спросят на собесе
- Чем Series отличается от DataFrame, что такое индекс?
- Что будет при сложении двух Series с разными индексами?
- Почему целая колонка с пропуском стала float, как этого избежать?
- Что изменил Copy-on-Write в pandas 3.0?
- Когда ставить
set_index, а когда держать ключ колонкой?