Pandy procent całości z grupowaniem

148

Jest to oczywiście proste, ale jako tępy nowicjusz utknąłem.

Mam plik CSV zawierający 3 kolumny, stan, identyfikator biura i sprzedaż dla tego biura.

Chcę obliczyć procent sprzedaży na biuro w danym stanie (suma wszystkich procentów w każdym stanie to 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'})

To zwraca:

                  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

Nie potrafię wymyślić, jak „sięgnąć” do statepoziomu, groupbyaby zsumować salescałość, stateaby obliczyć ułamek.

erikcw
źródło
3
df['sales'] / df.groupby('state')['sales'].transform('sum')wydaje się być najjaśniejszą odpowiedzią.
Paul Rougieux

Odpowiedzi:

207

Odpowiedź Paula H. jest prawidłowa, że ​​będziesz musiał zrobić drugi groupbyobiekt, ale możesz obliczyć procent w prostszy sposób - wystarczy, groupbyże state_officepodzielisz saleskolumnę przez jej sumę. Kopiując początek odpowiedzi Paula 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()))

Zwroty:

                     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
exp1orer
źródło
1
Co tu się dzieje? Jak rozumiem, xjest to jakiś rodzaj tabeli, więc 100 * xintuicyjnie nie ma sensu (zwłaszcza gdy niektóre komórki zawierają ciągi, takie jak AZ, ...).
dhardy
5
@dhardy state_officeto seria z wieloma indeksami - więc jest to tylko jedna kolumna, której wszystkie wartości są liczbowe. Po wykonaniu grupowania każdy xjest podzbiorem tej kolumny. Czy to ma sens?
exp1orer
2
Mógłby, ale nie zadziałał dla mnie. Czy pandy w Pythonie 3 działają trochę inaczej?
dhardy
1
Co to level=0znaczy?
van_d39
3
@Veenit oznacza, że ​​grupujesz według pierwszego poziomu indeksu, a nie według jednej z kolumn.
exp1orer
54

Musisz utworzyć drugi obiekt grupowania, który grupuje według stanów, a następnie użyj divmetody:

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'kwarg w divpowiada pandy do nadawania / join bazę dataframes na wartości na statepoziomie indeksu.

Paul H.
źródło
4
Czy ta metoda działa, jeśli masz 3 indeksy? Najpierw zrobiłem groupby na 3 kolumnach. Potem zrobiłem drugie grupowanie tylko na 2 i obliczyłem sumę. Następnie próbuję użyćdiv ale z, level=["index1", "index2"]ale to mi mówi Join on level between two MultiIndex objects is ambiguous.
Ger
@Ger To działa, ale nie ma możliwości odgadnięcia, co robisz źle na podstawie tego opisu. Poszukaj trochę więcej w witrynie. Jeśli nic nie znajdziesz, utwórz nowe pytanie z odtwarzalnym przykładem, który demonstruje problem. stackoverflow.com/questions/20109391/…
Paul H
34

Dla zwięzłości użyłbym 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

W przypadku wielu grup musisz użyć transformacji (używając df Radicala ):

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

Wydaje się, że jest to nieco bardziej wydajne niż inne odpowiedzi (tylko mniej niż dwa razy szybciej niż odpowiedź Radicala, dla mnie ~ 0,08 s).

Andy Hayden
źródło
5
To jest super szybkie. Poleciłbym to jako preferowane podejście do pand. Naprawdę korzysta z wektoryzacji numpy'ego i indeksowania pand.
Charles
U mnie też się to udało, ponieważ pracuję z wieloma grupami. Dzięki.
Irene
27

Myślę, że to wymaga analizy porównawczej. Używając oryginalnej ramki DataFrame 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

Jak skomentował swoją odpowiedź, Andy w pełni wykorzystuje wektoryzację i indeksowanie pand.

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

3,42 ms ± 16,7 μs na pętlę
(średnia ± odchylenie standardowe z 7 cykli po 100 pętli)


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') * 100

4,66 ms ± 24,4 μs na pętlę
(średnia ± odchylenie standardowe z 7 przebiegów, po 100 pętli każda)


3rd exp1orer

Jest to najwolniejsza odpowiedź, obliczana x.sum()dla każdego xna poziomie 0.

Dla mnie jest to nadal przydatna odpowiedź, choć nie w obecnej formie. W celu szybkiego EDA dla mniejszych zestawów danych, applyumożliwia użycie łańcuchów metod do zapisania tego w jednym wierszu. Dlatego usuwamy potrzebę decydowania o nazwie zmiennej, która w rzeczywistości jest bardzo kosztowna obliczeniowo dla Twojego najcenniejszego zasobu (Twojego mózgu !!).

