Pandas: Penghentian pelabelan ulang dicts di groupby.agg membawa banyak masalah

Dibuat pada 19 Nov 2017  ·  37Komentar  ·  Sumber: pandas-dev/pandas

Masalah ini dibuat berdasarkan diskusi dari #15931 setelah penghentian pelabelan ulang dicts di groupby.agg . Banyak dari apa yang dirangkum di bawah ini sudah dibahas dalam diskusi sebelumnya. Saya akan merekomendasikan khususnya https://github.com/pandas-dev/pandas/pull/15931#issuecomment -336139085 di mana masalahnya juga dinyatakan dengan jelas.

Motivasi di balik penghentian #15931 sebagian besar terkait dengan menghadirkan antarmuka yang konsisten untuk agg() antara Series dan Dataframe (lihat juga #14668 untuk konteksnya).

Fungsi pelabelan ulang dengan dict bersarang telah dijelaskan oleh beberapa orang sebagai terlalu kompleks dan/atau tidak konsisten dan karenanya tidak digunakan lagi.

Namun, ini ada harganya: ketidakmungkinan untuk menggabungkan dan mengganti nama pada saat yang sama menyebabkan masalah yang sangat mengganggu dan beberapa ketidakcocokan ke belakang di mana tidak ada solusi yang masuk akal tersedia:

  • _[mengganggu]_ tidak ada lagi kendali atas nama-nama kolom yang dihasilkan
  • _[mengganggu]_ Anda perlu menemukan cara untuk mengganti nama MultiIndex _after_ melakukan agregasi, membutuhkan untuk melacak urutan kolom di dua tempat dalam kode.... tidak praktis sama sekali dan kadang-kadang benar-benar tidak mungkin (kasus di bawah ini ).
  • ️ _ [breaking] _ tidak dapat menerapkan lebih dari satu callable dengan nama internal yang sama pada kolom input yang sama. Ini menghasilkan dua sub-kasus:

    • _ [breaking] _ Anda tidak dapat menerapkan lagi dua atau lebih lambda aggregator pada kolom yang sama

    • _ [breaking] _ Anda tidak dapat menerapkan lagi dua atau lebih agregator dari fungsi parsial kecuali Anda mengubah atribut __name__ tersembunyinya

Contoh

_(harap dicatat, ini adalah contoh yang dibuat untuk tujuan menunjukkan masalah dalam kode sesingkat mungkin, tetapi semua masalah yang ditunjukkan di sini memang menggigit saya dalam kehidupan nyata sejak perubahan, dan dalam situasi yang tidak sesederhana di sini )_

Masukan Dataframe

mydf = pd.DataFrame(
    {
        'cat': ['A', 'A', 'A', 'B', 'B', 'C'],
        'energy': [1.8, 1.95, 2.04, 1.25, 1.6, 1.01],
        'distance': [1.2, 1.5, 1.74, 0.82, 1.01, 0.6]
    },
    index=range(6)
)
  cat  distance  energy
0   A      1.20    1.80
1   A      1.50    1.95
2   A      1.74    2.04
3   B      0.82    1.25
4   B      1.01    1.60
5   C      0.60    1.01

Sebelum:

mudah untuk menulis dan membaca, dan bekerja seperti yang diharapkan

import numpy as np
import statsmodels.robust as smrb
from functools import partial

# median absolute deviation as a partial function
# in order to demonstrate the issue with partial functions as aggregators
mad_c1 = partial(smrb.mad, c=1)

# renaming and specifying the aggregators at the same time
# note that I want to choose the resulting column names myself
# for example "total_xxxx" instead of just "sum"
mydf_agg = mydf.groupby('cat').agg({
    'energy': {
        'total_energy': 'sum',
        'energy_p98': lambda x: np.percentile(x, 98),  # lambda
        'energy_p17': lambda x: np.percentile(x, 17),  # lambda
    },
    'distance': {
        'total_distance': 'sum',
        'average_distance': 'mean',
        'distance_mad': smrb.mad,   # original function
        'distance_mad_c1': mad_c1,  # partial function wrapping the original function
    },
})

menghasilkan

          energy                             distance
    total_energy energy_p98 energy_p17 total_distance average_distance distance_mad distance_mad_c1
cat
A           5.79     2.0364     1.8510           4.44            1.480     0.355825           0.240
B           2.85     1.5930     1.3095           1.83            0.915     0.140847           0.095
C           1.01     1.0100     1.0100           0.60            0.600     0.000000           0.000

dan yang tersisa hanyalah:

# get rid of the first MultiIndex level in a pretty straightforward way
mydf_agg.columns = mydf_agg.columns.droplevel(level=0)

Selamat menari memuji panda !

Setelah

import numpy as np
import statsmodels.robust as smrb
from functools import partial

# median absolute deviation as a partial function
# in order to demonstrate the issue with partial functions as aggregators
mad_c1 = partial(smrb.mad, c=1)

# no way of choosing the destination's column names...
mydf_agg = mydf.groupby('cat').agg({
    'energy': [
        'sum',
        lambda x: np.percentile(x, 98), # lambda
        lambda x: np.percentile(x, 17), # lambda
    ],
    'distance': [
        'sum',
        'mean',
        smrb.mad, # original function
        mad_c1,   # partial function wrapping the original function
    ],
})

Di atas rusak karena fungsi lambda semuanya akan menghasilkan kolom bernama <lambda> yang menghasilkan

SpecificationError: Function names must be unique, found multiple named <lambda>

Regresi yang tidak kompatibel ke belakang: seseorang tidak dapat menerapkan dua lambda yang berbeda ke kolom asli yang sama lagi.

Jika seseorang menghapus lambda x: np.percentile(x, 98) dari atas, kami mendapatkan masalah yang sama dengan fungsi parsial yang mewarisi nama fungsi dari fungsi aslinya:

SpecificationError: Function names must be unique, found multiple named mad

Akhirnya, setelah menimpa atribut __name__ dari parsial (misalnya dengan mad_c1.__name__ = 'mad_c1' ) kita mendapatkan:

    energy          distance
       sum <lambda>      sum   mean       mad mad_c1
cat
A     5.79   1.8510     4.44  1.480  0.355825  0.240
B     2.85   1.3095     1.83  0.915  0.140847  0.095
C     1.01   1.0100     0.60  0.600  0.000000  0.000

dengan diam

  • satu kolom hilang (persentil ke-98)
  • penanganan kolom MultiIndex
  • dan penggantian nama kolom

untuk menangani dalam langkah terpisah.

Tidak ada kontrol yang mungkin untuk nama kolom setelah agregasi, yang terbaik yang bisa kita dapatkan dengan cara otomatis adalah beberapa kombinasi nama kolom asli dan _nama fungsi agregat_ seperti ini:

mydf_agg.columns = ['_'.join(col) for col in mydf_agg.columns]

yang mengakibatkan:

     energy_sum  energy_<lambda>  distance_sum  distance_mean  distance_mad distance_mad_c1
cat
A          5.79           1.8510          4.44          1.480      0.355825           0.240
B          2.85           1.3095          1.83          0.915      0.140847           0.095
C          1.01           1.0100          0.60          0.600      0.000000           0.000

dan jika Anda benar-benar perlu memiliki nama yang berbeda, Anda dapat melakukannya seperti ini:

