Структуры данных в pandas

Чтобы начать работать с pandas, рассмотрим две основные структуры: Series и DataFrame. Они не являются универсальными решениями любых задач, однако эти структуры предоставляют прочный легкий в использовании фундамент для большинства приложений.

Класс Series

Series (ряд) — объект, типа одномерного массива, содержащий последовательность значений (типов, аналогичных типам NumPy) и связанный с ним массив меток данных, называемых индексами. Создадим простейший объект типа Series только из массива данных:

In [2]: obj = pd.Series([4, 7, -5, 3])

In [3]: obj
Out[3]: 
0    4
1    7
2   -5
3    3
dtype: int64

Строковое представление объекта Series в интерактивном режиме отображает индексы слева, а данные справа. Так как мы не определили индексы, то по умолчанию индексы содержат целые числа от 0 до N-1 (где N — длина массива данных). Можно получить представление в виде массива и индексы ряда с помощью атрибутов values и index:

In [4]: obj.values
Out[4]: array([ 4,  7, -5,  3])

In [5]: obj.index     # как range(4)
Out[5]: RangeIndex(start=0, stop=4, step=1)

Часто желательно создать ряд с индексами, идентифицирующими каждую точку данный с меткой:

In [6]: obj2 = pd.Series([4, 7, -5, 3], index=['d', 'b', 'a', 'c'])

In [7]: obj2
Out[7]: 
d    4
b    7
a   -5
c    3
dtype: int64

In [8]: obj2.index
Out[8]: Index(['d', 'b', 'a', 'c'], dtype='object')

В отличие от массивов NumPy, можно использовать метки при индексации при выборе отдельных значений или набора значений:

In [9]: obj2['a']
Out[9]: -5

In [10]: obj2['d'] = 6

In [11]: obj2[['c', 'a', 'd']]
Out[11]: 
c    3
a   -5
d    6
dtype: int64

Использование функций NumPy или операций подобных NumPy, таких как фильтрация с помощью булевых массивов, умножение на скаляр или вычисление математических функций, сохраняет значения индексов:

In [12]: obj2[obj2 > 0]
Out[12]: 
d    6
b    7
c    3
dtype: int64

In [13]: obj2 * 2
Out[13]: 
d    12
b    14
a   -10
c     6
dtype: int64

In [14]: import numpy as np

In [15]: np.exp(obj2)
Out[15]: 
d     403.428793
b    1096.633158
a       0.006738
c      20.085537
dtype: float64

Ряды можно рассматривать как словари фиксированной длины:

In [16]: 'b' in obj2
Out[16]: True

In [17]: 'e' in obj2
Out[17]: False

Если имеются данные, содержащиеся в словаре, можно создать ряд из него:

In [18]: sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}

In [19]: obj3 = pd.Series(sdata)

In [20]: obj3
Out[20]: 
Ohio      35000
Texas     71000
Oregon    16000
Utah       5000
dtype: int64

Если передается только словарь, то индексами ряда будут ключи словаря в том порядке, в котором были при создании словаря. Можно изменить порядок индекса передавая ключи словаря в порядке, который нужен:

In [21]: states = ['California', 'Ohio', 'Oregon', 'Texas']

In [22]: obj4 = pd.Series(sdata, index=states)

In [23]: obj4
Out[23]: 
California        NaN
Ohio          35000.0
Oregon        16000.0
Texas         71000.0
dtype: float64

Здесь три значения, найденные в sdata, были размещены в соответствующих местах, но так как не было найдено значение для 'California', оно отображается как NaN (не число), которое в pandas используется для обозначение пропущенных значений или значений «NA» (not available). Поскольку 'Юта' не была включена в states, этот элемент исключается из результирующего объекта. Функции isnull и notnull в pandas используются для обнаружения отсутствующих данных:

In [24]: pd.isnull(obj4)
Out[24]: 
California     True
Ohio          False
Oregon        False
Texas         False
dtype: bool

In [25]: pd.notnull(obj4)
Out[25]: 
California    False
Ohio           True
Oregon         True
Texas          True
dtype: bool

Класс Series также имеет эти методы:

In [26]: obj4.isnull()
Out[26]: 
California     True
Ohio          False
Oregon        False
Texas         False
dtype: bool

Полезное свойство Series заключается в том, что она автоматически происходит выравнивание по индексам в арифметических операциях:

In [27]: obj3
Out[27]: 
Ohio      35000
Texas     71000
Oregon    16000
Utah       5000
dtype: int64

