Гайд по обработке данных с помощью Pandas: часть первая

Разбираем, как начать работу с Pandas. В этой части самые базовые приёмы: чтение и запись данных, индексирование, агрегация и другие основы.

Обложка: Гайд по обработке данных с помощью Pandas: часть первая

Pandas — это библиотека на Python, предназначенная для обработки и анализа структурированных табличных данных. С её помощью можно фильтровать, сортировать, агрегировать и преобразовывать данные, а также интегрироваться с различными источниками для чтения и записи. Благодаря такому богатому функционалу, эффективности и удобству использования, Pandas — один из наиболее популярных инструментов в области анализа данных.

Руководитель группы видеоаналитики MTS AI Андрей Дугин рассказывает, как начать работу с Pandas и избежать распространённых ошибок.

Где и для чего применяется Pandas

Pandas скорее академический исследовательский инструмент: для промышленных масштабов производительность у библиотеки относительно низкая, управление памятью не самое эффективное и нет параллелизма. Тем не менее, библиотека отлично подходит дата-сайентистам и аналитикам для прототипирования и исследования гипотез в нересурснозатратных проектах. Её можно применить практически в любой области, где требуется обработка и анализ табличных данных или временных рядов:

  • Предиктивная аналитика для агрегаторов. Например, на сайтах недвижимости. Pandas поможет структурировать массив данных по квартирам: этаж, площадь, количество комнат, год постройки дома и так далее. На этих данных можно строить модели машинного обучения и предсказывать, сколько будет стоить недвижимость.
  • Финансовый анализ. Pandas пригодится для анализа динамики цен акций, котировок валют, индексов и так далее. Это помогает при прогнозировании рыночных трендов, определении рисков и оценке инвестиционных стратегий.
  • Маркетинговый анализ. Проанализировав с помощью Pandas данные о продажах, потребительском поведении, рекламных кампаниях можно определять оптимальные стратегии маркетинга.
  • Исследования в области биоинформатики. Pandas помогает анализировать и обрабатывать биологические данные для выявления закономерностей, например, в болезнях.
  • Обработка данных сенсоров и IoT. С помощью Pandas можно обрабатывать и анализировать данные сенсоров, полученных от устройств IoT, чтобы управлять системами, мониторить и диагностировать их.

Рассмотрим некоторые базовые приёмы работы с Pandas на основе классического датасета Titanic. Его используют студенты, изучающие машинное обучение, для предсказания шансов пассажиров на выживание в зависимости от их пола, возраста, класса каюты и других факторов. Подробнее почитать о значении полей можно в описании датасета на Kaggle.

Начало работы с Pandas

DISCLAIMER

  • Поскольку примеры кода и вывод выполняются в Jupyter Notebook, то для отображения результата нам не обязательно оборачивать выражения в функцию print(), однако это может понадобиться в других IDE.
  • По этой же причине при демонстрации мы иногда не модифицируем исходные данные (не делаем присваивание A = B, а показываем только правую часть выражения; используем inplace=False), а просто выводим результат.

Установка и импорт

Pandas легко установить через стандартный пакетный менеджер Python, используя команду pip install pandas.

После установки следует импортировать саму библиотеку. Поскольку Pandas строится на базе библиотеки NumPy — инструментария для работы с многомерными массивами — для удобства рекомендуется импортировать и её. Она также будет установлена автоматически в процессе установки Pandas.

			import numpy as np
import pandas as pd
		

Структуры хранения данных

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

Структур хранения данных в Pandas две — Series и DataFrame.

  • Series — это одномерный массив данных с метками. Он может хранить различные типы данных, включая числа, строки и произвольные объекты Python. Каждому элементу в Series соответствует метка, доступ к которой можно получить через атрибут index.
			series = pd.Series([1, 2, 3], index=["a", "b", "c"], dtype=np.uint8)
series
		
Гайд по обработке данных с помощью Pandas: часть первая_19
Здесь и далее будет приводиться вывод в Jupyter Notebook
			series.index
		
Гайд по обработке данных с помощью Pandas: часть первая_21

Есть проводить аналогию со словарём dict, то индекс — это ключи словаря, а сам массив данных — значения, к которым можно получить доступ по ключу:

			series["b"]  # Получаем доступ по метке, как по ключу в словаре
		
