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

3517

Ядром pandas являются две структуры данных, в которых происходят все операции:

  • Series
  • Dataframes

Series — это структура, используемая для работы с последовательностью одномерных данных, а Dataframe — более сложная и подходит для нескольких измерений.

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

Однако особенности этих структур основаны на одной черте — интеграции в их структуру объектов index и labels (метки). С их помощью структурами становится очень легко манипулировать.

Series (серии)

Series — это объект библиотеки pandas, спроектированный для представления одномерных структур данных, похожих на массивы, но с дополнительными возможностями. Его структура проста, ведь он состоит из двух связанных между собой массивов. Основной содержит данные (данные любого типа NumPy), а в дополнительном, index, хранятся метки.

Структура объекта series

Создание объекта Series

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

>>> s = pd.Series([12,-4,7,9])
>>> s
0    12
1    -4
2     7
3     9
dtype: int64
Python data course

Как можно увидеть по выводу, слева отображаются значения индексов, а справа — сами значения (данные).

Если не определить индекс при объявлении объекта, метки будут соответствовать индексам (положению в массиве) элементов объекта Series.

Однако лучше создавать Series, используя метки с неким смыслом, чтобы в будущем отделять и идентифицировать данные вне зависимости от того, в каком порядке они хранятся.

В таком случае необходимо будет при вызове конструктора включить параметр index и присвоить ему массив строк с метками.

>>> s = pd.Series([12,-4,7,9], index=['a','b','c','d'])
>>> s
a    12
b    -4
c     7
d     9
dtype: int64

Если необходимо увидеть оба массива, из которых состоит структура, можно вызвать два атрибута: index и values.

>>> s.values
array([12, -4, 7, 9], dtype=int64)
>>> s.index
Index(['a', 'b', 'c', 'd'], dtype='object')

Выбор элементов по индексу или метке

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

>>> s[2] 
7

Или же можно выбрать метку, соответствующую положению индекса.

>>> s['b'] 
-4

Таким же образом можно выбрать несколько элементов массива numpy с помощью следующей команды:

>>> s[0:2]
a    12
b    -4
dtype: int64

В этом случае можно использовать соответствующие метки, но указать их список в массиве.

>>> s[['b','c']]
b   -4
c    7
dtype: int64

Присваивание значений элементам

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

>>> s[1] = 0 
>>> s
a    12
b     0
c     7
d     9
dtype: int64
>>> s['b'] = 1
>>> s
a    12
b     1
c     7
d     9
dtype: int64

Создание Series из массивов NumPy

Новый объект Series можно создать из массивов NumPy и уже существующих Series.

>>> arr = np.array([1,2,3,4])
>>> s3 = pd.Series(arr)
>>> s3
0    1
1    2
2    3
3    4
dtype: int32
>>> s4 = pd.Series(s)
>>> s4
a    12
b     1
c     7
d     9
dtype: int64

Важно запомнить, что значения в массиве NumPy или оригинальном объекте Series не копируются, а передаются по ссылке. Это значит, что элементы объекта вставляются динамически в новый Series. Если меняется оригинальный объект, то меняются и его значения в новом.

>>> s3
0    1
1    2
2    3
3    4
dtype: int32
>>> arr[2] = -2 
>>> s3
0    1
1    2
2    -2
3    4
dtype: int32

На этом примере можно увидеть, что при изменении третьего элемента массива arr, меняется соответствующий элемент и в s3.

Фильтрация значений

Благодаря тому что основной библиотекой в pandas является NumPy, многие операции, применяемые к массивам NumPy, могут быть использованы и в случае с Series. Одна из таких — фильтрация значений в структуре данных с помощью условий.

Например, если нужно узнать, какие элементы в Series больше 8, то можно написать следующее:

>>> s[s > 8]
a    12
d     9
dtype: int64

Операции и математические функции

Другие операции, такие как операторы (+, -, * и /), а также математические функции, работающие с массивами NumPy, могут использоваться и для Series.

