Основная функциональность

Приведем основные подходы к работе с данными, содержащимися в Series и DataFrame.

Переиндексация

Важный метод в объектах pandas — это reindex, который создает новый объект с данными, согласованными с новым индексом. Рассмотрим пример:

In [84]: obj = pd.Series([4.5, 7.2, -5.3, 3.6], index=['d', 'b', 'a', 'c'])

In [85]: obj
Out[85]: 
d    4.5
b    7.2
a   -5.3
c    3.6
dtype: float64

Вызов reindex в объекте Series переупорядочивает данные в соответствии с новым индексом, вводя пропущенные значения, если какие-либо значения индекса еще не присутствовали:

In [86]: obj2 = obj.reindex(['a', 'b', 'c', 'd', 'e'])

In [87]: obj2
Out[87]: 
a   -5.3
b    7.2
c    3.6
d    4.5
e    NaN
dtype: float64

ля упорядоченных данных, таких как временные ряды, может быть желательно выполнить некоторую интерполяцию или заполнение значений при переиндексации. Аргумент method позволяет нам сделать это, используя метод такой как ffill (forward-fill), который заполняет «вперед» значениями ряд:

In [88]: obj3 = pd.Series(['blue', 'purple', 'yellow'], index=[0, 2, 4])

In [89]: obj3
Out[89]: 
0      blue
2    purple
4    yellow
dtype: object

In [90]: obj3.reindex(range(6), method='ffill')
Out[90]: 
0      blue
1      blue
2    purple
3    purple
4    yellow
5    yellow
dtype: object

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

In [91]: frame = pd.DataFrame(np.arange(9).reshape((3, 3)), 
    ...: index=['a', 'c', 'd'], 
    ...: columns=['Ohio', 'Texas', 'California'])

In [92]: frame
Out[92]: 
   Ohio  Texas  California
a     0      1           2
c     3      4           5
d     6      7           8

In [93]: frame2 = frame.reindex(['a', 'b', 'c', 'd'])

In [94]: frame2
Out[94]: 
   Ohio  Texas  California
a   0.0    1.0         2.0
b   NaN    NaN         NaN
c   3.0    4.0         5.0
d   6.0    7.0         8.0

Столбцы переиндексируются с помощью аргумента columns:

In [95]: states = ['Texas', 'Utah', 'California']

In [96]: frame.reindex(columns=states)
Out[96]: 
   Texas  Utah  California
a      1   NaN           2
c      4   NaN           5
d      7   NaN           8

В таблице pandas:functionality:tbl:1 представлены аргументы функции reindex.

Таблица 3. Аргументы функции reindex

Аргумент Описание
index Новая последовательность для использования в качестве индекса. Может быть экземпляром Index или любой последовательности Python
method Метод интерполяции (заполнения): ffil (forward-fill) — прямое заполнение, bfill (backward-fill) — обратное заполнение
fill_value Подставляется это значения при заполнении пропущенных данных, которые появляются при переиндексации
limit При заполнении задает максимальный размер шага (по количеству элементов) заполнения
tolerance При заполнении задает максимальный размер шага (в абсолютном числовом расстоянии) для заполнения неточных совпадений
level Сопоставляет простой Index на уровне MultiIndex; в противном случае выбирает подмножество
copy Если True, всегда копирует данные, даже если новый индекс эквивалентен старому; если False не копирует данные, если индексы эквивалентны

Удаление записей с оси

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

In [97]: obj = pd.Series(np.arange(5.), index=['a', 'b', 'c', 'd', 'e'])

In [98]: obj
Out[98]: 
a    0.0
b    1.0
c    2.0
d    3.0
e    4.0
dtype: float64

In [99]: new_obj = obj.drop('c')

In [100]: new_obj
Out[100]: 
a    0.0
b    1.0
d    3.0
e    4.0
dtype: float64

In [101]: obj.drop(['d', 'c'])
Out[101]: 
a    0.0
b    1.0
e    4.0
dtype: float64

В DataFrame значения индекса могут быть удалены с любой оси:

In [102]: data = pd.DataFrame(np.arange(16).reshape((4, 4)),
     ...: index=['Ohio', 'Colorado', 'Utah', 'New York'],
     ...: columns=['one', 'two', 'three', 'four'])

In [103]: data
Out[103]: 
          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7
Utah        8    9     10    11
New York   12   13     14    15

Вызов drop с последовательностью меток удаляет значения из меток строк (ось 0):

In [104]: data.drop(['Colorado', 'Ohio'])
Out[104]: 
          one  two  three  four
Utah        8    9     10    11
New York   12   13     14    15

