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

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

В данной статье мы рассмотрим 5 продвинутых функций, узнаем, для чего они нужны, и как ими пользоваться. Теперь обработка данных станет еще веселее!

(1) Настройка опций и параметров

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

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

import pandas as pd

display_settings = {
    'max_columns': 10,
    'expand_frame_repr': True,  # Развернуть на несколько страниц
    'max_rows': 10,
    'precision': 2,
    'show_dimensions': True
}

for op, value in display_settings.items():
    pd.set_option("display.{}".format(op), value)

В коде выше показывается, что Pandas может отображать не более 10 строк и 10 столбцов, а максимально допустимое количество знаков после запятой — 2. Таким образом, при добавлении большого DataFrame в терминал или Jupyter Notebook, он не превратится в мешанину из данных.

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

(2) Объединение DataFrame

Мало кто знает, что DataFrame в Pandas можно объединить несколькими способами. Каждый из этих способов выдает разный результат, поэтому при выборе руководствуйтесь собственными целями. Кроме того, в оба способа заложены наборы параметров для более глубокой настройки слияния. Давайте поговорим о них подробнее.

Конкатенация

Конкатенация относится к самым известным методам объединения фреймов данных; ее можно рассматривать, как «стекинг». И этот самый стекинг бывает вертикальным или горизонтальным.

К примеру, у вас есть огромный набор данных в CSV-формате, и для упрощения обработки имеет смысл разделить его на несколько файлов. Это весьма распространенная практика для огромных наборов данных и довольно часто она называется «фрагментацией» или «шардингом».

При загрузке данных в Pandas вы можете создать единый DataFrame с помощью вертикальной пристыковки фрейма данных из каждого CSV-файла. Предположим, у нас есть 3 фрагмента по 5 миллионов строк в каждом. Тогда после вертикального стекинга наш итоговый фрейм данных будет содержать 15 миллионов строк.

В коде ниже показана вертикальная конкатенация DataFrame в Pandas.

# Вертикальная конкатенация
pd.concat([october_df, november_df, december_df], axis=0)

Похожий результат можно получить при разделении набора данных не по строкам, а по столбцам. В таком случае, из каждого CSV-файла берется по нескольку столбцов со всеми строками. Это чем-то похоже на фрагментацию наборов данных по признакам. Далее выполняется горизонтальный стекинг для объединения столбцов/признаков.

# Горизонтальная конкатенация
pd.concat([features_1to5_df, features_6to10_df, features_11to15_df], axis=1)

Слияние

Слияние — это более сложный и мощный тип объединения DataFrame в стиле SQL. Фреймы данных объединяются по какому-то общему атрибуту.

Допустим, у вас есть два DataFrame с описанием YouTube-канала. Один из фреймов — это список ID пользователей и общее количество времени, которое каждый пользователь провел на канале. Другой фрейм содержит тот же список с ID и количество просмотренных роликов по каждому пользователю. Благодаря слиянию мы сможем объединить два фрейма данных в один по ID пользователя, а затем добавим туда единой строкой ID, затраченное время и количество просмотренных роликов.

Слияние двух DataFrame в Pandas делается через функцию merge. Пример ее использования можно найти ниже. Параметры left и right задают два DataFrame для объединения, а в on отмечается столбец для сопоставления данных.

pd.merge(left=ids_and_time_df,
         right=ids_and_videos_df,
         on="id")

Чтобы еще качественнее имитировать SQL-объединение, воспользуйтесь параметром how. Он позволяет выбрать тип SQL-объединения: внутреннее, внешнее, слева или справа. Подробнее об SQL-объединениях читайте на W3Schools.

(3) Изменение формы DataFrame

Существует несколько способов изменения формы и структуры фреймов данных в Pandas. От простых и легких до мощных и сложных. Давайте рассмотрим три наиболее популярные разновидности. Во всех примерах мы будем работать с набором данных о супергероях.

import pandas as pd

players_data = {'Player': ['Superman', 'Batman', 'Thanos', 'Batman', 'Thanos',
   'Superman', 'Batman', 'Thanos', 'Black Widow', 'Batman', 'Thanos', 'Superman'],
   'Year': [2000,2000,2000,2001,2001,2002,2002,2002,2003,2004,2004,2005],
   'Points':[23,43,45,65,76,34,23,78,89,76,92,87]}
   
df = pd.DataFrame(players_data)

print(df)

