Библиотека pandas#

pandas (panel data) — самая популярная библиотека для работы с таблицами в python. pandas — сторонняя библиотека, а значит требует дополнительной установки в общем случае, но входит в дистрибутив anaconda.

conda install pandas

или

python -m pip install pandas

При импортировании pandas часто пользуются псевдонимом pd.

import pandas as pd

Tip

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

Удобную шпаргалку по pandas можно найти здесь.

Основные объекты pandas#

Своё название библиотека pandas берет от английского термина panel data (панельные данные). Своим названием pandas подсказывает то, как табличные данные представляются в ней. Дело в том, что в pandas существует асимметрия относительно строк и столбцов: чтобы таблицы в pandas работали эффективно, необходимо чтобы в столбцах этой таблицы были данные одинакового типа. В случае панельных данных, такой эффект достигается естественным образом, если поставить в соответствие каждой измеряемой величине столбец, а ось времени отложить вместе со строками.

В pandas есть два основных типа объекта: DataFrame — соответствует таблице, Series — соответствует одному столбцу таблицы, но может существовать и сам по себе.

../../_images/hierarchy.svg

DataFrame#

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

../../_images/dataframe.svg

Больше всего DataFrame напоминает лист (SpreadSheet) из Excel таблицы или SQL таблицу. Объекты DataFrame изменяемы, но с некоторыми ограничениями. Можно изменять значения в ячейках таблицы, добавлять новые столбцы на месте, но для добавления строк придется создать новый объект. Ниже приведен пример таблицы pandas, созданной из python словаря.

import pandas as pd


# Создание таблицы из словаря. 
# Ключи словаря используются в качестве названий столбцов
# Значения используются в качестве содержимого таблицы
df = pd.DataFrame(
    {
        "Name": ["Ivan","Alex","Jane"],
        "Age": [22, 35, 58],
        "Sex": ["male", "male", "female"],
    }
)

df
Name Age Sex
0 Ivan 22 male
1 Alex 35 male
2 Jane 58 female

Series#

Каждый столбец DataFrameSeries.

../../_images/series.svg

Код в ячейке ниже демонстрирует, что созданный ранее DataFrame состоит из трех Series, каждый из которых соответствует одному из столбцов таблицы.

for column_name in df:
    print(f"{column_name=}: {type(df[column_name])}")
    print(df[column_name])
    print("-" * 80)
column_name='Name': <class 'pandas.core.series.Series'>
0    Ivan
1    Alex
2    Jane
Name: Name, dtype: object
--------------------------------------------------------------------------------
column_name='Age': <class 'pandas.core.series.Series'>
0    22
1    35
2    58
Name: Age, dtype: int64
--------------------------------------------------------------------------------
column_name='Sex': <class 'pandas.core.series.Series'>
0      male
1      male
2    female
Name: Sex, dtype: object
--------------------------------------------------------------------------------

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

Объект pandas.Series может существовать и вне таблицы (DataFrame). В целом он похож на одномерный numpy.ndarray, но имеет расширенный индекс. Ниже приводится пример создания pd.Series из списка целых чисел, которому в качестве индекса присваиваются строки.

# Создание pd.Series из списка
# В качестве индекса указывается массив строк
age = pd.Series(
    [22, 35, 58], 
    index=["Ivan", "Alex", "Jane"]
)

age
Ivan    22
Alex    35
Jane    58
dtype: int64

Особенности индексирования в pandas#

Главным отличием от массивов NumPy заключается в том, что объекты в pandas индексированы специальным объектом pd.index. Благодаря этому таблицы и столбцы можно удобно и эффективно индексировать не только целочисленным смещением, как в NumPy, но и строковыми значениями, датами, временными рядами и любыми другими хэшируемыми объектами. В ячейке ниже приводится пример такого индексирования: первое значение 22 столбца age имеет нулевое смещение от начала и записано под меткой "Ivan", что позволяет получить это значение и по метке "Ivan" и по 0 индексу.

print(f"{age.iloc[0]=}") 
print(f"{age.loc['Ivan']=}")
age.iloc[0]=22
age.loc['Ivan']=22

В этом отношении объекты pandas похожи на словари, так как они позволяют быстрый доступ к данным по индексу (как бы ключу), но в отличие от словарей

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

  2. объекты pandas упорядочены, т.е. являются последовательностями и их элементы пронумерованы (словари не являются последовательностями).

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

math = pd.Series({
    "Иванов": 5,
    "Петров": 3,
    "Сидоров": 4,
    "Фадеев": 2
}, name="Математика", dtype=pd.Int8Dtype)

physics = pd.Series({
    "Сидоров": 5,
    "Иванов": 3,
    "Петров": 4
}, name="Физика", dtype=pd.Int8Dtype)

chemistry = pd.Series({
    "Петров": 5,
    "Сидоров": 3,
    "Иванов": 4
}, name="Химия", dtype=pd.Int8Dtype)

for discipline in (math, physics, chemistry):
    print(discipline)
    print("_"*80)
Иванов     5
Петров     3
Сидоров    4
Фадеев     2
Name: Математика, dtype: object
________________________________________________________________________________
Сидоров    5
Иванов     3
Петров     4
Name: Физика, dtype: object
________________________________________________________________________________
Петров     5
Сидоров    3
Иванов     4
Name: Химия, dtype: object
________________________________________________________________________________

Заметьте, что в каждом из столбцов студенты перечислены в разном порядке, а также студент “Фадеев” явился только на первый экзамен по математике, а остальные пропустил. Свести все эти столбцы в единую таблицу pandas очень легко именно из-за индекса.

grades = pd.DataFrame({
    "Математика": math, 
    "Физика": physics, 
    "Химия": chemistry
})

grades
Математика Физика Химия
Иванов 5 3 4
Петров 3 4 5
Сидоров 4 5 3
Фадеев 2 NaN NaN

За счет применения индекса при создании столбцов math, physics и chemistry библиотека pandas сама разобралась с выравниванием значений этих столбцов. Т.к. только в одном из столбцов присутствовала оценка по метке "Фадеев", то библиотека pandas интерпретировало это так, будто бы в остальных столбцах значение по этой метке пропущено, что в pandas моделируется значением NaN.

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

from itertools import product

grades = pd.DataFrame({
    "Математика": [5, 4, 5, 5, 4, 4],
    "Физика": [4, 5, 4, 4, 5, 5],
    "Химия": [5, 5, 5, 5, 5, 5]
    },
    index=pd.MultiIndex.from_tuples(
        list(product(["Иванов", "Петров", "Сидоров"], ["Осень", "Весна"])),
        names=("Фамилия", "Семестр")
    )
)

grades
Математика Физика Химия
Фамилия Семестр
Иванов Осень 5 4 5
Весна 4 5 5
Петров Осень 5 4 5
Весна 5 4 5
Сидоров Осень 4 5 5
Весна 4 5 5

Ниже выводятся индексы созданных ранее объектов pandas.

Индекс таблицы сгенерировался автоматически, т.к. не был явно указан при создании. В таком случае всегда генерируется RangeIndex, который в точности соответствует обычному индексу смещения относительно начала.

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

Индекс столбца Age соответствует переданному при создании списку имён.

age.index
Index(['Ivan', 'Alex', 'Jane'], dtype='object')