Я пытаюсь найти способ выбрать строки, составляющие самую большую подгруппу внутри другой группы в Pandas DataFrame, и у меня возникли некоторые трудности.
Вот пример набора данных, который поможет точно объяснить, что я пытаюсь сделать. Ниже приведен код, позволяющий самостоятельно воссоздать этот набор данных, если хотите.
Предположим, я хочу сгруппировать эту таблицу по Col1
и выяснить, какое уникальное значение Col2
имеет наибольшее количество строк (внутри каждой группы Col1
). Кроме того, я не хочу просто знать, какая группа самая большая — я хочу найти способ выбрать строки из исходного DataFrame, соответствующие этому описанию.
Итак, в этом случае мы легко можем увидеть, что для Col1= = "Group A"
значение Col2
с наибольшим количеством строк равно "Type 3"
, а для Col1= = "Group B"
значение Col2
с наибольшим количеством строк равно "Type 6"
.
Это означает, что я хочу выбрать строки с RowID in [10005, 10006, 10007, 10008, 10009, 10010, 10011, 10012, 10013, 10014, 10015]
.
Поэтому результат, который я ищу, будет следующим:
Я нашел решение, но оно очень запутанное. Вот пошаговое объяснение того, что я сделал:
Шаг 1. Во-первых, для каждой группы Col1
я хочу подсчитать количество строк, существующих для каждого уникального значения Col2
. Это довольно просто: я могу просто сделать groupby(['Col1','Col2'])
и увидеть размер каждой группы.
Вот как это выглядит:
Обратите внимание, что для Col1= = "Group A"
, Col2= = "Type 1"
имеет 2 наблюдения, Col2= = "Type 2"
имеет 2 наблюдения и Col2= = "Type 3"
имеет 6 наблюдений — как и ожидалось на основе наших исходных данных.
Шаг 2: Это сложнее: для каждой группы Col1
я хочу найти значение Col2
, имеющее наибольшее количество строк из шага 1.
Вот как это выглядит:
Обратите внимание, что мы видим только случаи с «максимальным количеством строк».
Шаг 3. Наконец, я хочу отфильтровать исходные данные, чтобы отображались ТОЛЬКО те строки, которые соответствуют этой конкретной группе: те, которые были найдены на шаге 2.
Вот код, иллюстрирующий мой пример:
# Importing the relevant library
import pandas as pd
# Creating my small reproducible example
my_df = pd.DataFrame({'RowID':[10001,10002,10003,10004,10005,10006,10007,10008,10009,10010,10011,10012,10013,10014,10015,10016,10017,10018,10019,10020],
'Col1':['Group A','Group A','Group A','Group A','Group A','Group A','Group A','Group A','Group A','Group A','Group B','Group B','Group B','Group B','Group B','Group B','Group B','Group B','Group B','Group B'],
'Col2':['Type 1','Type 1','Type 2','Type 2','Type 3','Type 3','Type 3','Type 3','Type 3','Type 3','Type 6','Type 6','Type 6','Type 6','Type 6','Type 3','Type 3','Type 2','Type 2','Type 2'],
'Col3':[100,200,300,400,500,600,700,800,900,1000,2000,1900,1800,1700,1600,1500,1400,1300,1200,1100],
'Col4':['Alice','Bob','Carl','Dave','Earl','Fred','Greg','Henry','Iris','Jasmine','Kris','Lonnie','Manny','Norbert','Otis','Pearl','Quaid','Randy','Steve','Tana']})
# Solving Step 1: finding the unique groupings and their relative sizes
temp1 = my_df.groupby(['Col1','Col2']).agg({'RowID':'count'}).reset_index()
# Solving Step 2: finding which grouping is the largest
temp2 = temp1.groupby(['Col1']).agg({'RowID':'max'}).reset_index()
# Solving Step 3: finding which rows of the original DataFrame match what was
# found in Step 2
# Step 3 Part 1: Finding the actual combination of `Col1` & `Col2` that let to
# the largest number of rows
temp3 = (temp1
.rename(columns = {'RowID':'RowID_count'})
.merge(temp2
.rename(columns = {'RowID':'RowID_max'}),
how='left',
on='Col1')
.assign(RowID_ismax = lambda _df: _df['RowID_count']== _df['RowID_max'])
.query('RowID_ismax')
.drop(columns=['RowID_count','RowID_max']))
# Step 3 Part 2: Finding the matching rows in the original dataset and
# filtering it down
result = (my_df
.merge(temp3,
how='left',
on=['Col1','Col2'])
.assign(RowID_ismax = lambda _df: _df['RowID_ismax'].fillna(False))
.query('RowID_ismax')
.reset_index(drop=True)
.drop(columns=['RowID_ismax']))
Решение, приведенное выше, ОЧЕНЬ запутанное, полное утверждений assign
и lambda
наряду с несколькими последовательными groupby
и reset_index
, что наводит на мысль, что я подхожу к этому неправильно.
Любая помощь в этом вопросе будет принята с благодарностью.
Короткий код для этого может value_counts + idxmax , а затем объединить:
keep = my_df[['Col1', 'Col2']].value_counts().groupby(level='Col1').idxmax()
out = my_df.merge(pd.DataFrame(keep.tolist(), columns=['Col1', 'Col2']))
Выход:
RowID Col1 Col2 Col3 Col4
0 10005 Group A Type 3 500 Earl
1 10006 Group A Type 3 600 Fred
2 10007 Group A Type 3 700 Greg
3 10008 Group A Type 3 800 Henry
4 10009 Group A Type 3 900 Iris
5 10010 Group A Type 3 1000 Jasmine
6 10011 Group B Type 6 2000 Kris
7 10012 Group B Type 6 1900 Lonnie
8 10013 Group B Type 6 1800 Manny
9 10014 Group B Type 6 1700 Norbert
10 10015 Group B Type 6 1600 Otis
Промежуточные продукты:
# my_df[['Col1', 'Col2']].value_counts()
Col1 Col2
Group A Type 3 6
Group B Type 6 5
Type 2 3
Group A Type 1 2
Type 2 2
Group B Type 3 2
Name: count, dtype: int64
# my_df[['Col1', 'Col2']].value_counts().groupby(level='Col1').idxmax()
Col1
Group A (Group A, Type 3)
Group B (Group B, Type 6)
Name: count, dtype: object
В качестве альтернативы можно использовать groupby.transform (который выберет несколько групп, если их несколько с максимальным размером):
s = my_df.groupby(['Col1', 'Col2']).transform('size')
out = my_df[s.groupby(my_df['Col1']).transform('max').eq(s)]
Это здорово, спасибо вам огромное!