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 state
poziomu, groupby
aby zsumować sales
całość, state
aby obliczyć ułamek.
df['sales'] / df.groupby('state')['sales'].transform('sum')
wydaje się być najjaśniejszą odpowiedzią.Odpowiedzi:
Odpowiedź Paula H. jest prawidłowa, że będziesz musiał zrobić drugi
groupby
obiekt, ale możesz obliczyć procent w prostszy sposób - wystarczy,groupby
żestate_office
podzieliszsales
kolumnę przez jej sumę. Kopiując początek odpowiedzi Paula H.Zwroty:
źródło
x
jest to jakiś rodzaj tabeli, więc100 * x
intuicyjnie nie ma sensu (zwłaszcza gdy niektóre komórki zawierają ciągi, takie jakAZ
, ...).state_office
to seria z wieloma indeksami - więc jest to tylko jedna kolumna, której wszystkie wartości są liczbowe. Po wykonaniu grupowania każdyx
jest podzbiorem tej kolumny. Czy to ma sens?level=0
znaczy?Musisz utworzyć drugi obiekt grupowania, który grupuje według stanów, a następnie użyj
div
metody:level='state'
kwarg wdiv
powiada pandy do nadawania / join bazę dataframes na wartości nastate
poziomie indeksu.źródło
div
ale z,level=["index1", "index2"]
ale to mi mówiJoin on level between two MultiIndex objects is ambiguous
.Dla zwięzłości użyłbym SeriesGroupBy:
W przypadku wielu grup musisz użyć transformacji (używając df Radicala ):
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).
źródło
Myślę, że to wymaga analizy porównawczej. Używając oryginalnej ramki DataFrame OP,
1 Andy Hayden
Jak skomentował swoją odpowiedź, Andy w pełni wykorzystuje wektoryzację i indeksowanie pand.
3,42 ms ± 16,7 μs na pętlę
(średnia ± odchylenie standardowe z 7 cykli po 100 pętli)
2. Paul H.
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żdegox
na poziomie 0.Dla mnie jest to nadal przydatna odpowiedź, choć nie w obecnej formie. W celu szybkiego EDA dla mniejszych zestawów danych,
apply
umoż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,
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,
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ą!
źródło
(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
:Więc używając
transformation
, rozwiązaniem jest 1-liniowa:A jeśli drukujesz:
źródło
transform('max')
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
Po zgrupowaniu wygląda to tak:
Tablicowa metoda znajdowania procentu:
Ta metoda zajmuje około ~ 0,15 sekundy
Najlepsza metoda odpowiedzi (przy użyciu funkcji lambda):
Ta metoda zajmuje około ~ 21 sekund, aby uzyskać ten sam wynik.
Wynik:
źródło
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:
Zauważ, że jeśli nadal używasz Pythona 2, będziesz musiał zamienić x w mianowniku wyrażenia lambda przez float (x).
źródło
* 100
zrobienie tego procentu.groupby
obiektu, jest bardzo zwięzły i bardzo logicznie czyta od lewej do prawej.Najbardziej eleganckim sposobem znajdowania wartości procentowych w kolumnach lub indeksie jest użycie
pd.crosstab
.Przykładowe dane
Ramka danych wyjściowych wygląda następująco
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.
źródło
Możesz
sum
całośćDataFrame
i podzielić przezstate
sumę:Zwroty
Należy jednak pamiętać, że działa to tylko dlatego, że wszystkie kolumny inne niż
state
są liczbowe, co umożliwia sumowanie całej ramki DataFrame. Na przykład, jeślioffice_id
zamiast tego jest znak, pojawi się błąd:źródło
groupby
kolumny są numeryczne. Ale poza tym jest całkiem elegancki. Czy istnieje sposób, aby działał z innymistr
kolumnami?Myślę, że to załatwi sprawę w 1 linii:
źródło
Prosty sposób, którego użyłem, to scalenie po 2 groupby, a następnie wykonanie prostego podziału.
źródło
Zwroty:
źródło
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ć aliasemstate_office_sales
:state_total_sales
jeststate_office_sales
pogrupowany według sum windex level 0
(skrajny lewy).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:
Aby to jeszcze lepiej zilustrować, oto suma częściowa z a,
XX
która nie ma odpowiednika. Pandy dopasują się do lokalizacji na podstawie indeksu i nazw kolumn, gdzie nie ma nakładania się pandy zignorują to:Staje się to bardzo jasne, gdy nie ma wspólnych indeksów ani kolumn. Tutaj
missing_index_totals
jest równe zstate_total_sales
wyjątkiem tego, że nie ma nazwy indeksu.źródło
Rozwiązanie one-line:
Zwraca serię wskaźników na biuro - może być używany samodzielnie lub przypisany do oryginalnej ramki Dataframe.
źródło