mydf_agg.rename({
    "energy_sum": "total_energy",
    "energy_<lambda>": "energy_p17",
    "distance_sum": "total_distance",
    "distance_mean": "average_distance"
    }, inplace=True)

tetapi itu berarti Anda harus berhati-hati untuk menjaga agar kode penggantian nama (yang sekarang harus ditempatkan di tempat lain dalam kode) sinkron dengan kode tempat agregasi didefinisikan...

Pengguna panda sedih (yang tentu saja masih menyukai panda)


Saya setuju untuk konsistensi, dan pada saat yang sama saya sangat menyesali penghentian fungsionalitas _agregat dan rename_. Saya harap contoh di atas membuat poin rasa sakit menjadi jelas.


Solusi yang memungkinkan

  • Hapus fungsi pelabelan ulang dict-of-dict
  • Sediakan API lain untuk bisa melakukannya (tapi kenapa harus ada dua metode untuk tujuan utama yang sama, yaitu agregasi?)
  • ??? (terbuka untuk saran)

_Opsional baca:_

Sehubungan dengan diskusi yang disebutkan di atas dalam permintaan tarik yang telah berlangsung selama beberapa bulan, saya baru-baru ini menyadari salah satu alasan mengapa saya begitu terganggu oleh penghentian ini: "menggabungkan dan mengganti nama" adalah hal yang wajar untuk dilakukan dengan GROUP BY agregasi di SQL karena di SQL Anda biasanya memberikan nama kolom tujuan langsung di sebelah ekspresi agregasi, misalnya SELECT col1, avg(col2) AS col2_mean, stddev(col2) AS col2_var FROM mytable GROUP BY col1 .

Saya _not_ mengatakan bahwa Pandas tentu saja harus menyediakan fungsionalitas yang sama dengan SQL. Tetapi contoh yang diberikan di atas menunjukkan mengapa API dict-of-dict menurut saya merupakan solusi yang bersih dan sederhana untuk banyak kasus penggunaan.

(* Saya pribadi tidak setuju bahwa pendekatan dict-of-dict itu rumit.)

API Design Groupby

Komentar yang paling membantu

Untuk apa nilainya, saya juga sangat mendukung untuk tidak mengurangi fungsionalitasnya.

Alasan besar bagi saya adalah bahwa ada sesuatu yang sangat aneh tentang pencampuran ruang nama fungsi Python (sesuatu yang berkaitan dengan implementasi tertentu) dengan data nama kolom (sesuatu yang seharusnya tidak diketahui tentang implementasi). Fakta bahwa kita melihat kolom (berpotensi beberapa kolom) bernama '<lambda>' menyebabkan saya mengalami disonansi kognitif yang parah.

Pendekatan penggantian nama ini menarik, karena ada langkah perantara ini di mana nama kolom yang tidak perlu (dan terbuka) dibawa-bawa. Lebih jauh lagi, mereka sulit untuk secara andal, secara sistematis mengganti nama karena ada potensi ketergantungan pada implementasinya.

Selain itu, fungsionalitas dict bersarang memang kompleks, tetapi ini adalah operasi kompleks yang sedang dilakukan.

TL;DR Tolong jangan depresiasi. :)

Semua 37 komentar

@zertrin : Terima kasih telah menyatukan ini. Saya melihat ada banyak diskusi di #15931 tentang ini. Karena saya belum bisa membaca ini secara lengkap, saya tidak bisa berkomentar saat ini. Namun demikian, izinkan saya melakukan ping:

@jreback @jorisvandenbossche @TomAugspurger @chris-b1

Saya setuju bahwa mengganti nama dengan implementasi agg ini sangat kikuk dan rusak dalam contoh ini. Dikte bersarang agak rumit tetapi menulisnya seperti yang Anda lakukan membuatnya sangat jelas apa yang terjadi.

Saya kira mungkin ada parameter names ditambahkan ke agg yang akan membuat kamus memetakan kolom agregasi ke nama barunya. Anda bahkan dapat menambahkan parameter lain drop_index sebagai boolean untuk menentukan apakah akan mempertahankan level indeks atas.

Jadi sintaksnya akan berubah menjadi:

agg_dict = {'energy': ['sum',
                       lambda x: np.percentile(x, 98), # lambda
                       lambda x: np.percentile(x, 17), # lambda
                      ],
            'distance': ['sum',
                         'mean',
                         smrb.mad, # original function
                         mad_c1,   # partial function wrapping the original function
                        ]
           }

name_dict = {'energy':['energy_sum', 'energy_p98', 'energy_p17'],
             'distance':['distance_sum', 'distance_mean', 'distance_mad', 'distance_mad_c1']}


mydf.groupby('cat').agg(agg_dict, names=name_dict, drop_index=True)

Atau mungkin, seluruh metode baru agg_assign dapat dibuat, yang akan bekerja mirip dengan DataFrame.assign :

mydf.groupby('cat').agg_assign(energy_sum=lambda x: x.energy.sum(),
                               energy_p98=lambda x: np.percentile(x.energy, 98),
                               energy_p17=lambda x: np.percentile(x.energy, 17),
                               distance_sum=lambda x: x.distance.sum(),
                               distance_mean=lambda x: x.distance.mean(),
                               distance_mad=lambda x: smrb.mad(x.distance),
                               distance_mad_c1=lambda x: mad_c1(x.distance))

Saya sebenarnya lebih menyukai opsi ini.

Untuk apa nilainya, saya juga sangat mendukung untuk tidak mengurangi fungsionalitasnya.

Alasan besar bagi saya adalah bahwa ada sesuatu yang sangat aneh tentang pencampuran ruang nama fungsi Python (sesuatu yang berkaitan dengan implementasi tertentu) dengan data nama kolom (sesuatu yang seharusnya tidak diketahui tentang implementasi). Fakta bahwa kita melihat kolom (berpotensi beberapa kolom) bernama '<lambda>' menyebabkan saya mengalami disonansi kognitif yang parah.

Pendekatan penggantian nama ini menarik, karena ada langkah perantara ini di mana nama kolom yang tidak perlu (dan terbuka) dibawa-bawa. Lebih jauh lagi, mereka sulit untuk secara andal, secara sistematis mengganti nama karena ada potensi ketergantungan pada implementasinya.

Selain itu, fungsionalitas dict bersarang memang kompleks, tetapi ini adalah operasi kompleks yang sedang dilakukan.

TL;DR Tolong jangan depresiasi. :)

Kontribusi saya dimotivasi oleh dua hal.

  1. Saya mengetahui dan setuju dengan motivasi untuk mengurangi API Panda yang membengkak. Bahkan jika saya salah arah dalam hal motivasi yang dirasakan untuk mengurangi elemen API "membengkak", saya masih berpendapat bahwa API Pandas dapat disederhanakan.
  2. Saya pikir lebih baik memiliki buku masak yang bagus dengan resep yang bagus daripada menyediakan API untuk memenuhi keinginan dan keinginan semua orang. Saya tidak mengklaim bahwa penggantian nama melalui kamus bersarang memenuhi syarat sebagai keinginan yang memuaskan seperti yang sudah ada dan kami sedang mendiskusikan penghentiannya. Tapi itu terletak pada spektrum antara API yang disederhanakan dan yang lainnya.

