Series в pandas — это одномерный массив значений, у каждого из которых есть метка (индекс), причем у всей Series один тип данных (dtype). Исключение по смыслу — тип object: формально он один, но внутри могут лежать разнородные объекты Python. Столбец таблицы DataFrame — это тоже Series: отсюда и связь двух главных структур pandas. Ниже — как создать Series, как обращаться к элементам по меткам и по позициям, почему сложение двух Series иногда дает NaN и какие методы нужны чаще всего.
Содержание
- Четыре части любой Series
- Минимальный пример
- Как создать Series
- Индекс: loc, iloc и квадратные скобки
- Векторные операции и выравнивание по индексу
- Пропуски: NaN, isna и fillna
- value_counts, map и apply
- Series и DataFrame: столбец и строка
- Типичные ошибки
- Выводы
- Где применяется / связь с практикой
- FAQ
Примеры проверены 24.09.2026 на pandas 3.0.6 и Python 3.14.7. В pandas 2.x часть поведения отличается — такие места отмечены.
Четыре части любой Series
Чтобы дальше не путаться, разведем термины:
| Часть | Что это | Как получить |
|---|---|---|
| значения (values) | сами данные, внутри обычно массив NumPy | s.to_numpy() |
| индекс (index) | метки элементов: числа, строки, даты | s.index |
| тип (dtype) | один тип на всю Series: int64, float64, str, Int64 |
s.dtype |
| имя (name) | подпись; у столбца DataFrame совпадает с его названием | s.name |
Ключевое отличие от списка Python: индекс — это метки, а не номера позиций. По умолчанию метки совпадают с позициями 0, 1, 2, но после фильтрации, сортировки или создания из словаря они расходятся.
Минимальный пример
Установка: pip install pandas (NumPy подтянется как зависимость). Pandas 3 требует Python 3.11 или новее: на Python 3.10 и старше та же команда молча поставит pandas 2.x, и часть примеров ниже поведет себя иначе. Проверить версию можно так: print(pd.__version__).
import pandas as pd
sales = pd.Series([120, 95, 140], index=["пн", "вт", "ср"], name="продажи")
print(sales)
print(sales["вт"])
print(sales.mean())
пн 120
вт 95
ср 140
Name: продажи, dtype: int64
95
118.33333333333333
Слева печатается индекс, справа значения, внизу имя и тип. Обращение sales["вт"] идет по метке, а mean() считает среднее сразу по всем значениям, без цикла.
Как создать Series
import pandas as pd
a = pd.Series([10, 20, 30]) # из списка
b = pd.Series({"Москва": 13.1, "Казань": 1.3}) # из словаря: ключи -> индекс
c = pd.Series(0, index=["x", "y", "z"]) # скаляр размножается по индексу
d = pd.Series(["Python", "SQL", None]) # строки
e = pd.Series([1, 2, None]) # целые с пропуском
f = pd.Series([1, 2, None], dtype="Int64") # целые с пропуском, nullable-тип
for s in (a, b, c, d, e, f):
print(s.dtype, list(s.index))
int64 [0, 1, 2]
float64 ['Москва', 'Казань']
int64 ['x', 'y', 'z']
str [0, 1, 2]
float64 [0, 1, 2]
Int64 [0, 1, 2]
На что смотреть:
- Строки в pandas 3 по умолчанию получают тип
str(отдельный строковый dtype). В pandas 2.x та же Series была быobject. - Один пропуск превращает целые в
float64: обычныйint64не умеет хранить NaN. Если нужны именно целые с пропусками, укажитеdtype="Int64"(с большой буквы) — пропуск тогда печатается как<NA>.
Индекс: loc, iloc и квадратные скобки
У Series два способа адресации, и их важно не смешивать:
| Способ | Ищет по | Срез a:b |
Пример |
|---|---|---|---|
s.loc[...] |
метке индекса | включает правую границу | s.loc[10:30] |
s.iloc[...] |
позиции (0, 1, 2…) | не включает правую границу, как в списке | s.iloc[0:3] |
s[...] |
в pandas 3 — по метке для одиночного ключа | зависит от типа индекса | лучше явно loc/iloc |
import pandas as pd
s = pd.Series([100, 200, 300, 400], index=[10, 20, 30, 40])
print(s.loc[20]) # 200 - метка 20
print(s.iloc[1]) # 200 - вторая позиция
print(s.loc[10:30].tolist()) # [100, 200, 300] - метка 30 включена
print(s.iloc[0:3].tolist()) # [100, 200, 300] - позиция 3 не включена
print(s[0]) # KeyError: 0 - метки 0 нет
Последняя строка падает с KeyError: 0: квадратные скобки с одним числом ищут метку, а не позицию. При целочисленном индексе так было и в pandas 2.x. А вот для Series со строковым индексом (t[0] при индексе a, b, c) поведение поменялось: в pandas 2.x такой вызов еще срабатывал как позиционный с предупреждением FutureWarning, в 3.0 этот откат убран, и тоже получается KeyError: 0. Исправление — писать s.iloc[0], если нужен первый элемент.
Фильтрация условием тоже идет через индексацию: условие дает булеву Series той же длины.
print(s[s > 150].tolist()) # [200, 300, 400]
print(s.loc[(s > 150) & (s < 400)].tolist()) # [200, 300]
Условия соединяются через & и |, каждое в скобках. Слова and/or здесь не работают — об этом ниже, в разделе ошибок.
Векторные операции и выравнивание по индексу
Арифметика над Series выполняется сразу для всех элементов: s * 2, s + 10, s / s.sum(). Но при операции с двумя Series pandas сопоставляет элементы по меткам, а не по позициям. Это главная особенность, которая удивляет новичков.
import pandas as pd
jan = pd.Series({"Анна": 5, "Борис": 3, "Вера": 7})
feb = pd.Series({"Борис": 4, "Вера": 2, "Глеб": 6})
print(jan + feb)
print(jan.add(feb, fill_value=0))
Анна NaN
Борис 7.0
Вера 9.0
Глеб NaN
dtype: float64
Анна 5.0
Борис 7.0
Вера 9.0
Глеб 6.0
dtype: float64
Индекс результата — объединение меток обеих Series. Где метка есть только с одной стороны, получается NaN, а тип из-за этого становится float64. Метод add(..., fill_value=0) считает отсутствующее значение нулем. Аналогично работают sub, mul, div.
Обратная ситуация: данные идут в одном порядке, но индексы перепутаны, и нужно сложить именно по позициям.
x = pd.Series([1, 2, 3], index=["a", "b", "c"])
y = pd.Series([10, 20, 30], index=["c", "b", "a"])
print((x + y).tolist()) # [31, 22, 13] - сложено по меткам
print(x.to_numpy() + y.to_numpy()) # [11 22 33] - сложено по позициям
Какой вариант верный, зависит от смысла данных. Если метки — это ключи (ФИО, id товара), выравнивание защищает от ошибки сдвига. Если метки случайные (после фильтрации), сначала выровняйте индекс через reset_index(drop=True) или работайте с массивом to_numpy().
Пропуски: NaN, isna и fillna
import pandas as pd
e = pd.Series([1.5, None, 3.0])
print(len(e), e.count(), e.isna().sum()) # 3 2 1
print(e.fillna(0).tolist()) # [1.5, 0.0, 3.0]
len считает все элементы, count() — только непустые. Статистика вроде mean() и sum() по умолчанию пропуски пропускает. Сравнение s == None для поиска пропусков не подходит — используйте isna().
value_counts, map и apply
import pandas as pd
langs = pd.Series(["python", "sql", "python", "java", "python", "sql"])
print(langs.value_counts())
print(langs.map({"python": "Python", "sql": "SQL"}))
print(langs.str.len().tolist())
print(langs.apply(lambda w: w.upper() if len(w) > 3 else w).tolist())
python 3
sql 2
java 1
Name: count, dtype: int64
0 Python
1 SQL
2 Python
3 NaN
4 Python
5 SQL
dtype: str
[6, 3, 6, 4, 6, 3]
['PYTHON', 'sql', 'PYTHON', 'JAVA', 'PYTHON', 'sql']
value_counts()— частоты значений по убыванию; сnormalize=True— доли. Результат сам является Series: значения стали индексом.map(словарь)— замена по справочнику. Значение, которого нет в словаре, становится NaN (здесьjava), и это легко пропустить..str— векторные строковые методы (len,lower,contains), они быстрее и корочеapply.apply(функция)— произвольная функция для каждого элемента. Она удобна, но выполняется циклом на Python, поэтому для арифметики и строк сначала ищите встроенный векторный метод.
Series и DataFrame: столбец и строка
DataFrame можно представить как набор Series с общим индексом строк. Выбор одного столбца возвращает Series, выбор строки через loc — тоже Series, но уже с названиями столбцов в индексе.
import pandas as pd
df = pd.DataFrame({"курс": ["Python", "SQL", "Java"], "цена": [100, 80, 120]})
price = df["цена"]
print(type(price).__name__, price.name) # Series цена
print(df.loc[0])
cols = ["курс", "цена"]
print(type(df.loc[:, cols]).__name__) # DataFrame
курс Python
цена 100
Name: 0, dtype: object
Строка смешивает текст и число, поэтому ее тип — object. Выбор списка столбцов возвращает уже DataFrame. Подробно про таблицы — фильтрацию, группировку, объединение — в статье о работе с DataFrame.
Copy-on-Write: изменения через Series в pandas 3
С pandas 3.0 по умолчанию включен режим Copy-on-Write: любой результат индексации ведет себя как отдельная копия. Для Series это значит следующее.
price = df["цена"]
price.iloc[0] = 999
print(df["цена"].tolist(), price.tolist()) # [100, 80, 120] [999, 80, 120]
df["цена"][0] = 999 # цепочка присваиваний
print(df["цена"].tolist()) # [100, 80, 120] + ChainedAssignmentError
df.loc[0, "цена"] = 999 # одно действие
print(df["цена"].tolist()) # [999, 80, 120]
Изменение извлеченной Series не трогает DataFrame, а цепочка df["цена"][0] = ... не работает и выдает предупреждение ChainedAssignmentError. В pandas 2.x без включенного Copy-on-Write первые два варианта могли изменить исходную таблицу, отсюда много старых примеров в интернете, которые в 3.0 молча ничего не меняют. Правильный способ — присваивание одним шагом через df.loc[строка, столбец].
Типичные ошибки
| Симптом | Причина | Что делать |
|---|---|---|
KeyError: 0 при s[0] |
скобки ищут метку, а не позицию | s.iloc[0] |
| после сложения появились NaN | индексы двух Series не совпали | add(..., fill_value=0) или reset_index(drop=True) |
целые стали 1.0, 2.0 |
в данных есть пропуск | dtype="Int64" или fillna до приведения |
ValueError: The truth value of a Series is ambiguous |
if s: или and/or с Series |
s.empty, s.any(), s.all(); в условиях — & и побитовое ИЛИ |
| изменение не попало в таблицу | Copy-on-Write в pandas 3 | df.loc[...] = ... одним шагом |
Про ошибку truth value is ambiguous: в pandas 3.0.6 ее текст по-прежнему предлагает a.bool(), но метод Series.bool() в 3.0 удален. Опирайтесь на empty, any(), all() или item().
Выводы
- Series — одномерные данные с индексом-метками и одним dtype; столбец DataFrame — это Series.
locработает по меткам и включает правую границу среза,iloc— по позициям; в pandas 3s[0]ищет метку.- Операции над двумя Series выравниваются по индексу: несовпавшие метки дают NaN,
fill_valueэто исправляет. - Для частот, замен и преобразований —
value_counts,map,.str;applyоставлять для нестандартной логики. - В pandas 3 действует Copy-on-Write: менять таблицу через
df.loc, а не через извлеченную Series.
Где применяется / связь с практикой
Series встречается в любой задаче анализа данных: столбец выручки, частоты категорий, результат groupby по одному показателю, временной ряд с датами в индексе. Понимание выравнивания по индексу и Copy-on-Write избавляет от тихих ошибок в отчетах, когда код отрабатывает без исключений, но считает не то.
Освойте тему на практике
Системно пройти pandas, NumPy, визуализацию и подготовку данных для аналитики можно на курсе «Python для аналитики». Разобрать отдельные темы с преподавателями вживую можно на открытых уроках Otus.
FAQ
Чем Series отличается от списка Python и от массива NumPy?
У списка нет меток и может быть смесь типов; у массива NumPy есть единый тип, но нет индекса-меток. Series добавляет к массиву индекс, имя и выравнивание по меткам при операциях.
Как превратить Series в DataFrame и обратно?
s.to_frame() дает таблицу из одного столбца с именем Series (или 0, если имени нет), а df["столбец"] возвращает Series обратно. Несколько Series с общим индексом объединяет pd.concat(..., axis=1).
Как узнать, есть ли значение среди элементов Series?
Оператор in проверяет индекс, а не значения: "вт" in sales вернет True по метке. Для значений нужно s.isin([95]).any() или 95 in s.to_numpy().