Для операторов можно написать простое арифметическое уравнение.

>>> s / 2
a    6.0
b    0.5
c    3.5
d    4.5
dtype: float64

Но в случае с математическими функциями NumPy необходимо указать функцию через np, а Series передать в качестве аргумента.

>>> np.log(s)
a    2.484907
b    0.000000
c    1.945910
d    2.197225
dtype: float64

Количество значений

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

Так, можно объявить Series, в котором будут повторяющиеся значения.

>>> serd = pd.Series([1,0,2,1,2,3], index=['white','white','blue','green',' green','yellow']) 
>>> serd
white     1
white     0
blue      2
green     1
green     2
yellow    3
dtype: int64

Чтобы узнать обо всех значениях в Series, не включая дубли, можно использовать функцию unique(). Возвращаемое значение — массив с уникальными значениями, необязательно в том же порядке.

>>> serd.unique() 
array([1, 0, 2, 3], dtype=int64)

На unique() похожа функция value_counts(), которая возвращает не только уникальное значение, но и показывает, как часто элементы встречаются в Series.

>>> serd.value_counts()
2    2
1    2
3    1
0    1
dtype: int64

Наконец, isin() показывает, есть ли элементы на основе списка значений. Она возвращает булевые значения, которые очень полезны при фильтрации данных в Series или в колонке Dataframe.

>>> serd.isin([0,3])
white     False
white      True
blue      False
green     False
green     False
yellow     True
dtype: bool
>>> serd[serd.isin([0,3])]
white     0
yellow    3
dtype: int64

Значения NaN

В предыдущем примере мы попробовали получить логарифм отрицательного числа и результатом стало значение NaN. Это значение (Not a Number) используется в структурах данных pandas для обозначения наличия пустого поля или чего-то, что невозможно обозначить в числовой форме.

Как правило, NaN — это проблема, для которой нужно найти определенное решение, особенно при работе с анализом данных. Эти данные часто появляются при извлечении информации из непроверенных источников или когда в самом источнике недостает данных. Также значения NaN могут генерироваться в специальных случаях, например, при вычислении логарифмов для отрицательных значений, в случае исключений при вычислениях или при использовании функций. Есть разные стратегии работы со значениями NaN.

Несмотря на свою «проблемность» pandas позволяет явно определять NaN и добавлять это значение в структуры, например, в Series. Для этого внутри массива достаточно ввести np.NaN в том месте, где требуется определить недостающее значение.

>>> s2 = pd.Series([5,-3,np.NaN,14]) 
>>> s2
0     5.0
1    -3.0
2     NaN
3    14.0
dtype: float64

Функции isnull() и notnull() очень полезны для определения индексов без значения.

>>> s2.isnull()
0    False
1    False
2     True
3    False
dtype: bool
>>> s2.notnull()
0     True
1     True
2    False
3     True
dtype: bool

Они возвращают два объекта Series с булевыми значениями, где True указывает на наличие значение, а NaN — на его отсутствие. Функция isnull() возвращает True для значений NaN в Series, а notnull()True в тех местах, где значение не равно NaN. Эти функции часто используются в фильтрах для создания условий.

>>> s2[s2.notnull()]
0     5.0
1    -3.0
3    14.0
dtype: float64
s2[s2.isnull()]
2   NaN
dtype: float64

Series из словарей

Series можно воспринимать как объект dict (словарь). Эта схожесть может быть использована на этапе объявления объекта. Даже создавать Series можно на основе существующего dict.

>>> mydict = {'red': 2000, 'blue': 1000, 'yellow': 500,
 'orange': 1000}
>>> myseries = pd.Series(mydict)
>>> myseries
blue      1000
orange    1000
red       2000
yellow     500
dtype: int64

На этом примере можно увидеть, что массив индексов заполнен ключами, а данные — соответствующими значениями. В таком случае соотношение будет установлено между ключами dict и метками массива индексов. Если есть несоответствие, pandas заменит его на NaN.