Juga, Pandas Series dan objek DataFrame memiliki metode pipe untuk memfasilitasi pipelining. Dalam segmen dokumen ini dibahas bahwa kita dapat menggunakan pipe untuk memproksi metode sebagai pengganti subkelas. Dalam semangat yang sama, kita bisa menggunakan baru GroupBy.pipe untuk melakukan peran yang sama dan memungkinkan kita untuk membangun metode proxy untuk objek groupby.

Saya akan menggunakan contoh @zertrin

import numpy as np
import statsmodels.robust as smrb
from functools import partial

# The DataFrame offered up above
mydf = pd.DataFrame(
    {
        'cat': ['A', 'A', 'A', 'B', 'B', 'C'],
        'energy': [1.8, 1.95, 2.04, 1.25, 1.6, 1.01],
        'distance': [1.2, 1.5, 1.74, 0.82, 1.01, 0.6]
    },
    index=range(6)
)

# Identical dictionary passed to `agg`
funcs = {
    'energy': {
        'total_energy': 'sum',
        'energy_p98': lambda x: np.percentile(x, 98),  # lambda
        'energy_p17': lambda x: np.percentile(x, 17),  # lambda
    },
    'distance': {
        'total_distance': 'sum',
        'average_distance': 'mean',
        'distance_mad': smrb.mad,   # original function
        'distance_mad_c1': mad_c1,  # partial function wrapping the original function
    },
}

# Write a proxy method to be passed to `pipe`
def agg_assign(gb, fdict):
    data = {
        (cl, nm): gb[cl].agg(fn)
        for cl, d in fdict.items()
        for nm, fn in d.items()
    }
    return pd.DataFrame(data)

# All the API we need already exists with `pipe`
mydf.groupby('cat').pipe(agg_assign, fdict=funcs)

Yang mengakibatkan

            distance                                                 energy                        
    average_distance distance_mad distance_mad_c1 total_distance energy_p17 energy_p98 total_energy
cat                                                                                                
A              1.480     0.355825           0.240           4.44     1.8510     2.0364         5.79
B              0.915     0.140847           0.095           1.83     1.3095     1.5930         2.85
C              0.600     0.000000           0.000           0.60     1.0100     1.0100         1.01

Metode pipe membuat penambahan API baru tidak diperlukan dalam banyak kasus. Itu juga, menyediakan sarana untuk pengganti fungsionalitas usang yang sedang kita diskusikan. Oleh karena itu, saya akan cenderung untuk maju dengan penghentian tersebut.

Saya sangat menyukai ide tdpetrou - untuk menggunakan: names=name_dict .

Ini bisa membuat semua orang bahagia. Ini memberi kita kemungkinan untuk mengganti nama kolom dengan mudah seperti yang kita inginkan.

Tidak juga, seperti yang disebutkan dalam posting awal saya, ini tidak akan menyelesaikan masalah decoupling tempat operasi agregat didefinisikan dari nama kolom yang dihasilkan, membutuhkan upaya ekstra untuk memastikan keduanya "disinkronkan".

Saya tidak mengatakan itu solusi yang buruk (setelah semua itu memecahkan masalah lain), tetapi itu tidak akan semudah dan sejelas pendekatan dict of dict. Maksud saya di sini bahwa pada waktu penulisan Anda perlu menjaga kedua dict dari daftar disinkronkan, dan ketika membaca sumbernya, pembaca harus berusaha untuk mencocokkan nama-nama di dict kedua dari daftar dengan definisi agregat di dict pertama dari daftar. Itu dua kali upaya dalam setiap kasus.

Dikte bersarang agak rumit tetapi menulisnya seperti yang Anda lakukan membuatnya sangat jelas apa yang terjadi.

Saya masih tidak mengerti mengapa semua orang sepertinya mengatakan bahwa dict of dict itu rumit. Bagi saya itu cara paling jelas untuk melakukannya.

Yang mengatakan, jika kata kunci names adalah satu-satunya solusi yang nyaman bagi tim panda, itu masih merupakan peningkatan dari situasi saat ini.

@pirsquared solusi menarik dengan API saat ini. Meskipun menurut saya tidak cukup mudah untuk dipahami (saya tidak begitu mengerti cara kerjanya :confused: )

Saya memulai utas di subreddit ilmu data - Apa yang Anda benci tentang panda? . Seseorang mengemukakan penghinaan mereka untuk MultiIndex yang dikembalikan setelah groupby dan menunjuk ke kata kerja dplyr do yang diimplementasikan dalam plydata . Kebetulan bekerja persis seperti agg_assign jadi itu cukup menarik.

@zertrin agg_assign akan lebih unggul dari pendekatan dict of dict Anda dan identik dengan agregasi sql serta memungkinkan beberapa kolom untuk berinteraksi satu sama lain dalam agregasi. Itu juga akan bekerja secara identik dengan DataFrame.assign .

Adakah pemikiran @TomAugspurger ?

...
mydf.groupby('cat').agg(agg_dict, nama=nama_dict, drop_index=Benar)

Meskipun ini memecahkan masalah, seseorang perlu menyelaraskan kunci dan nilai di dua tempat. Saya pikir API (seperti yang disarankan untuk .agg_assign ) yang tidak memerlukan kode pembukuan seperti itu kurang rawan kesalahan.

Ada juga masalah kode pembersihan setelah menggunakan API. Ketika groupby operasi mengembalikan MultiIndex dataframe, dalam banyak kasus pengguna membatalkan MultiIndex . Cara deklaratif langsung menggunakan .agg_assign , menyarankan tidak ada hierarki, tidak ada MultiIndex keluaran, tidak ada pembersihan setelahnya.

Berdasarkan pola penggunaan, saya pikir output multi-indeks harus benar-benar memilih dan tidak memilih keluar.

Saya awalnya skeptis tentang proposisi agg_assign , tetapi dua komentar terakhir meyakinkan saya bahwa ini bisa menjadi solusi yang baik.

Terutama memikirkan kemungkinan untuk menggunakannya dalam bentuk agg_assign(**relabeling_dict) dan dengan demikian dapat mendefinisikan relabeling_dict seperti ini:

relabeling_dict = {
    'energy_sum': lambda x: x.energy.sum(),
    'energy_p98': lambda x: np.percentile(x.energy, 98),
    'energy_p17': lambda x: np.percentile(x.energy, 17),
    'distance_sum': lambda x: x.distance.sum(),
    'distance_mean': lambda x: x.distance.mean(),
    'distance_mad': lambda x: smrb.mad(x.distance),
    'distance_mad_c1': lambda x: mad_c1(x.distance)
}

Itu akan cukup fleksibel dan menyelesaikan semua masalah yang disebutkan dalam OP saya.

@zertrin @has2k1

Saya sedang memikirkan ini sedikit lagi dan fungsi ini sudah ada dengan apply . Anda cukup mengembalikan Seri dengan indeks sebagai nama kolom baru dan nilai sebagai agregasi. Ini memungkinkan spasi dalam nama dan memberi Anda kemampuan untuk memesan kolom persis seperti yang Anda inginkan:

def my_agg(x):
    data = {'energy_sum': x.energy.sum(),
            'energy_p98': np.percentile(x.energy, 98),
            'energy_p17': np.percentile(x.energy, 17),
            'distance sum' : x.distance.sum(),
            'distance mean': x.distance.mean(),
            'distance MAD': smrb.mad(x.distance),
            'distance MAD C1': mad_c1(x.distance)}
    return pd.Series(data, index=list_of_column_order)

