Панды процент от общего числа с groupby
это, очевидно, просто,но как numpy newbe я застрял.
у меня есть CSV-файл, который содержит 3 столбца, состояние, идентификатор офиса и продажи для этого офиса.
Я хочу рассчитать процент продаж на офис в данном штате (общее количество всех процентов в каждом штате составляет 100%).
df = pd.DataFrame({'state': ['CA', 'WA', 'CO', 'AZ'] * 3,
'office_id': range(1, 7) * 2,
'sales': [np.random.randint(100000, 999999)
for _ in range(12)]})
df.groupby(['state', 'office_id']).agg({'sales': 'sum'})
возвращает:
sales
state office_id
AZ 2 839507
4 373917
6 347225
CA 1 798585
3 890850
5 454423
CO 1 819975
3 202969
5 614011
WA 2 163942
4 369858
6 959285
Я не могу понять, как "дотянуться" до
9 ответов:
ответ пола Х правильно, что вам придется сделать второй
groupbyобъект, но вы можете рассчитать процент более простым способом - простоgroupbythestate_officeи разделитьsalesстолбец по его сумме. Копирование начала ответа Павла H:# From Paul H import numpy as np import pandas as pd np.random.seed(0) df = pd.DataFrame({'state': ['CA', 'WA', 'CO', 'AZ'] * 3, 'office_id': list(range(1, 7)) * 2, 'sales': [np.random.randint(100000, 999999) for _ in range(12)]}) state_office = df.groupby(['state', 'office_id']).agg({'sales': 'sum'}) # Change: groupby state_office and divide by sum state_pcts = state_office.groupby(level=0).apply(lambda x: 100 * x / float(x.sum()))возвращает:
sales state office_id AZ 2 16.981365 4 19.250033 6 63.768601 CA 1 19.331879 3 33.858747 5 46.809373 CO 1 36.851857 3 19.874290 5 43.273852 WA 2 34.707233 4 35.511259 6 29.781508
вам нужно сделать второй объект groupby, который группируется по состояниям, а затем использовать
divспособ:import numpy as np import pandas as pd np.random.seed(0) df = pd.DataFrame({'state': ['CA', 'WA', 'CO', 'AZ'] * 3, 'office_id': list(range(1, 7)) * 2, 'sales': [np.random.randint(100000, 999999) for _ in range(12)]}) state_office = df.groupby(['state', 'office_id']).agg({'sales': 'sum'}) state = df.groupby(['state']).agg({'sales': 'sum'}) state_office.div(state, level='state') * 100 sales state office_id AZ 2 16.981365 4 19.250033 6 63.768601 CA 1 19.331879 3 33.858747 5 46.809373 CO 1 36.851857 3 19.874290 5 43.273852 WA 2 34.707233 4 35.511259 6 29.781508the
level='state'kwarg вdivговорит пандам транслировать / присоединяться к базе фреймов данных по значениям вstateуровень индекса.
для краткости я бы использовал SeriesGroupBy:
In [11]: c = df.groupby(['state', 'office_id'])['sales'].sum().rename("count") In [12]: c Out[12]: state office_id AZ 2 925105 4 592852 6 362198 CA 1 819164 3 743055 5 292885 CO 1 525994 3 338378 5 490335 WA 2 623380 4 441560 6 451428 Name: count, dtype: int64 In [13]: c / c.groupby(level=0).sum() Out[13]: state office_id AZ 2 0.492037 4 0.315321 6 0.192643 CA 1 0.441573 3 0.400546 5 0.157881 CO 1 0.388271 3 0.249779 5 0.361949 WA 2 0.411101 4 0.291196 6 0.297703 Name: count, dtype: float64для нескольких групп вы должны использовать преобразования (используя радикалами ДФ):
In [21]: c = df.groupby(["Group 1","Group 2","Final Group"])["Numbers I want as percents"].sum().rename("count") In [22]: c / c.groupby(level=[0, 1]).transform("sum") Out[22]: Group 1 Group 2 Final Group AAHQ BOSC OWON 0.331006 TLAM 0.668994 MQVF BWSI 0.288961 FXZM 0.711039 ODWV NFCH 0.262395 ... Name: count, dtype: float64это кажется немного более эффективным, чем другие ответы (чуть менее чем в два раза быстрее ответа радикала, для меня ~0.08 s).
Я знаю, что это старый вопрос, но exp1orer это ответ очень медленным для наборов данных с большим количеством уникальных групп (вероятно, из-за лямбды). Я построил их ответ, чтобы превратить его в вычисление массива, так что теперь это супер быстро! Ниже приведен пример кода:
создать тестовый фрейм данных с 50 000 уникальных групп
import random import string import pandas as pd import numpy as np np.random.seed(0) # This is the total number of groups to be created NumberOfGroups = 50000 # Create a lot of groups (random strings of 4 letters) Group1 = [''.join(random.choice(string.ascii_uppercase) for _ in range(4)) for x in range(NumberOfGroups/10)]*10 Group2 = [''.join(random.choice(string.ascii_uppercase) for _ in range(4)) for x in range(NumberOfGroups/2)]*2 FinalGroup = [''.join(random.choice(string.ascii_uppercase) for _ in range(4)) for x in range(NumberOfGroups)] # Make the numbers NumbersForPercents = [np.random.randint(100, 999) for _ in range(NumberOfGroups)] # Make the dataframe df = pd.DataFrame({'Group 1': Group1, 'Group 2': Group2, 'Final Group': FinalGroup, 'Numbers I want as percents': NumbersForPercents})при группировке это выглядит так:
Numbers I want as percents Group 1 Group 2 Final Group AAAH AQYR RMCH 847 XDCL 182 DQGO ALVF 132 AVPH 894 OVGH NVOO 650 VKQP 857 VNLY HYFW 884 MOYH 469 XOOC GIDS 168 HTOY 544 AACE HNXU RAXK 243 YZNK 750 NOYI NYGC 399 ZYCI 614 QKGK CRLF 520 UXNA 970 TXAR MLNB 356 NMFJ 904 VQYG NPON 504 QPKQ 948 ... [50000 rows x 1 columns]массив метод поиска процент:
# Initial grouping (basically a sorted version of df) PreGroupby_df = df.groupby(["Group 1","Group 2","Final Group"]).agg({'Numbers I want as percents': 'sum'}).reset_index() # Get the sum of values for the "final group", append "_Sum" to it's column name, and change it into a dataframe (.reset_index) SumGroup_df = df.groupby(["Group 1","Group 2"]).agg({'Numbers I want as percents': 'sum'}).add_suffix('_Sum').reset_index() # Merge the two dataframes Percents_df = pd.merge(PreGroupby_df, SumGroup_df) # Divide the two columns Percents_df["Percent of Final Group"] = Percents_df["Numbers I want as percents"] / Percents_df["Numbers I want as percents_Sum"] * 100 # Drop the extra _Sum column Percents_df.drop(["Numbers I want as percents_Sum"], inplace=True, axis=1)этот метод занимает около ~0.15 секунд
метод верхнего ответа (с использованием лямбда-функции):
state_office = df.groupby(['Group 1','Group 2','Final Group']).agg({'Numbers I want as percents': 'sum'}) state_pcts = state_office.groupby(level=['Group 1','Group 2']).apply(lambda x: 100 * x / float(x.sum()))этот метод занимает около ~21 секунды, чтобы получить тот же результат.
результат:
Group 1 Group 2 Final Group Numbers I want as percents Percent of Final Group 0 AAAH AQYR RMCH 847 82.312925 1 AAAH AQYR XDCL 182 17.687075 2 AAAH DQGO ALVF 132 12.865497 3 AAAH DQGO AVPH 894 87.134503 4 AAAH OVGH NVOO 650 43.132050 5 AAAH OVGH VKQP 857 56.867950 6 AAAH VNLY HYFW 884 65.336290 7 AAAH VNLY MOYH 469 34.663710 8 AAAH XOOC GIDS 168 23.595506 9 AAAH XOOC HTOY 544 76.404494
вы можете
sumвесьDataFrameразделить наstateитого:# Copying setup from Paul H answer import numpy as np import pandas as pd np.random.seed(0) df = pd.DataFrame({'state': ['CA', 'WA', 'CO', 'AZ'] * 3, 'office_id': list(range(1, 7)) * 2, 'sales': [np.random.randint(100000, 999999) for _ in range(12)]}) # Add a column with the sales divided by state total sales. df['sales_ratio'] = (df / df.groupby(['state']).transform(sum))['sales'] dfвозвращает
office_id sales state sales_ratio 0 1 405711 CA 0.193319 1 2 535829 WA 0.347072 2 3 217952 CO 0.198743 3 4 252315 AZ 0.192500 4 5 982371 CA 0.468094 5 6 459783 WA 0.297815 6 1 404137 CO 0.368519 7 2 222579 AZ 0.169814 8 3 710581 CA 0.338587 9 4 548242 WA 0.355113 10 5 474564 CO 0.432739 11 6 835831 AZ 0.637686но учтите, что это работает только потому, что все столбцы, кроме
stateявляются числовыми, что позволяет суммировать весь фрейм данных. Например, еслиoffice_idэто символ вместо этого, вы получаете сообщение об ошибке:df.office_id = df.office_id.astype(str) df['sales_ratio'] = (df / df.groupby(['state']).transform(sum))['sales']TypeError: неподдерживаемые типы операндов для/:' str 'и'str'
простой способ, который я использовал, - это слияние после того, как 2 groupby затем делают простое разделение.
import numpy as np import pandas as pd np.random.seed(0) df = pd.DataFrame({'state': ['CA', 'WA', 'CO', 'AZ'] * 3, 'office_id': list(range(1, 7)) * 2, 'sales': [np.random.randint(100000, 999999) for _ in range(12)]}) state_office = df.groupby(['state', 'office_id'])['sales'].sum().reset_index() state = df.groupby(['state'])['sales'].sum().reset_index() state_office = state_office.merge(state, left_on='state', right_on ='state', how = 'left') state_office['sales_ratio'] = 100*(state_office['sales_x']/state_office['sales_y']) state office_id sales_x sales_y sales_ratio 0 AZ 2 222579 1310725 16.981365 1 AZ 4 252315 1310725 19.250033 2 AZ 6 835831 1310725 63.768601 3 CA 1 405711 2098663 19.331879 4 CA 3 710581 2098663 33.858747 5 CA 5 982371 2098663 46.809373 6 CO 1 404137 1096653 36.851857 7 CO 3 217952 1096653 19.874290 8 CO 5 474564 1096653 43.273852 9 WA 2 535829 1543854 34.707233 10 WA 4 548242 1543854 35.511259 11 WA 6 459783 1543854 29.781508
Я думаю, что это будет делать трюк в 1 строку:
df.groupby(['state', 'office_id']).sum().transform(lambda x: x/np.sum(x)*100)
Я думаю, что это нуждается в бенчмаркинге. Используя исходный фрейм данных OP,
df = pd.DataFrame({ 'state': ['CA', 'WA', 'CO', 'AZ'] * 3, 'office_id': range(1, 7) * 2, 'sales': [np.random.randint(100000, 999999) for _ in range(12)] })
1-й Энди Хайден
как прокомментировал свой ответ, Энди в полной мере использует векторизацию и индексирование панд.
c = df.groupby(['state', 'office_id'])['sales'].sum().rename("count") c / c.groupby(level=0).sum()3.42 МС ± 16.7 МС на цикл
(среднее ± станд. разработка. из 7 трасс, 100 петель каждая)
2-й Paul H
state_office = df.groupby(['state', 'office_id']).agg({'sales': 'sum'}) state = df.groupby(['state']).agg({'sales': 'sum'}) state_office.div(state, level='state') * 1004.66 МС ± 24.4 МКС для каждого цикла
(среднее ± станд. разработка. из 7 трасс, 100 петель каждая)
3-й exp1orer
это самый медленный ответ, как он рассчитывает
x.sum()для каждогоxна уровне 0.для меня это все-таки полезный ответ, хотя и не в его нынешнем виде. Для быстрого EDA на небольших наборах данных,
applyпозволяет использовать способ сцепления написать это в одну строку. Поэтому мы снимаем необходимость принятия решения о переменной имя, которое на самом деле очень ресурсоемким для вашего самого ценного ресурса (ваш мозг!!).вот модификация,
( df.groupby(['state', 'office_id']) .agg({'sales': 'sum'}) .groupby(level=0) .apply(lambda x: 100 * x / float(x.sum())) )10.6 МС ± 81.5 МКС для каждого цикла
(среднее ± станд. разработка. из 7 трасс, 100 петель каждая)
так что никто не будет заботиться о 6 мс на небольшой набор данных. Тем не менее, это 3-кратное ускорение, и на большем наборе данных с высокой мощностью groupbys это сделает массивным разница.
добавляя к приведенному выше коду, мы делаем фрейм данных с формой (12,000,000, 3) с 14412 категориями состояний и 600 office_ids,
import string import numpy as np import pandas as pd np.random.seed(0) groups = [ ''.join(i) for i in zip( np.random.choice(np.array([i for i in string.ascii_lowercase]), 30000), np.random.choice(np.array([i for i in string.ascii_lowercase]), 30000), np.random.choice(np.array([i for i in string.ascii_lowercase]), 30000), ) ] df = pd.DataFrame({'state': groups * 400, 'office_id': list(range(1, 601)) * 20000, 'sales': [np.random.randint(100000, 999999) for _ in range(12)] * 1000000 })используя Энди,
2 s ± 10.4 МС на цикл
(среднее ± станд. разработка. из 7 запусков, 1 цикл каждый)и exp1orer
19 s ± 77.1 МС на цикл
(среднее ± станд. разработка. из 7 запусков, 1 цикл каждый)Итак, теперь мы видим, что X10 ускоряется на большом, высоком наборы элементов.
обязательно УФ эти три ответа, если вы УФ этот!!
df = pd.DataFrame({'state': ['CA', 'WA', 'CO', 'AZ'] * 3, 'office_id': list(range(1, 7)) * 2, 'sales': [np.random.randint(100000, 999999) for _ in range(12)]}) grouped = df.groupby(['state', 'office_id']) 100*grouped.sum()/df[["state","sales"]].groupby('state').sum()возвращает:
sales state office_id AZ 2 54.587910 4 33.009225 6 12.402865 CA 1 32.046582 3 44.937684 5 23.015735 CO 1 21.099989 3 31.848658 5 47.051353 WA 2 43.882790 4 10.265275 6 45.851935
Comments