>>> colors = ['red','yellow','orange','blue','green']
>>> myseries = pd.Series(mydict, index=colors)
>>> myseries
red       2000.0
yellow     500.0
orange    1000.0
blue      1000.0
green        NaN
dtype: float64

Операции с сериями

Вы уже видели, как выполнить арифметические операции на объектах Series и скалярных величинах. То же возможно и для двух объектов Series, но в таком случае в дело вступают и метки.

Одно из главных достоинств этого типа структур данных в том, что он может выравнивать данные, определяя соответствующие метки.

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

>>> mydict2 = {'red':400,'yellow':1000,'black':700}
>>> myseries2 = pd.Series(mydict2)
>>> myseries + myseries2
black        NaN
blue         NaN
green        NaN
orange       NaN
red       2400.0
yellow    1500.0
dtype: float64

Новый объект получает только те элементы, где метки совпали. Все остальные тоже присутствуют, но со значением NaN.

DataFrame (датафрейм)

Dataframe — это табличная структура данных, напоминающая таблицы из Microsoft Excel. Ее главная задача — позволить использовать многомерные Series. Dataframe состоит из упорядоченной коллекции колонок, каждая из которых содержит значение разных типов (числовое, строковое, булевое и так далее).

структура dataframe

В отличие от Series у которого есть массив индексов с метками, ассоциированных с каждым из элементов, Dataframe имеет сразу два таких. Первый ассоциирован со строками (рядами) и напоминает таковой из Series. Каждая метка ассоциирована со всеми значениями в ряду. Второй содержит метки для каждой из колонок.

Dataframe можно воспринимать как dict, состоящий из Series, где ключи — названия колонок, а значения — объекты Series, которые формируют колонки самого объекта Dataframe. Наконец, все элементы в каждом объекте Series связаны в соответствии с массивом меток, называемым index.

Создание Dataframe

Простейший способ создания Dataframe — передать объект dict в конструктор DataFrame(). Объект dict содержит ключ для каждой колонки, которую требуется определить, а также массив значений для них.

Если объект dict содержит больше данных, чем требуется, можно сделать выборку. Для этого в конструкторе Dataframe нужно определить последовательность колонок с помощью параметра column. Колонки будут созданы в заданном порядке вне зависимости от того, как они расположены в объекте dict.

>> data = {'color' : ['blue', 'green', 'yellow', 'red', 'white'],
        'object' : ['ball', 'pen', 'pencil', 'paper', 'mug'],
        'price' : [1.2, 1.0, 0.6, 0.9, 1.7]}
>>> frame = pd.DataFrame(data)
>>> frame
colorobjectprice
0blueball1.2
1greenpen1.0
2yellowpencil0.6
3redpaper0.9
4whitemug1.7

Даже для объектов Dataframe если метки явно не заданы в массиве index, pandas автоматически присваивает числовую последовательность, начиная с нуля. Если же индексам Dataframe нужно присвоить метки, необходимо использовать параметр index и присвоить ему массив с метками.

>>> frame2 = pd.DataFrame(data, columns=['object', 'price'])
>>> frame2
objectprice
0ball1.2
1pen1.0
2pencil0.6
3paper0.9
4mug1.7

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

В большинстве случаев простейший способ создать матрицу значений — использовать np.arrange(16).reshape((4,4)). Это формирует матрицу размером 4х4 из чисел от 0 до 15.

>>> frame3 = pd.DataFrame(np.arange(16).reshape((4,4)),
...                       index=['red', 'blue', 'yellow', 'white'],
...                       columns=['ball', 'pen', 'pencil', 'paper'])
>>> frame3

Выбор элементов

Если нужно узнать названия всех колонок Dataframe, можно вызвать атрибут columns для экземпляра объекта.

>>> frame.columns
Index(['color', 'object', 'price'], dtype='object')

То же можно проделать и для получения списка индексов.