mydf.groupby('cat').apply(my_agg)

Jadi, mungkin tidak perlu metode baru dan sebagai gantinya hanya contoh yang lebih baik di dokumen.

@tdpetrou , Anda benar. Saya lupa cara kerja apply karena saya menggunakan versi saya

Hum memang, tidak ada kemungkinan saya akan berpikir untuk menggunakannya dalam konteks agregasi hanya dengan membaca dokumen namun ...
Selain itu, saya masih menemukan solusi dengan apply agak terlalu berbelit-belit. Pendekatan agg_assign tampak lebih lugas dan dapat dimengerti.

Karena tidak pernah ada pernyataan tentang itu, apakah pendekatan dict-of-dict (yang, meskipun saat ini tidak digunakan lagi, sudah diterapkan dan juga menyelesaikan semua masalah ini) benar-benar tidak mungkin?

Kecuali untuk pendekatan agg_assign , dict-of-dict tampaknya masih yang paling sederhana, dan tidak memerlukan pengkodean apa pun, hanya tidak digunakan lagi.

Keuntungan dan kerugian dari pendekatan agg_assign adalah mendorong pemilihan kolom ke dalam metode agregasi . Dalam semua contoh, x diteruskan ke lambda adalah sesuatu seperti self.get_group(group) untuk setiap grup dalam self , objek DataFrameGroupBy . Ini bagus karena memisahkan naming , yang ada di **kwargs , dari selection , yang ada di function.

Kekurangannya adalah bahwa fungsi agregasi generik Anda yang bagus sekarang harus memperhatikan pemilihan kolom. Tidak ada makan siang gratis! Itu berarti Anda akan mendapatkan banyak pembantu seperti lambda x: x[col].min . Anda juga harus berhati-hati dengan hal-hal seperti np.min yang mengurangi semua dimensi, vs. pd.DataFrame.min , yang mengurangi lebih dari axis=0 . Itu sebabnya sesuatu seperti agg_assign tidak akan setara dengan apply . apply masih beroperasi berdasarkan kolom untuk metode tertentu.

Saya tidak yakin tentang pengorbanan ini vs. metode dict-of-dicts, tapi saya penasaran untuk mendengar pemikiran orang lain. Berikut adalah sketsa kasar dari agg_assign , yang telah saya panggil yang saya sebut agg_table untuk menekankan bahwa fungsi-fungsi dilewatkan pada tabel, bukan kolom:

from collections import defaultdict

import pandas as pd
import numpy as np
from pandas.core.groupby import DataFrameGroupBy

mydf = pd.DataFrame(
    {
        'cat': ['A', 'A', 'A', 'B', 'B', 'C'],
        'energy': [1.8, 1.95, 2.04, 1.25, 1.6, 1.01],
        'distance': [1.2, 1.5, 1.74, 0.82, 1.01, 0.6]
    },
    index=range(6)
)


def agg_table(self, **kwargs):
    output = defaultdict(dict)
    for group in self.groups:
        for k, v in kwargs.items():
            output[k][group] = v(self.get_group(group))

    return pd.concat([pd.Series(output[k]) for k in output],
                     keys=list(output),
                     axis=1)

DataFrameGroupBy.agg_table = agg_table

Penggunaan

>>> gr = mydf.groupby("cat")
>>> gr.agg_table(n=len,
                 foo=lambda x: x.energy.min(),
                 bar=lambda y: y.distance.min())

   n   foo   bar
A  3  1.80  1.20
B  2  1.25  0.82
C  1  1.01  0.60

Saya kira kita bisa melakukan sedikit untuk membuat kinerja ini tidak terlalu buruk, tetapi tidak sebanyak .agg ...

Bisakah seseorang dari Tim Inti Pandas tolong jelaskan apa alasan utama untuk menghentikan pelabelan ulang dicts di groupby.agg ?

Saya dapat dengan mudah memahami jika itu menyebabkan terlalu banyak masalah untuk mempertahankan kode, tetapi jika ini tentang kompleksitas bagi pengguna akhir - saya juga akan memilih untuk mengembalikannya, karena cukup jelas dibandingkan dengan solusi yang diperlukan ...

Terima kasih!

Bisakah seseorang dari Tim Inti Pandas tolong jelaskan apa alasan utama penghentian pelabelan ulang dicts di groupby.agg?

Apakah Anda melihat https://github.com/pandas-dev/pandas/pull/15931/files#diff -52364fb643114f3349390ad6bcf24d8fR461?

Alasan utamanya adalah bahwa dict-keys kelebihan beban untuk melakukan dua hal. Untuk Series / SeriesGroupBy, mereka untuk penamaan. Untuk DataFrame/DataFrameGroupBy, mereka untuk memilih kolom.

In [32]: mydf.aggregate({"distance": "min"})
Out[32]:
distance    0.6
dtype: float64

In [33]: mydf.aggregate({"distance": {"foo": "min"}})
/Users/taugspurger/Envs/pandas-dev/bin/ipython:1: FutureWarning: using a dict with renaming is deprecated and will be removed in a future version
  #!/Users/taugspurger/Envs/pandas-dev/bin/python3.6
Out[33]:
     distance
foo       0.6

In [34]: mydf.distance.agg({"foo": "min"})
Out[34]:
foo    0.6
Name: distance, dtype: float64

In [35]: mydf.groupby("cat").agg({"distance": {"foo": "min"}})
/Users/taugspurger/Envs/pandas-dev/lib/python3.6/site-packages/pandas/pandas/core/groupby.py:4201: FutureWarning: using a dict with renaming is deprecated and will be removed in a future version
  return super(DataFrameGroupBy, self).aggregate(arg, *args, **kwargs)
Out[35]:
    distance
         foo
cat
A       1.20
B       0.82
C       0.60

In [36]: mydf.groupby("cat").distance.agg({"foo": "min"})
/Users/taugspurger/Envs/pandas-dev/bin/ipython:1: FutureWarning: using a dict on a Series for aggregation
is deprecated and will be removed in a future version
  #!/Users/taugspurger/Envs/pandas-dev/bin/python3.6
Out[36]:
      foo
cat
A    1.20
B    0.82
C    0.60

Ini bukan hal yang paling membingungkan di panda mungkin, jadi mungkin kita bisa mengunjunginya kembali :) Saya mungkin kehilangan beberapa kasus Edge. Tetapi bahkan jika kami menghapus agregasi dict-of-dicts, kami masih memiliki inkonsistensi antara penamaan dan pemilihan kolom:

Untuk Seri/SeriGroupDengan kunci kamus selalu untuk penamaan output.

Untuk DataFrame / DataFrameGroupby, kunci dict selalu untuk seleksi. Dengan dict-of-dicts kami memilih kolom, dan kemudian dict bagian dalam adalah untuk menamai output, seperti Seri / SeriesGroupBy.

Kami membahas ini secara singkat sebelumnya (di suatu tempat dalam diskusi panjang tentang penghentian), dan saya mengusulkan sesuatu yang serupa di sini: https://github.com/pandas-dev/pandas/pull/14668#issuecomment -274508089 . Tetapi pada akhirnya hanya penghentian yang diterapkan, dan bukan ide untuk membuat fungsionalitas lain menggunakan dicts (fungsi 'mengganti nama') lebih mudah.

