Tỷ lệ gấu trúc trong tổng số với nhóm


147

Điều này rõ ràng là đơn giản, nhưng là một người mới khó chịu, tôi đang bị mắc kẹt.

Tôi có tệp CSV chứa 3 cột, Trạng thái, ID văn phòng và Bán hàng cho văn phòng đó.

Tôi muốn tính tỷ lệ phần trăm doanh thu trên mỗi văn phòng ở một trạng thái nhất định (tổng cộng tất cả tỷ lệ phần trăm ở mỗi tiểu bang là 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'})

Điều này trả về:

                  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

Tôi dường như không thể tìm ra cách "vươn tới" đến statemức groupbytổng cộng salescho toàn bộ stateđể tính phân số.


3
df['sales'] / df.groupby('state')['sales'].transform('sum')dường như là câu trả lời rõ ràng nhất
Paul Rougieux

Câu trả lời:


207

Câu trả lời Paul H là đúng mà bạn sẽ phải thực hiện một giây groupbyđối tượng, nhưng bạn có thể tính toán tỷ lệ phần trăm trong một cách đơn giản - chỉ cần groupbysự state_officevà chia salescột bằng tổng của nó. Sao chép phần đầu câu trả lời của 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()))

Trả về:

                     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

1
Những gì đang xảy ra ở đây? Theo tôi hiểu, đây xlà một loại bảng, do đó, 100 * xkhông có ý nghĩa trực giác (đặc biệt là khi một số ô có chứa các chuỗi như AZ, ...).
dhardy 6/2/2015

5
@dhardy state_officelà Sê-ri có Đa chỉ mục - vì vậy, đây chỉ là một cột có các giá trị đều là số. Sau khi bạn thực hiện nhóm, mỗi nhóm xlà một tập hợp con của cột đó. Điều đó có ý nghĩa?
exp1orer 8/2/2015

2
Nó có thể, nhưng nó không làm việc cho tôi. Có phải gấu trúc trong Python 3 hoạt động hơi khác một chút không?
dhardy

1
level=0nghĩa là gì?
van_d39

3
@Veenit có nghĩa là bạn đang nhóm theo cấp độ đầu tiên của chỉ mục, thay vì theo một trong các cột.
exp1orer

54

Bạn cần tạo một đối tượng nhóm thứ hai nhóm theo các trạng thái, sau đó sử dụng divphương thức:

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

các level='state'kwarg trong divnói với gấu trúc để phát sóng / join các dataframes căn cứ vào giá trị trong statemức độ của chỉ số.


4
Phương pháp này có hiệu quả không nếu bạn có 3 chỉ mục? Lần đầu tiên tôi làm một nhóm trên 3 cột. Sau đó, tôi đã làm một nhóm thứ hai chỉ trên 2 và tính tổng. Sau đó, tôi cố gắng sử dụng divnhưng với level=["index1", "index2"]nó nhưng nó cho tôi biết điều đó Join on level between two MultiIndex objects is ambiguous.
Ger

@Ger Nó hoạt động, nhưng không có cách nào tôi có thể đoán được những gì bạn đang làm sai từ mô tả đó. Tìm kiếm xung quanh trang web nhiều hơn một chút. Nếu bạn không tìm thấy bất cứ điều gì, hãy tạo một câu hỏi mới với một ví dụ có thể lặp lại để chứng minh vấn đề. stackoverflow.com/questions/20109391/ hy
Paul H

34

Để đơn giản, tôi sử dụng 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

Đối với nhiều nhóm, bạn phải sử dụng biến đổi (sử dụng Radical'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

Điều này có vẻ hiệu quả hơn một chút so với các câu trả lời khác (chỉ bằng hai lần tốc độ của câu trả lời cấp tiến, đối với tôi ~ 0,08 giây).


5
Đây là siêu nhanh. Tôi muốn giới thiệu đây là cách tiếp cận gấu trúc ưa thích. Thực sự tận dụng lợi thế của vector hóa numpy và lập chỉ mục gấu trúc.
Charles

Điều này cũng làm việc tốt với tôi, vì tôi đang làm việc với nhiều nhóm. Cảm ơn.
irene

27

Tôi nghĩ rằng điều này cần điểm chuẩn. Sử dụng DataFrame gốc của 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 Andy Hayden

Như đã nhận xét về câu trả lời của mình, Andy tận dụng tối đa việc vector hóa và lập chỉ mục gấu trúc.

c = df.groupby(['state', 'office_id'])['sales'].sum().rename("count")
c / c.groupby(level=0).sum()

3,42 ms ± 16,7 Nhận xét trên mỗi vòng lặp
(trung bình ± std. Dev của 7 lần chạy, mỗi vòng 100 lần)


Paul 2

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 Nút trên mỗi vòng lặp
(trung bình ± std. Dev của 7 lần chạy, mỗi vòng 100 vòng)


3 exp1orer

Đây là câu trả lời chậm nhất vì nó tính toán x.sum()cho từng xcấp độ 0.

Đối với tôi, đây vẫn là một câu trả lời hữu ích, mặc dù không ở dạng hiện tại. Để nhanh chóng EDA trên các bộ dữ liệu nhỏ hơn, applycho phép bạn sử dụng phương thức xâu chuỗi để viết điều này trong một dòng duy nhất. Do đó, chúng tôi loại bỏ nhu cầu quyết định tên của một biến, thực sự rất tốn kém về mặt tính toán cho tài nguyên quý giá nhất của bạn (bộ não của bạn !!).

Đây là sửa đổi,

(
    df.groupby(['state', 'office_id'])
    .agg({'sales': 'sum'})
    .groupby(level=0)
    .apply(lambda x: 100 * x / float(x.sum()))
)

10,6 ms ± 81,5 Lời nói trên mỗi vòng lặp
(trung bình ± std. Dev của 7 lần chạy, mỗi vòng 100 vòng)


Vì vậy, không ai sẽ quan tâm đến 6ms trên một tập dữ liệu nhỏ. Tuy nhiên, đây là tốc độ tăng gấp 3 lần và, trên một tập dữ liệu lớn hơn với các nhóm có số lượng thẻ cao, điều này sẽ tạo ra sự khác biệt lớn.

Thêm vào mã trên, chúng tôi tạo một DataFrame có hình dạng (12.000.000, 3) với 14412 danh mục trạng thái và 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
})