Oto modyfikacja,

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

10,6 ms ± 81,5 μs na pętlę
(średnia ± odchylenie standardowe z 7 przebiegów, po 100 pętli każda)


Więc nikogo nie obchodzi 6 ms na małym zestawie danych. Jest to jednak 3-krotnie szybsze i na większym zbiorze danych z grupami o wysokiej kardynalności będzie to miało ogromne znaczenie.

Dodając do powyższego kodu, tworzymy ramkę DataFrame o kształcie (12 000 000, 3) z 14412 kategoriami stanu i 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
})

Używając Andy's,

2 s ± 10,4 ms na pętlę
(średnia ± odchylenie standardowe 7 przebiegów, po 1 pętli)

i exp1orer

19 s ± 77,1 ms na pętlę
(średnia ± odchylenie standardowe z 7 przebiegów, po 1 pętli)

Teraz widzimy przyspieszenie x10 na dużych zestawach danych o wysokiej kardynalności.


Upewnij się, że te trzy odpowiedzi zostały poddane promieniowaniu UV, jeśli tę jedną!

Stoły Little Bobby
źródło
17

(To rozwiązanie jest inspirowane tym artykułem https://pbpython.com/pandas_transform.html )

Poniższe rozwiązanie jest najprostsze (i prawdopodobnie najszybsze) przy użyciu transformation:

Transformacja: chociaż agregacja musi zwrócić zredukowaną wersję danych, transformacja może zwrócić pewną przekształconą wersję pełnych danych do ponownego połączenia. W przypadku takiej transformacji dane wyjściowe mają taki sam kształt jak dane wejściowe.

Więc używając transformation, rozwiązaniem jest 1-liniowa:

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

A jeśli drukujesz:

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
Caner
źródło
3
@Cancer To moja ulubiona odpowiedź, ponieważ zachowuje df jako df (bez konwersji na serie) i dodaje tylko kolumnę%. Dziękuję
T.Fung
transform('max')
Odmiana
11

Wiem, że jest to stare pytanie, ale odpowiedź exp1orera jest bardzo powolna w przypadku zbiorów danych z dużą liczbą unikalnych grup (prawdopodobnie z powodu lambda). Wykorzystałem ich odpowiedź, aby przekształcić ją w obliczenia tablicowe, więc teraz jest super szybka! Poniżej przykładowy kod:

Utwórz testową ramkę danych z 50 000 unikatowych grup

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

Po zgrupowaniu wygląda to tak:

                             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]

Tablicowa metoda znajdowania procentu:

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

Ta metoda zajmuje około ~ 0,15 sekundy

Najlepsza metoda odpowiedzi (przy użyciu funkcji 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()))

Ta metoda zajmuje około ~ 21 sekund, aby uzyskać ten sam wynik.

Wynik:

      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
Radykalny Edward
źródło
9

Zdaję sobie sprawę, że są tu już dobre odpowiedzi.

Niemniej jednak chciałbym wnieść swój własny, ponieważ czuję, że na takie proste, proste pytanie powinno być krótkie rozwiązanie, które jest zrozumiałe na pierwszy rzut oka.

Powinien również działać w taki sposób, że mogę dodać wartości procentowe jako nową kolumnę, pozostawiając resztę ramki danych nietkniętą. Wreszcie, powinno to w oczywisty sposób uogólniać przypadek, w którym istnieje więcej niż jeden poziom grupowania (np. Stan i kraj zamiast tylko stanu).

Poniższy fragment spełnia te kryteria:

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

Zauważ, że jeśli nadal używasz Pythona 2, będziesz musiał zamienić x w mianowniku wyrażenia lambda przez float (x).

MightyCurious
źródło
To najlepsza odpowiedź IMO. Jedyną rzeczą do dodania byłoby * 100zrobienie tego procentu.
Bouncner
1
@Bouncner: Tak, ściśle mówiąc, musiałbyś pomnożyć przez 100, aby otrzymać procent - lub zmienić nazwę nowej zmiennej z „sales_percentage” na „sales_ratio”. Osobiście wolę to drugie i odpowiednio zredagowałem odpowiedź. Dzięki za wzmiankę!
MightyCurious
2
To jednak nie działa, jeśli masz wiele poziomów.
Irene
@irene: Słuszna uwaga, dzięki! Prawdopodobnie w tym przypadku zadziała df.reset_index (). Groupby (['stan']) ['sprzedaż']. Transform (lambda x: x / x.sum ()). A może coś przeoczę?
MightyCurious,
1
Ta odpowiedź jest świetna. Nie wymaga tworzenia tymczasowego groupbyobiektu, jest bardzo zwięzły i bardzo logicznie czyta od lewej do prawej.
C. Braun
7