Masalahnya adalah bahwa dicts keduanya digunakan untuk 'seleksi' (di kolom mana Anda ingin fungsi ini diterapkan) dan 'mengganti nama' (apa yang seharusnya menjadi nama kolom yang dihasilkan saat menerapkan fungsi ini). Sintaks alternatif, selain dicts, dapat berupa argumen kata kunci, seperti yang dibahas di sini dalam proposal agg_assign .
Saya masih mendukung untuk mengeksplorasi kemungkinan ini, apakah itu dalam agg itu sendiri atau dalam metode baru seperti agg_assign .

Apa yang saya usulkan saat itu adalah sesuatu yang mirip dengan agg_assign tetapi menggunakan dict per kata kunci alih-alih fungsi lambda. Diterjemahkan ke contoh di sini, ini akan menjadi seperti:

mydf.groupby('cat').agg(
    energy_sum={'energy': 'sum'},
    energy_p98={'energy': lambda x: np.percentile(x, 98)},
    energy_p17={'energy': lambda x: np.percentile(x, 17)},
    distance_sum={'distance': 'sum'},
    distance_mean={'distance': 'mean'},
    distance_mad={'distance': smrb.mad},
    distance_mad_c1={'distance': mad_c1})

Saya tidak yakin ini tentu lebih mudah dibaca atau lebih mudah untuk ditulis sebagai versi dengan semua lambda, tetapi, yang ini berpotensi lebih berkinerja, karena panda masih dapat menggunakan implementasi yang dioptimalkan untuk jumlah, rata-rata, dll pada kolom tempat Anda melakukannya tidak memiliki lambda atau fungsi yang ditentukan pengguna.

Pertanyaan besar dengan pendekatan ini adalah apa arti df.groupby('cat').agg(foo='mean') ? Itu secara logis akan menerapkan 'berarti' ke semua kolom karena Anda tidak membuat pilihan apa pun (mirip dengan {'col1' : {'foo': 'mean'}, 'col2': {'foo':'mean'}, 'col3': ...} sebelumnya). Tapi, itu akan menghasilkan kolom multi-diindeks, sedangkan pada contoh di atas saya pikir akan lebih baik untuk tidak berakhir dengan kolom MI.

Saya pikir hal di atas dapat dilakukan dengan kompatibel di dalam agg , tetapi pertanyaannya adalah apakah ini diperlukan.
Saya juga berpikir ini akan meluas ke kasus series seperti:

mydf.groupby('cat').distance.agg(
    distance_sum='sum',
    distance_mean='mean',
    distance_mad=smrb.mad,
    distance_mad_c1=mad_c1)

(dan Anda bahkan dapat mempertimbangkan untuk melakukan hal di atas satu kali untuk 'jarak' dan sekali untuk 'energi' dan menggabungkan hasilnya jika Anda tidak menyukai semua dicts/lamda)

@TomAugspurger Dalam contoh Anda implementasi sederhana dari agg_table , bukankah lebih baik untuk mengulangi berbagai fungsi yang akan diterapkan, daripada mengulangi grup, dan pada akhirnya menggabungkan kolom baru dengan axis=1 alih-alih menggabungkan baris yang baru dibentuk dengan axis=0 ?

BTW, @zertrin @tdpetrou @smcateer @pirsquared dan lainnya, terima kasih banyak telah mengangkat masalah ini dan memberikan umpan balik yang terperinci. Umpan balik dan keterlibatan masyarakat seperti itu sangat penting!

Saya sebenarnya sangat menyukai pola yang disarankan oleh @tdpetrou (menggunakan apply dengan fungsi yang mengembalikan Seri) - mungkin bahkan lebih baik daripada dict of dicts.

Jika fungsi mengembalikan pd.Series(data, index=data.keys()) apakah kita dijamin mendapatkan indeks dalam urutan yang benar? (Hanya memikirkan cara terbaik untuk menerapkan pola dalam kode saya - dengan risiko menyimpang dari topik).

Sunting: maaf, saya salah memahami inti argumen indeks (ini opsional di sini, hanya diperlukan jika Anda ingin menentukan urutan kolom - mengembalikan pd.Series(data) melakukan pekerjaan untuk saya).

Apakah contoh @tdpetrou akan berfungsi dengan agregasi first & last ?

Saya harus menggunakan kepala/ekor seperti ini

def agg_funcs(x):
    data = {'start':x['DATE_TIME'].head(1).values[0],
           'finish':x['DATE_TIME'].tail(1).values[0],
           'events':len(x['DATE_TIME'])}
    return pd.Series(data, index = list(data.keys()))

results = df.groupby('col').apply(agg_funcs)

Saya masih ingin membahas ini, tetapi saya rasa itu tidak akan selesai untuk 0,23.

Bisakah pendekatan @tdpetrou bekerja tanpa mendefinisikan fungsi yang tidak akan pernah kita gunakan lagi dalam kode kita? Berasal dari dunia Q/Kdb+ (mirip dengan SQL) Saya bingung mengapa kita perlu membuat variabel/fungsi temporal untuk pernyataan pemilihan sederhana.

OP di sini.

Sejujurnya, setelah sekian lama dan banyak diskusi di #15931 dan di sini, saya masih tidak yakin bahwa ini adalah ide yang baik untuk mencela pelabelan ulang dicts.

Pada akhirnya, tidak ada alternatif yang diusulkan di sini yang lebih intuitif bagi pengguna daripada pendekatan dict pelabelan ulang saat ini IMHO. Ketika di dokumentasi, hanya dengan satu contoh jelas cara kerjanya, dan sangat fleksibel.

Tentu saja pengembang panda mungkin masih berpikir sebaliknya, hanya mengikuti sudut pandang pengguna.

Bahkan pendekatan dict pelabelan ulang tidak terlalu intuitif. Menurut pendapat saya sintaksnya harus mirip dengan SQL - func(column_name) as new_column_name . Dengan Python kita bisa melakukan ini dengan tupel tiga item. (func, column_name, new_column_name) . Ini adalah bagaimana dexplo melakukan agregasi groupby.

dexplo

@zertrin apakah Anda memiliki umpan balik tentang proposal saya di atas: https://github.com/pandas-dev/pandas/issues/18366/#issuecomment -349089667
Pada akhirnya, itu semacam membalikkan urutan dict: alih-alih "{col: {name: func}}" itu akan menjadi semacam "**{name: {col: func}}"

@jorisvandenbossche Saya telah mempertimbangkan pendekatan Anda. Masalahnya, saya tidak benar-benar melihat keuntungan tambahan apa yang dibawanya dari pendekatan saat ini.

Untuk membuatnya lebih blak-blakan, diberikan pilihan berikut:

  1. Undeprecate perilaku saat ini yang berfungsi dengan baik (beberapa baris kode penghentian untuk dihapus, tambahkan kembali bagian dokumentasi yang telah dihapus)
  2. Terapkan proposal Anda (perubahan signifikan yang harus dibuat dalam kode, lanjutkan dengan penghentian pendekatan saat ini, kebutuhan bagi semua pengguna untuk menyesuaikan kode mereka)