Sử dụng Andy,

2 s ± 10,4 ms mỗi vòng lặp
(trung bình ± std. Dev của 7 lần chạy, mỗi vòng 1 vòng)

và exp1orer

19 s ± 77,1 ms mỗi vòng lặp
(trung bình ± std. Dev của 7 lần chạy, mỗi vòng 1 vòng)

Vì vậy, bây giờ chúng ta thấy x10 tăng tốc trên các bộ dữ liệu cardinality lớn, cao.


Hãy chắc chắn để UV ba câu trả lời nếu bạn UV câu này !!


16

(Giải pháp này được lấy cảm hứng từ bài viết này https://pbpython.com/pandas_transform.html )

Tôi thấy giải pháp sau đây là đơn giản nhất (và có thể là nhanh nhất) bằng cách sử dụng transformation:

Chuyển đổi: Trong khi tập hợp phải trả về một phiên bản rút gọn của dữ liệu, chuyển đổi có thể trả lại một số phiên bản đã chuyển đổi của dữ liệu đầy đủ để kết hợp lại. Đối với một chuyển đổi như vậy, đầu ra có hình dạng giống như đầu vào.

Vì vậy, sử dụng transformation, giải pháp là 1-liner:

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

Và nếu bạn in:

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

2
@Cancer Đây là câu trả lời yêu thích của tôi vì nó giữ df dưới dạng df (không chuyển đổi thành chuỗi) và chỉ thêm một cột%. Cảm ơn bạn
T.Fung

Sự thay đổi của câu trả lời này đã làm việc rất tốt với tôitransform('max')
Sheldore

11

Tôi biết rằng đây là một câu hỏi cũ, nhưng câu trả lời của exp1 rất chậm đối với các bộ dữ liệu có số lượng lớn các nhóm duy nhất (có thể là do lambda). Tôi đã xây dựng câu trả lời của họ để biến nó thành một phép tính mảng để bây giờ nó siêu nhanh! Dưới đây là mã ví dụ:

Tạo khung dữ liệu thử nghiệm với 50.000 nhóm duy nhất

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

Khi được nhóm lại, nó trông giống như:

                             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]

Phương pháp tìm phần trăm mảng:

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

Phương pháp này mất khoảng ~ 0,15 giây

Phương pháp trả lời hàng đầu (sử dụng chức năng lambda):

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

Phương pháp này mất khoảng ~ 21 giây để tạo ra kết quả tương tự.

Kết quả:

      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

9

Tôi nhận ra đã có câu trả lời tốt ở đây.

Tuy nhiên, tôi muốn đóng góp của riêng mình, bởi vì tôi cảm thấy đối với một câu hỏi cơ bản, đơn giản như thế này, nên có một giải pháp ngắn gọn có thể hiểu được trong nháy mắt.

Nó cũng hoạt động theo cách mà tôi có thể thêm tỷ lệ phần trăm dưới dạng một cột mới, khiến phần còn lại của khung dữ liệu không bị ảnh hưởng. Cuối cùng nhưng không kém phần quan trọng, nó nên khái quát một cách rõ ràng cho trường hợp trong đó có nhiều cấp độ nhóm (ví dụ: tiểu bang và quốc gia thay vì chỉ trạng thái).

Đoạn mã sau đáp ứng các tiêu chí sau:

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

Lưu ý rằng nếu bạn vẫn đang sử dụng Python 2, bạn sẽ phải thay thế x trong mẫu số của thuật ngữ lambda bằng float (x).


Đây là câu trả lời tốt nhất IMO. Điều duy nhất để thêm sẽ là * 100làm cho nó một tỷ lệ phần trăm.
Bouncner

1
@Bouncner: Có, nói đúng ra, bạn sẽ phải nhân với 100 để có tỷ lệ phần trăm - hoặc đổi tên biến mới từ "sales_percentage" thành "sales_ratio". Cá nhân, tôi thích cái sau, và tôi chỉnh sửa câu trả lời cho phù hợp. Cảm ơn đã đề cập!
MightyCantly

