Pandas Prozentsatz der Gesamtzahl mit groupby

147

Das ist natürlich einfach, aber als numpy Neuling stecke ich fest.

Ich habe eine CSV-Datei, die 3 Spalten enthält, den Status, die Büro-ID und den Vertrieb für dieses Büro.

Ich möchte den Prozentsatz des Umsatzes pro Büro in einem bestimmten Bundesstaat berechnen (die Summe aller Prozentsätze in jedem Bundesstaat beträgt 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'})

Dies gibt zurück:

                  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

Ich kann anscheinend nicht herausfinden, wie man das stateNiveau von "erreicht", groupbyum das salesfür das Ganze statezu summieren, um den Bruch zu berechnen.

erikcw
quelle
3
df['sales'] / df.groupby('state')['sales'].transform('sum')scheint die klarste Antwort zu sein.
Paul Rougieux

Antworten:

207

Die Antwort von Paul H ist richtig, dass Sie ein zweites groupbyObjekt erstellen müssen, aber Sie können den Prozentsatz auf einfachere Weise berechnen - nur groupbydie state_officeund dividieren Sie die salesSpalte durch ihre Summe. Kopieren Sie den Anfang von Paul Hs Antwort:

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

Kehrt zurück:

                     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
quelle
1
Was ist denn hier los? Soweit ich weiß, xhandelt es sich um eine Tabelle, 100 * xdie intuitiv keinen Sinn ergibt (insbesondere, wenn einige der Zellen Zeichenfolgen wie AZ... enthalten).
Dhardy
5
@dhardy state_officeist eine Serie mit einem Multi-Index - es ist also nur eine Spalte, deren Werte alle numerisch sind. Nachdem Sie die Gruppierung durchgeführt haben, ist jede xeine Teilmenge dieser Spalte. Ist das sinnvoll?
Exp1orer
2
Es könnte sein, aber es hat bei mir nicht funktioniert. Funktionieren Pandas in Python 3 etwas anders?
Dhardy
1
Was heißt level=0das
van_d39
3
@Veenit bedeutet, dass Sie nach der ersten Ebene des Index und nicht nach einer der Spalten gruppieren.
Exp1orer
54

Sie müssen ein zweites groupby-Objekt erstellen, das nach den Status gruppiert, und dann die folgende divMethode verwenden:

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

Das level='state'kwarg in divweist Pandas an, die Datenrahmen basierend auf den Werten auf der Indexebene zu senden / zu verbinden state.

Paul H.
quelle
4
Funktioniert diese Methode, wenn Sie 3 Indizes haben? Ich habe zuerst ein Groupby auf 3 Spalten gemacht. Dann habe ich eine zweite Gruppe mit nur 2 gemacht und die Summe berechnet. Dann versuche ich es divdoch mit zu benutzen level=["index1", "index2"]aber es sagt mir das Join on level between two MultiIndex objects is ambiguous.
Ger
@Ger Es funktioniert, aber ich kann anhand dieser Beschreibung auf keinen Fall erkennen, was Sie falsch machen. Suchen Sie auf der Website etwas mehr. Wenn Sie nichts finden, erstellen Sie eine neue Frage mit einem reproduzierbaren Beispiel, das das Problem demonstriert. stackoverflow.com/questions/20109391/…
Paul H
34

Aus Gründen der Übersichtlichkeit würde ich die SeriesGroupBy verwenden:

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

Für mehrere Gruppen müssen Sie transform verwenden (mit Radicals 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

Dies scheint etwas performanter zu sein als die anderen Antworten (nur weniger als doppelt so schnell wie Radicals Antwort, für mich ~ 0,08 s).

Andy Hayden
quelle
5
Das geht super schnell. Ich würde dies als bevorzugten Pandas-Ansatz empfehlen. Nutzt wirklich die Vektorisierung von Numpy und die Indizierung von Pandas.
Charles
Das hat auch bei mir gut funktioniert, da ich mit mehreren Gruppen arbeite. Vielen Dank.
irene
27

Ich denke, das muss verglichen werden. Verwenden des ursprünglichen DataFrame von 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

Wie in seiner Antwort kommentiert, nutzt Andy die Vektorisierung und die Indizierung von Pandas voll aus.

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

3,42 ms ± 16,7 µs pro Schleife
(Mittelwert ± Standardabweichung von 7 Läufen, jeweils 100 Schleifen)


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 pro Schleife
(Mittelwert ± Standardabweichung von 7 Läufen, jeweils 100 Schleifen)


3. Exp1orer

Dies ist die langsamste Antwort, da sie x.sum()für jede xin Stufe 0 berechnet wird .

Für mich ist dies immer noch eine nützliche Antwort, wenn auch nicht in der aktuellen Form. Für eine schnelle EDA bei kleineren Datensätzen applykönnen Sie die Methodenverkettung verwenden , um diese in eine einzelne Zeile zu schreiben. Wir müssen daher nicht mehr über den Namen einer Variablen entscheiden, was für Ihre wertvollste Ressource (Ihr Gehirn !!) tatsächlich sehr rechenintensiv ist .

Hier ist die Modifikation,

(
    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 pro Schleife
(Mittelwert ± Standardabweichung von 7 Läufen, jeweils 100 Schleifen)


Bei einem kleinen Datensatz kümmert sich also niemand um 6 ms. Dies ist jedoch eine dreifache Beschleunigung, und bei einem größeren Datensatz mit Gruppen mit hoher Kardinalität wird dies einen massiven Unterschied bewirken.

Zusätzlich zum obigen Code erstellen wir einen DataFrame mit der Form (12.000.000, 3) mit 14412 Statuskategorien und 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
})

Mit Andys,

2 s ± 10,4 ms pro Schleife
(Mittelwert ± Standardabweichung von 7 Läufen, jeweils 1 Schleife)

und exp1orer

19 s ± 77,1 ms pro Schleife
(Mittelwert ± Standardabweichung von 7 Läufen, jeweils 1 Schleife)

Jetzt wird x10 bei großen Datensätzen mit hoher Kardinalität schneller.


Achten Sie darauf, diese drei Antworten zu UV, wenn Sie diese UV!

Kleine Bobby Tische
quelle
16

(Diese Lösung ist von diesem Artikel https://pbpython.com/pandas_transform.html inspiriert. )

Ich finde die folgende Lösung am einfachsten (und wahrscheinlich am schnellsten) transformation:

Transformation: Während die Aggregation eine reduzierte Version der Daten zurückgeben muss, kann die Transformation eine transformierte Version der vollständigen Daten zur Rekombination zurückgeben. Für eine solche Transformation hat die Ausgabe dieselbe Form wie die Eingabe.

So verwenden transformation, ist die Lösung 1-Liner:

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

Und wenn Sie drucken:

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
quelle
2
@Cancer Dies ist meine Lieblingsantwort, da der df als df beibehalten wird (ohne in Serien konvertiert zu werden) und lediglich eine% -Spalte hinzugefügt wird. Vielen Dank
T.Fung
Die Variation dieser Antwort hat bei mir sehr gut funktioniert mittransform('max')
Sheldore
11

Ich weiß, dass dies eine alte Frage ist, aber die Antwort von exp1orer ist für Datensätze mit einer großen Anzahl eindeutiger Gruppen (wahrscheinlich aufgrund des Lambda) sehr langsam. Ich habe aus ihrer Antwort aufgebaut, um daraus eine Array-Berechnung zu machen, und jetzt ist es super schnell! Unten ist der Beispielcode:

Erstellen Sie den Testdatenrahmen mit 50.000 eindeutigen Gruppen

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

In Gruppen sieht es so aus:

                             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]

Array-Methode zum Ermitteln des Prozentsatzes:

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

Diese Methode dauert ca. 0,15 Sekunden

Top-Antwortmethode (mit Lambda-Funktion):

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

Diese Methode benötigt ca. 21 Sekunden, um das gleiche Ergebnis zu erzielen.

Das Ergebnis:

      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
Radikaler Edward
quelle
9

Mir ist klar, dass es hier bereits gute Antworten gibt.

Ich möchte dennoch meinen eigenen Beitrag leisten, da ich der Meinung bin, dass es für eine elementare, einfache Frage wie diese eine kurze Lösung geben sollte, die auf einen Blick verständlich ist.

Es sollte auch so funktionieren, dass ich die Prozentsätze als neue Spalte hinzufügen kann, wobei der Rest des Datenrahmens unberührt bleibt. Last but not least sollte es auf offensichtliche Weise auf den Fall verallgemeinert werden, in dem es mehr als eine Gruppierungsebene gibt (z. B. Staat und Land statt nur Staat).

Das folgende Snippet erfüllt diese Kriterien:

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

Beachten Sie, dass Sie, wenn Sie noch Python 2 verwenden, das x im Nenner des Lambda-Terms durch float (x) ersetzen müssen.

MightyCurious
quelle
Dies ist die beste Antwort IMO. Das einzige, was hinzugefügt werden müsste, wäre * 100, es zu einem Prozentsatz zu machen.
Bouncner
1
@Bouncner: Ja, genau genommen müssten Sie mit 100 multiplizieren, um einen Prozentsatz zu erhalten - oder die neue Variable von "sales_percentage" in "sales_ratio" umbenennen. Ich persönlich bevorzuge letzteres und habe die Antwort entsprechend bearbeitet. Danke fürs Erwähnen!
MightyCurious
2
Dies funktioniert jedoch nicht, wenn Sie mehrere Ebenen haben.
Irene
@irene: Guter Punkt, danke! Wahrscheinlich würde in diesem Fall df.reset_index (). Groupby (['state']) ['sales']. Transform (lambda x: x / x.sum ()) funktionieren. Oder übersehe ich etwas?
MightyCurious
1
Diese Antwort ist großartig. Es beinhaltet nicht das Erstellen eines temporären groupbyObjekts, ist sehr präzise und liest sich sehr logisch von links nach rechts.
C. Braun
7

Die eleganteste Methode zum Auffinden von Prozentsätzen in Spalten oder Indizes ist die Verwendung pd.crosstab.

Beispieldaten

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

Der Ausgabedatenrahmen ist wie folgt

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

Geben Sie einfach den Index, die Spalten und die zu aggregierenden Werte an. Das Schlüsselwort normalize berechnet je nach Kontext% über Index oder Spalten hinweg.

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

Sie können sumdas Ganze DataFrameund durch die stateSumme teilen :

# 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

Kehrt zurück

    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

Beachten Sie jedoch, dass dies nur funktioniert, weil alle Spalten außer statenumerisch sind und die Summierung des gesamten DataFrame ermöglichen. Wenn beispielsweise office_idstattdessen ein Zeichen angezeigt wird, wird eine Fehlermeldung angezeigt:

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

TypeError: Nicht unterstützte Operandentypen für /: 'str' und 'str'

iggy
quelle
Ich habe bearbeitet, um festzustellen, dass dies nur funktioniert, wenn alle Spalten außer der groupbySpalte numerisch sind. Aber sonst ist es ziemlich elegant. Gibt es eine Möglichkeit, damit es mit anderen strSpalten funktioniert ?
Max Ghenis
Nicht so weit ich weiß: stackoverflow.com/questions/34099684/…
iggy
2

Ich denke, das würde den Trick in einer Zeile machen:

df.groupby(['state', 'office_id']).sum().transform(lambda x: x/np.sum(x)*100)
louisD
quelle
Ich glaube, es dauert alle Spalten des Datensatzes. In diesem Fall gibt es nur einen. Wenn Sie mehrere haben und diese Operation für eine einzelne ausführen möchten, geben Sie sie einfach nach dem Ausdruck groupby an: df.groupby (['state', 'office_id']) [[IHR SPALTENNAME HIER]]. Etcetc, wenn Sie möchten
Um
@ LouisD: Ich mag Ihren Ansatz, es kurz zu halten, sehr. Wenn ich versuche, die Spalte wie von Ihnen vorgeschlagen neu zuzuweisen, erhalte ich leider zwei Fehler: "ValueError: Nicht übereinstimmende Puffer-D-Typen, erwartete 'Python-Objekt', aber 'lange lange'" und zusätzlich (während der Behandlung der ersten Ausnahme): " TypeError: Inkompatibler Index der eingefügten Spalte mit dem Frame-Index "Der von mir verwendete Code war der folgende: df ['Prozent'] = df.groupby (['state', 'office_id']). Sum (). Transform (lambda x: x / np.sum (x) * 100) Daher werde ich eine separate Antwort veröffentlichen, um dies zu beheben.
MightyCurious
1

Die einfache Art und Weise, die ich verwendet habe, ist eine Zusammenführung nach den beiden Gruppen, die dann eine einfache Division durchführen.

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
emporgehobener Lemur
quelle
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()

Kehrt zurück:

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

Als jemand, der auch Pandas lernt, fand ich die anderen Antworten etwas implizit, da Pandas den größten Teil der Arbeit hinter den Kulissen verbirgt. Das heißt, wie die Operation funktioniert, indem Spalten- und Indexnamen automatisch abgeglichen werden. Dieser Code sollte einer schrittweisen Version der von @ exp1orer akzeptierten Antwort entsprechen

Mit dem dfwerde ich es beim Alias ​​nennen 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_saleswird state_office_salesnach Gesamtsummen in index level 0(ganz links) gruppiert .

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

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

Da die beiden Datenrahmen einen Indexnamen und einen Spaltennamen gemeinsam haben, finden Pandas die entsprechenden Speicherorte über gemeinsam genutzte Indizes wie:

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

Um dies noch besser zu veranschaulichen, ist hier eine Teilsumme mit einer XX, die kein Äquivalent hat. Pandas stimmen mit der Position basierend auf Index- und Spaltennamen überein, wobei es keine Überlappung gibt. Pandas ignorieren dies:

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

Dies wird sehr deutlich, wenn keine gemeinsam genutzten Indizes oder Spalten vorhanden sind. Hier missing_index_totalsist gleich, state_total_salesaußer dass es keinen Indexnamen hat.

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

Einzeilige Lösung:

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

Dies gibt eine Reihe von Pro-Office-Verhältnissen zurück - kann einzeln verwendet oder dem ursprünglichen Datenrahmen zugewiesen werden.

ribitskiyb
quelle