Saya tidak mengerti mengapa kita harus memilih 2 kecuali itu membawa keuntungan yang berarti dan nyata dari perspektif pengembang dan pengguna.

Untuk mengatasi beberapa poin dalam proposal Anda di atas:

Masalahnya adalah bahwa dicts keduanya digunakan untuk 'seleksi' (di kolom mana Anda ingin fungsi ini diterapkan) dan 'mengganti nama' (apa yang seharusnya menjadi nama kolom yang dihasilkan saat menerapkan fungsi ini).

Karena itu didokumentasikan dengan baik sebelumnya, saya tidak percaya itu adalah masalah bagi users . Secara pribadi, saya langsung mengerti dengan melihat contoh-contoh dalam dokumentasi. (EDIT: dan saya pikir: _"yay! konstruksi yang sangat berguna, persis sama dengan apa yang saya cari. Bagus."_)

Sintaks alternatif, selain dari dicts, bisa berupa argumen kata kunci

Salah satu hal yang menarik untuk menggunakan pendekatan dict-of-dict adalah bahwa pengguna dapat dengan mudah menghasilkannya secara dinamis dengan beberapa kode lain. Seperti yang Anda tunjukkan dalam komentar tepat di atas yang ini, pindah ke argumen kata kunci seperti dalam proposisi Anda masih memungkinkan untuk ini melalui konstruksi **{name: {col: func}} . Jadi saya tidak menentang proposal Anda. Saya hanya tidak melihat nilai tambah dan perlunya perubahan seperti itu ketika kami telah mencapai tingkat fungsionalitas yang sama dengan sistem yang diterapkan saat ini.

Pada akhirnya, proposal Anda akan _oke_ jika pandas core dev memiliki perasaan yang kuat terhadap pendekatan saat ini. Saya hanya tidak melihat manfaat apa pun sebagai _user_. (sebenarnya saya melihat kelemahan mengubah semua kode pengguna yang ada untuk membuatnya berfungsi kembali dengan proposisi baru).

@zertrin kami membahas ini kemarin dengan beberapa sebelum menjawab komentar Anda, hanya untuk mencerminkan pemikiran kita kemarin.


Jadi pertama-tama, gagasan bahwa fungsionalitas dasar seperti SQL "SELECT avg(col2) as col2_avg" harus berfungsi dan mudah, adalah sesuatu yang kami setujui sepenuhnya, dan kami benar-benar ingin memiliki solusi untuk ini.

Terlepas dari alasan asli kami memutuskan untuk menghentikan ini (yang mungkin atau mungkin tidak sekuat itu), dicts (usang) saat ini juga tidak begitu ideal, karena ini menciptakan MultiIndex yang sebenarnya tidak pernah Anda inginkan:

In [1]: df = pd.DataFrame({'A': ['a', 'b', 'a'], 'B': range(3), 'C': [.1, .2, .3]})

In [3]: gr = df.groupby('A')

In [4]: gr.agg({'B': {'b_sum': 'sum'}, 'C': {'c_mean': 'mean', 'c_count': 'count'}})
Out[4]: 
        C            B
  c_count c_mean b_sum
A                     
a       2    0.2     2
b       1    0.2     1

Di atas, tingkat pertama MultiIndex adalah berlebihan, karena Anda telah secara khusus mengganti nama kolom (dalam contoh di OP, ini juga langsung diikuti dengan menjatuhkan tingkat pertama kolom).
Namun sulit untuk mengubah ini karena Anda juga dapat melakukan hal-hal seperti gr.agg(['sum', 'mean']) atau (campuran) gr.agg({'B': ['sum', 'mean'], 'C': {'c_mean': 'mean', 'c_count': 'count'}}) jika MultiIndex diperlukan dan masuk akal.