>>> frame.index
RangeIndex(start=0, stop=5, step=1)

Весь же набор данных можно получить с помощью атрибута values.

>>> frame.values
array([['blue', 'ball', 1.2],
       ['green', 'pen', 1.0],
       ['yellow', 'pencil', 0.6],
       ['red', 'paper', 0.9],
       ['white', 'mug', 1.7]], dtype=object)

Указав в квадратных скобках название колонки, можно получить значений в ней.

>>> frame['price']
0    1.2
1    1.0
2    0.6
3    0.9
4    1.7
Name: price, dtype: float64

Возвращаемое значение — объект Series. Название колонки можно использовать и в качестве атрибута.

>>> frame.price
0    1.2
1    1.0
2    0.6
3    0.9
4    1.7
Name: price, dtype: float64

Для строк внутри Dataframe используется атрибут loc со значением индекса нужной строки.

>>> frame.loc[2]
color     yellow
object    pencil
price        0.6
Name: 2, dtype: object

Возвращаемый объект — это снова Series, где названия колонок — это уже метки массива индексов, а значения — данные Series.

Для выбора нескольких строк можно указать массив с их последовательностью.

>>> frame.loc[[2,4]]
colorobjectprice
2yellowpencil0.6
4whitemug1.7

Если необходимо извлечь часть Dataframe с конкретными строками, для этого можно использовать номера индексов. Она выведет данные из соответствующей строки и названия колонок.

>>> frame[0:1]
colorobjectprice
2yellowpencil0.6
4whitemug1.7

Возвращаемое значение — объект Dataframe с одной строкой. Если нужно больше одной строки, необходимо просто указать диапазон.

>>> frame[1:3]
colorobjectprice
0blueball1.2

Наконец, если необходимо получить одно значение из объекта, сперва нужно указать название колонки, а потом — индекс или метку строки.

>>> frame['object'][3]
'paper'

Присваивание и замена значений

Разобравшись с логикой получения доступа к разным элементам Dataframe, можно следовать ей же для добавления новых или изменения уже существующих значений.

Например, в структуре Dataframe массив индексов определен атрибутом index, а строка с названиями колонок — columns. Можно присвоить метку с помощью атрибута name для этих двух подструктур, чтобы идентифицировать их.

>>> frame.index.name = 'id'
>>> frame.columns.name = 'item'
>>> frame
itemcolorobjectprice
id
0blueball1.2
1greenpen1.0
2yellowpencil0.6
3redpaper0.9
4whitemug1.7

Одна из главных особенностей структур данных pandas — их гибкость. Можно вмешаться на любом уровне для изменения внутренней структуры данных. Например, добавление новой колонки — крайне распространенная операция.

Ее можно выполнить, присвоив значение экземпляру Dataframe и определив новое имя колонки.

>>> frame['new'] = 12 
>>> frame
itemcolorobjectpricenew
id
0blueball1.212
1greenpen1.012
2yellowpencil0.612
3redpaper0.912
4whitemug1.712

Здесь видно, что появилась новая колонка new со значениями 12 для каждого элемента.

Для обновления значений можно использовать массив.

frame['new'] = [3.0, 1.3, 2.2, 0.8, 1.1]
frame
itemcolorobjectpricenew
id
0blueball1.23.0
1greenpen1.01.3
2yellowpencil0.62.2
3redpaper0.90.8
4whitemug1.71.1

Тот же подход используется для обновления целой колонки. Например, можно применить функцию np.arrange() для обновления значений колонки с помощью заранее заданной последовательности.

Колонки Dataframe также могут быть созданы с помощью присваивания объекта Series одной из них, например, определив объект Series, содержащий набор увеличивающихся значений с помощью np.arrange().

>>> ser = pd.Series(np.arange(5)) 
>>> ser
0    0
1    1
2    2
3    3
4    4
dtype: int32
frame['new'] = ser
frame
itemcolorobjectpricenew
id
0blueball1.20
1greenpen1.01
2yellowpencil0.62
3redpaper0.93
4whitemug1.74