In [28]: obj4
Out[28]: 
California        NaN
Ohio          35000.0
Oregon        16000.0
Texas         71000.0
dtype: float64

In [29]: obj3 + obj4
Out[29]: 
California         NaN
Ohio           70000.0
Oregon         32000.0
Texas         142000.0
Utah               NaN
dtype: float64

Как объекты Series, так и из индексы имеют атрибут name:

In [30]: obj4.name = 'population'

In [31]: obj4.index.name = 'state'

In [32]: obj4
Out[32]: 
state
California        NaN
Ohio          35000.0
Oregon        16000.0
Texas         71000.0
Name: population, dtype: float64

Можно изменять индексы рядов присваиванием:

In [33]: obj
Out[33]: 
0    4
1    7
2   -5
3    3
dtype: int64

In [34]: obj.index = ['Bob', 'Steve', 'Jeff', 'Ryan']

In [35]: obj
Out[35]: 
Bob      4
Steve    7
Jeff    -5
Ryan     3
dtype: int64

Класс DataFrame

DataFrame представляет собой прямоугольную таблицу данных и содержит упорядоченную коллекцию столбцов, каждый из которых может иметь различный тип значения (числовой, строковый, логический и т.д.). DataFrame имеет индексы столбцов и строк.

Есть много способов создания объекта DataFrame, хотя один из наиболее распространенных — это использование списков, словарей или массивов NumPy:

In [36]: data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'], 
    ...: 'year': [2000, 2001, 2002, 2001, 2002, 2003], 
    ...: 'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}

In [37]: frame = pd.DataFrame(data)

Полученный в результате DataFrame получит автоматически индексацию для строк (как в Series), а индексом столбцов будут ключи словаря:

In [38]: frame                                                                                                                                                                                                                                 
Out[38]: 
    state  year  pop
0    Ohio  2000  1.5
1    Ohio  2001  1.7
2    Ohio  2002  3.6
3  Nevada  2001  2.4
4  Nevada  2002  2.9
5  Nevada  2003  3.2

Если используется блокнот Jupyter, объекты DataFrame будут отображаться в виде более удобной для просмотра HTML-таблицы.

Для больших DataFrames метод head выбирает только первые пять строк:

In [39]: frame.head()
Out[39]: 
    state  year  pop
0    Ohio  2000  1.5
1    Ohio  2001  1.7
2    Ohio  2002  3.6
3  Nevada  2001  2.4
4  Nevada  2002  2.9

Можно задавать другой порядок столбцов:

In [40]: pd.DataFrame(data, columns=['year', 'state', 'pop'])
Out[40]: 
   year   state  pop
0  2000    Ohio  1.5
1  2001    Ohio  1.7
2  2002    Ohio  3.6
3  2001  Nevada  2.4
4  2002  Nevada  2.9
5  2003  Nevada  3.2

Если передать столбец, который не содержится в словаре, то в результате будет столбец с отсутствующими значениями:

In [41]: frame2 = pd.DataFrame(data, columns=['year', 'state', 'pop', 'debt'], 
    ...: index=['one', 'two', 'three', 'four', 
    ...: 'five', 'six'])

In [42]: frame2
Out[42]: 
       year   state  pop debt
one    2000    Ohio  1.5  NaN
two    2001    Ohio  1.7  NaN
three  2002    Ohio  3.6  NaN
four   2001  Nevada  2.4  NaN
five   2002  Nevada  2.9  NaN
six    2003  Nevada  3.2  NaN

In [43]: frame2.columns
Out[43]: Index(['year', 'state', 'pop', 'debt'], dtype='object')

К столбцу DataFrame можно получить доступ как к ряду с помощью нотацией подобной словарю или через атрибут:

In [44]: frame2['state']
Out[44]: 
one        Ohio
two        Ohio
three      Ohio
four     Nevada
five     Nevada
six      Nevada
Name: state, dtype: object

In [45]: frame2.year
Out[45]: 
one      2000
two      2001
three    2002
four     2001
five     2002
six      2003
Name: year, dtype: int64

Замечание

IPython предоставлет доступ по атрибуту (например, frame2.year) по автодополнению с помощью клавиши .

Вариант frame2[column] работает для любых имен столбцов, в то время как frame2.column работает только если имя столбца является допустимым в Python именем переменной.

К строкам можно получить доступ по позиции или с помощью специального атрибута loc:

