Пошаговые примеры и различные варианты использования Pandas GroupBy

Введение

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

Когда я впервые изучал groupby, я был немного сбит с толку, поскольку, похоже, groupby можно было использовать по-разному. Цель этого поста — объяснить, как работает groupby, на конкретных примерах и в различных вариантах использования.

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

Данные

Чтобы проиллюстрировать примеры, связанные с groupby, полезно сначала иметь какие-то данные. Здесь я предоставляю изготовленный на заказ DataFrame, состоящий из некоторой информации о нескольких странах. Здесь у нас есть Соединенные Штаты и Канада в Северной Америке; Соединенное Королевство, Франция и Германия в Европе; и Китай, Япония и Корея в Азии.

import pandas as pd
df = pd.DataFrame({
    "country" : ["United States", "Canada", "United Kingdom", "France", "Germany", "China", "Japan", "South Korea"],
    "continent" : ["North America", "North America", "Europe", "Europe", "Europe", "Asia", "Asia", "Asia"],
    "population" : [332722557, 38711108, 67081234, 67853000, 83222442, 1412600000, 125502000, 51745000],
    "area" : [3796742, 3855100, 93628, 247368, 137882, 3705407, 145937, 38690],
    "population percentage of world": [4.18, 0.487, 0.843, 0.853, 1.05, 17.8, 1.58, 0.650]
})
df

Вот как выглядит полученный DataFrame:

В этом DataFrame единица площади находится в квадратных милях, а процент населения мира в процентах.

Группировать по объектам

Теперь, когда у нас есть данные, давайте начнем изучать возможности groupby.

Допустим, мы хотим разделить наши данные по континентам. Мы можем начать с выполнения:

df.groupby("continent")

Обратите внимание, что выполнение этого в ячейке jupyter печатает объект groupby, хранящийся по определенному адресу памяти. Что происходит за капотом, так это то, что объект groupby сгруппировал DataFrame на основе указанного нами столбца, continent.

GroupBy с функциями агрегирования

Чтобы получить что-то более полезное, нам нужно указать groupby, что делать после того, как мы сгруппируем наши данные.

Приведем пример. Предположим, мы хотим напечатать количество записей (строк) на каждом континенте и сохранить все столбцы. Как мы могли этого добиться? Вот самый простой способ сделать это:

df.groupby("continent").count()

Здесь count() принимает все записи, принадлежащие каждой группе (континенту), и подсчитывает количество записей, эффективно объединяя записи каждой группы в число, представляющее количество. count() является примером функции агрегирования. Другие популярные функции агрегирования включают sum(), min() и max(), которые выполняют соответствующие операции над каждой группой. Вот список всех функций агрегации, поддерживаемых объектом groupby.

GroupBy с функциями агрегирования — мультииндекс

Использование одной функции агрегации для объекта groupby — это прекрасно, но что, если вам нужны разные функции агрегации для каждого столбца? Например, скажем, для каждого континента мы хотим подсчитать страну, а также минимальное, максимальное и общее население. Как мы можем этого добиться?

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

df_agg = df.groupby("continent").agg({"country": "count", "population": ["sum", "min", "max"]})
df_agg

Полученный DataFrame выглядит следующим образом:

Это то, чего мы хотим. Как видите, теперь у нас есть подсчет по стране и три статистики по населению.

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

print(df_agg.columns)

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

print(df_agg[("population", "max")])

GroupBy с функциями агрегирования — единый индекс

Если вы не хотите иметь дело с несколькими индексами, а хотите вернуться к одному индексу, вы можете добиться этого, указав новые имена столбцов в качестве аргументов в функции agg:

df_agg_single = df.groupby("continent").agg(country_count = ("country", "count"), population_sum = ("population", "sum"), population_min = ("population", "min"), population_max = ("population", "max"))
df_agg_single

Это даст результат с одним индексом с новыми именами столбцов, указанными вами:

Мы можем проверить это, вернувшись назад и распечатав столбцы и получив доступ через одиночные индексы.

print(df_agg_single.columns)

print(df_agg_single["population_max"])

GroupBy с функцией применения

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

Например, допустим, вы хотите рассчитать плотность населения континента на основе стран внутри континента в данных. Мы также хотим рассчитать это для континента, только если процент его населения в мире превышает 3%. С тем, что у нас есть сейчас, мы не можем добиться этого с помощью функций агрегации.

Нам нужно что-то еще. И это что-то ещеapply, функция Pandas, которая позволяет нам указать нашу собственную функцию обработки после группировки данных, в нашем случае, по континентам.

Если нам нужна общая численность населения и плотность населения, решение будет выглядеть примерно так:

def process_continent(continent):
    result = {}
    if continent["population percentage of world"].sum() > 3:
        result["population"] = continent["population"].sum()
        result["population density"] = result["population"] / continent["area"].sum()
    return pd.Series(result, index = ["population", "population density"], dtype = "float64")
df_density = df.groupby("continent").apply(process_continent)
df_density

Как видите, мы передаем нашу определенную функцию process_continent в apply. В process_continent у нас есть доступ к каждой группе, соответствующей каждому континенту. Мы проверяем, превышает ли его процент населения мира 3; если это так, то мы вычисляем его общую численность и плотность и возвращаем результат в виде Серии.

Вы могли заметить, что есть NaN записей для Европы. Это связано с тем, что в наших данных население Европы не превышает 3%, и, следовательно, ничего не возвращается. Чтобы исключить его из результирующего DataFrame, мы можем использовать .dropna():

df_density.dropna()

И, наконец, у нас есть DataFrame, показывающий именно то, что мы хотим :)

Заключение

Если вы дочитали до этого места, поздравляю и надеюсь, что вы узнали что-то полезное из этого поста. Я надеюсь, что этот пост дал более четкое объяснение того, как работает groupby, и как его можно использовать на практике. Короче говоря, groupby хорошо работает с функциями агрегирования, и если требуются более конкретные операции с несколькими столбцами, то apply можно использовать с пользовательской функцией.

Если вам это нужно, вот ссылка на все коды, обсуждаемые в этом посте.

Это все для этого! Следите за будущими статьями и не забудьте подписаться на меня, если вы найдете мои статьи полезными.

Вот некоторые из моих других постов, не стесняйтесь проверить их:











Рекомендации

группби, панды

Pandas GroupBy: ваше руководство по группировке данных в Python, настоящий Python

Питон | Pandas dataframe.groupby(), GeeksForGeeks

Панды Python — GroupBy, TutorialsPoint

Список стран и зависимостей по населению, Википедия

Список стран и зависимостей по площади, Википедия