"""
         Player  Year  Points
0      Superman  2000      23
1        Batman  2000      43
2        Thanos  2000      45
3        Batman  2001      65
4        Thanos  2001      76
5      Superman  2002      34
6        Batman  2002      23
7        Thanos  2002      78
8   Black Widow  2003      89
9        Batman  2004      76
10       Thanos  2004      92

Транспонирование

Простейший способ. Здесь мы заменяем строки DataFrame на столбцы. Если у вас есть 5 000 строк и 10 столбцов, то после транспонирования вы получите 10 строк и 5 000 столбцов.

import pandas as pd

players_data = {'Player': ['Superman', 'Batman', 'Thanos', 'Batman', 'Thanos',
   'Superman', 'Batman', 'Thanos', 'Black Widow', 'Batman', 'Thanos', 'Superman'],
   'Year': [2000,2000,2000,2001,2001,2002,2002,2002,2003,2004,2004,2005],
   'Points':[23,43,45,65,76,34,23,78,89,76,92,87]}
   
df = pd.DataFrame(players_data)

print(df)

"""
         Player  Year  Points
0      Superman  2000      23
1        Batman  2000      43
2        Thanos  2000      45
3        Batman  2001      65
4        Thanos  2001      76
5      Superman  2002      34
6        Batman  2002      23
7        Thanos  2002      78
8   Black Widow  2003      89
9        Batman  2004      76
10       Thanos  2004      92
11     Superman  2005      87
"""

Groupby

Основное назначение Groupby — это разделение фреймов данных на части по определенным ключам. После разделения DataFrame вы сможете пройтись циклом по каждой из частей или выполнить дополнительные действия.

Например, в коде ниже мы создали фрейм данных по игрокам с их стажем (Years) и количеством очков (Points). Затем с помощью groupby мы разделили DataFrame на несколько частей по полю «игрок» (Player). Таким образом, каждый игрок оказался в собственной группе, показывающей, сколько очков данный игрок набрал за каждый год активности.

groups_df = df.groupby('Player')

for player, group in groups_df:
   print("----- {} -----".format(player))
   print(group)
   print("")
   
### Это выводит следующее
"""
----- Batman -----
   Player  Year  Points
1  Batman  2000      43
3  Batman  2001      65
6  Batman  2002      23
9  Batman  2004      76

----- Black Widow -----
        Player  Year  Points
8  Black Widow  2003      89

----- Superman -----
      Player  Year  Points
0   Superman  2000      23
5   Superman  2002      34
11  Superman  2005      87

----- Thanos -----
    Player  Year  Points
2   Thanos  2000      45
4   Thanos  2001      76
7   Thanos  2002      78
10  Thanos  2004      92

"""

Стекинг

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

Лучше всего все это показано в коде ниже.

df = df.stack()

print(df)

"""
0   Player       Superman
    Year             2000
    Points             23
1   Player         Batman
    Year             2000
    Points             43
2   Player         Thanos
    Year             2000
    Points             45
3   Player         Batman
    Year             2001
    Points             65
4   Player         Thanos
    Year             2001
    Points             76
5   Player       Superman
    Year             2002
    Points             34
6   Player         Batman
    Year             2002
    Points             23
7   Player         Thanos
    Year             2002
    Points             78
8   Player    Black Widow
    Year             2003
    Points             89
9   Player         Batman
    Year             2004
    Points             76
10  Player         Thanos
    Year             2004
    Points             92
11  Player       Superman
    Year             2005
    Points             87

"""

(4) Работа с данными о дате и времени

Библиотека Datetime — это неотъемлемая часть Python. Всякий раз, встретив данные о дате или времени, вы обращаетесь к Datetime. К счастью для нас, Pandas придумала собственные возможности для использования объектов Datetime.

Давайте рассмотрим их на примере. В коде ниже мы создаем DataFrame с 4 столбцами (день, месяц, год, нужные данные), а затем сортируем их по году и месяцу. Как видите, получается слишком громоздко: для хранения данных используется целых 3 столбца, тогда как по факту календарная дата — это всего лишь одно значение.

from itertools import product
import pandas as pd
import numpy as np

col_names = ["Day", "Month", "Year"]

df = pd.DataFrame(list(product([10, 11, 12], [8, 9], [2018, 2019])),
                   columns=col_names)

df['data'] = np.random.randn(len(df))

df = df.sort_values(['Year', 'Month'], ascending=[True, True])

print(df)


"""
    Day  Month  Year      data
0    10      8  2018  1.685356
4    11      8  2018  0.441383
8    12      8  2018  1.276089
2    10      9  2018 -0.260338
6    11      9  2018  0.404769
10   12      9  2018 -0.359598
1    10      8  2019  0.145498
5    11      8  2019 -0.731463
9    12      8  2019 -1.451633
3    10      9  2019 -0.988294
7    11      9  2019 -0.687049
11   12      9  2019 -0.067432
"""

Навести здесь порядок нам поможет datetime.

Pandas придумала отличную функцию to_datetime(), которая сжимает несколько столбцов DataFrame и преобразует их в единый объект Datetime. Сразу при задании нужного формата нам становятся доступны все гибкие возможности библиотеки Datetime.

Чтобы воспользоваться функцией to_datetime(), передадим ей все данные о дате из соответствующих столбцов (Day, Month, Year). После преобразования этих данных в формат Datetime, остальные столбцы нам больше не потребуются. Если хотите, можете от них избавиться. Вот, как все работает:

from itertools import product
import pandas as pd
import numpy as np

col_names = ["Day", "Month", "Year"]

df = pd.DataFrame(list(product([10, 11, 12], [8, 9], [2018, 2019])),
                   columns=col_names)

df['data'] = np.random.randn(len(df))

df = df.sort_values(['Year', 'Month'], ascending=[True, True])

df.insert(loc=0, column="date", value=pd.to_datetime(df[col_names]))
df = df.drop(col_names, axis=1).squeeze()

print(df)

"""
         date      data