Удалить значения в столбцах можно передавая параметр axis=1 или axis=columns:

In [105]: data.drop('two', axis=1)
Out[105]: 
          one  three  four
Ohio        0      2     3
Colorado    4      6     7
Utah        8     10    11
New York   12     14    15

In [106]: data.drop(['two', 'four'], axis='columns')
Out[106]: 
          one  three
Ohio        0      2
Colorado    4      6
Utah        8     10
New York   12     14

Многие функции, такие как drop, которые изменяют размер или форму Series или DataFrame, могут изменять сам объект (in-place) без создания нового объекта:

In [107]: obj.drop('c', inplace=True)

In [108]: obj
Out[108]: 
a    0.0
b    1.0
d    3.0
e    4.0
dtype: float64

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

Будьте осторожны с параметром inplace, так как происходит удаление данных.

Арифметические операции и выравнивание данных

Важной особенностью pandas для некоторых приложений является поведение арифметических операций между объектами с разными индексами. При сложении объекты в случае, когда любые пары индексов отличаются, соответствующий индекс в результате является объединением исходных индексов:

In [109]: s1 = pd.Series([7.3, -2.5, 3.4, 1.5], index=['a', 'c', 'd', 'e'])

In [110]: s2 = pd.Series([-2.1, 3.6, -1.5, 4, 3.1], index=['a', 'c', 'e', 'f', 'g'])

In [111]: s1
Out[111]: 
a    7.3
c   -2.5
d    3.4
e    1.5
dtype: float64

In [112]: s2
Out[112]: 
a   -2.1
c    3.6
e   -1.5
f    4.0
g    3.1
dtype: float64

In [113]: s1 + s2
Out[113]: 
a    5.2
c    1.1
d    NaN
e    0.0
f    NaN
g    NaN
dtype: float64

Выравнивание данных вводит пропущенные значения в местах меток, которые не пересекаются. Пропущенные значения будут распространяться в дальнейших арифметических вычислениях.

В случае DataFrame выравнивание осуществляется как для строк, так и для столбцов:

In [114]: df1 = pd.DataFrame(np.arange(9.).reshape((3, 3)), columns=list('bcd'), 
     ...: index=['Ohio', 'Texas', 'Colorado'])

In [115]: df2 = pd.DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'), 
     ...: index=['Utah', 'Ohio', 'Texas', 'Oregon'])

In [116]: df1
Out[116]: 
            b    c    d
Ohio      0.0  1.0  2.0
Texas     3.0  4.0  5.0
Colorado  6.0  7.0  8.0

In [117]: df2
Out[117]: 
          b     d     e
Utah    0.0   1.0   2.0
Ohio    3.0   4.0   5.0
Texas   6.0   7.0   8.0
Oregon  9.0  10.0  11.0

Сумма введенных объектов вернет новый объект DataFrame, чьи индексы и столбцы являются объединениями индексов и столбцов двух складываемых объектов:

In [118]: df1 + df2
Out[118]: 
            b   c     d   e
Colorado  NaN NaN   NaN NaN
Ohio      3.0 NaN   6.0 NaN
Oregon    NaN NaN   NaN NaN
Texas     9.0 NaN  12.0 NaN
Utah      NaN NaN   NaN NaN

Так как столбцы 'c' и 'e' не находятся одновременно в обоих объектах DataFrame, в результате они содержат отсутствующие значения. Такое же происходит и со строками.

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

In [119]: df1 = pd.DataFrame({'A': [1, 2]})

In [120]: df2 = pd.DataFrame({'B': [3, 4]})

In [121]: df1
Out[121]: 
   A
0  1
1  2

In [122]: df2
Out[122]: 
   B
0  3
1  4

In [123]: df1 - df2
Out[123]: 
    A   B
0 NaN NaN
1 NaN NaN

Арифметические методы с заполнением значений

In [124]: df1 = pd.DataFrame(np.arange(12.).reshape((3, 4)), 
     ...: columns=list('abcd'))

In [125]: df2 = pd.DataFrame(np.arange(20.).reshape((4, 5)), 
     ...: columns=list('abcde'))

In [126]: df2.loc[1, 'b'] = np.nan

In [127]: df1
Out[127]: 
     a    b     c     d
0  0.0  1.0   2.0   3.0
1  4.0  5.0   6.0   7.0
2  8.0  9.0  10.0  11.0

In [128]: df2
Out[128]: 
      a     b     c     d     e
0   0.0   1.0   2.0   3.0   4.0
1   5.0   NaN   7.0   8.0   9.0
2  10.0  11.0  12.0  13.0  14.0
3  15.0  16.0  17.0  18.0  19.0