In [46]: frame2.loc['three']
Out[46]: 
year     2002
state    Ohio
pop       3.6
debt      NaN
Name: three, dtype: object

Можно менять значения столбцов. Например, пустой столбцу debt можно присвоить скалярное значение или массив:

In [47]: frame2['debt'] = 16.5

In [48]: frame2
Out[48]: 
       year   state  pop  debt
one    2000    Ohio  1.5  16.5
two    2001    Ohio  1.7  16.5
three  2002    Ohio  3.6  16.5
four   2001  Nevada  2.4  16.5
five   2002  Nevada  2.9  16.5
six    2003  Nevada  3.2  16.5

In [49]: frame2['debt'] = np.arange(6.)

In [50]: frame2
Out[50]: 
       year   state  pop  debt
one    2000    Ohio  1.5   0.0
two    2001    Ohio  1.7   1.0
three  2002    Ohio  3.6   2.0
four   2001  Nevada  2.4   3.0
five   2002  Nevada  2.9   4.0
six    2003  Nevada  3.2   5.0

При присваивании столбцу списка или массива, их длина должна быть той же, что и длина DataFrame. Если присваивать объект Series, то его метки будут выровнены по индексу DataFrame, при этом будут вставляться отсутствующие значения для любых «дыр»:

In [51]: val = pd.Series([-1.2, -1.5, -1.7], index=['two', 'four', 'five'])

In [52]: frame2['debt'] = val

In [53]: frame2
Out[53]: 
       year   state  pop  debt
one    2000    Ohio  1.5   NaN
two    2001    Ohio  1.7  -1.2
three  2002    Ohio  3.6   NaN
four   2001  Nevada  2.4  -1.5
five   2002  Nevada  2.9  -1.7
six    2003  Nevada  3.2   NaN

При присваивании отсутствующего столбца в объекте DataFrame добавится новый столбец. Ключевое слово del удаляет столбец, как и для словарей:

In [54]: frame2['eastern'] = frame2.state == 'Ohio'

In [55]: frame2
Out[55]: 
       year   state  pop  debt  eastern
one    2000    Ohio  1.5   NaN     True
two    2001    Ohio  1.7  -1.2     True
three  2002    Ohio  3.6   NaN     True
four   2001  Nevada  2.4  -1.5    False
five   2002  Nevada  2.9  -1.7    False
six    2003  Nevada  3.2   NaN    False

Предупреждение

Новый столбец не может быть добавлен с помощью синтаксиса frame2.eastern.

In [56]: del frame2['eastern']

In [57]: frame2.columns
Out[57]: Index(['year', 'state', 'pop', 'debt'], dtype='object')

Предупреждение

Столбец, возвращаемый при индексации DataFrame, является представлением данных, а не копией. Таким образом, любые изменения в объекте Series будут отражены в объекте DataFrame. Столбец можно явно скопировать с помощью метода Series.copy.

Другой распространенной формой представления данных является вложенный словарь словарей:

In [58]: 

Если вложенный словарь передать в конструктор DataFrame, pandas интерпретирует ключи внешнего словаря как столбцы, а внутренние ключи — как индексы:

In [59]: frame3 = pd.DataFrame(pop)

In [60]: frame3
Out[60]: 
      Nevada  Ohio
2001     2.4   1.7
2002     2.9   3.6
2000     NaN   1.5

Можно транспонировать DataFrame:

In [61]: frame3.T

Можно задать порядок индексов:

In [62]: pd.DataFrame(pop, index=[2001, 2002, 2003])
Out[62]: 
      Nevada  Ohio
2001     2.4   1.7
2002     2.9   3.6
2003     NaN   NaN

Словари рядов обрабатываются практически также:

In [63]: pdata = {'Ohio': frame3['Ohio'][:-1], 'Nevada': frame3['Nevada'][:2]}

In [64]: pd.DataFrame(pdata)
Out[64]: 
      Nevada   Ohio
2000   NaN     1.5
2001   2.4     1.7

Полный список параметров, которые можно передавать в конструктор DataFrame, можно найти в таблице pandas:data-struct:tbl:1.

Если для индекса и столбцов DataFrame установлены атрибуты name, они также будут отображены:

In [65]: frame3.index.name = 'year'; frame3.columns.name = 'state'

In [66]: frame3
Out[66]: 
state  Nevada  Ohio
year               
2001      2.4   1.7
2002      2.9   3.6
2000      NaN   1.5

Как и в случае Series, атрибут values возвращает данные, содержащиеся в DataFrame, в виде двумерного массива:

