pandas GroupBy: Ваше руководство по группировке данных в Python
Оглавление
- Предварительные условия
- Пример 1: Набор данных Конгресса США
- Пример 2: Набор данных о качестве воздуха
- Пример 3: Набор данных агрегатора новостей
- Группа панд: Собираем все это воедино
- Заключение
- Часто задаваемые вопросы
Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Посмотрите его вместе с письменным руководством, чтобы углубить свое понимание: pandas GroupBy: Группировка реальных данных в Python
Метод pandas .groupby() позволяет эффективно анализировать и преобразовывать наборы данных при работе с данными в Python. С помощью df.groupby() вы можете разделить фрейм данных на группы на основе значений столбцов, применить функции к каждой группе и объединить результаты в новый фрейм данных. Этот метод необходим для решения таких задач, как агрегирование, фильтрация и преобразование сгруппированных данных.
К концу этого урока вы поймете, что:
- Вызов
.groupby("column_name")разбивает фрейм данных на группы, применяет функцию к каждой группе и объединяет результаты. - Чтобы сгруппировать по нескольким столбцам, вы можете передать список имен столбцов в
.groupby(). - Распространенные методы агрегации в pandas включают
.sum(),.mean(), и.count(). - Вы можете использовать пользовательские функции с помощью pandas
.groupby()для выполнения определенных операций с группами.
В этом руководстве предполагается, что у вас есть некоторый опыт работы с самим pandas, включая то, как считывать CSV-файлы в память в виде объектов pandas с помощью read_csv(). Если вам нужно освежить информацию, ознакомьтесь с Чтение CSV-файлов с помощью pandas и pandas: как читать и записывать файлы.
Вы можете скачать исходный код для всех примеров из этого руководства, перейдя по ссылке ниже:
Загрузите наборы данных: Нажмите здесь, чтобы загрузить наборы данных, которые вы будете использовать, чтобы узнать о группах панд в этом руководстве.
Предварительные условия
Прежде чем продолжить, убедитесь, что у вас установлена последняя версия pandas, доступная в новой виртуальной среде :
PS> python -m venv venv PS> venv\Scripts\activate (venv) PS> python -m pip install pandasпредварительно> кодовый блок>$ python3 -m venv venv $ source venv/bin/activate (venv) $ python -m pip install pandasпредварительно> кодовый блок>В этом руководстве вы сосредоточитесь на трех наборах данных:
- Набор данных Конгресса США содержит общедоступную информацию о прошлых членах Конгресса и иллюстрирует несколько фундаментальных возможностей
.groupby().- Набор данных о качестве воздуха содержит периодические показания газового датчика. Это позволит вам работать с числами с плавающей точкой и данными временных рядов.
- Набор данных агрегатора новостей содержит метаданные о нескольких сотнях тысяч новостных статей. Вы будете работать со строками и выполнять редактирование текста с помощью
.groupby().Вы можете скачать исходный код для всех примеров из этого руководства, перейдя по ссылке ниже:
Загрузите наборы данных: Нажмите здесь, чтобы загрузить наборы данных, которые вы будете использовать, чтобы узнать о группах панд в этом руководстве.
Как только вы загрузите файл
.zip, распакуйте его в папку с именемgroupby-data/в вашем текущем каталоге. Прежде чем читать дальше, убедитесь, что ваше дерево каталогов выглядит следующим образом:./ │ └── groupby-data/ │ ├── legislators-historical.csv ├── airqual.csv └── news.csvУстановив
pandas, активировав вашу виртуальную среду и загрузив наборы данных, вы готовы к работе!Пример 1: Набор данных Конгресса США
Вы сразу перейдете к делу, проанализировав набор данных об исторических членах Конгресса. Вы можете прочитать CSV-файл в формате pandas
DataFrameс помощьюread_csv():pandas_legislators.pyкодовый блок>import pandas as pd dtypes = { "first_name": "category", "gender": "category", "type": "category", "state": "category", "party": "category", } df = pd.read_csv( "groupby-data/legislators-historical.csv", dtype=dtypes, usecols=list(dtypes) + ["birthday", "last_name"], parse_dates=["birthday"] )Набор данных содержит имена и фамилии членов, дату рождения, пол, тип (
"rep"для Палаты представителей или"sen"для Сената), штат США и политическую партию. Вы можете использоватьdf.tail()для просмотра последних нескольких строк набора данных:>>> from pandas_legislators import df >>> df.tail() last_name first_name birthday gender type state party 11970 Garrett Thomas 1972-03-27 M rep VA Republican 11971 Handel Karen 1962-04-18 F rep GA Republican 11972 Jones Brenda 1959-10-24 F rep MI Democrat 11973 Marino Tom 1952-08-15 M rep PA Republican 11974 Jones Walter 1943-02-10 M rep NC Republicanпредварительно> кодовый блок>В
DataFrameиспользуются категориальные типы для экономии пространства:>>> df.dtypes last_name object first_name category birthday datetime64[ns] gender category type category state category party category dtype: objectпредварительно> кодовый блок>Вы можете видеть, что большинство столбцов набора данных имеют тип
category, что снижает нагрузку на память вашего компьютера.Группа
Hello, World!из группы пандТеперь, когда вы ознакомились с набором данных, вы начнете с
Hello, World!для операции pandas GroupBy. Каково количество членов Конгресса в разбивке по штатам за всю историю сбора данных? В SQL вы могли бы найти этот ответ с помощью инструкцииSELECT:SELECT state, count(name) FROM df GROUP BY state ORDER BY state;предварительно> кодовый блок>Вот почти аналогичный показатель в pandas:
>>> n_by_state = df.groupby("state")["last_name"].count() >>> n_by_state.head(10) state AK 16 AL 206 AR 117 AS 2 AZ 48 CA 361 CO 90 CT 240 DC 2 DE 97 Name: last_name, dtype: int64предварительно> кодовый блок>Вы вызываете
.groupby()и передаете имя столбца, по которому вы хотите сгруппировать, а именно"state". Затем вы используете["last_name"], чтобы указать столбцы, для которых вы хотите выполнить фактическую агрегацию.В качестве первого аргумента
.groupby()можно передать гораздо больше, чем просто имя столбца. Вы также можете указать любое из следующих значений:
- A
listиз нескольких имен столбцов- А
dictили пандыSeries- Массив NumPy или pandas
Index, или повторяющийся массив, подобный этомуВот пример совместной группировки по двум столбцам, в которой отображается количество членов Конгресса в разбивке по штатам, а затем по полу:
>>> df.groupby(["state", "gender"])["last_name"].count() state gender AK F 0 M 16 AL F 3 M 203 AR F 5 ... WI M 196 WV F 1 M 119 WY F 2 M 38 Name: last_name, Length: 116, dtype: int64предварительно> кодовый блок>Аналогичный SQL-запрос будет выглядеть следующим образом:
SELECT state, gender, count(name) FROM df GROUP BY state, gender ORDER BY state, gender;предварительно> кодовый блок>Как вы увидите далее,
.groupby()и сопоставимые операторы SQL являются близкими родственниками, но часто функционально не идентичны.pandas GroupBy против SQL
Сейчас самое время рассказать об одном существенном различии между операцией pandas GroupBy и SQL-запросом, описанным выше. Результирующий набор SQL-запроса содержит три столбца:
stategendercountВ версии для pandas сгруппированные столбцы помещаются в
MultiIndexиз результирующихSeriesпо умолчанию:>>> n_by_state_gender = df.groupby(["state", "gender"])["last_name"].count() >>> type(n_by_state_gender) <class 'pandas.core.series.Series'> >>> n_by_state_gender.index[:5] MultiIndex([('AK', 'M'), ('AL', 'F'), ('AL', 'M'), ('AR', 'F'), ('AR', 'M')], names=['state', 'gender'])предварительно> кодовый блок>Чтобы более точно имитировать результат SQL и поместить сгруппированные столбцы обратно в столбцы результата, вы можете использовать
as_index=False:>>> df.groupby(["state", "gender"], as_index=False)["last_name"].count() state gender last_name 0 AK F 0 1 AK M 16 2 AL F 3 3 AL M 203 4 AR F 5 .. ... ... ... 111 WI M 196 112 WV F 1 113 WV M 119 114 WY F 2 115 WY M 38 [116 rows x 3 columns]предварительно> кодовый блок>В результате получается
DataFrameс тремя столбцами и символомRangeIndex, вместоSeriesс символомMultiIndex. Короче говоря, использованиеas_index=Falseсделает ваш результат более похожим на вывод SQL по умолчанию для аналогичной операции.Примечание: В
df.groupby(["state", "gender"])["last_name"].count()вы также могли бы использовать.size()вместо.count(), поскольку вы знаете, что там нет фамилийNaN. Использование.count()исключаетNaNзначений, в то время как.size()включает все,NaNили нет.Также обратите внимание, что приведенные выше SQL-запросы явно используют
ORDER BY, в то время как.groupby()этого не делает. Это потому, что.groupby()делает это по умолчанию с помощью своего параметраsort, который равенTrue, если вы не укажете иное:>>> # Don't sort results by the sort keys >>> df.groupby("state", sort=False)["last_name"].count() state DE 97 VA 432 SC 251 MD 305 PA 1053 ... AK 16 PI 13 VI 4 GU 4 AS 2 Name: last_name, dtype: int64предварительно> кодовый блок>Далее вы перейдете к объекту, который на самом деле создает
.groupby().Как работает pandas GroupBy
Прежде чем углубляться в детали, сделайте шаг назад и посмотрите на сам
.groupby():>>> by_state = df.groupby("state") >>> print(by_state) <pandas.core.groupby.generic.DataFrameGroupBy object at 0x107293278>предварительно> кодовый блок>Что такое
DataFrameGroupBy? Значение.__str__(), которое отображается в функции печати, не дает вам много информации о том, что это такое на самом деле и как оно работает. Причина, по которой объектDataFrameGroupByможет быть труден для восприятия, заключается в том, что он ленив по своей природе. На самом деле он не выполняет никаких операций для получения полезного результата, пока вы ему не скажете.Примечание: В этом руководстве общий термин объект pandas GroupBy относится как к
DataFrameGroupBy, так и кSeriesGroupByобъекты, у которых много общего.Один из терминов, который часто используется наряду с
.groupby(), - это разделение-применение-объединение. Это относится к цепочке из трех этапов:
- Разделите таблицу на группы.
- Примените некоторые операции к каждой из этих небольших таблиц.
- Объедините результатов.
Проверить
df.groupby("state")может быть сложно, потому что он практически ничего из этого не делает, пока вы что-то не сделаете с результирующим объектом. Объект pandas GroupBy задерживает практически каждую часть процесса разделения-применения-объединения до тех пор, пока вы не вызовете для него метод.Итак, как вы можете мысленно разделить этапы разделения, применения и объединения, если вы не можете представить, что какой-либо из них происходит изолированно? Один из полезных способов проверить объект pandas GroupBy и увидеть разбиение в действии - это выполнить итерацию по нему:
>>> for state, frame in by_state: ... print(f"First 2 entries for {state!r}") ... print("------------------------") ... print(frame.head(2), end="\n\n") ... First 2 entries for 'AK' ------------------------ last_name first_name birthday gender type state party 6619 Waskey Frank 1875-04-20 M rep AK Democrat 6647 Cale Thomas 1848-09-17 M rep AK Independent First 2 entries for 'AL' ------------------------ last_name first_name birthday gender type state party 912 Crowell John 1780-09-18 M rep AL Republican 991 Walker John 1783-08-12 M sen AL Republicanпредварительно> кодовый блок>Если вы работаете над сложной задачей агрегирования, то итерация по объекту pandas GroupBy может стать отличным способом визуализации разделения части split-apply-combine.
Существует несколько других методов и свойств, которые позволяют просматривать отдельные группы и их разбиения. Атрибут
.groupsпредоставит вам словарь пар{group name: group label}. Например,by_state.groups- этоdictс состояниями в качестве ключей. Вот значение для ключа"PA":>>> by_state.groups["PA"] Int64Index([ 4, 19, 21, 27, 38, 57, 69, 76, 84, 88, ... 11842, 11866, 11875, 11877, 11887, 11891, 11932, 11945, 11959, 11973], dtype='int64', length=1053)предварительно> кодовый блок>Каждое значение представляет собой последовательность расположения индексов для строк, принадлежащих к этой конкретной группе. В приведенных выше выходных данных, 4, 19, и 21 являются первыми индексами в
df, при которых состояние равно"PA".Вы также можете использовать
.get_group()для перехода к подтаблице из одной группы:>>> by_state.get_group("PA") last_name first_name birthday gender type state party 4 Clymer George 1739-03-16 M rep PA NaN 19 Maclay William 1737-07-20 M sen PA Anti-Administration 21 Morris Robert 1734-01-20 M sen PA Pro-Administration 27 Wynkoop Henry 1737-03-02 M rep PA NaN 38 Jacobs Israel 1726-06-09 M rep PA NaN ... ... ... ... ... ... ... ... 11891 Brady Robert 1945-04-07 M rep PA Democrat 11932 Shuster Bill 1961-01-10 M rep PA Republican 11945 Rothfus Keith 1962-04-25 M rep PA Republican 11959 Costello Ryan 1976-09-07 M rep PA Republican 11973 Marino Tom 1952-08-15 M rep PA Republicanпредварительно> кодовый блок>Это практически эквивалентно использованию
.loc[]. Вы могли бы получить тот же результат, используя что-то вродеdf.loc[df["state"] == "PA"].Также стоит упомянуть, что
.groupby()выполняет некоторую, но не всю работу по разделению, создавая экземпляр классаGroupingдля каждого передаваемого вами ключа. Однако многие методы классаBaseGrouper, который содержит эти группировки, вызываются лениво, а не в.__init__(), и многие из них также используют кэшированный дизайн свойств.Далее, что насчет части применить? Вы можете представить этот этап процесса как применение одной и той же операции (или вызываемой) к каждой вложенной таблице, созданной на этапе разделения.
Из объекта pandas GroupBy
by_stateвы можете выбрать начальный штат США иDataFrameс помощьюnext(). Когда вы выполните итерацию по объекту pandas GroupBy, вы получите пары, которые можно разложить на две переменные:>>> state, frame = next(iter(by_state)) # First tuple from iterator >>> state 'AK' >>> frame.head(3) last_name first_name birthday gender type state party 6619 Waskey Frank 1875-04-20 M rep AK Democrat 6647 Cale Thomas 1848-09-17 M rep AK Independent 7442 Grigsby George 1874-12-02 M rep AK NaNпредварительно> кодовый блок>Теперь вспомните свою первоначальную полную операцию:
>>> df.groupby("state")["last_name"].count() state AK 16 AL 206 AR 117 AS 2 AZ 48 ...предварительно> кодовый блок>Этап применить, когда он применяется к вашему единственному подмножеству
DataFrame, будет выглядеть следующим образом:>>> frame["last_name"].count() # Count for state == 'AK' 16предварительно> кодовый блок>Вы можете видеть, что результат, равный 16, соответствует значению для
AKв объединенном результате.На последнем шаге, объединить, берутся результаты всех примененных операций со всеми вложенными таблицами и объединяются обратно интуитивно понятным способом.
Читайте дальше, чтобы ознакомиться с другими примерами процесса разделения-применения-объединения.
Пример 2: Набор данных о качестве воздуха
Набор данных о качестве воздуха содержит ежечасные показания газового датчика в Италии. Пропущенные значения в CSV-файле обозначаются символом -200. Вы можете использовать
read_csv(), чтобы объединить два столбца в временную метку, используя при этом подмножество других столбцов:pandas_airqual.pyкодовый блок>import pandas as pd df = pd.read_csv( "groupby-data/airqual.csv", parse_dates=[["Date", "Time"]], na_values=[-200], usecols=["Date", "Time", "CO(GT)", "T", "RH", "AH"] ).rename( columns={ "CO(GT)": "co", "Date_Time": "tstamp", "T": "temp_c", "RH": "rel_hum", "AH": "abs_hum", } ).set_index("tstamp")В результате получается
DataFrameсDatetimeIndexи четырьмяfloatстолбцами:>>> from pandas_airqual import df >>> df.head() co temp_c rel_hum abs_hum tstamp 2004-03-10 18:00:00 2.6 13.6 48.9 0.758 2004-03-10 19:00:00 2.0 13.3 47.7 0.726 2004-03-10 20:00:00 2.2 11.9 54.0 0.750 2004-03-10 21:00:00 2.2 11.0 60.0 0.787 2004-03-10 22:00:00 1.6 11.2 59.6 0.789предварительно> кодовый блок>Здесь
co- это среднее значение окиси углерода за этот час, в то время какtemp_c,rel_hum, иabs_hum- это средняя температура по Цельсию, относительная влажность воздуха и абсолютная влажность воздуха за этот час соответственно. Наблюдения проводились с марта 2004 по апрель 2005 года:>>> df.index.min() Timestamp('2004-03-10 18:00:00') >>> df.index.max() Timestamp('2005-04-04 14:00:00')предварительно> кодовый блок>До сих пор вы группировали столбцы, задавая их названия как
str, например, какdf.groupby("state"). Но.groupby()гораздо более гибкий способ, чем этот! Вы увидите, как это делается дальше.Группировка по производным массивам
Ранее вы видели, что первый параметр
.groupby()может принимать несколько различных аргументов:
- Столбец или список столбцов
- А
dictили пандыSeries- Массив NumPy или pandas
Index, или подобный массиву, повторяющийся из нихВы можете воспользоваться последним вариантом, чтобы сгруппировать данные по дням недели. Используйте значение индекса
.day_name()для создания набора строкIndex. Вот первые десять наблюдений:>>> day_names = df.index.day_name() >>> type(day_names) <class 'pandas.core.indexes.base.Index'> >>> day_names[:10] Index(['Wednesday', 'Wednesday', 'Wednesday', 'Wednesday', 'Wednesday', 'Wednesday', 'Thursday', 'Thursday', 'Thursday', 'Thursday'], dtype='object', name='tstamp')предварительно> кодовый блок>Затем вы можете взять этот объект и использовать его в качестве ключа
.groupby(). В pandasday_names- это , подобный массиву. Это одномерная последовательность надписей.Примечание: Для доступа к pandas
Series, а не кIndex, вам понадобится.dt. к таким методам, как.day_name(). Еслиser- это вашSeries, то вам понадобитсяser.dt.day_name().Теперь передайте этот объект в
.groupby(), чтобы найти среднее значение содержания монооксида углерода (co) по дням недели:>>> df.groupby(day_names)["co"].mean() tstamp Friday 2.543 Monday 2.017 Saturday 1.861 Sunday 1.438 Thursday 2.456 Tuesday 2.382 Wednesday 2.401 Name: co, dtype: float64предварительно> кодовый блок>Процесс разделения-применения-объединения работает в основном так же, как и раньше, за исключением того, что на этот раз разделение выполняется для искусственно созданного столбца. Этот столбец не существует в самом фрейме данных, а является производным от него.
Что, если бы вы захотели сгруппировать данные не только по дням недели, но и по часам дня? В результате должно получиться
7 * 24 = 168наблюдений. Для этого вы можете передать список объектов, подобных массиву. В этом случае вы передадите pandasInt64Indexобъекты:>>> hr = df.index.hour >>> df.groupby([day_names, hr])["co"].mean().rename_axis(["dow", "hr"]) dow hr Friday 0 1.936 1 1.609 2 1.172 3 0.887 4 0.823 ... Wednesday 19 4.147 20 3.845 21 2.898 22 2.102 23 1.938 Name: co, Length: 168, dtype: float64предварительно> кодовый блок>Вот еще один аналогичный пример, в котором используется
.cut()для разбиения значений температуры на дискретные интервалы:>>> import pandas as pd >>> bins = pd.cut(df["temp_c"], bins=3, labels=("cool", "warm", "hot")) >>> df[["rel_hum", "abs_hum"]].groupby(bins).agg(["mean", "median"]) rel_hum abs_hum mean median mean median temp_c cool 57.651 59.2 0.666 0.658 warm 49.383 49.3 1.183 1.145P hot 24.994 24.1 1.293 1.274предварительно> кодовый блок>В данном случае
binsна самом деле являетсяSeries:>>> type(bins) <class 'pandas.core.series.Series'> >>> bins.head() tstamp 2004-03-10 18:00:00 cool 2004-03-10 19:00:00 cool 2004-03-10 20:00:00 cool 2004-03-10 21:00:00 cool 2004-03-10 22:00:00 cool Name: temp_c, dtype: category Categories (3, object): [cool < warm < hot]предварительно> кодовый блок>Является ли это
Series, массивом NumPy или списком, не имеет значения. Важно то, чтоbinsпо-прежнему служит в качестве последовательности меток, состоящей изcool,warm, иhot. Если бы вы действительно захотели, то вы также могли бы использовать массивCategoricalили даже обычный старыйlist:
- Собственный список Python:
df.groupby(bins.tolist())- массив панд
Categorical:df.groupby(bins.values)Как вы можете видеть,
.groupby()интеллектуален и может обрабатывать множество различных типов входных данных. Любой из них даст одинаковый результат, поскольку все они функционируют как последовательность меток, по которым выполняется группировка и разделение.Повторная выборка
Вы сгруппировали
dfпо дням недели с помощьюdf.groupby(day_names)["co"].mean(). Теперь рассмотрим что-то другое. Что, если бы вы захотели сгруппировать данные наблюдения по году и кварталу? Вот один из способов добиться этого:>>> # See an easier alternative below >>> df.groupby([df.index.year, df.index.quarter])["co"].agg( ... ["max", "min"] ... ).rename_axis(["year", "quarter"]) max min year quarter 2004 1 8.1 0.3 2 7.3 0.1 3 7.5 0.1 4 11.9 0.1 2005 1 8.7 0.1 2 5.0 0.3предварительно> кодовый блок>Вся эта операция, в качестве альтернативы, может быть выражена с помощью повторной выборки. Одно из применений повторной выборки - это группировка по времени на основе. Все, что вам нужно сделать, это передать частотную строку, например,
"Q"для"quarterly", а pandas сделает все остальное:>>> df.resample("Q")["co"].agg(["max", "min"]) max min tstamp 2004-03-31 8.1 0.3 2004-06-30 7.3 0.1 2004-09-30 7.5 0.1 2004-12-31 11.9 0.1 2005-03-31 8.7 0.1 2005-06-30 5.0 0.3предварительно> кодовый блок>Часто, когда вы используете
.resample(), вы можете выразить операции группировки на основе времени гораздо более сжато. Результат может немного отличаться от более подробного аналога.groupby(), но вы часто обнаружите, что.resample()дает вам именно то, что вы ищете.Пример 3: Набор данных новостного агрегатора
Теперь вы будете работать с третьим и последним набором данных, который содержит метаданные о нескольких сотнях тысяч новостных статей и группирует их в тематические кластеры:
pandas_news.pyкодовый блок>import pandas as pd def parse_millisecond_timestamp(ts): """Convert ms since Unix epoch to UTC datetime instance.""" return pd.to_datetime(ts, unit="ms") df = pd.read_csv( "groupby-data/news.csv", sep="\t", header=None, index_col=0, names=["title", "url", "outlet", "category", "cluster", "host", "tstamp"], parse_dates=["tstamp"], date_parser=parse_millisecond_timestamp, dtype={ "outlet": "category", "category": "category", "cluster": "category", "host": "category", }, )Чтобы считывать данные в память с правильным
dtype, вам нужна вспомогательная функция для анализа столбца временной метки. Это связано с тем, что оно выражается как количество миллисекунд, прошедших с момента эпохи Unix, а не как доли секунды. Если вы хотите узнать больше о работе со временем в Python, ознакомьтесь с Использование Python datetime для работы с датами и временем.Аналогично тому, что вы делали ранее, вы можете использовать категорию
dtypeдля эффективного кодирования столбцов, которые имеют относительно небольшое количество уникальных значений относительно длины столбца.Каждая строка набора данных содержит заголовок, URL-адрес, название издательства и домен, а также временную метку публикации.
cluster- это случайный идентификатор тематического кластера, к которому относится статья.category- это категория новостей, которая содержит следующие параметры:
bдля бизнесаtдля науки и техникиeдля развлеченияmдля здоровьяВот первая строка:
>>> from pandas_news import df >>> df.iloc[0] title Fed official says weak data caused by weather,... url http://www.latimes.com/business/money/la-fi-mo... outlet Los Angeles Times category b cluster ddUyU0VZz0BRneMioxUPQVP6sIxvM host www.latimes.com tstamp 2014-03-10 16:52:50.698000 Name: 1, dtype: objectпредварительно> кодовый блок>Теперь, когда вы получили представление о данных, вы можете начать задавать более сложные вопросы по этому поводу.
Использование лямбда-функций в
.groupby()Этот набор данных наталкивает на гораздо более сложные вопросы. Вот случайный, но значимый вопрос: какие сми больше всего говорят о Федеральной резервной системе? Для простоты предположим, что это влечет за собой поиск упоминаний
"Fed"с учетом регистра. Имейте в виду, что это может привести к некоторым ложным срабатываниям с такими терминами, как"Federal government".Чтобы подсчитать упоминания по аутлетам, вы можете вызвать
.groupby()в аутлете, а затем буквально.apply()функцию для каждой группы, используя лямбда-функцию Python:>>> df.groupby("outlet", sort=False)["title"].apply( ... lambda ser: ser.str.contains("Fed").sum() ... ).nlargest(10) outlet Reuters 161 NASDAQ 103 Businessweek 93 Investing.com 66 Wall Street Journal \(blog\) 61 MarketWatch 56 Moneynews 55 Bloomberg 53 GlobalPost 51 Economic Times 44 Name: title, dtype: int64предварительно> кодовый блок>Давайте разберем это, поскольку существует несколько последовательных вызовов методов. Как и ранее, вы можете выделить первую группу и соответствующий ей объект pandas, взяв первый
tupleиз итератора pandas GroupBy:>>> title, ser = next(iter(df.groupby("outlet", sort=False)["title"])) >>> title 'Los Angeles Times' >>> ser.head() 1 Fed official says weak data caused by weather,... 486 Stocks fall on discouraging news from Asia 1124 Clues to Genghis Khan's rise, written in the r... 1146 Elephants distinguish human voices by sex, age... 1237 Honda splits Acura into its own division to re... Name: title, dtype: objectпредварительно> кодовый блок>В данном случае
ser- это пандаSeries, а неDataFrame. Это потому, что вы выполнили вызов.groupby()с помощью["title"]. Таким образом, в каждой подтаблице выбирается только один столбец.Далее следует
.str.contains("Fed"). Это возвращает логическое значениеSeriesто естьTrue, когда в заголовке статьи регистрируется совпадение при поиске. Конечно же, первая строка начинается с"Fed official says weak data caused by weather,..."и загорается следующим образом:True:>>> ser.str.contains("Fed") 1 True 486 False 1124 False 1146 False 1237 False ... 421547 False 421584 False 421972 False 422226 False 422905 False Name: title, Length: 1976, dtype: boolпредварительно> кодовый блок>Следующим шагом будет выполнение
.sum()этогоSeries. Посколькуboolтехнически является просто специализированным типомint, вы можете суммироватьSeriesизTrueиFalseточно так же, как вы бы суммировали последовательность1и0:>>> ser.str.contains("Fed").sum() 17предварительно> кодовый блок>Результатом является количество упоминаний
"Fed"в Los Angeles Times в наборе данных. Та же процедура применяется к Reuters, NASDAQ, Businessweek и остальным сайтам.Повышение производительности
.groupby()Теперь вернитесь еще раз к
.groupby().apply(), чтобы понять, почему этот шаблон может быть неоптимальным. Чтобы получить некоторую справочную информацию, ознакомьтесь с Как ускорить ваши проекты на pandas. Что может произойти с.apply(), так это то, что он будет эффективно выполнять цикл Python для каждой группы. В то время как шаблон.groupby().apply()может обеспечить некоторую гибкость, он также может помешать pandas иным образом использовать свои оптимизации на основе Cython.Все это означает, что всякий раз, когда вы задумываетесь об использовании
.apply(), спросите себя, есть ли способ выразить операцию в виде векторизованного способа. В этом случае вы можете воспользоваться тем фактом, что.groupby()принимает не только одно или несколько имен столбцов, но и множество структур, подобных массиву:
- Одномерный числовой массив
- Список
- Панда
SeriesилиIndexТакже обратите внимание, что
.groupby()является допустимым методом экземпляра дляSeries, а не только дляDataFrame, поэтому вы можете по существу, инвертируйте логику разделения. Имея это в виду, вы можете сначала создатьSeriesлогических значений, которые указывают, содержит ли заголовок"Fed":>>> mentions_fed = df["title"].str.contains("Fed") >>> type(mentions_fed) <class 'pandas.core.series.Series'>предварительно> кодовый блок>Теперь
.groupby()также является методомSeries, поэтому вы можете сгруппировать одинSeriesв другой:>>> import numpy as np >>> mentions_fed.groupby( ... df["outlet"], sort=False ... ).sum().nlargest(10).astype(np.uintc) outlet Reuters 161 NASDAQ 103 Businessweek 93 Investing.com 66 Wall Street Journal \(blog\) 61 MarketWatch 56 Moneynews 55 Bloomberg 53 GlobalPost 51 Economic Times 44 Name: title, dtype: uint32предварительно> кодовый блок>Два
Seriesне обязательно должны быть столбцами одного и того же объектаDataFrame. Они просто должны быть одинаковой формы:>>> mentions_fed.shape (422419,) >>> df["outlet"].shape (422419,)предварительно> кодовый блок>Наконец, вы можете преобразовать результат обратно в целое число без знака с помощью
np.uintc, если вы хотите получить максимально компактный результат. Вот прямое сравнение двух версий, которое приведет к одинаковому результату:pandas_news_performance.pyкодовый блок>import timeit import numpy as np from pandas_news import df def test_apply(): """Version 1: using `.apply()`""" df.groupby("outlet", sort=False)["title"].apply( lambda ser: ser.str.contains("Fed").sum() ).nlargest(10) def test_vectorization(): """Version 2: using vectorization""" mentions_fed = df["title"].str.contains("Fed") mentions_fed.groupby( df["outlet"], sort=False ).sum().nlargest(10).astype(np.uintc) print(f"Version 1: {timeit.timeit(test_apply, number=3)}") print(f"Version 2: {timeit.timeit(test_vectorization, number=3)}")Вы используете модуль
timeitдля оценки времени работы обеих версий. Если вы хотите узнать больше о тестировании производительности вашего кода, то Функции таймера Python: три способа мониторинга вашего кода стоит прочитать.Теперь запустите скрипт, чтобы посмотреть, как работают обе версии:
(venv) $ python pandas_news_performance.py Version 1: 2.5422707499965327 Version 2: 0.3260433749965159предварительно> кодовый блок>При трехкратном запуске функция
test_apply()занимает 2,54 секунды, в то время какtest_vectorization()занимает всего 0,33 секунды. Это впечатляющая разница во времени процессора для нескольких сотен тысяч строк. Подумайте, насколько существенной становится разница, когда ваш набор данных увеличивается до нескольких миллионов строк!Примечание: В этом примере для простоты приведены некоторые детали в данных. А именно, поисковый запрос
"Fed"может также содержать упоминания о таких вещах, как"Federal government".
Series.str.contains()также принимает скомпилированное регулярное выражение в качестве аргумента, если вы хотите пофантазировать и использовать выражение, включающее отрицательный предварительный просмотр.Возможно, вы также захотите подсчитать не только общее количество упоминаний, но и долю упоминаний по отношению ко всем статьям, опубликованным новостным изданием.
Группа "панды": Собираем все это воедино
Если вы вызовете
dir()для объекта pandas GroupBy, то увидите там достаточно методов, от которых у вас закружится голова! Бывает сложно уследить за всеми функциональными возможностями объекта pandas GroupBy. Один из способов прояснить ситуацию - разделить различные методы на то, что они делают и как себя ведут.В широком смысле методы объекта pandas GroupBy делятся на несколько категорий:
Методы агрегирования (также называемые методами сокращения) объединяют множество точек данных в агрегированную статистику по этим точкам данных. В качестве примера можно взять сумму, среднее значение или медиану десяти чисел, где результатом является всего лишь одно число.
Методы фильтрации возвращаются к вам с подмножеством исходных
DataFrame. Чаще всего это означает использование.filter()для удаления целых групп на основе некоторой сравнительной статистики об этой группе и ее подтаблице. Также имеет смысл включить в это определение ряд методов, которые исключают определенные строки из каждой группы.Методы преобразования возвращают
DataFrameс той же формой и индексами, что и у оригинала, но с другими значениями. Как при использовании методов агрегации, так и при использовании методов фильтрации результирующийDataFrameобычно будет меньше по размеру, чем входнойDataFrame. Это не относится к трансформации, которая трансформирует сами индивидуальные ценности, но сохраняет форму оригиналаDataFrame.Мета-методы в меньшей степени связаны с исходным объектом, к которому вы обращались
.groupby(), и больше ориентированы на предоставление вам информации высокого уровня, такой как количество групп и индексы этих групп.Методы построения графиков имитируют API построения графиков для pandas
SeriesилиDataFrame, но обычно разбивают выходные данные на несколько подзаголовков.В официальной документации содержится собственное объяснение этих категорий. Они, в некоторой степени, открыты для интерпретации, и в этом руководстве могут быть небольшие расхождения в классификации методов.
Существует несколько методов группирования объектов pandas, которые не совсем соответствуют указанным выше категориям. Эти методы обычно создают промежуточный объект, который не является
DataFrameилиSeries. Например,df.groupby().rolling()создаетRollingGroupbyобъект, для которого затем можно вызвать методы агрегации, фильтрации или преобразования.Если вы хотите погрузиться глубже, то документация по API для
DataFrame.groupby(),DataFrame.resample()иpandas.Grouper- это ресурсы для изучения методов и объектов.В pandas docs также есть еще одна отдельная таблица со своей собственной классификационной схемой. Выберите ту, которая вам подходит и кажется наиболее интуитивно понятной!
Заключение
В этом руководстве вы подробно ознакомились с
.groupby(), в том числе с его дизайном, API и с тем, как объединить методы для получения данных в структуру, которая соответствует вашим целям.Ты научился:
- Как использовать pandas Групповые операции с реальными данными
- Как работает цепочка операций разделение-применение-объединение и как вы можете разбить ее на этапы
- Как методы объекта pandas GroupBy могут быть классифицированы на основе их назначения и результата
В
.groupby()содержится гораздо больше информации, чем вы можете изложить в одном уроке. Но, надеюсь, этот урок стал хорошей отправной точкой для дальнейшего изучения!Вы можете скачать исходный код для всех примеров из этого руководства, перейдя по ссылке ниже:
Загрузите наборы данных: Нажмите здесь, чтобы загрузить наборы данных, которые вы будете использовать, чтобы узнать о группах панд в этом руководстве.
Часто задаваемые вопросы
Теперь, когда у вас есть некоторый опыт работы с pandas
.groupby()в Python, вы можете использовать вопросы и ответы, приведенные ниже, чтобы проверить свое понимание и резюмировать то, что вы узнали.Эти часто задаваемые вопросы относятся к наиболее важным понятиям, которые вы рассмотрели в этом руководстве. Нажмите на переключатель Показывать/скрывать рядом с каждым вопросом, чтобы открыть ответ.
Вы используете
.groupby(), чтобы разделить фрейм данных на группы на основе некоторых критериев, выполнить операции с каждой группой, а затем снова объединить результаты вместе.
Для группировки по нескольким столбцам в pandas вы передаете список имен столбцов в
.groupby(). Затем вы можете выполнять операции с комбинациями этих столбцов.
Вы обрабатываете пропущенные значения, используя такие методы, как
.fillna()или.dropna()до или после применения.groupby(), чтобы обеспечить точные результаты.
Для оптимизации производительности используйте векторизованные операции вместо
.apply()и рассмотрите возможность использования категориальных типов данных для сокращения использования памяти.
Агрегирование сводит данные к сводной статистике, преобразование применяет функции, сохраняя форму исходного фрейма данных, а фильтрация выбирает подмножества данных на основе определенных критериев.
<статус завершения article-slug="pandas-groupby" class="btn-group mb-0" data-api-article-bookmark-url="/api/v1/articles/pandas-groupby/bookmark/" url статуса завершения data-api-article-url="/api/v1/articles/pandas-groupby/завершение_статуса/"> статус завершения> <кнопка поделиться bluesky-text="Интересная статья на #Python от @realpython.com :" email-body="Ознакомьтесь с этой статьей о Python:%0A%0Apandas GroupBy: Ваше руководство по группировке данных в Python" email-subject="Статья о Python для вас" twitter-text="Интересная #Статья о Python от @realpython:" url="https://realpython.com/pandas-groupby /" url-title="pandas GroupBy: ваше руководство по группировке данных в Python"> кнопка поделиться>Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Посмотрите его вместе с письменным руководством, чтобы углубить свое понимание: pandas GroupBy: Группировка реальных данных в Python
Back to Top