0  2018-08-10 -0.328973
4  2018-08-11 -0.670790
8  2018-08-12 -1.360565
2  2018-09-10 -0.401973
6  2018-09-11 -1.238754
10 2018-09-12  0.957695
1  2019-08-10  0.571126
5  2019-08-11 -1.320735
9  2019-08-12  0.196036
3  2019-09-10 -1.717800
7  2019-09-11  0.074606
11 2019-09-12 -0.643198
"""

(5) Классификация элементов по группам

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

Взгляните на код ниже. Здесь приводится список продуктов, которые хотелось бы классифицировать:

import pandas as pd

foods = pd.Series(["Bread", "Rice", "Steak", "Ham", "Chicken",
                       "Apples", "Potatoes", "Mangoes", "Fish",
                       "Bread", "Rice", "Steak", "Ham", "Chicken",
                       "Apples", "Potatoes", "Mangoes", "Fish",
                       "Apples", "Potatoes", "Mangoes", "Fish",
                       "Apples", "Potatoes", "Mangoes", "Fish",
                       "Bread", "Rice", "Steak", "Ham", "Chicken",
                       "Bread", "Rice", "Steak", "Ham", "Chicken",
                       "Bread", "Rice", "Steak", "Ham", "Chicken",
                       "Apples", "Potatoes", "Mangoes", "Fish",
                       "Apples", "Potatoes", "Mangoes", "Fish",
                       "Apples", "Potatoes", "Mangoes", "Fish",
                       "Bread", "Rice", "Steak", "Ham", "Chicken",
                       "Bread", "Rice", "Steak", "Ham", "Chicken",])

groups_dict = {
    "Protein": ["Steak", "Ham", "Chicken", "Fish"],
    "Carbs": ["Bread", "Rice", "Apples", "Potatoes", "Mangoes"]
}

Здесь мы выносим наш список в Series и создаем словарь с нужной нам классификацией Protein или Carbs. Это простейший пример. Но если бы эта Series была достаточно крупной (например, в 1 000 000 элементов длиной), то прохождение по ней циклом оказалось бы совершенно не разумным решением.

В таком случае, вместо обычного цикла for, лучше написать отдельную функцию, которая будет использовать встроенную Pandas-функцию map()для оптимизации процесса. В коде ниже все подробно расписано.

def membership_map(pandas_series, groups_dict):
    groups = {x: k for k, v in groups_dict.items() for x in v}
    mapped_series = pandas_series.map(groups)
    return mapped_series
    
mapped_data = membership_map(foods, groups_dict)
print(list(mapped_data))

В этой функции мы проходимся циклом по всему словарю и создаем новый словарь, в котором ключами служат все вариации элементов из Series, а значениями являются признаки Protein или Carbs. Далее мы просто применяем встроенную функцию map() и классифицируем все значения из Series.

Вот, что у нас получается:

['Carbs', 'Carbs', 'Protein', 'Protein', 'Protein', 'Carbs', 'Carbs', 'Carbs', 'Protein', 'Carbs', 'Carbs', 'Protein', 'Protein', 'Protein', 'Carbs', 'Carbs', 'Carbs', 'Protein', 'Carbs', 'Carbs', 'Carbs', 'Protein', 'Carbs', 'Carbs', 'Carbs', 'Protein', 'Carbs', 'Carbs', 'Protein', 'Protein', 'Protein', 'Carbs', 'Carbs', 'Protein', 'Protein', 'Protein', 'Carbs', 'Carbs', 'Protein', 'Protein', 'Protein', 'Carbs', 'Carbs', 'Carbs', 'Protein', 'Carbs', 'Carbs', 'Carbs', 'Protein', 'Carbs', 'Carbs', 'Carbs', 'Protein', 'Carbs', 'Carbs', 'Protein', 'Protein', 'Protein', 'Carbs', 'Carbs', 'Protein', 'Protein', 'Protein']

Заключение

Вот и все. Теперь вы узнали о 5 продвинутых возможностях Pandas и вариантах их применения.

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


Перевод статьи George Seif: 5 Advanced Features of Pandas and How to Use Them

Предыдущая статьяПишем интерфейсы командной строки в Python как профи
Следующая статьяMVVM на Android с компонентами архитектуры + библиотека Koin