2
Điều này không hoạt động mặc dù nếu bạn có nhiều cấp độ.
irene

@irene: Điểm tốt, cảm ơn! Có lẽ trong trường hợp đó, df.reset_index (). Groupby (['state']) ['sales']. Transform (lambda x: x / x.sum ()) sẽ hoạt động. Hay tôi đang nhìn một cái gì đó?
MightyCpered

1
Câu trả lời này là tuyệt vời. Nó không liên quan đến việc tạo ra một groupbyđối tượng tạm thời , siêu súc tích và đọc rất logic từ trái sang phải.
C. Braun

7

Cách thanh lịch nhất để tìm tỷ lệ phần trăm trên các cột hoặc chỉ mục là sử dụng pd.crosstab.

Dữ liệu mẫu

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

Khung dữ liệu đầu ra là như thế này

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

Chỉ cần chỉ định chỉ mục, cột và các giá trị để tổng hợp. Từ khóa chuẩn hóa sẽ tính% trên các chỉ mục hoặc các cột tùy thuộc vào ngữ cảnh.

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%

3

Bạn có thể chia sumtoàn bộ DataFramevà chia cho statetổng số:

# 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

Trả về

    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

Nhưng lưu ý rằng điều này chỉ hoạt động vì tất cả các cột không phải statelà số, cho phép tính tổng của toàn bộ DataFrame. Ví dụ: nếu office_idlà ký tự thay vào đó, bạn sẽ gặp lỗi:

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

TypeError: loại toán hạng không được hỗ trợ cho /: 'str' và 'str'


Tôi đã chỉnh sửa để lưu ý rằng điều này chỉ hoạt động khi tất cả các cột ngoại trừ groupbycột là số. Nhưng nó là khá thanh lịch. Có cách nào để làm cho nó hoạt động với các strcột khác không?
Max Ghenis

Không xa như tôi biết: stackoverflow.com/questions/34099684/ từ
iggy

2

Tôi nghĩ rằng điều này sẽ thực hiện các mẹo trong 1 dòng:

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

Tôi tin rằng nó cần tất cả các cột của bộ dữ liệu. trong trường hợp này, chỉ có một. Nếu bạn có một số và muốn thực hiện thao tác này trên một singe, chỉ cần chỉ định nó sau biểu thức nhóm: df.groupby (['state', 'office_id']) [[TÊN MÀU SẮC CỦA BẠN TẠI ĐÂY]]. để giữ cho các cột khác không bị ảnh hưởng, chỉ cần gán lại các cột cụ thể
louisD

@louisD: Tôi rất thích cách tiếp cận của bạn là cố gắng giữ cho nó ngắn gọn. Thật không may, khi tôi cố gắng gán lại cột như bạn đề xuất, tôi gặp hai lỗi: "ValueError: Buffer dtype không khớp, dự kiến ​​'đối tượng Python' nhưng có 'dài'" và thêm vào đó (trong quá trình xử lý ngoại lệ đầu tiên): " LoạiError: chỉ mục không tương thích của cột được chèn với chỉ mục khung "Mã tôi đã sử dụng là như sau: df ['Perc'] = df.groupby (['state', 'office_id']). Sum (). Transform (lambda x: x / np.sum (x) * 100) Do đó, tôi sẽ đăng một câu trả lời riêng để khắc phục điều này.
MightyCpered

1

Cách đơn giản mà tôi đã sử dụng là hợp nhất sau khi chia nhóm sau đó thực hiện phép chia đơn giản.

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 = 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()

Trả về:

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

0

Là một người cũng đang học gấu trúc, tôi thấy những câu trả lời khác hơi ẩn ý vì gấu trúc che giấu hầu hết các công việc đằng sau hậu trường. Cụ thể là cách hoạt động của hoạt động bằng cách tự động khớp tên cột và chỉ mục. Mã này phải tương đương với phiên bản từng bước của câu trả lời được chấp nhận của @ exp1orer

Với df, tôi sẽ gọi nó bằng bí danh 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được state_office_salesnhóm theo tổng số tiền trong index level 0(ngoài cùng bên trái).

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

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

Bởi vì hai datafram chia sẻ một tên chỉ mục và gấu trúc tên cột sẽ tìm thấy các vị trí thích hợp thông qua các chỉ mục được chia sẻ như:

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

Để minh họa điều này thậm chí còn tốt hơn, đây là một phần tổng với một XXkhông có tương đương. Gấu trúc sẽ khớp vị trí dựa trên tên chỉ mục và cột, nơi không có gấu trúc chồng chéo sẽ bỏ qua nó:

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

Điều này trở nên rất rõ ràng khi không có chỉ mục hoặc cột được chia sẻ. Ở đây missing_index_totalstương đương với state_total_salesngoại trừ việc nó không có tên chỉ mục.

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

-1

Giải pháp một dòng:

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

Điều này trả về một loạt các tỷ lệ trên mỗi văn phòng - có thể được sử dụng riêng hoặc được gán cho Dataframe gốc.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.