Гайд по обработке данных с помощью Pandas: часть первая_24
  • DataFrame — это двумерная структура данных, представляющая собой таблицу с метками для строк и столбцов. Каждый столбец в DataFrame является объектом типа Series. Вместе они формируют двумерную таблицу с общим индексом. В DataFrame присутствуют две оси индексации: index для строк и columns для столбцов. Метки столбцов — это их названия.
			dataframe = pd.DataFrame([[1, "Ivan", 5.0], [2, "Sergey", 4.3], [3, "Dmitry", 4.5]], columns=["#", "Name", "Score"])
dataframe
		
Гайд по обработке данных с помощью Pandas: часть первая_27
			dataframe["Name"]  # Колонка — это объект Series
		
Гайд по обработке данных с помощью Pandas: часть первая_29
			dataframe["Name"].name  # У объекта Series есть собственное имя
		
Гайд по обработке данных с помощью Pandas: часть первая_31

Операции чтения и записи данных

С помощью Pandas можно читать и записывать данные из различных источников: баз данных, файлов в форматах CSV, Excel, JSON и тому подобных. Для каждого типа данных существуют специализированные функции: read_csv(), read_excel() и другие вида read_*().

			titanic = pd.read_csv("titanic.csv")
titanic.head()
		
Гайд по обработке данных с помощью Pandas: часть первая_35

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

			from urllib.parse import quote
url = quote("https://ru.wikipedia.org/wiki/Таблица", safe=":/")  # Кодируем кириллицу
pd.read_html(url)[0]  # Берём первую таблицу из списка всех найденных на веб-странице
		
Гайд по обработке данных с помощью Pandas: часть первая_38

Функции чтения принимают много параметров, так можно настраивать все необходимые опции, включая явное задание типов данных или соответствующих функций-конвертеров. Это позволяет предобработать данные или преобразовать их типы к более эффективным ещё на этапе чтения из источника.

Записать данные в файл так же просто, как и прочитать — используйте семейство методов .to_*(), например, .to_excel():

			titanic.to_excel("titanic.xlsx")
		

Первичное исследование данных

После того как данные загружены из источника, следует получить общее представление о них.

Первые несколько строк датафрейма можно получить с помощью метода .head(), а последние — .tail():

			titanic.head(5)
		
Гайд по обработке данных с помощью Pandas: часть первая_46

Тип данных object чаще всего соответствует строковым значениям:

			titanic.dtypes
		
Гайд по обработке данных с помощью Pandas: часть первая_49

Pandas позволяет автоматически подобрать наиболее эффективные представления с помощью метода .convert_dtypes():

			titanic.convert_dtypes().dtypes  # Строковые значения теперь представлены типом string, а не object
		
Гайд по обработке данных с помощью Pandas: часть первая_52

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

			titanic.info()
		
Гайд по обработке данных с помощью Pandas: часть первая_55
Например, в датасете 891 строка, однако количество ненулевых (non-null) значений в колонках Age, Cabin и Embarked меньше, значит, некоторые данные отсутствуют

Некоторую статистику по числовым столбцам можно собрать с помощью метода .describe():

			titanic.describe()
		
Гайд по обработке данных с помощью Pandas: часть первая_58

Количество различных значений можно посчитать с помощью .value_counts() — как для категориальных, так и для непрерывных данных:

			titanic["Survived"].value_counts(normalize=True) # Какой процент пассажиров выжил?
		
Гайд по обработке данных с помощью Pandas: часть первая_61
			titanic["Pclass"].value_counts(dropna=False)  # Сколько пассажиров было в каждом классе?
		
Гайд по обработке данных с помощью Pandas: часть первая_63

Индексирование

Операции обращения к элементам данных называются индексированием.

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

			titanic.set_index("Name", inplace=True)  # Производим изменения на месте — inplace
		
			titanic.head()
		
Гайд по обработке данных с помощью Pandas: часть первая_69

Элементы индекса называются метками (лейблами):

			titanic.index  # Это вертикальный индекс; для списка используйте titanic.index.tolist()
		
Гайд по обработке данных с помощью Pandas: часть первая_72

Названия колонок — тоже метки:

			titanic.columns  # Это горизонтальный индекс
		
Гайд по обработке данных с помощью Pandas: часть первая_75

К колонкам можно обращаться по их названиям:

			titanic["Ticket"]
		
Гайд по обработке данных с помощью Pandas: часть первая_78

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

			titanic.Ticket;  # То же самое, что titanic["Ticket"]
		

Метод .loc[] есть и у Series, и у DataFrame. Он позволяет обращаться к данным по меткам:

			titanic.loc["Heikkinen, Miss. Laina", "Ticket"]  # Метод объекта DataFrame
		