In [67]: frame3.values
Out[67]: 
array([[2.4, 1.7],
       [2.9, 3.6],
       [nan, 1.5]])

Таблица 1. Возможные входные данные для конструктора DataFrame

Тип Примечания
Двумерный ndarray Матрица данных, передающаяся с необязательными метками строк и столбцов
dict массивов, list, tuple Каждая последовательность становится столбцом в DataFrame. Все последовательности должны быть одинаковой длины
Структурированный массив (или массив записей) NumPy Обрабатывается как предыдущий случай
dict объектов типа Series Каждое значение становится столбцом. Индексы из каждой серии объединяются вместе, чтобы сформировать индекс строки результата, если не передан явный индекс
dict объектов типа dict Каждый внутренний словарь становится столбцом. Ключи объединяются для формирования индекса строки, как в предыдущем случае
list объектов dict или Series Каждый элемент становится строкой в DataFrame. Оббъединение ключей dict или индексов Series становится метками столбцов DataFrame
list объектов list или tuple Обрабатывается как случай двумерного массива
DataFrame Используются индексы DataFrame , если не переданы другие
маскированный массив NumPy Как случай двумерного массива, за исключением того, что маскированные значения становятся пропущенными (NA) значениями в итоговом DataFrame

Объекты типа Index

Объекты типа Index в pandas отвечают за хранение меток осей и других метаданных (таких как имя или имя оси).

Любой массив или другая последовательность меток, которые используются при создании Series или DataFrame, преобразуется в Index:

In [68]: obj = pd.Series(range(3), index=['a', 'b', 'c'])

In [69]: index = obj.index

In [70]: index
Out[70]: Index(['a', 'b', 'c'], dtype='object')

In [71]: index[1:]
Out[71]: Index(['b', 'c'], dtype='object')

Объекты Index — неизменяемый тип и не может изменяться пользователем:

In [72]: index[1] = 'd'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-75-a452e55ce13b> in <module>
----> 1 index[1] = 'd'

/usr/lib/python3.8/site-packages/pandas/core/indexes/base.py in __setitem__(self, key, value)
   3907 
   3908     def __setitem__(self, key, value):
-> 3909         raise TypeError("Index does not support mutable operations")
   3910 
   3911     def __getitem__(self, key):

TypeError: Index does not support mutable operations

Неизменяемость делает более безопасным совместное использование объектов Index:

In [73]: labels = pd.Index(np.arange(3))

In [74]: lables
Out[74]: Int64Index([0, 1, 2], dtype='int64')

In [75]: obj2 = pd.Series([1.5, -2.5, 0], index=lables)

In [76]: obj2
Out[76]: 
0    1.5
1   -2.5
2    0.0
dtype: float64

In [77]: obj2.index is lables
Out[77]: True

С объектами Index можно работать как с массивами фиксированного размера:

In [78]: frame3
Out[78]: 
state  Nevada  Ohio
year               
2001      2.4   1.7
2002      2.9   3.6
2000      NaN   1.5

In [79]: frame3.columns
Out[79]: Index(['Nevada', 'Ohio'], dtype='object', name='state')

In [80]: 'Ohio' in frame3.columns
Out[80]: True

In [81]: 2003 in frame3.index
Out[81]: False

В отличие от множеств Python объекты Index могут содержать повторяющиеся метки:

In [82]: dup_labels = pd.Index(['foo', 'foo', 'bar', 'bar'])

In [83]: dup_labels
Out[83]: Index(['foo', 'foo', 'bar', 'bar'], dtype='object')

Каждый объект Index имеет ряд методов и свойств. Некоторые полезные из них приведены в таблице pandas:data-struct:tbl:2.

Таблица 2. Некоторые методы и свойства Index

Метод Описание
append Добавляет дополнительные объекты Index, создавая новый объект Index
difference Возвращает разность множеств как Index
intersection Возвращает пересечение множеств
union Возвращает объединение множеств
isin Возвращает логический массив, указывающий, содержится ли каждое значение в переданной коллекции
delete Возвращает новый объект Index с удаленным элементом по индексу i
drop Возвращает новый объект Index, удаляя переданные значения
insert Возвращает новый объект Index, вставляя по индексу i элемент
is_monotonic Возвращает True, если каждый элемент больше либо равен предыдущего
is_unique Возвращает True, если объект Index не содержит дупликатов
unique Возвращает массив уникальных занчений в объекте Index