Сложение этих объектов приводит к значениям NA в местах, которые не перекрываются:

In [129]: df1 + df2
Out[129]: 
      a     b     c     d   e
0   0.0   2.0   4.0   6.0 NaN
1   9.0   NaN  13.0  15.0 NaN
2  18.0  20.0  22.0  24.0 NaN
3   NaN   NaN   NaN   NaN NaN

Для заполнения отсутствующих значений можно воспользоваться функцией add с дополнительным аргументом fill_value:

In [130]: df1.add(df2, fill_value=0)
Out[130]: 
      a     b     c     d     e
0   0.0   2.0   4.0   6.0   4.0
1   9.0   5.0  13.0  15.0   9.0
2  18.0  20.0  22.0  24.0  14.0
3  15.0  16.0  17.0  18.0  19.0

В таблице pandas:functionality:tbl:2 представлены методы для арифметических операций. У каждого из них есть аналог, начинающийся с буквы r, у которого переставлены аргументы. Приведенные ниже примеры эквивалентны:

In [131]: 1 / df1
Out[131]: 
       a         b         c         d
0    inf  1.000000  0.500000  0.333333
1  0.250  0.200000  0.166667  0.142857
2  0.125  0.111111  0.100000  0.090909

In [132]: df1.rdiv(1)
Out[132]: 
       a         b         c         d
0    inf  1.000000  0.500000  0.333333
1  0.250  0.200000  0.166667  0.142857
2  0.125  0.111111  0.100000  0.090909

Соответственно, при переиндексации Series или DataFrame вы также можете указать другое значение заполнения:

In [133]: df1.reindex(columns=df2.columns, fill_value=0)
Out[133]: 
     a    b     c     d  e
0  0.0  1.0   2.0   3.0  0
1  4.0  5.0   6.0   7.0  0
2  8.0  9.0  10.0  11.0  0

Таблица 4. Гибкие арифметические методы

