Вступление

Кем бы вы ни были — специалистом по обработке данных, инженером данных, аналитиком данных или экспертом по машинному обучению — рано или поздно вам придется иметь дело с табличными данными. Если же вы пользователь Python, вам просто не обойтись без библиотеки табличных данных — Pandas 🐼. Поскольку табличные данные вездесущи, Pandas, пожалуй, является одной из самых используемых библиотек среди всех нас, работающих с данными.

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

Вот почему мне хотелось бы внести свой вклад в изменение этой ситуации. Я помогу вам повысить уровень знаний о Pandas, чтобы ваш код стал более приятным для чтения и более производительным! Я познакомлю вас с четырьмя лучшими функциями Pandas, а именно: assign, map, query и explode

Великолепная четверка

Для изучения функционала Pandas нам понадобятся данные. В приведенных ниже примерах будет использован знаменитый набор данных Iris. Если вы не хотите устанавливать специальную библиотеку только для получения данных, просто введите data = pd.read_csv('https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv'). Этого будет достаточно.

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

Assign

Первый метод, который будет нами рассмотрен, — assign(присваивание).

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

import pandas as pd
data = pd.read_csv("https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv")

grouped = (
data.groupby("species")
.agg(["mean"])
.assign(
fancy_column=lambda df: df["sepal_width"]["mean"]
/ df["sepal_width"]["mean"].mean(),
useless_column="I am useless"
)
)

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

  1. Скалярное значение с установлением в него всех записей нового столбца.
  2. Массив (или серию), что приводит к использованию массива (серии) в качестве значений столбца. При этом массив должен иметь ту же длину, что и датафрейм, в котором вызывается метод assign.
  3. Функцию или, в более общем случае, вызываемый объект, которая принимает датафрейм в качестве единственного входного параметра и возвращает скаляр или серию. Возвращаемая серия должна быть той же длины, что и входной DataFrame.

Особенностью последнего варианта является то, что входным датафреймом для вызываемой таблицы является тот, для которого был вызван метод assign. Что это означает и почему это так удобно? Например, если вы сначала сделаете groupby c агрегациями, а затем вызовете assign, то сможете использовать агрегаты в своей функции для вычисления значений нового столбца. Вы можете видеть это в коде примера. Как по мне, очень компактно и в то же время выразительно.

В этом же примере показано, как создать несколько столбцов с помощью одного вызова assign. Нужно просто передать несколько аргументов ключевых слов — по одному для каждого создаваемого столбца. Самое замечательное в этом то, что последующие присваивания могут использовать ранее созданные или измененные столбцы. Для наглядности выполните df.assign(x_axis=np.random.rand(len(df)), y_axis=lambda in_: in_.x_axis ** 2). Это пример того, как второе присваивание использует столбец, созданный в первом.

Напоследок хочу упомянуть о маленькой хитрости, которая позволяет создавать столбцы, не имеющие собственных имен переменных Python. Для этого необходимо создать словарь с именами столбцов в качестве ключей и операциями присваивания в качестве значений и передать его в распакованном виде методу assign 😵. Это не так сложно, как может показаться. Чтобы было понятнее, обратите внимание на пример, демонстрирующий создание двух новых столбцов с недопустимыми именами переменных Python: df.assign(**{'X-Axis': np.random.rand(len(df)), 'Y-Axis': lambda in_: in_['X-Axis'] ** 2}) .

Map

Еще одна удобная функция, которая может быть вызвана на объектах Pandas Series, — это функция map(преобразование). С помощью map можно заменить любое значение в серии другим значением. Вот простой пример, в котором произведено несколько подстановок с использованием вызова функции assign 😃.

import pandas as pd
data = pd.read_csv(
"https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv"
).assign(
to_big_to_small=lambda df: (df.sepal_width > 3).map({True: "Too Big", False: "Perfect"}),
inverted_name=lambda df: df.species.map(lambda name: name[::-1]),
)

