Saya memiliki bingkai data dengan tiga kolom string. Saya tahu bahwa hanya satu nilai di kolom ke-3 yang valid untuk setiap kombinasi dari dua yang pertama. Untuk membersihkan data, saya harus mengelompokkan berdasarkan bingkai data dengan dua kolom pertama dan memilih nilai paling umum dari kolom ketiga untuk setiap kombinasi.
Kode saya:
import pandas as pd
from scipy import stats
source = pd.DataFrame({'Country' : ['USA', 'USA', 'Russia','USA'],
'City' : ['New-York', 'New-York', 'Sankt-Petersburg', 'New-York'],
'Short name' : ['NY','New','Spb','NY']})
print source.groupby(['Country','City']).agg(lambda x: stats.mode(x['Short name'])[0])
Baris terakhir kode tidak berfungsi, ia mengatakan "Kesalahan kunci 'Nama pendek'" dan jika saya mencoba mengelompokkan hanya menurut Kota, maka saya mendapat AssertionError. Apa yang bisa saya lakukan untuk memperbaikinya?
.value_counts(ascending=False)
?ascending=False
sudah menjadi nilai default, jadi tidak perlu menyetel urutan secara eksplisit.pd.Series.mode
sekarang lebih tepat dan lebih cepat.Panda> = 0,16
pd.Series.mode
tersedia!Gunakan
groupby
,,GroupBy.agg
dan terapkanpd.Series.mode
fungsi ke setiap grup:source.groupby(['Country','City'])['Short name'].agg(pd.Series.mode) Country City Russia Sankt-Petersburg Spb USA New-York NY Name: Short name, dtype: object
Jika ini diperlukan sebagai DataFrame, gunakan
source.groupby(['Country','City'])['Short name'].agg(pd.Series.mode).to_frame() Short name Country City Russia Sankt-Petersburg Spb USA New-York NY
Hal yang berguna tentang itu
Series.mode
adalah selalu mengembalikan Seri, membuatnya sangat kompatibel denganagg
danapply
, terutama saat merekonstruksi keluaran groupby. Ini juga lebih cepat.# Accepted answer. %timeit source.groupby(['Country','City']).agg(lambda x:x.value_counts().index[0]) # Proposed in this post. %timeit source.groupby(['Country','City'])['Short name'].agg(pd.Series.mode) 5.56 ms ± 343 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) 2.76 ms ± 387 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Berurusan dengan Berbagai Mode
Series.mode
juga berfungsi dengan baik bila ada beberapa mode:source2 = source.append( pd.Series({'Country': 'USA', 'City': 'New-York', 'Short name': 'New'}), ignore_index=True) # Now `source2` has two modes for the # ("USA", "New-York") group, they are "NY" and "New". source2 Country City Short name 0 USA New-York NY 1 USA New-York New 2 Russia Sankt-Petersburg Spb 3 USA New-York NY 4 USA New-York New
source2.groupby(['Country','City'])['Short name'].agg(pd.Series.mode) Country City Russia Sankt-Petersburg Spb USA New-York [NY, New] Name: Short name, dtype: object
Atau, jika Anda menginginkan baris terpisah untuk setiap mode, Anda dapat menggunakan
GroupBy.apply
:source2.groupby(['Country','City'])['Short name'].apply(pd.Series.mode) Country City Russia Sankt-Petersburg 0 Spb USA New-York 0 NY 1 New Name: Short name, dtype: object
Jika Anda tidak peduli mode mana yang dikembalikan selama salah satunya, maka Anda memerlukan lambda yang memanggil
mode
dan mengekstrak hasil pertama.source2.groupby(['Country','City'])['Short name'].agg( lambda x: pd.Series.mode(x)[0]) Country City Russia Sankt-Petersburg Spb USA New-York NY Name: Short name, dtype: object
Alternatif untuk (tidak) dipertimbangkan
Anda juga dapat menggunakan
statistics.mode
dari python, tetapi ...source.groupby(['Country','City'])['Short name'].apply(statistics.mode) Country City Russia Sankt-Petersburg Spb USA New-York NY Name: Short name, dtype: object
... itu tidak bekerja dengan baik ketika harus berurusan dengan banyak mode; a
StatisticsError
dibesarkan. Ini disebutkan di dokumen:Tapi Anda bisa lihat sendiri ...
statistics.mode([1, 2]) # --------------------------------------------------------------------------- # StatisticsError Traceback (most recent call last) # ... # StatisticsError: no unique mode; found 2 equally common values
sumber
df.groupby(cols).agg(pd.Series.mode)
tampaknya bekerja untuk saya. Jika itu tidak berhasil, tebakan kedua saya adalahdf.groupby(cols).agg(lambda x: pd.Series.mode(x).values[0])
.IndexError: index 0 is out of bounds for axis 0 with size 0
(mungkin karena ada grup di mana rangkaian hanya memiliki NaN). Menambahkandropna=False
memecahkan ini , tetapi tampaknya menaikkan'<' not supported between instances of 'float' and 'str'
(seri saya adalah string). (Senang membuat ini menjadi pertanyaan baru jika Anda mau.)def foo(x): m = pd.Series.mode(x); return m.values[0] if not m.empty else np.nan
dan kemudian gunakandf.groupby(cols).agg(foo)
. Jika itu tidak berhasil, atur implementasifoo
sebentar. Jika Anda masih mengalami masalah, saya sarankan membuka Q barunp.nan
, seseorang dapat melakukannya melaluidf.groupy(cols).agg(lambda x: x.mode(dropna=False).iloc[0])
mode, dengan asumsi Anda tidak peduli tentang ikatan dan hanya menginginkan satu mode.Karena
agg
, fungsi lambba mendapat aSeries
, yang tidak memiliki'Short name'
atribut.stats.mode
mengembalikan tupel dari dua larik, jadi Anda harus mengambil elemen pertama dari larik pertama dalam tupel ini.Dengan dua perubahan sederhana ini:
source.groupby(['Country','City']).agg(lambda x: stats.mode(x)[0][0])
kembali
sumber
scipy.stats
.Sedikit terlambat untuk permainan di sini, tapi saya mengalami beberapa masalah kinerja dengan solusi HYRY, jadi saya harus memikirkan yang lain.
Ini bekerja dengan menemukan frekuensi setiap nilai kunci, dan kemudian, untuk setiap kunci, hanya menyimpan nilai yang paling sering muncul bersamanya.
Ada juga solusi tambahan yang mendukung banyak mode.
Pada uji skala yang mewakili data yang saya kerjakan, runtime ini berkurang dari 37,4s menjadi 0,5s!
Berikut kode solusinya, beberapa contoh penggunaan, dan uji skala:
import numpy as np import pandas as pd import random import time test_input = pd.DataFrame(columns=[ 'key', 'value'], data= [[ 1, 'A' ], [ 1, 'B' ], [ 1, 'B' ], [ 1, np.nan ], [ 2, np.nan ], [ 3, 'C' ], [ 3, 'C' ], [ 3, 'D' ], [ 3, 'D' ]]) def mode(df, key_cols, value_col, count_col): ''' Pandas does not provide a `mode` aggregation function for its `GroupBy` objects. This function is meant to fill that gap, though the semantics are not exactly the same. The input is a DataFrame with the columns `key_cols` that you would like to group on, and the column `value_col` for which you would like to obtain the mode. The output is a DataFrame with a record per group that has at least one mode (null values are not counted). The `key_cols` are included as columns, `value_col` contains a mode (ties are broken arbitrarily and deterministically) for each group, and `count_col` indicates how many times each mode appeared in its group. ''' return df.groupby(key_cols + [value_col]).size() \ .to_frame(count_col).reset_index() \ .sort_values(count_col, ascending=False) \ .drop_duplicates(subset=key_cols) def modes(df, key_cols, value_col, count_col): ''' Pandas does not provide a `mode` aggregation function for its `GroupBy` objects. This function is meant to fill that gap, though the semantics are not exactly the same. The input is a DataFrame with the columns `key_cols` that you would like to group on, and the column `value_col` for which you would like to obtain the modes. The output is a DataFrame with a record per group that has at least one mode (null values are not counted). The `key_cols` are included as columns, `value_col` contains lists indicating the modes for each group, and `count_col` indicates how many times each mode appeared in its group. ''' return df.groupby(key_cols + [value_col]).size() \ .to_frame(count_col).reset_index() \ .groupby(key_cols + [count_col])[value_col].unique() \ .to_frame().reset_index() \ .sort_values(count_col, ascending=False) \ .drop_duplicates(subset=key_cols) print test_input print mode(test_input, ['key'], 'value', 'count') print modes(test_input, ['key'], 'value', 'count') scale_test_data = [[random.randint(1, 100000), str(random.randint(123456789001, 123456789100))] for i in range(1000000)] scale_test_input = pd.DataFrame(columns=['key', 'value'], data=scale_test_data) start = time.time() mode(scale_test_input, ['key'], 'value', 'count') print time.time() - start start = time.time() modes(scale_test_input, ['key'], 'value', 'count') print time.time() - start start = time.time() scale_test_input.groupby(['key']).agg(lambda x: x.value_counts().index[0]) print time.time() - start
Menjalankan kode ini akan mencetak sesuatu seperti:
key value 0 1 A 1 1 B 2 1 B 3 1 NaN 4 2 NaN 5 3 C 6 3 C 7 3 D 8 3 D key value count 1 1 B 2 2 3 C 2 key count value 1 1 2 [B] 2 3 2 [C, D] 0.489614009857 9.19386196136 37.4375009537
Semoga ini membantu!
sumber
agg({'f1':mode,'f2':np.sum})
agg
metode ini.Dua jawaban teratas di sini menyarankan:
df.groupby(cols).agg(lambda x:x.value_counts().index[0])
atau, lebih disukai
Namun keduanya gagal dalam kasus tepi sederhana, seperti yang ditunjukkan di sini:
df = pd.DataFrame({ 'client_id':['A', 'A', 'A', 'A', 'B', 'B', 'B', 'C'], 'date':['2019-01-01', '2019-01-01', '2019-01-01', '2019-01-01', '2019-01-01', '2019-01-01', '2019-01-01', '2019-01-01'], 'location':['NY', 'NY', 'LA', 'LA', 'DC', 'DC', 'LA', np.NaN] })
Pertama:
df.groupby(['client_id', 'date']).agg(lambda x:x.value_counts().index[0])
hasil
IndexError
(karena Seri kosong dikembalikan oleh grupC
). Kedua:df.groupby(['client_id', 'date']).agg(pd.Series.mode)
kembali
ValueError: Function does not reduce
, karena grup pertama mengembalikan daftar dua (karena ada dua mode). (Seperti yang didokumentasikan di sini , jika grup pertama mengembalikan mode tunggal, ini akan berhasil!)Dua solusi yang mungkin untuk kasus ini adalah:
import scipy x.groupby(['client_id', 'date']).agg(lambda x: scipy.stats.mode(x)[0])
Dan solusi yang diberikan kepada saya oleh cs95 di komentar di sini :
def foo(x): m = pd.Series.mode(x); return m.values[0] if not m.empty else np.nan df.groupby(['client_id', 'date']).agg(foo)
Namun, semua ini lambat dan tidak cocok untuk kumpulan data besar. Solusi yang akhirnya saya gunakan yang a) dapat menangani kasus-kasus ini dan b) jauh, jauh lebih cepat, adalah versi jawaban abw33 yang sedikit dimodifikasi (yang seharusnya lebih tinggi):
def get_mode_per_column(dataframe, group_cols, col): return (dataframe.fillna(-1) # NaN placeholder to keep group .groupby(group_cols + [col]) .size() .to_frame('count') .reset_index() .sort_values('count', ascending=False) .drop_duplicates(subset=group_cols) .drop(columns=['count']) .sort_values(group_cols) .replace(-1, np.NaN)) # restore NaNs group_cols = ['client_id', 'date'] non_grp_cols = list(set(df).difference(group_cols)) output_df = get_mode_per_column(df, group_cols, non_grp_cols[0]).set_index(group_cols) for col in non_grp_cols[1:]: output_df[col] = get_mode_per_column(df, group_cols, col)[col].values
Pada dasarnya, metode ini bekerja pada satu col pada satu waktu dan mengeluarkan df, jadi alih-alih
concat
, yang intensif, Anda memperlakukan yang pertama sebagai df, dan kemudian menambahkan larik keluaran (values.flatten()
) secara berulang sebagai kolom di df.sumber
Secara formal, jawaban yang benar adalah Solusi @eumiro. Masalah dari solusi @HYRY adalah ketika Anda memiliki urutan angka seperti [1,2,3,4] solusinya salah, yaitu, Anda tidak memiliki mode . Contoh:
>>> import pandas as pd >>> df = pd.DataFrame( { 'client': ['A', 'B', 'A', 'B', 'B', 'C', 'A', 'D', 'D', 'E', 'E', 'E', 'E', 'E', 'A'], 'total': [1, 4, 3, 2, 4, 1, 2, 3, 5, 1, 2, 2, 2, 3, 4], 'bla': [10, 40, 30, 20, 40, 10, 20, 30, 50, 10, 20, 20, 20, 30, 40] } )
Jika Anda menghitung seperti @HYRY Anda mendapatkan:
>>> print(df.groupby(['client']).agg(lambda x: x.value_counts().index[0])) total bla client A 4 30 B 4 40 C 1 10 D 3 30 E 2 20
Yang jelas salah (lihat nilai A yang seharusnya 1 dan bukan 4 ) karena tidak dapat menangani dengan nilai unik.
Jadi, solusi lainnya benar:
>>> import scipy.stats >>> print(df.groupby(['client']).agg(lambda x: scipy.stats.mode(x)[0][0])) total bla client A 1 10 B 4 40 C 1 10 D 3 30 E 2 20
sumber
Jika Anda ingin pendekatan lain untuk menyelesaikannya yang tidak bergantung pada
value_counts
atauscipy.stats
Anda dapat menggunakanCounter
koleksifrom collections import Counter get_most_common = lambda values: max(Counter(values).items(), key = lambda x: x[1])[0]
Yang bisa diterapkan pada contoh di atas seperti ini
src = pd.DataFrame({'Country' : ['USA', 'USA', 'Russia','USA'], 'City' : ['New-York', 'New-York', 'Sankt-Petersburg', 'New-York'], 'Short_name' : ['NY','New','Spb','NY']}) src.groupby(['Country','City']).agg(get_most_common)
sumber
pd.Series.mode
ataupd.Series.value_counts().iloc[0]
- tetapi jika Anda memiliki nilai NaN yang ingin Anda hitung, ini akan gagal. Setiap kejadian NaN akan terlihat berbeda dengan NaN lainnya, sehingga setiap NaN dihitung memiliki hitungan1
. Lihat stackoverflow.com/questions/61102111/…Jika Anda tidak ingin memasukkan nilai NaN , menggunakan
Counter
jauh lebih cepat daripadapd.Series.mode
ataupd.Series.value_counts()[0]
:def get_most_common(srs): x = list(srs) my_counter = Counter(x) return my_counter.most_common(1)[0][0] df.groupby(col).agg(get_most_common)
harus bekerja. Ini akan gagal jika Anda memiliki nilai NaN, karena setiap NaN akan dihitung secara terpisah.
sumber
Masalahnya di sini adalah kinerjanya, jika Anda memiliki banyak baris maka akan menjadi masalah.
Jika itu kasus Anda, silakan coba dengan ini:
import pandas as pd source = pd.DataFrame({'Country' : ['USA', 'USA', 'Russia','USA'], 'City' : ['New-York', 'New-York', 'Sankt-Petersburg', 'New-York'], 'Short_name' : ['NY','New','Spb','NY']}) source.groupby(['Country','City']).agg(lambda x:x.value_counts().index[0]) source.groupby(['Country','City']).Short_name.value_counts().groupby['Country','City']).first()
sumber
Pendekatan yang sedikit lebih ceroboh tetapi lebih cepat untuk kumpulan data yang lebih besar melibatkan mendapatkan penghitungan untuk kolom yang diminati, mengurutkan jumlah dari tertinggi ke terendah, dan kemudian menghilangkan duplikasi pada subset untuk hanya mempertahankan kasus terbesar. Contoh kodenya adalah sebagai berikut:
>>> import pandas as pd >>> source = pd.DataFrame( { 'Country': ['USA', 'USA', 'Russia', 'USA'], 'City': ['New-York', 'New-York', 'Sankt-Petersburg', 'New-York'], 'Short name': ['NY', 'New', 'Spb', 'NY'] } ) >>> grouped_df = source\ .groupby(['Country','City','Short name'])[['Short name']]\ .count()\ .rename(columns={'Short name':'count'})\ .reset_index()\ .sort_values('count', ascending=False)\ .drop_duplicates(subset=['Country', 'City'])\ .drop('count', axis=1) >>> print(grouped_df) Country City Short name 1 USA New-York NY 0 Russia Sankt-Petersburg Spb
sumber