IT이야기

그룹별 판다 비율

cyworld 2022. 3. 8. 21:59
반응형

그룹별 판다 비율

이것은 분명히 간단하지만, 아주 새것처럼 나는 막히고 있다.

나는 3개의 컬럼이 들어 있는 CSV 파일을 가지고 있다. 주, 사무실 ID, 그리고 그 사무실의 판매.

주어진 주의 사무실당 매출 비율을 계산하고 싶다(각 주의 모든 백분율 합계는 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

어떻게 하면 '다시 일어서느냐'는 방법을 알 수가 없는 것 같다.stategroupby총계하여sales전체로서state분수를 계산하다

Paul H의 대답은 옳다. 당신은 1초라도 더 기다려야 할 것이다.groupby개체, 하지만 더 간단한 방법으로 백분율을 계산할 수 있다 -- 그냥groupbystate_office그리고 그것을 나누다sales합계를 따지다Paul 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

하는 두 을 .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.781508

level='state'에 열중하다.div팬더에게 데이터프레임 기반을 방송/방송하라고 말한다.state지수의 수준

(이 솔루션은 이 기사 https://pbpython.com/pandas_transform.html)에서 영감을 받아 작성되었다.

다음 솔루션이 가장 간단하며(아마도 가장 빠를 것이다)transformation:

변환:통합은 데이터의 축소된 버전을 반환해야 하지만 변환은 전체 데이터의 일부 변환된 버전을 재결합으로 반환할 수 있다.이러한 변환의 경우 출력은 입력과 동일한 형상이다.

그래서 사용transformation 은 1-liner:

df['%'] = 100 * df['sales'] / df.groupby('state')['sales'].transform('sum')

인쇄할 경우:

print(df.sort_values(['state', 'office_id']).reset_index(drop=True))

   state  office_id   sales          %
0     AZ          2  195197   9.844309
1     AZ          4  877890  44.274352
2     AZ          6  909754  45.881339
3     CA          1  614752  50.415708
4     CA          3  395340  32.421767
5     CA          5  209274  17.162525
6     CO          1  549430  42.659629
7     CO          3  457514  35.522956
8     CO          5  280995  21.817415
9     WA          2  828238  35.696929
10    WA          4  719366  31.004563
11    WA          6  772590  33.298509

정확성을 위해 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

여러 그룹에 대해 변환(Richan's df 사용):

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

이것은 다른 대답보다 약간 더 성능이 좋은 것 같다(Richan의 대답 속도에서 두 배도 안 되는 속도, 나에겐 0.08s까지).

나는 이것이 벤치마킹이 필요하다고 생각한다.OP의 원래 DataFrame을 사용하여

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.42ms ± 16.7µs
(102 ± std).7회 주행 중 각 100회 반복)


제2회 폴 H

state_office = df.groupby(['state', 'office_id']).agg({'sales': 'sum'})
state = df.groupby(['state']).agg({'sales': 'sum'})
state_office.div(state, level='state') * 100

4.66 ms ± 24.4루프당 µs
(102 ± std).7회 주행 중 각 100회 반복)


제3차 엑스포러

이것은 계산해 보면 가장 느린 대답이다.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.6ms ± 81.5µs
(102 ± std).7회 주행 중 각 100회 반복)


그래서 아무도 작은 데이터 세트에 6ms를 신경 쓰지 않을 겁니다.그러나 이는 3배 빠른 속도이며 높은 카디널리티 그룹을 가진 대규모 데이터 집합에서 이는 엄청난 차이를 만들 것이다.

위의 코드에 추가하여, 우리는 14412개의 주 범주와 600개의 Office_ids를 가진 형태(1200만, 3)의 DataFrame을 만든다.

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초 ± 10.4ms
(102 ± std).7회 주행의 dev. 각 1회 반복)

그리고 expoorer.

루프당 19초 ± 77.1ms
(102 ± std).7회 주행의 dev. 각 1회 반복)

그래서 이제 우리는 크고 높은 카디널리티 데이터셋에서 x10의 속도를 볼 수 있다.


이 세 개의 답에 자외선 차단제를 뿌리면 꼭 자외선 차단해!!

나는 이미 여기에 좋은 해답이 있다는 것을 안다.

그럼에도 불구하고 나는 나 자신의 것에 기여하고 싶다. 왜냐하면 나는 이런 기초적이고 간단한 질문에 대해 느끼기 때문에 한눈에 이해할 수 있는 짧은 해결책이 있어야 한다.

또한 나머지 데이터프레임은 그대로 두고 내가 백분율을 새 컬럼으로 추가할 수 있는 방식으로 작동해야 한다.마지막으로, 두 개 이상의 그룹화 수준이 있는 경우(예: 국가만 아닌 주와 국가)에 대해 분명한 방법으로 일반화해야 한다.

다음 코드 조각은 이러한 기준을 충족한다.

df['sales_ratio'] = df.groupby(['state'])['sales'].transform(lambda x: x/x.sum())

Python 2를 계속 사용하는 경우 람다 항의 분모에 있는 x를 float(x)로 교체해야 한다는 점에 유의하십시오.

이것이 오래된 질문이라는 것은 알지만, 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

열 또는 색인에 걸쳐 백분율을 찾는 가장 우아한 방법은pd.crosstab.

샘플 데이터

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)]})

출력 데이터프레임은 다음과 같다.

