Ini jelas sederhana, tetapi sebagai pemula yang norak, saya terjebak.
Saya memiliki file CSV yang berisi 3 kolom, Negara Bagian, ID Kantor, dan Penjualan untuk kantor itu.
Saya ingin menghitung persentase penjualan per kantor di negara bagian tertentu (total semua persentase di setiap negara bagian adalah 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'})
Ini mengembalikan:
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
Aku tidak bisa mencari cara untuk "mencapai" ke state
tingkat groupby
untuk total sampai sales
untuk seluruh state
untuk menghitung fraksi.
df['sales'] / df.groupby('state')['sales'].transform('sum')
sepertinya jawaban yang paling jelas.Jawaban:
Jawaban Paulus H benar bahwa Anda akan harus membuat kedua
groupby
objek, tetapi Anda dapat menghitung persentase dengan cara sederhana - hanyagroupby
yangstate_office
dan membagisales
kolom dengan jumlah nya. Menyalin awal jawaban 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()))
Pengembalian:
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
sumber
x
ini adalah tabel dari beberapa jenis, jadi100 * x
tidak masuk akal secara intuitif (terutama ketika beberapa sel berisi string sepertiAZ
, ...).state_office
adalah Seri dengan Multi Indeks - jadi ini hanya satu kolom yang nilainya semuanya numerik. Setelah Anda melakukan groupby, masingx
- masing adalah subset dari kolom itu. Apakah itu masuk akal?level=0
maksudnyaAnda perlu membuat grup kedua dengan objek yang dikelompokkan berdasarkan negara bagian, dan kemudian menggunakan
div
metode: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
yang
level='state'
kwarg didiv
memberitahu panda untuk siaran / bergabung dasar dataframes pada nilai-nilai distate
tingkat indeks.sumber
div
tetapi denganlevel=["index1", "index2"]
tetapi itu memberi tahu saya ituJoin on level between two MultiIndex objects is ambiguous
.Untuk ringkasnya saya akan menggunakan 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
Untuk beberapa grup Anda harus menggunakan transform (menggunakan df Radical ):
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
Ini tampaknya sedikit lebih berkinerja daripada jawaban lainnya (hanya kurang dari dua kali kecepatan jawaban Radical, bagi saya ~ 0,08s).
sumber
Saya pikir ini perlu pembandingan. Menggunakan DataFrame asli 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
Seperti mengomentari jawabannya, Andy memanfaatkan sepenuhnya vektorisasi dan pengindeksan panda.
c = df.groupby(['state', 'office_id'])['sales'].sum().rename("count") c / c.groupby(level=0).sum()
3,42 ms ± 16,7 µs per loop
(rata-rata ± std. Dev. Dari 7 run, masing-masing 100 loop)
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 per loop
(rata-rata ± std. Dev. Dari 7 run, masing-masing 100 loop)
Exp1orer ke-3
Ini adalah jawaban paling lambat karena dihitung
x.sum()
untuk setiap jawabanx
di level 0.Bagi saya, ini masih merupakan jawaban yang berguna, meski tidak dalam bentuknya yang sekarang. Untuk EDA cepat pada set data yang lebih kecil,
apply
memungkinkan Anda menggunakan rangkaian metode untuk menulis ini dalam satu baris. Oleh karena itu, kami menghapus kebutuhan memutuskan nama variabel, yang sebenarnya sangat mahal secara komputasi untuk sumber daya Anda yang paling berharga (otak Anda !!).Berikut modifikasinya,
( 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 per loop
(rata-rata ± std. Dev. Dari 7 run, masing-masing 100 loop)
Jadi tidak ada yang peduli tentang 6ms pada kumpulan data kecil. Namun, ini adalah kecepatan 3x dan, pada kumpulan data yang lebih besar dengan grup berkardinalitas tinggi, ini akan membuat perbedaan besar.
Menambah kode di atas, kami membuat DataFrame dengan bentuk (12.000.000, 3) dengan 14412 kategori negara dan 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 })
Menggunakan Andy,
2 s ± 10,4 ms per loop
(rata-rata ± std. Dev. Dari 7 run, masing-masing 1 loop)
dan exp1orer
19 s ± 77.1 ms per loop
(rata-rata ± std. Dev. Dari 7 run, masing-masing 1 loop)
Jadi sekarang kita melihat kecepatan x10 pada kumpulan data berkardinalitas tinggi yang besar.
Pastikan untuk UV ketiga jawaban ini jika Anda UV yang satu ini !!
sumber
(Solusi ini terinspirasi dari artikel ini https://pbpython.com/pandas_transform.html )
Saya menemukan solusi berikut menjadi yang paling sederhana (dan mungkin tercepat) menggunakan
transformation
:Jadi dengan menggunakan
transformation
, solusinya adalah 1-liner:df['%'] = 100 * df['sales'] / df.groupby('state')['sales'].transform('sum')
Dan jika Anda mencetak:
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
sumber
transform('max')
Saya tahu ini adalah pertanyaan lama, tetapi jawaban exp1orer sangat lambat untuk kumpulan data dengan sejumlah besar grup unik (mungkin karena lambda). Saya membangun jawaban mereka untuk mengubahnya menjadi kalkulasi array jadi sekarang super cepat! Di bawah ini adalah contoh kode:
Buat kerangka data uji dengan 50.000 grup unik
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})
Saat dikelompokkan, tampilannya seperti:
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]
Metode array untuk menemukan persentase:
# 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)
Metode ini membutuhkan waktu sekitar ~ 0,15 detik
Metode jawaban teratas (menggunakan fungsi 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()))
Metode ini membutuhkan waktu sekitar ~ 21 detik untuk menghasilkan hasil yang sama.
Hasil:
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
sumber
Saya menyadari sudah ada jawaban yang bagus di sini.
Namun saya tetap ingin memberikan kontribusi saya sendiri, karena saya rasa untuk pertanyaan yang sederhana dan mendasar seperti ini, harus ada solusi singkat yang sekilas bisa dimengerti.
Ini juga harus bekerja dengan cara yang saya bisa menambahkan persentase sebagai kolom baru, membiarkan sisa kerangka data tidak tersentuh. Last but not least, itu harus menggeneralisasi dengan cara yang jelas untuk kasus di mana ada lebih dari satu tingkat pengelompokan (misalnya, negara bagian dan negara bukan hanya negara bagian).
Cuplikan berikut memenuhi kriteria ini:
df['sales_ratio'] = df.groupby(['state'])['sales'].transform(lambda x: x/x.sum())
Perhatikan bahwa jika Anda masih menggunakan Python 2, Anda harus mengganti x pada penyebut suku lambda dengan float (x).
sumber
* 100
menjadikannya persentase.groupby
objek sementara , sangat ringkas, dan membaca dengan sangat logis dari kiri ke kanan.Cara paling elegan untuk menemukan persentase di seluruh kolom atau indeks adalah dengan menggunakan
pd.crosstab
.Contoh data
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)]})
Dataframe keluarannya seperti ini
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
Cukup tentukan indeks, kolom, dan nilai yang akan digabungkan. Kata kunci normalisasi akan menghitung% di seluruh indeks atau kolom tergantung pada konteksnya.
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%
sumber
Anda bisa
sum
keseluruhanDataFrame
dan membaginya denganstate
total:# 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
Kembali
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
Namun perhatikan bahwa ini hanya berfungsi karena semua kolom selain
state
numerik, memungkinkan penjumlahan dari seluruh DataFrame. Misalnya, jikaoffice_id
adalah karakter, Anda mendapatkan error:df.office_id = df.office_id.astype(str) df['sales_ratio'] = (df / df.groupby(['state']).transform(sum))['sales']
sumber
groupby
kolom adalah numerik. Tapi sebaliknya cukup elegan. Apakah ada cara untuk membuatnya berfungsi denganstr
kolom lain ?Saya pikir ini akan melakukan trik dalam 1 baris:
df.groupby(['state', 'office_id']).sum().transform(lambda x: x/np.sum(x)*100)
sumber
Cara sederhana yang pernah saya gunakan adalah dengan menggabungkan 2 groupby kemudian melakukan pembagian sederhana.
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
sumber
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()
Pengembalian:
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
sumber
Sebagai seseorang yang juga mempelajari panda, saya menemukan jawaban lain agak tersirat karena panda menyembunyikan sebagian besar pekerjaan di balik layar. Yakni bagaimana operasi bekerja dengan mencocokkan nama kolom dan indeks secara otomatis. Kode ini harus setara dengan versi langkah demi langkah dari jawaban yang diterima @ exp1orer
Dengan
df
, saya akan menyebutnya dengan aliasstate_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
adalahstate_office_sales
dikelompokkan berdasarkan jumlah nilai diindex level 0
(paling kiri).In: state_total_sales = df.groupby(level=0).sum() state_total_sales Out: sales state AZ 2448009 CA 2832270 CO 1495486 WA 595859
Karena dua kerangka data berbagi nama indeks dan panda nama kolom akan menemukan lokasi yang sesuai melalui indeks bersama seperti:
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
Untuk mengilustrasikan ini lebih baik lagi, berikut adalah jumlah parsial dengan a
XX
yang tidak memiliki padanan. Panda akan mencocokkan lokasi berdasarkan indeks dan nama kolom, di mana tidak ada panda yang tumpang tindih akan mengabaikannya: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
Ini menjadi sangat jelas ketika tidak ada indeks atau kolom bersama. Ini
missing_index_totals
sama denganstate_total_sales
kecuali bahwa ia tidak memiliki nama-indeks.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
sumber
Solusi satu baris:
df.join( df.groupby('state').agg(state_total=('sales', 'sum')), on='state' ).eval('sales / state_total')
Ini mengembalikan Seri rasio per kantor - dapat digunakan sendiri atau ditetapkan ke Dataframe asli.
sumber