pd.Series

Создание

Создать pd.Series можно:

  1. Из скаляра (число, строка);

  2. Из итерируемого объекта (list, np.array и т.д.);

  3. Из словаря.

Создание pd.Series из скаляра создаёт столбец с одним элементом.

import pandas as pd
import numpy as np

s = pd.Series("abc")
s
0    abc
dtype: object

Вывод сообщает, что s — столбец из одного элемента "abc" типа object, индекс которого равен 0.

  • object — самый общий тип данных в столбце. В pd.Series типа object можно хранить данные любого типа, но этого следует по возможности избегать из соображений производительности, читабельности и удобства применений методов библиотеки pandas. Указать тип столбца можно дополнительным параметром dtype (“string” для строк).

  • при создании pd.Series скаляра, индекс единственного элемента будет равен 0. Можно явно указать индекс с помощью параметра index.

s = pd.Series("abc", dtype="string", index=[42])
s
42    abc
dtype: string

Создание столбцов из одного элемента применяется не очень часто, хотя и встречается. Более содержательный пример — создание pd.Series из списка или массива NumPy.

data = [7, 12, 42]
s = pd.Series(data)
s
0     7
1    12
2    42
dtype: int64

Создан столбец с элементами из списка data с такими индексами, какие они были в исходном списке.

У объекта pd.Series можно спросить значения аттрибутом values (возвращается np.array)

s.values
array([ 7, 12, 42], dtype=int64)

а индекс соответствующим атрибутом index

s.index
RangeIndex(start=0, stop=3, step=1)

Если индекс не указывается явно, то создаётся RangeIndex, т.е. как бы смещение от начала массива. Как и в случае скаляра, можно было бы явно указать индекс при создании столбца.

При создании pd.Series из словаря, ключи попадают в индекс столбца, а значения попадают в данные столбца.

d = {
    "a": 7,
    "b": 12,
    "c": 42
}
s = pd.Series(d)
s
a     7
b    12
c    42
dtype: int64

Индексация

Получать значение из столбца можно двумя способами:

  • по метке (используя индекс столбца);

  • по смещению.

Warning

Оператор [] по умолчанию возвращает значение по метке, но если индекс столбца не целочисленного типа, а значение между скобками — целое число, то индексирует по смещению. Т.е. есть некоторая неоднозначность в таком способе индексации, что может привести к ошибкам. Лучшей практикой считается применение методов pd.Series.loc и pd.Series.iloc, чтобы явно указать каким способом следует осуществлять индексацию.

Выражение s.loc[index] возвращает элемент(ы) столбца с индексом index, а метод s.iloc[i] возвращает i-й элемент столбца по смещению от начала (как оператор [] списков и массивов NumPy). Продемонстрируем это на примере столбца с индексом из символов.

symbols = "abcdefg"
s = pd.Series(data=range(len(symbols)), index=list(symbols))
print(s)

print(f'loc["a"]: {s.loc["b"]}, iloc[1]: {s.iloc[1]}')
a    0
b    1
c    2
d    3
e    4
f    5
g    6
dtype: int64
loc["a"]: 1, iloc[1]: 1

Т.е. до одного и того же элемента серии можно достучаться разными способами. Индексация по смещению (iloc) аналогична NumPy (в том числе и срезы). Индексация по индексу (loc) больше похожа на получение значения по ключу в словаре. Можно осуществлять срезы и по индексу, но в данном случае правая граница попадает в срез.

s["b":"f"]
b    1
c    2
d    3
e    4
f    5
dtype: int64

Warning

В одном столбце у разных элементов могут быть одинаковые метки (дубликаты). Метод loc возвратит все элементы с такой меткой, но срезы в таком случае могут сломаться из-за неоднозначности определения границ среза.

Обоими методами можно получить сразу несколько элементов pd.Series, передав в них список смещений/меток.

print(s.loc[["a", "d", "e"]])
print(s.iloc[[0, 3, 4]])
a    0
d    3
e    4
dtype: int64
a    0
d    3
e    4
dtype: int64

Индексация по смещению работает чуть быстрее, чем индексация по меткам, но последняя предоставляет более удобный функционал. Позже будет раскрыто, что в таблицах (DataFrame) эти же метки используются для индексации по строкам. Это позволяет:

  1. осуществлять быстрый поиск строк таблицы по метке. Это может значительно ускорить программы, постоянно осуществляющие такие запросы по значению какого-то конкретного столбца (или пары столбцов, pandas допускает MultiIndex), т.к. такая индексация по меткам гораздо быстрее, чем линейный (и даже бинарный) поиск по данным. Однако если в индексе много дубликатов, то эффективность такого способа индексации уменьшается;

  2. Выравнивать данные разных столбцов между собой. Это может пригодиться, если данные приходят из разных источников и упорядоченны по-разному, но все из них помечены однообразно. В таком случае можно не затрудняясь выравнять эти данные между собой средствами pandas.

Изменяемость pd.Series

Аналогично с массивами NumPy, можно изменять содержимое ячейки/среза, но нельзя изменять размер (длину) столбца.

Например, изменить содержимое ячейки по метке a можно сделать следующим образом.

s.loc['a'] = 42
s
a    42
b     1
c     2
d     3
e     4
f     5
g     6
dtype: int64

С изменением размера следует быть аккуратным. Следующая операция как бы увеличит размер столбца s. Но pandas перевыделяет память, копирует данные старого столбца и дозаписывает новое значение при каждом добавлении нового значения в столбец или новой строки в таблицу. Т.е. использовать объекты pandas для накопления строк по одной крайне неэффективно. Если есть такая необходимость, то обычно данные накапливают в контейнерах python (список, словарь) и трансформируют в pandas объект в самом конце или по накоплении некого блока информации. Альтернативой служит создание большой таблицы/столбца и работа только с первыми n строками, увеличивая n при необходимости.

s.loc['z'] = 42
s
a    42
b     1
c     2
d     3
e     4
f     5
g     6
z    42
dtype: int64

Операции над pd.Series

При выполнении операций над столбцами метки играют важную роль. Например, при сложении двух столбцов складываются значения с одинаковыми метками. Индекс результирующего столбца — объединение индексов столбцов слагаемых, а напротив тех меток, которые присутствуют в индексе только одного столбца записывается значение np.nan.

s1 = pd.Series([1, 2, 3], index=["a", "b", "c"], dtype="int")
s2 = pd.Series([10, 20, 30], index=["c", "b", "d"], dtype="int")
s = s1 + s2
s
a     NaN
b    22.0
c    13.0
d     NaN
dtype: float64

Рисунок ниже иллюстрирует, что произошло.

../_images/addition.png

Также можно заметить, что данные результирующего столбца имеют тип float64, несмотря на то, что исходные столбцы целочисленного типа. Это объясняется тем, что появившееся значения np.nan имеют такой тип.

Большинство методов массивов NumPy переопределены в pandas для pd.Series таким образом, чтобы обрабатывать пропущенные значения, которые моделируются с помощью np.nan.

s.mean()
17.5

Все логические операции работают аналогично NumPy (сравнение NaN с чем угодно всегда False) и также допускается логическая индексация (в случае булевой маски mask, .loc[mask] и .iloc[mask] делают одно и тоже, поэтому тут оправданно применение простых квадратных скобок []).

s[s > 10]
b    22.0
c    13.0
dtype: float64