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

7961

Ядром 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 разработчика RU / EN

Книги / Data Science / RU-EN

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

Какой будет результат выполнения кода — print(type(1J)) ?
Какой код выведет строку — "C:\Common\testString.doc" ?
Какой будет результат выполнения этого кода?
Какой цикл `for` выведет такой результат?
Что выведет этот код?
Александр
Я создал этот блог в 2018 году, чтобы распространять полезные учебные материалы, документации и уроки на русском. На сайте опубликовано множество статей по основам python и библиотекам, уроков для начинающих и примеров написания программ. Пишу на популярные темы: веб-разработка, работа с базами данных, data sciense и другие...