Наконец, для изменения одного значения нужно лишь выбрать элемент и присвоить ему новое значение.

>>> frame['price'][2] = 3.3

Вхождение значений

Функция isin() используется с объектами Series для определения вхождения значений в колонку. Она же подходит и для объектов Dataframe.

>>> frame.isin([1.0,'pen'])
itemcolorobjectpricenew
id
0FalseFalseFalseFalse
1FalseTrueTrueTrue
2FalseFalseFalseFalse
3FalseFalseFalseFalse
4FalseFalseFalseFalse

Возвращается Dataframe с булевыми значениями, где True указывает на те значения, где членство подтверждено. Если передать это значение в виде условия, тогда вернется Dataframe, где будут только значения, удовлетворяющие условию.

>>> frame[frame.isin([1.0,'pen'])]
itemcolorobjectpricenew
id
0NaNNaNNaNNaN
1NaNpen1.01.0
2NaNNaNNaNNaN
3NaNNaNNaNNaN
4NaNNaNNaNNaN

Удаление колонки

Для удаления целой колонки и всего ее содержимого используется команда del.

>>> del frame['new'] 
>>> frame
itemcolorobjectprice
id
0blueball1.2
1greenpen1.0
2yellowpencil3.3
3redpaper0.9
4whitemug1.7

Фильтрация

Даже для Dataframe можно применять фильтры, используя определенные условия. Например, вам нужно получить все значения меньше определенного числа (допустим, 1,2).

>>> frame[frame  <  1.2]
itemcolorobjectprice
id
0blueballNaN
1greenpen1.0
2yellowpencilNaN
3redpaper0.9
4whitemugNaN

Результатом будет Dataframe со значениями меньше 1,2 на своих местах. На месте остальных будет NaN.

Dataframe из вложенного словаря

В Python часто используется вложенный dict:

>>> nestdict = {'red': { 2012: 22, 2013: 33},
...             'white': { 2011: 13, 2012: 22, 2013: 16},
...             'blue': { 2011: 17, 2012: 27, 2013: 18}}

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

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

>>> nestdict = {'red': { 2012: 22, 2013: 33},
...             'white': { 2011: 13, 2012: 22, 2013: 16},
...             'blue': { 2011: 17, 2012: 27, 2013: 18}}
>>> frame2 = pd.DataFrame(nestdict)
>>> frame2
blueredwhite
201117NaN13
20122722.022
20131833.016

Транспонирование Dataframe

При работе с табличным структурами данных иногда появляется необходимость выполнить операцию перестановки (сделать так, чтобы колонки стали рядами и наоборот). pandas позволяет добиться этого очень просто. Достаточно добавить атрибут T.

>>> frame2.T
201120122013
blue17.027.018.0
redNaN22.033.0
white13.022.016.0

Объекты Index

Зная, что такое Series и Dataframes, и понимая как они устроены, проще разобраться со всеми их достоинствами. Главная особенность этих структур — наличие объекта Index, который в них интегрирован.

Объекты Index являются метками осей и содержат другие метаданные. Вы уже знаете, как массив с метками превращается в объект Index, и что для него нужно определить параметр index в конструкторе.

>>> ser = pd.Series([5,0,3,8,4], index=['red','blue','yellow','white','green'])
>>> ser.index
Index(['red', 'blue', 'yellow', 'white', 'green'], dtype='object')

В отличие от других элементов в структурах данных pandas (Series и Dataframe) объекты index — неизменяемые. Это обеспечивает безопасность, когда нужно передавать данные между разными структурами.

У каждого объекта Index есть методы и свойства, которые нужны, чтобы узнавать значения.

Методы Index

Есть методы для получения информации об индексах из структуры данных. Например, idmin() и idmax() — структуры, возвращающие индексы с самым маленьким и большим значениями.