Метод Описание
add, radd Сложение (+)
sub, rsub Вычитание (-)
div, rdiv Деление (/)
floordiv, rfloordiv Целочисленное деление (//)
mul, rmul Умножение (*)
pow, rpow Возведение в степень (**)

Операции между объектами DataFrame и Series

Как и для массивов NumPy разной размерности, существуют арифметические операции между объектами DataFrame и Series. В качестве примера рассмотрим разность между двумерным массивом и одной из его строк:

In [134]: arr = np.arange(12.).reshape((3, 4))

In [135]: arr
Out[135]: 
array([[ 0.,  1.,  2.,  3.],
       [ 4.,  5.,  6.,  7.],
       [ 8.,  9., 10., 11.]])

In [136]: arr[0]
Out[136]: array([0., 1., 2., 3.])

In [137]: arr - arr[0]
Out[137]: 
array([[0., 0., 0., 0.],
       [4., 4., 4., 4.],
       [8., 8., 8., 8.]])

При вычитании arr[0] из arr, операция осуществляется для каждой строки. Операции между DataFrame и Series производятся аналогично.

In [138]: frame = pd.DataFrame(np.arange(12.).reshape((4, 3)), 
     ...: columns=list('bde'), 
     ...: index=['Utah', 'Ohio', 'Texas', 'Oregon'])

In [139]: series = frame.iloc[0]

In [140]: frame
Out[140]: 
          b     d     e
Utah    0.0   1.0   2.0
Ohio    3.0   4.0   5.0
Texas   6.0   7.0   8.0
Oregon  9.0  10.0  11.0

In [141]: series
Out[141]: 
b    0.0
d    1.0
e    2.0
Name: Utah, dtype: float64

По умолчанию арифметические операции между DataFrame и Series приводят индексы объекта Series к столбцам объекта DataFrame, распространяя операцию по строкам:

In [142]: frame - series
Out[142]: 
          b    d    e
Utah    0.0  0.0  0.0
Ohio    3.0  3.0  3.0
Texas   6.0  6.0  6.0
Oregon  9.0  9.0  9.0

Если значение индекса не найдено ни в столбцах DataFrame, ни в индексе Series, объекты будут переиндексированы для формирования объединения:

In [143]: series2 = pd.Series(range(3), index=['b', 'e', 'f'])

In [144]: frame + series2
Out[144]: 
          b   d     e   f
Utah    0.0 NaN   3.0 NaN
Ohio    3.0 NaN   6.0 NaN
Texas   6.0 NaN   9.0 NaN
Oregon  9.0 NaN  12.0 NaN

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

In [145]: series3 = frame['d']

In [146]: frame
Out[146]: 
          b     d     e
Utah    0.0   1.0   2.0
Ohio    3.0   4.0   5.0
Texas   6.0   7.0   8.0
Oregon  9.0  10.0  11.0

In [147]: series3
Out[147]: 
Utah       1.0
Ohio       4.0
Texas      7.0
Oregon    10.0
Name: d, dtype: float64

In [148]: frame.sub(series3, axis='index')
Out[148]: 
          b    d    e
Utah   -1.0  0.0  1.0
Ohio   -1.0  0.0  1.0
Texas  -1.0  0.0  1.0
Oregon -1.0  0.0  1.0

Применение функций и отображение

Универсальные функции NumPy также работают с объектами pandas:

In [149]: frame = pd.DataFrame(np.random.randn(4, 3), columns=list('bde'), 
     ...: index=['Utah', 'Ohio', 'Texas', 'Oregon'])

In [150]: frame
Out[150]: 
               b         d         e
Utah   -0.373512 -0.408758 -0.865501
Ohio    0.644210 -1.974533  0.827712
Texas  -0.002276 -0.452517  0.904933
Oregon -0.458469  0.526754 -1.517036

In [151]: np.abs(frame)
Out[151]: 
               b         d         e
Utah    0.373512  0.408758  0.865501
Ohio    0.644210  1.974533  0.827712
Texas   0.002276  0.452517  0.904933
Oregon  0.458469  0.526754  1.517036

Другой частой операцией является применение функции к одномерным массивам для каждого столбца или строки. Метод apply объекта DataFrame выполняет это:

In [152]: f = lambda x: x.max() - x.min()

In [153]: frame.apply(f)
Out[153]: 
b    1.102679
d    2.501287
e    2.421969
dtype: float64

В результате мы получили объект Series, у которого индекс совпадает со столбцами объекта DataFrame.

Если задать параметр axis = 'columns' в функции apply, функция будет применяться к строкам:

In [154]: frame.apply(f, axis='columns')
Out[154]: 
Utah      0.491989
Ohio      2.802245
Texas     1.357449
Oregon    2.043790
dtype: float64

Многие из наиболее распространенных статистических методов (например, sum и mean) являются методами DataFrame, поэтому использование apply не обязательно.

Функция, передаваемая в apply, не обязана возвращать скалярное значение, она может также возвращать объект Series:

In [155]: frame
Out[155]: 
               b         d         e
Utah    0.485727  0.656970  0.799615
Ohio    1.296945 -0.736579  0.243545
Texas  -0.953977 -1.293465  0.154692
Oregon -0.270480  0.684838  1.046487

In [156]: def f(x): 
   ...:     return pd.Series([x.min(), x.max()], index=['min', 'max']) 

In [157]: frame.apply(f)
Out[157]: 
            b         d         e
min -0.953977 -1.293465  0.154692
max  1.296945  0.684838  1.046487

Также можно использовать поэлементные функции. Предположим, нужно получить форматированную строку для каждого значения в объекте frame. Это можно реализовать с помощью функции applymap:

In [158]: format = lambda x: '%.2f' % x

In [159]: frame.applymap(format)
Out[159]: 
            b      d     e
Utah     0.49   0.66  0.80
Ohio     1.30  -0.74  0.24
Texas   -0.95  -1.29  0.15
Oregon  -0.27   0.68  1.05

В функции applymap используется метод map класса Series:

In [160]: frame['e'].map(format)
Out[160]: 
Utah      0.80
Ohio      0.24
Texas     0.15
Oregon    1.05
Name: e, dtype: object

Сортировка и ранжирование

Одна из важных встроенных операций — это сортировка данных. Для того, чтобы выполнить лексикографическую сортировку по индексам строк или столбцов, можно использовать функцию sort_index, которая возвращает новый отсортированный объект:

In [161]: obj = pd.Series(range(4), index=['d', 'a', 'b', 'c'])

In [162]: obj.sort_index()
Out[162]: 
a    1
b    2
c    3
d    0
dtype: int64

Объект DataFrame можно сортировать по индексам на любой оси:

In [163]: frame = pd.DataFrame(np.arange(8).reshape((2, 4)), 
     ...: index=['three', 'one'], 
     ...: columns=['d', 'a', 'b', 'c'])
    
In [164]: frame.sort_index()
Out[164]: 
       d  a  b  c
one    4  5  6  7
three  0  1  2  3

In [165]: frame.sort_index(axis=1)
Out[165]: 
       a  b  c  d
three  1  2  3  0
one    5  6  7  4

Данные сортируются по возрастанию по умолчанию, но могут быть отсортированы также по убыванию:

In [166]: frame.sort_index(axis=1, ascending=False)
Out[166]: 
       d  c  b  a
three  0  3  2  1
one    4  7  6  5

Для сортировки объекта Series по значениям используется метод sort_values:

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

In [168]: obj.sort_values()
Out[168]: 
2   -3
3    2
0    4
1    7
dtype: int64

Все пропущенные значения по умолчанию сортируются в конец объекта Series:

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

In [170]: obj.sort_values()
Out[170]: 
4   -3.0
5    2.0
0    4.0
2    7.0
1    NaN
3    NaN
dtype: float64

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

In [171]: frame = pd.DataFrame({'b': [4, 7, -3, 2], 'a': [0, 1, 0, 1]})

In [172]: frame
Out[172]: 
   b  a
0  4  0
1  7  1
2 -3  0
3  2  1

In [173]: frame.sort_values(by='b')
Out[173]: 
   b  a
2 -3  0
3  2  1
0  4  0
1  7  1

Для сортировки по нескольким столбцам, необходимо передать список имен столбцов:

In [174]: frame.sort_values(by=['a', 'b'])
Out[174]: 
   b  a
2 -3  0
0  4  0
3  2  1
1  7  1

Ранжирование заключается в присвоении ранга от единицы до числа значений в массиве. Объекты Series и DataFrame имеют метод rank, который по умолчанию разрывает связи, присваивая каждой группе среднее значение ранга:

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

In [176]: obj.rank()
Out[176]: 
0    6.5
1    1.0
2    6.5
3    4.5
4    3.0
5    2.0
6    4.5
dtype: float64

Ранги также могут быть назначены в соответствии с порядком, в котором они наблюдаются в данных:

In [177]: obj.rank(method='first')
Out[177]: 
0    6.0
1    1.0
2    7.0
3    4.0
4    3.0
5    2.0
6    5.0
dtype: float64

Здесь вместо использования среднего ранга \( 6.5 \) для записей с индексами 0 и 2 они вместо этого были установлены на \( 6 \) и \( 7 \), потому что метка \( 0 \) предшествует метке \( 2 \) в данных.

В таблице pandas:functionality:tbl:3 представлен перечень методов построения ранга.

Объект DataFrame может вычислять ранги по строкам или по столбцам:

In [178]: frame = pd.DataFrame({'b': [4.3, 7, -3, 2], 'a': [0, 1, 0, 1], 
    ...: 'c': [-2, 5, 8, -2.5]})
    
In [179]: frame
Out[179]: 
     b  a    c
0  4.3  0 -2.0
1  7.0  1  5.0
2 -3.0  0  8.0
3  2.0  1 -2.5

In [180]: frame.rank(axis='columns')
Out[180]: 
     b    a    c
0  3.0  2.0  1.0
1  3.0  1.0  2.0
2  1.0  2.0  3.0
3  3.0  2.0  1.0

Таблица 5. Методы ранжирования

Метод Описание
average Используется по умолчанию. Присваивает среднее значение ранга каждому значению в группе
min Использует минимальный ранг для всей группы
max Использует максимальный ранг для всей группы
first Присваивает ранги в порядке появления значений в данных
dense Как method = 'min', но ранги между группами всегда увеличиваются на 1, а не на количество равных элементов в группе

Индексация с повторяющимися метками

В рассматриваемых выше примерах индексы имели единственные значения, без повторений. Хотя многие функции библиотеки pandas (например, reindex) требуют, чтобы метки были уникальными, это не обязательно. Рассмотрим ряд с повторяющимися индексами:

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

In [182]: obj
Out[182]: 
a    0
a    1
b    2
b    3
c    4
dtype: int64

У объекта Index есть атрибут is_unique, который дает информацию являются ли метки индекса уникальными:

In [183]: obj.index.is_unique
Out[183]: False

В случае, когда несколько данных имеют одинаковые метки, обращение по этому индексу вернет объект Series, в то время как для меток без дублирования возвращается скалярное значение:

In [184]: obj['a']
Out[184]: 
a    0
a    1
dtype: int64

In [185]: obj['c']
Out[185]: 4

Та же логика распространяется и на индексирование строк в DataFrame:

In [186]: df = pd.DataFrame(np.random.randn(4, 3), index=['a', 'a', 'b', 'b'])

In [187]: df
Out[187]: 
          0         1         2
a -0.131446  1.837088 -0.618983
a  0.505283  1.940973  0.619603
b  0.209986  0.225563  0.507683
b -0.763314  0.380339  1.837083

In [188]: df.loc['b']
Out[188]: 
          0         1         2
b  0.209986  0.225563  0.507683
b -0.763314  0.380339  1.837083