Гайд по обработке данных с помощью Pandas: часть первая_83
			titanic["Ticket"].loc["Heikkinen, Miss. Laina"]  # Метод объекта Series
		
Гайд по обработке данных с помощью Pandas: часть первая_85
			titanic.loc["Heikkinen, Miss. Laina", ["Ticket", "Age"]]  # Можно выбрать несколько строк или колонок
		
Гайд по обработке данных с помощью Pandas: часть первая_87

Метод .iloc[] работает аналогично, но вместо меток используются номера строк или столбцов:

			titanic.iloc[2, 7]  # Нумерация начинается с нуля
		
Гайд по обработке данных с помощью Pandas: часть первая_90
			titanic.iloc[0:3, 2:8]
		
Гайд по обработке данных с помощью Pandas: часть первая_92

С помощью .at[] можно обратиться к конкретной ячейке данных — как в Excel:

			titanic.at["Heikkinen, Miss. Laina", "Ticket"]
		
Гайд по обработке данных с помощью Pandas: часть первая_95

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

			series = pd.Series(2 ** np.arange(10))
series
		
Гайд по обработке данных с помощью Pandas: часть первая_98

Попытка попарно посчитать отношение следующего элемента к предыдущему приведёт к «странному» результату:

			series[1:] / series[:-1]
		
Гайд по обработке данных с помощью Pandas: часть первая_101

На самом деле мы хотели получить следующее:

			series[1:] / series[:-1].values  # У массива .values уже нет индекса, и элементы сопоставляются просто последовательными парами
		
Гайд по обработке данных с помощью Pandas: часть первая_104

Объединение данных

Для объединения нескольких датафреймов можно использовать близкие по смыслу функции: merge(), concat() и join(). Вот несколько базовых примеров:

			df1 = pd.DataFrame({"key": ["A", "B", "C", "D"], "value": [1, 2, 3, 4]})
df1
		
Гайд по обработке данных с помощью Pandas: часть первая_108
			df2 = pd.DataFrame({"key": ["B", "D", "E", "F"], "value": [5, 6, 7, 8]})
df2
		
Гайд по обработке данных с помощью Pandas: часть первая_110
			pd.merge(df1, df2, on="key", how="inner")
		
Гайд по обработке данных с помощью Pandas: часть первая_112
			pd.concat([df1, df2], axis=0, ignore_index=True)
		
Гайд по обработке данных с помощью Pandas: часть первая_114
			df1.join(df2, how="inner", lsuffix="_1", rsuffix="_2")
		
Гайд по обработке данных с помощью Pandas: часть первая_116

Агрегация данных

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

  • groupby() — для группировки и агрегации данных с большой гибкостью;
  • pivot_table() — для создания сводных таблиц с возможностью применения множественных агрегаций;
  • crosstab() — для подсчёта частоты встречаемости категорий.

Рассчитаем средний возраст пассажиров с разбиением по полу:

			titanic.groupby("Sex").Age.mean()
		
Гайд по обработке данных с помощью Pandas: часть первая_122

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

			pd.pivot_table(titanic, values="Survived", index="Sex", columns="Pclass", aggfunc="mean")
		
Гайд по обработке данных с помощью Pandas: часть первая_125

Посчитаем, сколько человек каждого пола было в трёх классах кают:

			pd.crosstab(titanic.Sex, titanic.Pclass)
		
Гайд по обработке данных с помощью Pandas: часть первая_128

Групповые операции

При обработке данных новички часто используют циклы для итераций по строкам или ячейкам. Такой код выполняется медленно. Лучше использовать методы .apply(), .map(), .transform(), которые работают быстрее за счёт векторизации.

			# Функция, которая в зависимости от возраста будет возвращать child/adult/senior
def categorize_age(age):
  if age < 18:
    return"child"
  if age < 60:
    return"adult"
  else:       
    return "senior"

# Создадим новую колонку, применив нашу функцию к существующей
titanic["Age_category"] = titanic["Age"].apply(categorize_age)
titanic[["Age", "Age_category"]].head()
		
Гайд по обработке данных с помощью Pandas: часть первая_132

Кстати, такое же разбиение на возрастные группы можно выполнить с помощью функции pd.cut():

			pd.cut(titanic.Age, bins=[0, 18, 60, titanic.Age.max()], labels=["child", "adult", "senior"]).head()
		
Гайд по обработке данных с помощью Pandas: часть первая_135

Работа с пропущенными данными

