Вступление
Кем бы вы ни были — специалистом по обработке данных, инженером данных, аналитиком данных или экспертом по машинному обучению — рано или поздно вам придется иметь дело с табличными данными. Если же вы пользователь 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
, передав имена этих столбцов в качестве аргументов ключевых слов функции и присвоив им значения, которые должны содержать результирующие столбцы. Как видите, для присвоения фактических значений можно использовать один из трех вариантов:
- Скалярное значение с установлением в него всех записей нового столбца.
- Массив (или серию), что приводит к использованию массива (серии) в качестве значений столбца. При этом массив должен иметь ту же длину, что и датафрейм, в котором вызывается метод assign.
- Функцию или, в более общем случае, вызываемый объект, которая принимает датафрейм в качестве единственного входного параметра и возвращает скаляр или серию. Возвращаемая серия должна быть той же длины, что и входной 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
:
- Можно передать функции map коллекционный тип, такой как словарь или серия. При этом значения серии будут использованы в качестве ключей для поиска соответствующих значений из данного типа-коллекции. Любое значение, которое не является частью типа-коллекции, будет отображено на
NaN
илиNone
. Чтобы продемонстрировать это, приведу еще один небольшой пример:pd.Series(range(3)).map({1:'medium', 2:'high'})
. При выполнении этого кода получается серия с элементами[NaN, 'medium', 'high']
. - Можно передать
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 для работы с датафреймами и сериями. Когда вы начнете использовать эти функции, ваш код станет более элегантным, читабельным и эффективным. Поверьте мне. Кроме того, используя эти функции, вы наверняка произведете впечатление на своих коллег, просматривающих ваш код 😎.
Читайте также:
- 10 веских причин изучить Python для занятий наукой о данных
- Ускоряем работу с pandas при помощи modin
- От Pandas к Pyspark
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Simon Hawe, Four Functions to Level up Your Pandas Skills