>>> ser.idxmin()
'blue'
>>> ser.idxmax()
'white'

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

Пока что были только те случаи, когда у индексов одной структуры лишь одна, уникальная метка. Для большинства функций это обязательное условие, но не для структур данных pandas.

Определим, например, Series с повторяющимися метками.

>>> serd = pd.Series(range(6), index=['white','white','blue','green', 'green','yellow']) 
>>> serd
white     0
white     1
blue      2
green     3
green     4
yellow    5
dtype: int64

Если метке соответствует несколько значений, то она вернет не один элемент, а объект Series.

>>> serd['white']
white    0
white    1
dtype: int64

То же применимо и к Dataframe. При повторяющихся индексах он возвращает Dataframe.

В случае с маленькими структурами легко определять любые повторяющиеся индексы, но если структура большая, то растет и сложность этой операции. Для этого в pandas у объектов Index есть атрибут is_unique. Он сообщает, есть ли индексы с повторяющимися метками в структуре (Series или Dataframe).

>>> serd.index.is_unique
False
>>> frame.index.is_unique
True

Операции между структурами данных

Теперь когда вы знакомы со структурами данных, Series и Dataframe, а также базовыми операциями для работы с ними, стоит рассмотреть операции, включающие две или более структур.

Гибкие арифметические методы

Уже рассмотренные операции можно выполнять с помощью гибких арифметических методов:

  • add()
  • sub()
  • div()
  • mul()

Для их вызова нужно использовать другую спецификацию. Например, вместо того чтобы выполнять операцию для двух объектов Dataframe по примеру frame1 + frame2, потребуется следующий формат:

>>> frame1.add(frame2)
ballmugpaperpenpencil
blue6.0NaNNaN6.0NaN
greenNaNNaNNaNNaNNaN
redNaNNaNNaNNaNNaN
white20.0NaNNaN20.0NaN
yellow19.0NaNNaN19.0NaN

Результат такой же, как при использовании оператора сложения +. Также стоит обратить внимание, что если названия индексов и колонок сильно отличаются, то результатом станет новый объект Dataframe, состоящий только из значений NaN.

Операции между Dataframe и Series

Pandas позволяет выполнять переносы между разными структурами, например, между Dataframe и Series. Определить две структуры можно следующим образом.

>>> frame = pd.DataFrame(np.arange(16).reshape((4,4)),
...           index=['red', 'blue', 'yellow', 'white'],
...           columns=['ball','pen','pencil','paper'])
>>> frame
ballpenpencilpaper
red0123
blue4567
yellow891011
white12131415
>>> ser = pd.Series(np.arange(4), index=['ball','pen','pencil','paper']) 
>>> ser
ball      0
pen       1
pencil    2
paper     3
dtype: int32

Они были специально созданы так, чтобы индексы в Series совпадали с названиями колонок в Dataframe. В таком случае можно выполнить прямую операцию.

>>> frame - ser
ballpenpencilpaper
red0000
blue4444
yellow8888
white12121212

По результату видно, что элементы Series были вычтены из соответствующих тому же индексу в колонках значений Dataframe.

Если индекс не представлен ни в одной из структур, то появится новая колонка с этим индексом и значениями NaN.

>>> ser['mug'] = 9 
>>> ser
ball      0
pen       1
pencil    2
paper     3
mug       9
dtype: int64
>>> frame - ser
ballmugpaperpenpencil
red0NaN000
blue4NaN444
yellow8NaN888
white12NaN121212

Тест на знание python

Что выведет этот код?
Какой будет результат выполнения этого кода?
Что выведет этот код?
Как узнать длину списка?
Что выведет этот код?
Александр
Я создал этот блог в 2018 году, чтобы распространять полезные учебные материалы, документации и уроки на русском. На сайте опубликовано множество статей по основам python и библиотекам, уроков для начинающих и примеров написания программ. Пишу на популярные темы: веб-разработка, работа с базами данных, data sciense и другие...