Najbardziej eleganckim sposobem znajdowania wartości procentowych w kolumnach lub indeksie jest użycie pd.crosstab .

Przykładowe dane

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

Ramka danych wyjściowych wygląda następująco

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

Po prostu określ indeks, kolumny i wartości do zagregowania. Słowo kluczowe normalize obliczy% w indeksie lub kolumnach w zależności od kontekstu.

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%
ajknzhol
źródło
3

Możesz sumcałość DataFramei podzielić przez statesumę:

# 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

Zwroty

    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

Należy jednak pamiętać, że działa to tylko dlatego, że wszystkie kolumny inne niż statesą liczbowe, co umożliwia sumowanie całej ramki DataFrame. Na przykład, jeśli office_idzamiast tego jest znak, pojawi się błąd:

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

TypeError: nieobsługiwane typy operandów dla /: „str” i „str”

iggy
źródło
Edytowałem, aby zauważyć, że działa to tylko wtedy, gdy wszystkie kolumny z wyjątkiem groupbykolumny są numeryczne. Ale poza tym jest całkiem elegancki. Czy istnieje sposób, aby działał z innymi strkolumnami?
Max Ghenis
2

Myślę, że to załatwi sprawę w 1 linii:

df.groupby(['state', 'office_id']).sum().transform(lambda x: x/np.sum(x)*100)
LouisD
źródło
Uważam, że zajmuje wszystkie kolumny zbioru danych. w tym przypadku jest tylko jeden. Jeśli masz kilka i chcesz wykonać tę operację na pojedynczym, po prostu określ je po wyrażeniu groupby: df.groupby (['stan', 'office_id']) [[TUTAJ TWOJA NAZWA KOLUMNY]]. Etcetc, jeśli chcesz aby pozostałe kolumny pozostały nietknięte, po prostu ponownie przypisano konkretne kolumny
louisD
@louisD: Bardzo podoba mi się twoje podejście, które polega na tym, że stara się mówić krótko. Niestety, kiedy próbuję ponownie przypisać kolumnę tak, jak sugerowałeś, otrzymuję dwa błędy: „ValueError: Buffer dtype mismatch, oczekiwano 'obiektu Pythona', ale otrzymałem 'long long'” oraz dodatkowo (podczas obsługi pierwszego wyjątku): " Błąd typu: niezgodny indeks wstawionej kolumny z indeksem ramki "Użyłem kodu: df ['percent'] = df.groupby (['state', 'office_id']). Sum (). Transform (lambda x: x / np.sum (x) * 100) Dlatego zamieszczę osobną odpowiedź, aby to naprawić.
MightyCurious
1

Prosty sposób, którego użyłem, to scalenie po 2 groupby, a następnie wykonanie prostego podziału.

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
upliftedLemur
źródło
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()

Zwroty:

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
Alessandro
źródło
0

Jako osoba, która również uczy się pand, znalazłem inne odpowiedzi nieco ukryte, ponieważ pandy ukrywają większość pracy za kulisami. Mianowicie w tym, jak działa operacja, automatycznie dopasowując nazwy kolumn i indeksów. Ten kod powinien być odpowiednikiem krok po kroku wersji zaakceptowanej odpowiedzi @ exp1orer

W przypadku df, będę to nazywać aliasem 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_salesjest state_office_salespogrupowany według sum w index level 0(skrajny lewy).

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

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

Ponieważ dwie ramki danych mają wspólną nazwę indeksu, a pandy z nazwami kolumn znajdą odpowiednie lokalizacje za pośrednictwem udostępnionych indeksów, takich jak:

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

Aby to jeszcze lepiej zilustrować, oto suma częściowa z a, XXktóra nie ma odpowiednika. Pandy dopasują się do lokalizacji na podstawie indeksu i nazw kolumn, gdzie nie ma nakładania się pandy zignorują to:

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

Staje się to bardzo jasne, gdy nie ma wspólnych indeksów ani kolumn. Tutaj missing_index_totalsjest równe z state_total_saleswyjątkiem tego, że nie ma nazwy indeksu.

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
Anders Solberg
źródło
-1

Rozwiązanie one-line:

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

Zwraca serię wskaźników na biuro - może być używany samodzielnie lub przypisany do oryginalnej ramki Dataframe.

ribitskiyb
źródło