print(df)

        state   office_id   sales
    0   CA  1   764505
    1   WA  2   313980
    2   CO  3   558645
    3   AZ  4   883433
    4   CA  5   301244
    5   WA  6   752009
    6   CO  1   457208
    7   AZ  2   259657
    8   CA  3   584471
    9   WA  4   122358
    10  CO  5   721845
    11  AZ  6   136928

인덱스, 열 및 집계할 값을 지정하십시오.정규화 키워드는 컨텍스트에 따라 인덱스 또는 컬럼에서 %를 계산한다.

result = pd.crosstab(index=df['state'], 
                     columns=df['office_id'], 
                     values=df['sales'], 
                     aggfunc='sum', 
                     normalize='index').applymap('{:.2f}%'.format)




print(result)
office_id   1   2   3   4   5   6
state                       
AZ  0.00%   0.20%   0.00%   0.69%   0.00%   0.11%
CA  0.46%   0.00%   0.35%   0.00%   0.18%   0.00%
CO  0.26%   0.00%   0.32%   0.00%   0.42%   0.00%
WA  0.00%   0.26%   0.00%   0.10%   0.00%   0.63%

넌 할 수 있다.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전체 DataFrame의 합계가 가능한 숫자.예를 들어, 다음과 같다.office_id대신 문자, 오류 발생:

df.office_id = df.office_id.astype(str)
df['sales_ratio'] = (df / df.groupby(['state']).transform(sum))['sales']

TypeError: /: 'str' 및 'str'에 대해 지원되지 않는 피연산자 유형

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)]})

df.groupby(['state', 'office_id'])['sales'].sum().rename("weightage").groupby(level = 0).transform(lambda x: x/x.sum())
df.reset_index()

출력:

    state   office_id   weightage
0   AZ  2   0.169814
1   AZ  4   0.192500
2   AZ  6   0.637686
3   CA  1   0.193319
4   CA  3   0.338587
5   CA  5   0.468094
6   CO  1   0.368519
7   CO  3   0.198743
8   CO  5   0.432739
9   WA  2   0.347072
10  WA  4   0.355113
11  WA  6   0.297815

내 생각엔 이게 한 줄의 속임수를 쓸 것 같아.

df.groupby(['state', 'office_id']).sum().transform(lambda x: x/np.sum(x)*100)

내가 사용한 간단한 방법은 두 그룹비가 간단한 분할을 한 후 합병하는 것이다.

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
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

팬더를 배우고 있는 사람으로서 나는 팬더가 일의 대부분을 뒤에 숨기기 때문에 다른 해답은 약간 함축되어 있다는 것을 발견했다.즉, 열 및 인덱스 이름을 자동으로 일치시켜 작업의 작동 방식.이 코드는 @exp1orer의 수락된 답변의 단계별 버전과 같아야 한다.

더 위드df가명으로 부르겠다.state_office_sales:

                  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

state_total_sales이다state_office_sales총합에 따라 그룹화된index level 0(맨 왼쪽).

In:   state_total_sales = df.groupby(level=0).sum()
      state_total_sales

Out: 
       sales
state   
AZ     2448009
CA     2832270
CO     1495486
WA     595859

두 데이터 프레임은 인덱스 이름을 공유하고 컬럼 이름 판다는 다음과 같은 공유 인덱스를 통해 적절한 위치를 찾을 수 있기 때문이다.

In:   state_office_sales / state_total_sales

Out:  

                   sales
state   office_id   
AZ      2          0.448640
        4          0.125865
        6          0.425496
CA      1          0.288022
        3          0.322169
        5          0.389809
CO      1          0.206684
        3          0.357891
        5          0.435425
WA      2          0.321689
        4          0.346325
        6          0.331986

이를 더욱 잘 설명하기 위해, 여기 다음과 같은 부분적인 합계가 있다.XX하게 될 것이다팬더들은 인덱스와 칼럼 이름을 기준으로 위치를 일치시킬 것이며, 팬더가 겹치지 않는 곳에서는 팬더를 무시할 것이다.

In:   partial_total = pd.DataFrame(
                      data   =  {'sales' : [2448009, 595859, 99999]},
                      index  =             ['AZ',    'WA',   'XX' ]
                      )
      partial_total.index.name = 'state'


Out:  
         sales
state
AZ       2448009
WA       595859
XX       99999
In:   state_office_sales / partial_total

Out: 
                   sales
state   office_id   
AZ      2          0.448640
        4          0.125865
        6          0.425496
CA      1          NaN
        3          NaN
        5          NaN
CO      1          NaN
        3          NaN
        5          NaN
WA      2          0.321689
        4          0.346325
        6          0.331986

이것은 공유 인덱스나 열이 없을 때 매우 명확해진다.여기missing_index_totals와 같다state_total_sales인덱스 이름이 없다는 것만 제외하고

In:   missing_index_totals = state_total_sales.rename_axis("")
      missing_index_totals

Out:  
       sales
AZ     2448009
CA     2832270
CO     1495486
WA     595859
In:   state_office_sales / missing_index_totals 

Out:  ValueError: cannot join with no overlapping index names

한 줄 솔루션:

df.join(
    df.groupby('state').agg(state_total=('sales', 'sum')),
    on='state'
).eval('sales / state_total')

이렇게 하면 자체에서 사용하거나 원래 데이터프레임에 할당할 수 있는 일련의 사무실별 비율이 반환된다.

참조URL: https://stackoverflow.com/questions/23377108/pandas-percentage-of-total-with-groupby

반응형