В этом коде используется map для условной установки записей столбца to_big_to_smallв две строки. Кроме того, мы используем map для добавления нового столбца inverted_name, который содержит инвертированное название каждого вида. Конечно, этот пример не очень полезен с точки зрения содержания, хотя иллюстрирует два варианта использования map:

  1. Можно передать функции map коллекционный тип, такой как словарь или серия. При этом значения серии будут использованы в качестве ключей для поиска соответствующих значений из данного типа-коллекции. Любое значение, которое не является частью типа-коллекции, будет отображено на NaN или None. Чтобы продемонстрировать это, приведу еще один небольшой пример: pd.Series(range(3)).map({1:'medium', 2:'high'}). При выполнении этого кода получается серия с элементами [NaN, 'medium', 'high'].
  2. Можно передать map функцию или вызываемый объект, который принимает скаляр в качестве входных данных и возвращает скалярное значение. После передачи эта функция будет поэлементно применена к каждой записи серии, используя каждое значение в качестве входного аргумента. Именно это отражено в строке 6, где инвертированы названия видов цветов.

Примечание: не используйте этот подход для выполнения каких-либо математических операций. Гораздо эффективнее применять его непосредственно к серии, так как в этом случае используется векторизация NumPy.

Наконец, если вы хотите выполнять операции над элементами не одной серии, а всего датафрейма, можете использовать функцию applymap, которая ведет себя так же, как и map.

Query

После изучения методов манипулирования контейнерами данных перейдем к рассмотрению способов извлечения данных из датафреймов. Одной из полезных функций для извлечения строк является функция query (запрашивание). Для начала приведу пример кода:

import pandas as pd
data = pd.read_csv("https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv")
length_th = 0.5
filtered_data = (data
.assign(**{"PW Squared": data["petal_width"] ** 2})
.query("`PW Squared` > 0.4 and petal_length > @length_th and species != 'setosa'")
)

Что тут сделано? Выбрано подмножество всех строк с помощью логического выражения фильтра, которое передано в query. Выражение фильтра — это строка, которая может содержать различные логические сравнения, такие как >,<,>=,<=,!=,==, и другие, для сопоставления столбцов датафрейма. Затем функция query оценивает это выражение и возвращает все строки, в которых выражение имеет истинное значение True.

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

Если требуется сослаться на какую-либо переменную из внешней области, можете сделать это, добавив к ее имени символ @, как показано для переменной length_th.

Стоит отметить, что можно выполнить те же действия, применяя операции индексирования. Однако при использовании query код получается не столь многословным и более приятным для чтения.

Explode

У последней функции, которая будет рассмотрена в этом посте, самое драматичное название — explode (разбиение) 💣. Для чего она нужна? Функция explode полезна в тех случаях, когда записи столбца похожи на список: она позволяет создавать новую строку для каждой записи в этих списках. При этом все остальные записи строки будут продублированы, как и индекс. Вызывая эту функцию, вы передаете имя столбца, содержащего спископодобные объекты. Для наглядности снова приведу пример:

import pandas as pd
n_rows = 3
result = pd.DataFrame(
{"a": [list(range(1 + i ** 2)) for i in range(n_rows)], "b": list(range(n_rows))}
).explode("a").astype({'a':int})

В этом примере видно, что сначала был создан датафрейм с 3 строками. Записи столбца a изначально представляли собой списки длиной 1, 2 и 4 соответственно. После разложения датафрейма по столбцу a получился датафрейм размером 1+2+4 = 7. Рекомендую скопировать данный пример и поэкспериментировать с ним.

Теперь вам должно быть понятно, почему эта функция называется explode: при ее использовании размер датафрейма может увеличиться. Предположим, у вас есть датафрейм с 500 000 строками и столбец, в котором хранятся списки из 100 записей. Вызвав функцию explode для этого столбца, вы в итоге получите 50 000 000 строк. Это может стать проблемой. Поэтому мой совет таков: используйте explode с осторожностью!

Важно понимать, что explode не изменяет тип данных столбца, к которому он применяется. Поскольку Pandas хранит спископодобные значения с текстовым объектом, это означает, что после разбиения столбца он по-прежнему будет иметь текстовой объект. Если нужно изменить его, придется сделать это явно, используя метод astype, как показано в коде примера.

Подведение итогов

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

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Simon Hawe, Four Functions to Level up Your Pandas Skills

Предыдущая статьяСтруктуры данных: двусвязный (двунаправленный) список
Следующая статьяЯзык C: операторы