В реальных датасетах часть данных может отсутствовать. В библиотеке Pandas эти пропуски обычно представлены как NaN (Not a Number) в ячейках DataFrame или Series. Чтобы с ними работать можно использовать ключевые методы:

  • .isna() — идентифицирует отсутствующие значения и возвращает булевую маску, где True указывает на пропущенные данные;
  • .fillna() — позволяет заполнить отсутствующие значения с помощью указанных значений или методов интерполяции;
  • .dropna() — удаляет строки или столбцы, содержащие отсутствующие значения.

Метод .isna() есть как у DataFrame, так и у Series, а результатом его вызова будет булевая маска того же класса:

			titanic.isna().head()
		
Гайд по обработке данных с помощью Pandas: часть первая_141
			titanic.loc[titanic.Age.isna()]  # Найдём строки, в которых отсутствует возраст пассажира
		
Гайд по обработке данных с помощью Pandas: часть первая_143
			titanic.dropna(subset=["Age"], inplace=False) # Удалим строки, в которых отсутствует возраст пассажира
		
Гайд по обработке данных с помощью Pandas: часть первая_145

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

Например, можно заменить их средними значениями в столбце:

			titanic.loc[titanic.Age.isna(), "Age"] = titanic.Age.mean()
		

Или заполнить предыдущим, либо последующим значением:

			titanic.Age.fillna(method="ffill", inplace=True)
		
Методы генерации и заполнения отсутствующих значений называются импутингом. В библиотекеscikit-learnесть модульsklearn.impute, в котором представлены несколько классов импутеров, основанных на различных алгоритмах.

Визуализация данных

Pandas интегрирована с библиотекой Matplotlib — инструментом для создания двумерных и трёхмерных графических представлений данных. В Pandas можно либо явно использовать её функции для визуализации данных, либо применять встроенные методы, такие как .plot() и .hist(). Второй вариант облегчает построение графиков, автоматически определяя наилучший способ визуализации данных.

			import matplotlib.pyplot as plt
titanic.loc[titanic.Sex == "male", "Age"].hist(bins=10);
		
Гайд по обработке данных с помощью Pandas: часть первая_155

То же самое можно сделать через прямой вызов функций matplotlib:

			plt.hist(titanic[titanic.Sex == "male"].Age, bins=10);
plt.grid(True)
plt.title("Распределение пассажиров-мужчин по возрасту");
plt.xlabel("Возрастной диапазон");
plt.ylabel("Количество человек");
		
Гайд по обработке данных с помощью Pandas: часть первая_158

Работа с временными рядами

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

Допустим, у нас есть статистика перевозки пассажиров некой авиакомпании. Сгенерируем эти данные по месяцам:

			# Сгенерируем диапазон дат с частотой 1 месяц
date_rng = pd.date_range(start="1949-01-01", end="1960-12-01", freq="MS")

# Создадим соответствующий датафрейм
df = pd.DataFrame(date_rng, columns=["date"])

# Случайным образом сгенерируем колонку с числом пассажиров в каждом месяце
df["passengers"] = np.random.randint(100, 500, size=len(date_rng))

# Сделаем колонку с датами индексной
df.set_index("date", inplace=True)

df.head()
		
Гайд по обработке данных с помощью Pandas: часть первая_163

А теперь рассчитаем среднемесячное количество пассажиров по каждому году:

			df_downsampled = df.resample("A").mean()
		
			plt.figure(figsize=(10, 6))
plt.plot(df.index, df["passengers"], label="Исходные данные")
plt.plot(df_downsampled.index, df_downsampled["passengers"], label="Среднее по годам", linestyle="--")
plt.xlabel("Год")
plt.ylabel("Количество пассажиров")
plt.grid(True)
plt.legend()
plt.show()
		
Гайд по обработке данных с помощью Pandas: часть первая_167

Применим скользящее окно и рассчитаем среднее количество пассажиров за последние 12 месяцев:

			df_rolled = df.rolling(12).mean()
		
			df_rolled.head(15)
		
Гайд по обработке данных с помощью Pandas: часть первая_171

В первых 11 строках по умолчанию появились значения NaN, так как количество проанализированных строк меньше размера окна. Это поведение можно изменить, задав параметр min_periods — минимальное количество строк, для которых определён результат:

			df.rolling(12, min_periods=1).mean().head()
		
Гайд по обработке данных с помощью Pandas: часть первая_174

В заключение

В этой части статьи мы рассмотрели, как использовать Pandas для разных базовых задач. Во второй части разберём, как сделать работу с Pandas эффективнее, узнаем, какие есть альтернативы для этой библиотеки и какие источники можно почитать, чтобы ещё лучше в ней ориентироваться. Следите за обновлениями!