Bagaimana mengelompokkan satu kolom yang bias pada berbagai interval dari yang lain dalam panda?

Dec 12 2020

Saya memiliki pd.DataFrame berikut:

source = pd.DataFrame([[0.99, 0.98, 0.93, 0.81, 0.85, 0.71, 0.7, 0.69, 0.68, 0.66], 
              [100, 12, 312, 23, 2, 12, 32, 21, 21, 21]]).T

Saya ingin mengubahnya secepat mungkin menjadi:

desired_result = pd.DataFrame([[0.9, 0.8, 0.7, 0.6], [424, 25, 44, 63]]).T

Dimana di atas saya mendefinisikan interval 0.1yang saya terapkan ke kolom 0ke sourcedataframe dan menjumlahkan 1kolom dari dataframe yang sama. Idenya adalah bahwa ini harus bekerja dengan interval yang berbeda.

Apa yang saya coba:

  1. Saya berpikir untuk menggunakan pd.cuttetapi sepertinya bukan itu yang saya cari.

  2. Saya tahu bahwa jika saya menambahkan kolom baru sourcedengan nilai duplikat 0,9, 0,8, 0,7 dan 0,6 pada baris yang sesuai, maka saya dapat menggunakan groupbykolom baru ini dan kemudian sum, tetapi saya bertanya-tanya apakah ada cara yang lebih bersih dan lebih cepat untuk melakukan ini? mis. yang seperti ini:

interval = 0.1
source['ints'] = (source[0] / interval).astype(int)
result = source.groupby(source['ints']).sum().reset_index()
result

Namun hal di atas tidak akan berfungsi jika saya mengubah bentuk interval 0,1 menjadi 0,05 misalnya.

Bantuan apa pun akan dihargai.

Jawaban

3 PierreD Dec 12 2020 at 21:28

Untuk kecepatan: selalu coba untuk membuat vektor semampu Anda, dan hindari apply sebanyak mungkin.

Berikut adalah cara yang lebih cepat (kredit untuk @DavidErickson untuk sort=False):

interval = 0.1
source.groupby(np.trunc(source[0] / interval) * interval, sort=False)[1].sum().reset_index()
# out:
     0      1
0  0.9  424.0
1  0.8   25.0
2  0.7   12.0
3  0.6   95.0

Perbedaan kecepatan bisa sangat dramatis untuk yang besar df.

Coba dengan 1 juta baris, dikelompokkan dalam 10K nampan:

source = pd.DataFrame(np.random.normal(scale=1000, size=(int(1e6), 2)))

%%timeit
# ... (as above)
26.7 ms ± 292 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Dengan sebagai applygantinya:

1.51 s ± 11 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

(50x lebih lambat).

1 DavidErickson Dec 12 2020 at 20:55

Anda dapat menggunakan custom_roundfungsi yang saya buat 3 modifikasihttps://stackoverflow.com/a/40372261/6366770:

  1. Saya digunakan np.floorsebagai pengganti roundAnda ingin turun.
  2. Ini mengacaukan nilai yang berada di "perbatasan" dari sebuah bin, jadi saya menambahkan + base/100(jadi 0.9akan 0.9 + .009 = 0.909dan dibulatkan ke 0,9 bukannya salah ke 0,8), sehingga tepat di atas perbatasan dan membulatkan ke bawah dengan benar. Saya pikir ini akan melindungi Anda. Anda bisa melakukannya 1 / 1000agar aman.
  3. Jawaban yang saya bagikan sedang mencari int, begitu dihapus int, karena kita melihat pelampung yang membulat

source = pd.DataFrame(np.random.normal(scale=1000, size=(int(1e6), 2)))

def custom_round(x, y, base):
    return source.groupby((base * np.floor((x + (base / 100)) / base)), sort=False)[y].sum()


%timeit custom_round(source[0], 1, .1)
89.8 ms ± 1.14 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Di komputer saya, jawaban yang diterima lebih lambat:

102 ms ± 1.86 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)