Jadi salah satu proposal yang disebutkan dalam diskusi di atas, adalah memiliki cara untuk menentukan nama kolom akhir secara terpisah (misalnya https://github.com/pandas-dev/pandas/issues/18366#issuecomment-346683449).
Menambahkan misalnya kata kunci tambahan ke aggregate untuk menentukan nama kolom, seperti

gr.agg({'B': 'sum', 'C': ['mean', 'count']}, columns=['b_sum', 'c_mean', 'c_count'])

akan mungkin.
Namun, jika kita membagi spesifikasi kolom/fungsi dan nama kolom baru, kita juga dapat membuatnya lebih umum daripada kata kunci baru, dan melakukan sesuatu seperti:

gr.agg({'B': 'sum', 'C': ['mean', 'count']}).rename(columns=['b_sum', 'c_mean', 'c_count'])

Ini membutuhkan https://github.com/pandas-dev/pandas/issues/14829 untuk diselesaikan (sesuatu yang ingin kami lakukan untuk 0.24.0).
(Catatan penting: untuk ini kita harus memperbaiki duplikat masalah nama-nama fungsi lambda, jadi kami harus melakukan beberapa jenis deduplication otomatis nama jika kita ingin mendukung solusi ini.)


Kemudian, kami masih menyukai cara argumen kata kunci untuk mengganti nama. Alasan untuk ini adalah:

  • ini mirip dengan cara kerja assign di panda, dan juga konsisten dengan cara kerja groupby().aggregate() di ibis (dan juga mirip dengan tampilannya misalnya dplyr di R)
  • itu secara langsung memberi Anda nama kolom non-hierarki yang Anda inginkan (tanpa MultiIndex)
  • untuk kasus sederhana (juga misalnya untuk kasus Seri), saya pikir ini lebih sederhana seperti dict dari dict

Kami masih memiliki sedikit diskusi tentang bagaimana tampilannya. Apa yang saya usulkan di atas adalah (untuk menggunakan pemilihan kolom/fungsi yang setara seperti pada contoh pertama saya):

gr.agg(b_sum={'B': 'sum'}, c_mean={'C': 'mean'}, c_count={'C': 'count'})

Anda masih dapat membangun spesifikasi ini sebagai dict of dicts, tetapi dengan level dalam dan luar ditukar dibandingkan dengan versi saat ini (usang):

gr.agg(**{'b_sum': {'B': 'sum'}, 'c_mean': {'C': 'mean'}, 'c_count': {'C': 'count'})

(kita bisa memiliki contoh fungsi pembantu yang mengubah dicts yang ada ke dicts ke versi ini)

Namun, dict selalu hanya satu {col: func} , dan beberapa dict elemen tunggal itu terlihat agak aneh. Jadi alternatif yang kami pikirkan adalah menggunakan tupel:

gr.agg(b_sum=('B', 'sum'), c_mean=('C', 'mean'), c_count=('C', 'count'))

Ini terlihat sedikit lebih baik, tetapi di sisi lain dict {'B': 'sum'} konsisten dengan API lain untuk menentukan kolom tempat untuk menerapkan fungsi.


Kedua saran di atas (penggantian nama yang lebih mudah setelahnya, dan penamaan berbasis kata kunci) pada prinsipnya ortogonal, tetapi mungkin lebih baik memiliki keduanya (atau masih sesuatu yang lain berdasarkan diskusi lebih lanjut)

Terima kasih telah meneruskan di sini pemikiran terkini dari para pengembang

Saya mengakui kelemahan (, menurut pendapat saya, hanya) dari pendekatan dict-of-dict yang tidak digunakan lagi dengan MultiIndex. Bisa diratakan jika pengguna memberikan opsi tambahan (yeah YAO :-/ ).

Seperti yang disebutkan, saya tidak menentang versi kedua, selama masih memungkinkan untuk:

  • menghasilkan sesuatu secara dinamis entah bagaimana dan membongkarnya (terima kasih kepada konstruk **{} , yay Python!)
  • menjaga penggantian nama dan spesifikasi agregasi berdekatan (harus melacak dua daftar sehingga pesanan mereka tetap sama sangat mengganggu sebagai pengguna IMHO)
  • gunakan lambda atau fungsi parsial tanpa memerlukan solusi karena nama fungsi (berpotensi kurang atau bertentangan).

Dengan demikian, saran terakhir (dengan dicts atau tupel untuk pemetaan col>func) tidak apa-apa menurut saya.

Proposisi pertama di komentar sebelumnya dapat diterapkan jika Anda benar-benar menginginkannya, tetapi tanggapan saya tentang ini adalah, sebagai pengguna, saya tidak akan memilih untuk menggunakannya daripada alternatif kedua karena sulitnya menjaga hal-hal tetap sinkron antara dua daftar.

Dibahas pada pertemuan dev hari ini.

Ringkasan singkat

  1. @jorisvandenbossche akan mencoba mengimplementasikan gr.agg(b_sum=("B", "sum), ...) , yaitu ketika tidak ada arg diteruskan ke *GroupBy.agg , tafsirkan kwargs sebagai <output_name>=(<selection>, <aggfunc>)
  2. Orthogonal untuk masalah ini, kami ingin menerapkan MutliIndex.flatten dan memberikan kata kunci flatten=True ke .agg

Mungkin ini membantu: solusi saya untuk penghentian ini adalah fungsi pembantu yang menggantikan alias->aggr maps dengan daftar fungsi yang dinamai dengan benar:

def aliased_aggr(aggr, name):
    if isinstance(aggr,str):
        def f(data):
            return data.agg(aggr)
    else:
        def f(data):
            return aggr(data)
    f.__name__ = name
    return f

def convert_aggr_spec(aggr_spec):
    return {
        col : [ 
            aliased_aggr(aggr,alias) for alias, aggr in aggr_map.items() 
        ]  
        for col, aggr_map in aggr_spec.items() 
    }

yang memberikan perilaku lama dengan:

mydf_agg = mydf.groupby('cat').agg(convert_aggr_spec{
    'energy': {
        'total_energy': 'sum',
        'energy_p98': lambda x: np.percentile(x, 98),  # lambda
        'energy_p17': lambda x: np.percentile(x, 17),  # lambda
    },
    'distance': {
        'total_distance': 'sum',
        'average_distance': 'mean',
        'distance_mad': smrb.mad,   # original function
        'distance_mad_c1': mad_c1,  # partial function wrapping the original function
    },
}))

yang sama dengan

mydf_agg = mydf.groupby('cat').agg({
    'energy': [ 
        aliased_aggr('sum', 'total_energy'),
        aliased_aggr(lambda x: np.percentile(x, 98), 'energy_p98'),
        aliased_aggr(lambda x: np.percentile(x, 17), 'energy_p17')
    ],
    'distance': [
         aliased_aggr('sum', 'total_distance'),
         aliased_aggr('mean', 'average_distance'),
         aliased_aggr(smrb.mad, 'distance_mad'),
         aliased_aggr(mad_c1, 'distance_mad_c1'),
    ]
})

Ini berfungsi untuk saya, tetapi mungkin tidak akan berfungsi di beberapa kasus sudut ...

Pembaruan : menemukan bahwa penggantian nama tidak diperlukan, karena tupel dalam spesifikasi agregasi ditafsirkan sebagai (alias, aggr). Jadi fungsi alias_aggr tidak diperlukan, dan konversinya menjadi:

def convert_aggr_spec(aggr_spec):
    return {
        col : [ 
           (alias,aggr) for alias, aggr in aggr_map.items() 
        ]  
        for col, aggr_map in aggr_spec.items() 
    }

Saya hanya ingin berpadu di sini sebagai pengguna lain yang benar-benar kehilangan fungsi menggabungkan kolom pada fungsi apa pun dan segera mengganti namanya di baris yang sama. Saya _never_ menemukan diri saya menggunakan MultiIndex yang dikembalikan oleh panda - saya langsung meratakannya, atau saya sebenarnya ingin menentukan nama kolom saya secara manual karena itu sebenarnya berarti sesuatu yang spesifik.

Saya akan senang dengan salah satu pendekatan yang diusulkan di sini: Sintaks seperti SQL (saya benar-benar menemukan diri saya menggunakan .query() banyak di panda), kembali ke perilaku terdepresiasi, salah satu saran lainnya. Pendekatan saat ini sudah membawa saya ejekan dari rekan-rekan yang menggunakan R.

Saya baru-baru ini bahkan menemukan diri saya menggunakan PySpark alih-alih panda meskipun itu tidak perlu, hanya karena saya lebih menyukai sintaksnya:

df.groupby("whatever").agg(
    F.max("col1").alias("my_max_col"),
    F.avg("age_col").alias("average_age"),
    F.sum("col2").alias("total_yearly_payments")
)

Juga PySpark jauh lebih berbelit-belit untuk menulis daripada panda dalam banyak kasus, ini hanya terlihat jauh lebih bersih! Jadi saya sangat menghargai bahwa pekerjaan ini masih dilakukan :-)

Saya pikir kami memiliki sintaks yang disepakati untuk fungsi ini; kita membutuhkan seseorang untuk
menerapkannya.

Pada Wed, Mar 27, 2019 at 9:01 Thomas Kastl [email protected]
menulis:

Saya hanya ingin berpadu di sini sebagai pengguna lain yang benar-benar
kehilangan fungsi menggabungkan kolom pada fungsi apa pun dan
segera mengganti namanya di baris yang sama. Saya tidak pernah menemukan diri saya sendiri
menggunakan MultiIndex yang dikembalikan oleh panda - saya langsung meratakannya,
atau saya sebenarnya ingin menentukan nama kolom saya secara manual karena mereka
sebenarnya berarti sesuatu yang spesifik.

Saya akan senang dengan salah satu pendekatan yang diusulkan di sini: sintaks seperti SQL
(Saya sebenarnya sudah sering menggunakan .query() di panda),
kembali ke perilaku terdepresiasi, salah satu saran lainnya. Itu
pendekatan saat ini sudah membawa saya ejekan dari rekan-rekan yang menggunakan R.

Saya baru-baru ini bahkan menemukan diri saya menggunakan PySpark alih-alih panda
itu tidak perlu, hanya karena saya lebih menyukai sintaksnya:

df.groupby("apapun").agg( F.max("col1").alias("my_max_col"),
F.avg("usia_kol").alias("usia_rata-rata"),
F.sum("col2").alias("total_yearly_payments") )

Juga PySpark jauh lebih berbelit-belit untuk menulis daripada panda dalam banyak kasus,
ini hanya terlihat jauh lebih bersih! Jadi saya sangat menghargai pekerjaan itu
ini masih dilakukan :-)


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/pandas-dev/pandas/issues/18366#issuecomment-477168767 ,
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/ABQHIkCYYsah5siYA4_z0oop_ufIB3h8ks5va3nJgaJpZM4QjSLL
.

Saya akan mencoba untuk mendapatkan ini untuk 0.25.0

Saya telah memasang PR di https://github.com/pandas-dev/pandas/pull/26399. Ide dasarnya adalah mengizinkan campuran rename & agregasi khusus kolom ini dengan menggunakan **kwargs dengan pemahaman bahwa nilainya harus berupa tupel (selection, aggfunc) .

In [2]: df = pd.DataFrame({'kind': ['cat', 'dog', 'cat', 'dog'],
   ...:                    'height': [9.1, 6.0, 9.5, 34.0],
   ...:                    'weight': [7.9, 7.5, 9.9, 198.0]})

In [3]: df
Out[3]:
  kind  height  weight
0  cat     9.1     7.9
1  dog     6.0     7.5
2  cat     9.5     9.9
3  dog    34.0   198.0

In [4]: df.groupby('kind').agg(min_height=('height', 'min'), max_weight=('weight', 'max'))
Out[4]:
      min_height  max_weight
kind
cat          9.1         9.9
dog          6.0       198.0

Ini memiliki beberapa keterbatasan

  • Ini agak aneh bagi panda lainnya. Sytanx (output_name=(selection, aggfunc)) tidak benar-benar muncul di tempat lain (meskipun .assign menggunakan pola output_name=... )
  • Ejaan untuk nama keluaran yang bukan pengidentifikasi python jelek: .agg(**{'output name': (col, func)})
  • Ini hanya Python 3.6+, atau kita perlu beberapa peretasan jelek untuk 3.5 dan yang lebih lama, karena urutan **kwargs sebelumnya tidak dipertahankan
  • aggfunc harus berupa fungsi unary. Jika aggfunc khusus Anda memerlukan argumen tambahan, Anda harus menerapkannya sebagian terlebih dahulu

Dan ada detail implementasi, beberapa lambda aggfunc untuk kolom yang sama belum didukung, meskipun itu bisa diperbaiki nanti.


Saya menduga bahwa kebanyakan orang yang berlangganan di sini akan mendukung beberapa alternatif untuk perilaku yang tidak berlaku lagi. Apa yang orang pikirkan tentang yang satu ini secara khusus?

cc @WillAyd jika saya melewatkan salah satu kekhawatiran Anda.

Hai @TomAugspurger ,

Terima kasih telah membuat langkah ini maju.

Ini memiliki beberapa keterbatasan

  • Ini agak aneh bagi panda lainnya. Sytanx (output_name=(selection, aggfunc)) tidak benar-benar muncul di tempat lain (meskipun .assign menggunakan pola output_name=... )

Mau tak mau saya merasa bahwa argumen semacam ini tampaknya sangat mirip dengan argumen yang memotivasi untuk mencela implementasi yang ada sejak awal.

Bisakah Anda berbagi mengapa kami mendapat lebih banyak manfaat dari cara baru ini daripada cara lama _sehubungan dengan argumen tertentu_?

Satu manfaat yang sudah dapat saya pikirkan adalah (untuk py3.6+) kita dapat memilih urutan output kolom satu per satu.

  • Ejaan untuk nama keluaran yang bukan pengidentifikasi python jelek: .agg(**{'output name': (col, func)})

Entah bagaimana, cara lama lebih baik dalam hal itu. Tetapi seperti yang saya katakan sebelumnya, selama dimungkinkan untuk menggunakan konstruk **{...} untuk membangun agregasi secara dinamis, saya akan cukup senang.

  • Ini hanya Python 3.6+, atau kita perlu beberapa peretasan jelek untuk 3.5 dan yang lebih lama, karena urutan **kwargs sebelumnya tidak dipertahankan

Bagaimana cara kerjanya sebelumnya (fitur dict-of-dict yang ada)? apakah pesanan dijamin dalam beberapa cara?

  • aggfunc harus berupa fungsi unary. Jika aggfunc khusus Anda memerlukan argumen tambahan, Anda harus menerapkannya sebagian terlebih dahulu

Hanya untuk mengonfirmasi pemahaman saya: aggfunc dapat berupa panggilan apa pun yang mengembalikan nilai yang valid, bukan? (selain aggfung string yang "sering digunakan" seperti 'min' , 'max' , dll. ). Apakah ada perbedaan dibandingkan sebelumnya? (yaitu bukankah batasan unary sudah ada?)

Dan ada detail implementasi, beberapa lambda aggfunc untuk kolom yang sama belum didukung, meskipun itu bisa diperbaiki nanti.

Ya itu agak mengganggu, tetapi selama itu hanya batasan sementara dan terbuka untuk memperbaikinya, itu bisa berhasil.

Saya menduga bahwa kebanyakan orang yang berlangganan di sini akan mendukung beberapa alternatif untuk perilaku yang tidak berlaku lagi. Apa yang orang pikirkan tentang yang satu ini secara khusus?

Bagaimanapun, saya pikir agregat dan rename dalam satu langkah sangat penting untuk dijaga. Jika perilaku lama benar-benar bukan pilihan, maka alternatif ini bisa dilakukan.

Bisakah Anda berbagi mengapa kami mendapat lebih banyak manfaat dari cara baru ini daripada yang lama sehubungan dengan argumen khusus itu.

Saya mungkin salah mengingat, tetapi saya yakin SeriesGroupby.agg dan DataFrameGroupby.agg memiliki arti yang berbeda antara kunci luar dalam kamus (apakah ini pemilihan kolom atau penamaan keluaran?). Dengan sintaks ini, kita dapat secara konsisten memiliki kata kunci yang berarti nama keluaran.

Entah bagaimana, cara lama lebih baik dalam hal itu.

Apakah perbedaannya hanya ** ? Kalau tidak, saya pikir batasan yang sama dibagikan.

Bagaimana cara kerjanya sebelumnya (fitur dict-of-dict yang ada)? apakah pesanan dijamin dalam beberapa cara?

Menyortir kunci, itulah yang saya lakukan di PR sekarang.

Hanya untuk mengonfirmasi pemahaman saya: aggfunc dapat berupa panggilan apa pun yang mengembalikan nilai yang valid, bukan?

Inilah perbedaannya

In [21]: df = pd.DataFrame({"A": ['a', 'a'], 'B': [1, 2], 'C': [3, 4]})

In [22]: def aggfunc(x, myarg=None):
    ...:     print(myarg)
    ...:     return sum(x)
    ...:

In [23]: df.groupby("A").agg({'B': {'foo': aggfunc}}, myarg='bar')
/Users/taugspurger/sandbox/pandas/pandas/core/groupby/generic.py:1308: FutureWarning: using a dict with renaming is deprecated and will be removed in a future version
  return super().aggregate(arg, *args, **kwargs)
None
Out[23]:
    B
  foo
A
a   3

dengan proposal alternatif, kami memesan **kwargs untuk nama kolom keluaran. Jadi, Anda perlu functools.partitial(aggfunc, myarg='bar') .

Ok terima kasih, saya pikir pendekatan yang diusulkan adalah 👍 untuk iterasi pertama (dan akan benar-benar ok sebagai pengganti segera setelah beberapa batasan implementasi lambda dihapus)

Apakah halaman ini membantu?
0 / 5 - 0 peringkat