Pandas: يجلب إهمال إعادة تسمية الإملاءات في groupby.agg العديد من المشكلات

تم إنشاؤها على ١٩ نوفمبر ٢٠١٧  ·  37تعليقات  ·  مصدر: pandas-dev/pandas

تم إنشاء هذه المشكلة بناءً على المناقشة من # 15931 بعد إهمال إعادة تسمية الإملاء في groupby.agg . تمت مناقشة الكثير مما تم تلخيصه أدناه في المناقشة السابقة. أود أن أوصي على وجه الخصوص https://github.com/pandas-dev/pandas/pull/15931#issuecomment -336139085 حيث يتم تحديد المشكلات بوضوح أيضًا.

كان الدافع وراء إهمال # 15931 مرتبطًا في الغالب بإحضار واجهة متسقة لـ agg() بين السلسلة وإطار البيانات (انظر أيضًا # 14668 للاطلاع على السياق).

وصف البعض وظيفة إعادة التسمية باستخدام إملاء متداخل بأنها معقدة للغاية و / أو غير متسقة وبالتالي تم إهمالها.

ومع ذلك ، فإن هذا يأتي بسعر: تؤدي استحالة التجميع وإعادة التسمية في نفس الوقت إلى مشكلات مزعجة للغاية وبعض عدم التوافق مع الإصدارات السابقة حيث لا يتوفر حل بديل معقول:

  • _ [مزعج] _ لا مزيد من التحكم في أسماء الأعمدة الناتجة
  • _ [مزعج] _ تحتاج إلى إيجاد طريقة لإعادة تسمية MultiIndex _after_ لإجراء التجميع ، مما يتطلب تتبع ترتيب الأعمدة في مكانين في الكود .... غير عملي على الإطلاق وأحيانًا مستحيل تمامًا (الحالات أدناه ).
  • ⚠️ _ [كسر] _ لا يمكن تطبيق أكثر من استدعاء واحد بنفس الاسم الداخلي على نفس عمود الإدخال. ينتج عن هذا حالتان فرعيتان:

    • _ [كسر] _ لا يمكنك تطبيق أكثر من اثنين أو أكثر من مجمعي لامدا في نفس العمود

    • _ [كسر] _ لا يمكنك تطبيق مجمعين أو أكثر من الدوال الجزئية إلا إذا قمت بتغيير السمة المخفية __name__

مثال

_ (يرجى ملاحظة أن هذا مثال معد لغرض توضيح المشكلة في رمز قصير قدر الإمكان ، ولكن كل المشكلات الموضحة هنا أثرت علي في الحياة الواقعية منذ التغيير ، وفي المواقف ليست بهذه البساطة هنا ) _

إدخال 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

قبل:

سهل الكتابة والقراءة ويعمل بالشكل المتوقع

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
    },
})

النتائج في

          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

وكل ما تبقى هو:

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

رقصة سعيدة تمدح الباندا 💃 🕺!

بعد

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
    ],
})

الفواصل أعلاه لأن وظائف lambda ستؤدي جميعها إلى ظهور أعمدة باسم <lambda> والتي ينتج عنها

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

الانحدار غير المتوافق مع الإصدارات السابقة: لا يمكن تطبيق اثنين من lambdas مختلفة على نفس العمود الأصلي بعد الآن.

إذا أزال أحدهم lambda x: np.percentile(x, 98) من الأعلى ، فسنواجه نفس المشكلة مع الوظيفة الجزئية التي ترث اسم الوظيفة من الوظيفة الأصلية:

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

أخيرًا ، بعد الكتابة فوق السمة __name__ للجزء الجزئي (على سبيل المثال مع mad_c1.__name__ = 'mad_c1' ) نحصل على:

    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

مع لا يزال

  • عمود واحد مفقود (النسبة المئوية 98)
  • التعامل مع أعمدة MultiIndex
  • وإعادة تسمية الأعمدة

للتعامل معها في خطوة منفصلة.

لا يوجد تحكم ممكن في أسماء الأعمدة بعد التجميع ، أفضل ما يمكننا الحصول عليه بطريقة آلية هو مزيج من اسم العمود الأصلي واسم الوظيفة التجميعية مثل هذا:

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

مما يؤدي إلى:

     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

وإذا كنت حقًا بحاجة إلى أسماء مختلفة ، فيمكنك القيام بذلك على النحو التالي:

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

ولكن هذا يعني أنك بحاجة إلى توخي الحذر للحفاظ على رمز إعادة التسمية (الذي يجب أن يكون موجودًا الآن في مكان آخر في الرمز) متزامنًا مع الكود حيث يتم تحديد التجميع ...

مستخدم الباندا الحزين 😢 (الذي لا يزال يحب الباندا بالطبع)


أنا ملتزم بالاتساق ، وفي نفس الوقت يؤسفني بشدة إهمال وظيفة _ التجميع وإعادة التسمية. آمل أن توضح الأمثلة أعلاه نقاط الألم.


الحلول الممكنة

  • قم بإلغاء إهمال وظيفة إعادة تسمية ديكت-أوف-ديكت
  • قم بتوفير واجهة برمجة تطبيقات أخرى لتتمكن من القيام بذلك (ولكن لماذا يجب أن تكون هناك طريقتان لنفس الغرض الرئيسي ، ألا وهما التجميع؟)
  • ؟؟؟ (مفتوح للاقتراحات)

قراءة اختيارية: _

فيما يتعلق بالمناقشة المذكورة أعلاه في طلب السحب والتي كانت جارية بالفعل لبضعة أشهر ، لم أدرك إلا مؤخرًا أحد الأسباب التي تجعلني منزعجًا جدًا من هذا الإهمال: "التجميع وإعادة التسمية" أمر طبيعي تجميعات GROUP BY في SQL لأنك في SQL عادةً ما توفر اسم عمود الوجهة بجوار تعبير التجميع مباشرةً ، على سبيل المثال SELECT col1, avg(col2) AS col2_mean, stddev(col2) AS col2_var FROM mytable GROUP BY col1 .

أنا لا أقول إن Pandas يجب بالضرورة أن توفر نفس وظائف SQL بالطبع. لكن الأمثلة المقدمة أعلاه توضح لماذا كانت واجهة برمجة التطبيقات ديكت أوف ديكت في رأيي حلاً نظيفًا وبسيطًا للعديد من حالات الاستخدام.

(* أنا لا أوافق شخصيًا على أن نهج الإملاء ديكت معقد).

API Design Groupby

التعليق الأكثر فائدة

لما يستحق ، أنا أيضًا أؤيد بشدة عدم إهمال الوظيفة.

السبب الرئيسي بالنسبة لي هو أن هناك شيئًا غريبًا للغاية حول خلط مساحة اسم وظيفة Python (شيء له علاقة بالتنفيذ المعين) مع البيانات وأسماء الأعمدة (وهو أمر يجب ألا يعرفه بالتأكيد عن التنفيذ). حقيقة أننا نرى أعمدة (يحتمل أن تكون أعمدة متعددة) تسمى '<lambda>' تسبب لي تنافرًا معرفيًا شديدًا.

شبكات نهج إعادة التسمية ، نظرًا لوجود هذه الخطوة الوسيطة حيث يتم نقل أسماء الأعمدة غير الضرورية (والمكشوفة). علاوة على ذلك ، من الصعب إعادة تسميتها بشكل منهجي وموثوق به نظرًا لوجود تبعيات محتملة على التنفيذ.

بصرف النظر عن ذلك ، فإن وظيفة الإملاء المتداخلة معقدة بشكل واضح ، ولكنها عملية معقدة يتم تنفيذها.

TL ؛ DR الرجاء عدم الاستهلاك. :)

ال 37 كومينتر

zertrin : شكرا لتجميع هذا. رأيت أنه كان هناك الكثير من النقاش مرة أخرى في # 15931 حول هذا الموضوع. بما أنني لم أتمكن من قراءة هذا بالكامل ، لا يمكنني التعليق في الوقت الحالي. ومع ذلك ، اسمحوا لي أن ping:

MustafaHosny اللهم امين

أوافق على أن إعادة التسمية باستخدام تطبيق agg أمر صعب للغاية ومتعطل في هذا المثال. الإملاءات المتداخلة معقدة إلى حد ما ولكن كتابتها كما فعلت توضح ما يحدث.

أفترض أنه قد تكون هناك معلمة names تمت إضافتها إلى agg والتي قد تتطلب تعيين القاموس لأعمدة التجميع لأسمائهم الجديدة. يمكنك حتى إضافة معلمة أخرى drop_index كمعامل منطقي لتحديد ما إذا كنت ستحتفظ بمستوى الفهرس العلوي أم لا.

لذلك سيتحول بناء الجملة إلى:

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)

أو ربما ، يمكن إنشاء طريقة جديدة بالكامل agg_assign ، والتي ستعمل بشكل مشابه لـ 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))

أنا في الواقع أحب هذا الخيار أفضل بكثير.

لما يستحق ، أنا أيضًا أؤيد بشدة عدم إهمال الوظيفة.

السبب الرئيسي بالنسبة لي هو أن هناك شيئًا غريبًا للغاية حول خلط مساحة اسم وظيفة Python (شيء له علاقة بالتنفيذ المعين) مع البيانات وأسماء الأعمدة (وهو أمر يجب ألا يعرفه بالتأكيد عن التنفيذ). حقيقة أننا نرى أعمدة (يحتمل أن تكون أعمدة متعددة) تسمى '<lambda>' تسبب لي تنافرًا معرفيًا شديدًا.

شبكات نهج إعادة التسمية ، نظرًا لوجود هذه الخطوة الوسيطة حيث يتم نقل أسماء الأعمدة غير الضرورية (والمكشوفة). علاوة على ذلك ، من الصعب إعادة تسميتها بشكل منهجي وموثوق به نظرًا لوجود تبعيات محتملة على التنفيذ.

بصرف النظر عن ذلك ، فإن وظيفة الإملاء المتداخلة معقدة بشكل واضح ، ولكنها عملية معقدة يتم تنفيذها.

TL ؛ DR الرجاء عدم الاستهلاك. :)

مساهمتي مدفوعة بأمرين.

  1. أنا على علم وأوافق على الدافع لتقليل واجهة برمجة التطبيقات المتضخمة لباندا. حتى لو كنت مضللاً فيما يتعلق بالدوافع المتصورة لتقليل عناصر واجهة برمجة التطبيقات "المتضخمة" ، فلا يزال رأيي أنه يمكن تبسيط واجهة برمجة تطبيقات Pandas.
  2. أعتقد أنه من الأفضل أن يكون لديك كتاب طبخ جيد مع وصفات جيدة بدلاً من توفير API لتلبية رغبات ورغبات الجميع. أنا لا أدعي أن إعادة التسمية عبر القواميس المتداخلة مؤهلة كأهواء مرضية كما كانت موجودة بالفعل ونحن نناقش إهمالها. لكنها تقع على الطيف بين واجهة برمجة التطبيقات المبسطة وشيء ... آخر.

أيضًا ، كان لسلسلة Pandas وكائنات DataFrame طرق pipe لتسهيل خطوط الأنابيب. في مقطع المستند هذا ، تمت مناقشة أنه يمكننا استخدام pipe للتوكيل للطرق بدلاً من التصنيف الفرعي. وبنفس الروح، فإننا يمكن أن تستخدم الجديد GroupBy.pipe لأداء دور مماثل والسماح لنا ببناء الطرق بالوكالة للكائنات groupby.

سأستخدم مثال

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)

مما يؤدي إلى

            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

تجعل طريقة pipe إضافة واجهة برمجة تطبيقات جديدة غير ضرورية في كثير من الحالات. كما أنه يوفر وسائل لاستبدال الوظيفة المهملة التي نناقشها. لذلك ، أنا أميل إلى المضي قدمًا في الإهمال.

تعجبني حقًا فكرة tdpetrou - لاستخدام: names=name_dict .

هذا يمكن أن يجعل الجميع سعداء. يمنحنا إمكانية إعادة تسمية الأعمدة بسهولة كما نرغب.

ليس حقًا ، كما هو مذكور في رسالتي الأولية ، أن هذا لن يحل مشكلة فصل المكان الذي يتم فيه تحديد العملية المجمعة من اسم العمود الناتج ، مما يتطلب جهدًا إضافيًا للتأكد من أنهما "متزامنان".

لا أقول إن هذا حل سيئ (بعد كل شيء يحل المشكلات الأخرى) ، لكنه لن يكون سهلاً وواضحًا مثل نهج الإملاء. أعني هنا أنه في وقت الكتابة تحتاج إلى الحفاظ على تزامن إملاءات القوائم ، وعند قراءة المصدر ، يجب على القارئ بذل جهد لمطابقة الأسماء في الإملاء الثاني من القوائم مع التعريف الكلي في الإملاء الأول من القوائم. هذا هو ضعف الجهد في كل حالة.

الإملاءات المتداخلة معقدة إلى حد ما ولكن كتابتها كما فعلت توضح ما يحدث.

ما زلت لا أفهم لماذا يبدو أن الجميع يقولون أن الإملاء ديكت معقد. بالنسبة لي ، هذه أوضح طريقة للقيام بذلك.

ومع ذلك ، إذا كانت الكلمة الرئيسية names هي الحل الوحيد الذي يرتاح إليه فريق الباندا ، فسيظل ذلك بمثابة تحسن مقارنة بالوضع الحالي.

pirsquared حل مثير للاهتمام مع واجهة برمجة التطبيقات الحالية. على الرغم من أنه ليس من السهل فهمه في رأيي (لا أفهم حقًا كيف يعمل: مرتبك :)

لقد بدأت موضوعًا في subreddit الخاص بعلم البيانات - ما الذي تكرهه في الباندا؟ . أثار شخص ما ازدرائه لـ MultiIndex الذي تم إرجاعه بعد groupby وأشار إلى فعل dplyr do الذي يتم تنفيذه في plydata . يحدث أن تعمل تمامًا مثل agg_assign لذا كان ذلك مثيرًا للاهتمام.

سيكونzertrin agg_assign متفوقًا على نهج الإملاء الخاص بك ويكون مطابقًا لتجميعات sql بالإضافة إلى السماح لأعمدة متعددة بالتفاعل مع بعضها البعض داخل التجميع. سيعمل أيضًا بشكل مماثل لـ DataFrame.assign .

أي أفكار jreback @ TomAugspurger ؟

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

على الرغم من أن هذا يحل المشكلة ، يحتاج المرء إلى محاذاة المفاتيح والقيم في مكانين. أعتقد أن واجهة برمجة التطبيقات (كما هو مقترح لـ .agg_assign ) التي لا تتطلب مثل رمز مسك الدفاتر هي أقل عرضة للخطأ.

هناك أيضًا مشكلة رمز التنظيف بعد استخدام API. عندما تُرجع العمليات groupby إطار بيانات MultiIndex ، في معظم الحالات ، يتراجع المستخدم عن MultiIndex . الطريقة التصريحية المباشرة لاستخدام .agg_assign ، تشير إلى عدم وجود تسلسل هرمي ، وعدم وجود مخرجات MultiIndex ، وعدم تنظيف بعد ذلك.

استنادًا إلى أنماط الاستخدام ، أعتقد أن النواتج متعددة المؤشرات يجب أن تكون متفق عليها بشكل صارم وليس إلغاء الاشتراك.

كنت في البداية متشككًا بشأن عرض agg_assign ، لكن التعليقين الأخيرين أقنعاني بأن هذا يمكن أن يكون حلاً جيدًا.

التفكير بشكل خاص في إمكانية استخدامه بالصيغة agg_assign(**relabeling_dict) وبالتالي تكون قادرًا على تحديد relabeling_dict كما يلي:

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)
}

سيكون ذلك مرنًا تمامًا وسيحل جميع المشكلات المذكورة في OP الخاص بي.

تضمين التغريدة

كنت أفكر في هذا أكثر قليلاً وهذه الوظيفة موجودة بالفعل مع apply . يمكنك ببساطة إرجاع سلسلة مع فهرس كأسماء أعمدة جديدة وقيم كتجميع. يتيح ذلك وجود مسافات في الاسم ويمنحك القدرة على ترتيب الأعمدة بالطريقة التي تريدها بالضبط:

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)

لذلك ، قد لا تكون هناك حاجة لطريقة جديدة وبدلاً من ذلك مجرد مثال أفضل في المستندات.

@ tdpetrou ، أنت محق. لقد نسيت كيف يعمل apply لأنني أستخدم نسختي الخاصة بسبب التنفيذ المزدوج في عملية اختيار المسار السريع البطيء.

همهمة بالفعل ، لا توجد فرصة كنت سأفكر في استخدامها في سياق التجميع فقط من خلال قراءة المستند ولكن ...
علاوة على ذلك ، ما زلت أجد الحل مع apply معقدًا بعض الشيء. بدا نهج agg_assign أكثر وضوحًا ومفهومًا.

نظرًا لأنه لم يكن هناك أي بيان حقًا حول هذا الموضوع ، فهل النهج dict-of-dict (والذي ، على الرغم من أنه مهمل حاليًا ، تم تنفيذه بالفعل ويحل أيضًا جميع هذه المشكلات) حقًا غير وارد حقًا؟

باستثناء الأسلوب agg_assign ، لا يزال dict-of-dict يبدو الأكثر بساطة ، ولا يحتاج إلى أي ترميز ، فقط عدم الإهمال.

تتمثل فائدة وعيوب أسلوب agg_assign أنه يدفع بتحديد العمود إلى طريقة التجميع . في جميع الأمثلة ، تم تمرير x إلى lambda مثل self.get_group(group) لكل مجموعة في self ، كائن DataFrameGroupBy . هذا جيد لأنه يفصل بشكل واضح التسمية ، والتي هي في **kwargs ، من التحديد الموجود في الوظيفة.

العيب هو أن وظائف التجميع العامة اللطيفة لديك الآن يجب أن تهتم باختيار العمود. لا يوجد غداء مجاني! هذا يعني أنك ستنتهي مع العديد من المساعدين مثل lambda x: x[col].min . ستحتاج أيضًا إلى توخي الحذر مع أشياء مثل np.min مما يقلل من جميع الأبعاد ، مقابل pd.DataFrame.min ، مما يقلل من axis=0 . هذا هو السبب في أن شيئًا مثل agg_assign لن يكون معادلاً لـ apply . لا يزال apply يعمل بطريقة العمود باستخدام طرق معينة.

لست متأكدًا من هذه المفاضلات مقابل طريقة الإملاء ، لكنني أشعر بالفضول لسماع أفكار الآخرين. إليكم رسمًا تقريبيًا لـ agg_assign ، والذي سميته agg_table للتأكيد على أن الوظائف يتم تمريرها في الجداول ، وليس الأعمدة:

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

إستعمال

>>> 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

أظن أننا يمكن أن نفعل بعض الشيء لجعل أداء هذا أقل فظاعة ، ولكن ليس بقدر ما يفعله .agg ...

هل يمكن لشخص من فريق Pandas الأساسي أن يشرح السبب الرئيسي لإهمال إعادة تسمية الإملاء في groupby.agg ؟

يمكنني بسهولة فهم ما إذا كان يتسبب في الكثير من المشاكل للحفاظ على الكود ، ولكن إذا كان الأمر يتعلق بالتعقيد للمستخدم النهائي - فسأختار أيضًا إعادته ، لأنه واضح جدًا مقارنة بالحلول المطلوبة ...

شكرا لك!

هل يمكن لشخص من فريق Pandas الأساسي أن يشرح السبب الرئيسي لإهمال إعادة تسمية الإملاءات في groupby.agg؟

هل رأيت https://github.com/pandas-dev/pandas/pull/15931/files#diff -52364fb643114f3349390ad6bcf24d8fR461؟

كان السبب الأساسي هو أن مفاتيح الديدان كانت مثقلة بعمل شيئين. بالنسبة إلى Series / SeriesGroupBy ، فهي مخصصة للتسمية. بالنسبة إلى DataFrame / DataFrameGroupBy ، فهي مخصصة لتحديد عمود.

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

ربما لا يكون هذا هو الشيء الأكثر إرباكًا في حيوانات الباندا ، لذلك ربما يمكننا إعادة النظر فيه :) يفترض أنني أفتقد بعض الحالات المتطورة. ولكن حتى إذا أزلنا تجميعات الإملاء ، فلا يزال لدينا عدم تناسق بين التسمية واختيار العمود:

بالنسبة إلى Series / SeriesGroupBy ، تكون مفاتيح القاموس دائمًا لتسمية الإخراج.

بالنسبة إلى DataFrame / DataFrameGroupby ، تكون مفاتيح الإملاء دائمًا للاختيار. باستخدام ديكت-أوف-ديكتس ، نختار عمودًا ، ثم الإملاء الداخلي هو لتسمية المخرجات ، تمامًا مثل السلسلة / SeriesGroupBy.

لقد ناقشنا هذا باختصار من قبل (في مكان ما في المناقشة الطويلة حول الإهمال) ، واقترحت شيئًا مشابهًا هنا: https://github.com/pandas-dev/pandas/pull/14668#issuecomment -274508089. ولكن في النهاية تم تنفيذ الإهمال فقط ، وليس الأفكار الخاصة بجعل الوظائف الأخرى لاستخدام الإملاء (وظيفة "إعادة التسمية") أسهل.

تكمن المشكلة في أن الإملاء تم استخدامهما لـ "التحديد" (على أي عمود تريد تطبيق هذه الوظيفة) و "إعادة التسمية" (ما يجب أن يكون اسم العمود الناتج عند تطبيق هذه الوظيفة). يمكن أن تكون البنية البديلة ، بصرف النظر عن الإملاء ، عبارة عن وسيطات للكلمات الرئيسية ، كما تمت مناقشته هنا في اقتراح agg_assign .
ما زلت أؤيد استكشاف هذا الاحتمال ، سواء كان في agg نفسه أو بطريقة جديدة مثل agg_assign .

ما اقترحته في ذلك الوقت كان شيئًا مشابهًا لـ agg_assign ولكن باستخدام dt لكل كلمة رئيسية بدلاً من دالة lambda. مترجم إلى المثال هنا ، سيكون شيئًا مثل:

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})

لست متأكدًا من أن هذا سيكون بالضرورة أكثر قابلية للقراءة أو أسهل في الكتابة كإصدار مع جميع lambdas ، ولكن من المحتمل أن يكون هذا الإصدار أكثر أداءً ، حيث لا يزال بإمكان الباندا استخدام التطبيقات المحسّنة للجمع ، والوسط ، وما إلى ذلك في تلك الأعمدة حيث تفعل ليس لديها وظيفة lambda أو المستخدم المحدد.

سيكون السؤال الكبير مع هذا النهج هو ماذا يعني df.groupby('cat').agg(foo='mean') ؟ سيطبق ذلك منطقيًا "يعني" على جميع الأعمدة نظرًا لأنك لم تحدد أي تحديد (على غرار {'col1' : {'foo': 'mean'}, 'col2': {'foo':'mean'}, 'col3': ...} قبل). ولكن ، قد ينتج عن ذلك أعمدة متعددة الفهرسة ، بينما في المثال أعلاه أعتقد أنه سيكون من الجيد ألا ينتهي الأمر بأعمدة MI.

أعتقد أن ما سبق يمكن أن يتم بشكل عكسي متوافق داخل agg ، لكن السؤال هو ما إذا كان هذا ضروريًا.
أعتقد أيضًا أن هذا سيمتد بشكل جيد إلى حالة series مثل:

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

(ويمكنك حتى التفكير في القيام بما ورد أعلاه مرة واحدة من أجل "المسافة" ومرة ​​واحدة "للطاقة" وتسلسل النتيجة إذا لم تعجبك كل الإملاء / لامدا)

TomAugspurger في agg_table ، ألن يكون من الأفضل التكرار على الوظائف المختلفة التي سيتم تطبيقها ، بدلاً من تكرار المجموعات ، وفي النهاية تسلسل الأعمدة الجديدة حسب المحور = 1 بدلاً من تسلسل الصفوف المشكلة حديثًا حسب المحور = 0؟

راجع للشغل،zertrintdpetrousmcateerpirsquared وغيرها، شكرا جزيلا لطرح هذه القضية وإعطاء هذه التغذية المرتدة تفصيلا. هذه التعليقات والمشاركة المجتمعية مهمة جدًا !

يعجبني حقًا النمط الذي اقترحه @ tdpetrou (باستخدام تطبيق مع وظيفة تقوم بإرجاع سلسلة) - ربما أفضل من

إذا كانت الدالة ترجع pd.Series(data, index=data.keys()) فهل نضمن حصولنا على المؤشرات بالترتيب الصحيح؟ (مجرد التفكير في أفضل السبل لتنفيذ النمط في التعليمات البرمجية الخاصة بي - مع التعرض لخطر الانحراف عن الموضوع).

تحرير: آسف ، لقد أساءت فهم نقطة وسيطة الفهرس (إنها اختيارية هنا ، مطلوبة فقط إذا كنت تريد تحديد ترتيب الأعمدة - إرجاع pd.Series(data) يؤدي المهمة نيابة عني).

هل سيعمل مثال first & last ؟

اضطررت إلى اللجوء إلى الرأس / الذيل مثل هذا

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)

ما زلت أرغب في معالجة هذا الأمر ، لكنني لا أعتقد أنه سيتم إجراؤه لمدة 0.23.

هل يمكن أن يعمل نهج

OP هنا.

بصراحة ، بعد كل هذا الوقت والكثير من النقاش في # 15931 وهنا ، ما زلت غير مقتنع بأن هذه فكرة جيدة لإهمال إعادة تسمية الإملاءات.

في النهاية ، لا يعد أي من البدائل المقترحة هنا أكثر سهولة للمستخدمين من نهج إعادة التسمية الحالي IMHO. عندما كان في الوثائق ، مع مثال واحد فقط ، كان من الواضح كيف يعمل هذا ، وهو مرن للغاية.

بالطبع قد لا يزال مطورو الباندا يفكرون بخلاف ذلك ، فقط يتناغمون مع وجهة نظر المستخدم.

حتى نهج إعادة تسمية الدكت ليس بديهيًا جدًا. في رأيي ، يجب أن يكون بناء الجملة مشابهًا لـ SQL - func(column_name) as new_column_name . في Python ، يمكننا القيام بذلك باستخدام مجموعة مكونة من ثلاثة عناصر. (func, column_name, new_column_name) . هذه هي الطريقة التي يقوم بها dexplo بالتجميع.

dexplo

zertrin ، هل لديك ملاحظات على اقتراحي أعلاه: https://github.com/pandas-dev/pandas/issues/18366/#issuecomment -349089667
في النهاية ، هذا نوع من عكس ترتيب dict: بدلاً من "{col: {name: func}}" سيكون نوعًا من "** {name: {col: func}}"

jorisvandenbossche لقد

وبصراحة أكثر ، بالنظر إلى الخيارات التالية:

  1. رفض السلوك الحالي الذي يعمل بشكل جيد (بضعة أسطر من رمز الإيقاف لإزالة ، وأعد إضافة جزء من التوثيق الذي تمت إزالته)
  2. تنفيذ الاقتراح الخاص بك (التغييرات الهامة التي يجب إجراؤها في الكود ، والمتابعة مع إهمال النهج الحالي ، وضرورة قيام جميع المستخدمين بتكييف الكود الخاص بهم)

لا أفهم لماذا يجب أن نختار 2 إلا إذا كانت تقدم مزايا هادفة وملموسة من منظور المطور والمستخدم.

لمعالجة بعض النقاط في اقتراحك أعلاه:

تكمن المشكلة في أن الإملاء تم استخدامهما لـ "التحديد" (على أي عمود تريد تطبيق هذه الوظيفة) و "إعادة التسمية" (ما يجب أن يكون اسم العمود الناتج عند تطبيق هذه الوظيفة).

نظرًا لأنه تم توثيقه جيدًا من قبل ، لا أعتقد أنه كان يمثل مشكلة للمستخدمين . أنا شخصياً أدركت النقطة التي نظرت فيها على الفور إلى الأمثلة الموجودة في الوثائق. (تحرير: وفكرت: _ "رائع! بناء مفيد جدًا ، يتطابق تمامًا مع ما كنت أبحث عنه. رائع." _)

يمكن أن تكون البنية البديلة ، بصرف النظر عن الإملاء ، عبارة عن وسيطات للكلمات الرئيسية

أحد الأشياء الجذابة لاستخدام نهج ديكت-أوف-ديكت هو أنه يمكن للمستخدمين بسهولة إنشائه ديناميكيًا باستخدام بعض التعليمات البرمجية الأخرى. كما أشرت في التعليق الموجود أعلى هذا التعليق مباشرةً ، فإن الانتقال إلى وسيطات الكلمات الرئيسية كما في اقتراحك لا يزال يسمح بذلك عبر الإنشاء **{name: {col: func}} . لذا فأنا لست ضد اقتراحك. أنا فقط لا أرى القيمة المضافة وضرورة مثل هذه التغييرات عندما نحقق بالفعل نفس المستوى من الوظائف مع النظام المطبق الحالي.

في النهاية ، سيكون اقتراحك "حسنًا" إذا كان لدى مطور الباندا الأساسية شعور قوي ضد النهج الحالي. أنا فقط لا أرى أي فائدة بصفتي مستخدمًا. (في الواقع ، أرى عيب تغيير كل كود المستخدم الحالي لجعله يعمل مرة أخرى مع الاقتراح الجديد).

zertrin ناقشنا هذا بالأمس مع بعض قبل الرد على تعليقك ، لأعكس أفكارنا بالأمس فقط.


لذلك أولًا ، فإن الفكرة القائلة بأن الوظيفة الأساسية مثل SQL "SELECT avg (col2) as col2_avg" يجب أن تعمل وتكون سهلة ، هو شيء نتفق عليه تمامًا ، ونريد حقًا أن يكون لدينا حل لذلك.

وبصرف النظر عن الأسباب الأصلية قررنا استنكر هذه (التي قد تكون أو لا تكون تلك قوي)، و(إهمال) dicts الحالية dicts هي أيضا ليست هذا المثل الأعلى، وهذا يخلق MultiIndex ان كنت فعلا تريد أبدا:

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

في ما ورد أعلاه ، المستوى الأول من MultiIndex غير ضروري ، لأنك قمت بالفعل بإعادة تسمية الأعمدة على وجه التحديد (في المثال في OP ، يتبع هذا أيضًا بشكل مباشر إسقاط المستوى الأول من الأعمدة).
ومع ذلك ، من الصعب تغيير هذا لأنه يمكنك أيضًا القيام بأشياء مثل gr.agg(['sum', 'mean']) أو (مختلط) gr.agg({'B': ['sum', 'mean'], 'C': {'c_mean': 'mean', 'c_count': 'count'}}) كان هناك حاجة إلى MultiIndex ومن المنطقي.

لذلك كان أحد المقترحات التي تم ذكرها في المناقشة أعلاه هو إيجاد طريقة لتحديد أسماء الأعمدة النهائية بشكل منفصل (على سبيل المثال https://github.com/pandas-dev/pandas/issues/18366#issuecomment-346683449).
إضافة على سبيل المثال كلمة رئيسية إضافية إلى aggregate لتحديد أسماء الأعمدة ، مثل

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

سيكون من الممكن.
ومع ذلك ، إذا قمنا بتقسيم مواصفات العمود / الوظيفة وأسماء الأعمدة الجديدة ، فيمكننا أيضًا جعل هذا أكثر عمومية من كلمة رئيسية جديدة ، والقيام بشيء مثل:

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

هذا يحتاج إلى حل https://github.com/pandas-dev/pandas/issues/14829 (وهو شيء نريد القيام به مقابل 0.24.0).
(ملاحظة هامة: لهذا نحن بحاجة إلى إصلاح أسماء مشكلة مكررة من وظائف لامدا، لذلك ينبغي لنا أن نفعل نوعا من إلغاء البيانات المكررة التلقائي للأسماء إذا كنا نريد لدعم هذا الحل).


بعد ذلك ، ما زلنا نحب طريقة وسيطات الكلمات الرئيسية لإعادة التسمية. أسباب ذلك:

  • إنه مشابه لكيفية عمل assign في الباندا ، ويتوافق أيضًا مع كيفية عمل groupby().aggregate() في ibis (وهو مشابه أيضًا لكيفية ظهوره في مثال dplyr في R)
  • يمنحك مباشرةً أسماء الأعمدة غير الهرمية التي تريدها (بدون MultiIndex)
  • بالنسبة للحالات البسيطة (على سبيل المثال أيضًا لحالة السلسلة) ، أعتقد أنه أبسط مثل إملاء الأمر

ما زلنا نناقش قليلاً كيف يمكن أن تبدو. ما اقترحته أعلاه كان (لاستخدام العمود المكافئ / اختيار الوظيفة كما في الأمثلة الأولى):

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

لا يزال بإمكانك إنشاء هذه المواصفات كإملاء لإملاءات ، ولكن مع تبديل المستوى الداخلي والخارجي مقارنة بالإصدار الحالي (المهمل):

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

(يمكن أن يكون لدينا مثال على وظيفة المساعد الذي يحول الإملاءات الحالية للإملاءات إلى هذا الإصدار)

ومع ذلك ، فإن الإملاء هو دائمًا {col: func} ، وتلك العناصر الفردية المتعددة تبدو غريبة بعض الشيء. لذا فإن البديل الذي فكرنا فيه هو استخدام tuples:

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

يبدو هذا أفضل قليلاً ، ولكن من ناحية أخرى ، فإن {'B': 'sum'} deb يتوافق مع واجهات برمجة التطبيقات الأخرى لتحديد العمود الذي سيتم تطبيق الوظيفة عليه.


كلا الاقتراحين أعلاه (إعادة التسمية الأسهل بعد ذلك ، والتسمية القائمة على الكلمات الرئيسية) هما من حيث المبدأ متعامدين ، ولكن قد يكون من الجيد أن يكون لديك كلاهما (أو لا يزال شيئًا آخر بناءً على مزيد من المناقشة)

نشكرك على إعادة توجيه الأفكار الحالية من المطورين هنا 😃

أقر بالعيب (في رأيي ، فقط) في نهج الإملاء المهمل مع مؤشر MultiIndex الناتج. يمكن تسطيحها إذا اجتاز المستخدم خيارًا إضافيًا (نعم YAO: - /).

كما ذكرنا ، أنا لست ضد الإصدار الثاني ، طالما بقي ممكنًا من أجل:

  • إنشاء الأشياء ديناميكيًا بطريقة ما وفك حزمها (بفضل إنشاء **{} ، yay Python!)
  • احتفظ بإعادة التسمية ومواصفات التجميع قريبًا من بعضهما البعض (الحاجة إلى تتبع قائمتين بحيث يظل ترتيبهما كما هو أمر مزعج تمامًا مثل IMHO للمستخدم)
  • استخدام وظائف lambda أو وظائف جزئية دون الحاجة إلى حلول بديلة بسبب (احتمال نقص أو تعارض) أسماء الوظائف.

على هذا النحو ، فإن الاقتراح الأخير (مع إملاء أو مجموعات لتعيين col> func) لا بأس به على ما أعتقد.

يمكن تنفيذ الاقتراح الأول في التعليق السابق إذا كنت تريد ذلك حقًا ، ولكن ملاحظاتي على ذلك هي أنني ، كمستخدم ، لن أختار استخدامه بدلاً من البديل الثاني بسبب ألم إبقاء الأشياء متزامنة بينهما قائمتين.

نوقشت في اجتماع التنمية اليوم.

ملخص قصير

  1. سيحاولjorisvandenbossche تنفيذ gr.agg(b_sum=("B", "sum), ...) ، أي عندما لا يكون هناك arg تم تمريره إلى *GroupBy.agg ، فسر kwargs على أنها <output_name>=(<selection>, <aggfunc>)
  2. بشكل متعامد مع هذه المشكلات ، نود تنفيذ MutliIndex.flatten وتقديم كلمة رئيسية flatten=True إلى .agg

ربما يساعد هذا: الحل البديل الخاص بي للإيقاف هي هذه الوظائف المساعدة التي تحل محل الاسم المستعار-> خرائط aggr بقائمة من الوظائف المسماة بشكل صحيح:

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() 
    }

الذي يعطي السلوك القديم مع:

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
    },
}))

وهو نفس

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'),
    ]
})

هذا يناسبني ، لكن ربما لن ينجح في بعض الحالات الجانبية ...

تحديث : اكتشف أن إعادة التسمية ليست ضرورية ، حيث يتم تفسير المجموعات في مواصفات التجميع على أنها (الاسم المستعار ، aggr). لذلك ليست هناك حاجة إلى وظيفة alias_aggr ، ويصبح التحويل:

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() 
    }

أريد فقط أن أتحدث هنا كمستخدم آخر يفتقد حقًا وظيفة تجميع عمود في أي وظيفة وإعادة تسميته على الفور في نفس الصف. لم أجد نفسي أبدًا أستخدم MultiIndex التي أرجعها الباندا - إما أن أقوم بتسويتها على الفور ، أو أريد في الواقع تحديد أسماء الأعمدة الخاصة بي يدويًا لأنها تعني في الواقع شيئًا محددًا.

سأكون سعيدًا بأي من الأساليب المقترحة هنا: بناء جملة يشبه SQL (أجد نفسي بالفعل أستخدم .query() كثيرًا في الباندا بالفعل) ، والعودة إلى السلوك الذي تم استهلاكه ، وأي من الاقتراحات الأخرى. لقد أثار النهج الحالي بالفعل السخرية من الزملاء الذين يستخدمون R.

لقد وجدت نفسي مؤخرًا أستخدم PySpark بدلاً من الباندا على الرغم من أنه لم يكن ضروريًا ، فقط لأنني أحب بناء الجملة أكثر من ذلك بكثير:

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")
)

كما أن PySpark أكثر تعقيدًا في الكتابة من الباندا في معظم الحالات ، وهذا يبدو أنظف كثيرًا! لذلك أنا أقدر بالتأكيد أن العمل على هذا لا يزال قائمًا :-)

أعتقد أن لدينا صيغة متفق عليها لهذه الوظيفة ؛ نحن بحاجة إلى شخص ما
تنفيذه.

يوم الأربعاء 27 مارس 2019 الساعة 9:01 صباحًا Thomas Kastl [email protected]
كتب:

أريد فقط أن أتجاذب أطراف الحديث هنا بصفتي مستخدمًا آخر بالفعل ، حقًا
فقدان وظيفة تجميع عمود في أي دالة و
إعادة تسميته على الفور في نفس الصف. لم اجد نفسي ابدا
باستخدام MultiIndex التي أرجعها الباندا - إما أن أقوم بتسويتها على الفور ،
أو أرغب في تحديد أسماء الأعمدة يدويًا لأنها
يعني في الواقع شيئًا محددًا.

سأكون سعيدًا بأي من الأساليب المقترحة هنا: بناء جملة يشبه SQL
(أجد نفسي بالفعل أستخدم .query () كثيرًا في حيوانات الباندا بالفعل) ،
العودة إلى السلوك المستهلك ، أي من الاقتراحات الأخرى. ال
لقد أثار النهج الحالي بالفعل السخرية من الزملاء الذين يستخدمون R.

لقد وجدت نفسي مؤخرًا أستخدم PySpark بدلاً من الباندا على الرغم من ذلك
لم يكن ضروريًا ، فقط لأنني أحب بناء الجملة أكثر من ذلك بكثير:

df.groupby ("أيا كان"). agg (F.max ("col1"). alias ("my_max_col") ،
F.avg ("age_col"). alias ("average_age") ،
F.sum ("col2"). alias ("total_yearly_payments"))

كما يعد PySpark أكثر تعقيدًا في الكتابة من الباندا في معظم الحالات ،
هذا يبدو أكثر نظافة! لذلك أنا أقدر بالتأكيد هذا العمل
هذا لا يزال يتم :-)

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/pandas-dev/pandas/issues/18366#issuecomment-477168767 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/ABQHIkCYYsah5siYA4_z0oop_ufIB3h8ks5va3nJgaJpZM4QjSLL
.

سأحاول الوصول إلى هذا مقابل 0.25.0

لقد قمت بوضع العلاقات العامة على https://github.com/pandas-dev/pandas/pull/26399. الفكرة الأساسية هي السماح لهذا المزيج من إعادة التسمية والتجميع الخاص بالأعمدة باستخدام **kwargs مع فهم أن القيم يجب أن تكون tuples (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

هذا له بعض القيود

  • إنه غريب إلى حد ما بالنسبة لبقية حيوانات الباندا. لا يظهر sytanx (output_name=(selection, aggfunc)) أي مكان آخر (على الرغم من أن .assign يستخدم النمط output_name=... )
  • تهجئة أسماء المخرجات التي ليست معرّفات Python قبيحة: .agg(**{'output name': (col, func)})
  • إنها Python 3.6+ فقط ، أو نحتاج إلى بعض الاختراقات القبيحة مقابل 3.5 وما قبلها ، نظرًا لأن ترتيب **kwargs لم يتم الاحتفاظ به مسبقًا
  • يجب أن يكون aggfunc دالة أحادية. إذا احتاج aggfunc المخصص إلى وسيطات إضافية ، فستحتاج إلى تطبيقه جزئيًا أولاً

وهناك تفاصيل تنفيذية ، لم يتم دعم aggfuncs lambda المتعددة لنفس العمود حتى الآن ، على الرغم من أنه يمكن إصلاح ذلك لاحقًا.


أظن أن معظم الأشخاص المشتركين هنا سيكونون داعمين لبعض البدائل للسلوك المهمل. ما رأي الناس في هذا على وجه التحديد؟

ccWillAyd إذا فاتني أي من مخاوفك.

مرحبا TomAugspurger ،

شكرا لجعل هذا التحرك إلى الأمام.

هذا له بعض القيود

  • إنه غريب إلى حد ما بالنسبة لبقية حيوانات الباندا. لا يظهر sytanx (output_name=(selection, aggfunc)) أي مكان آخر (على الرغم من أن .assign يستخدم النمط output_name=... )

لا يسعني إلا أن أشعر أن هذا النوع من الحجة يبدو مشابهًا تمامًا لتلك التي دفعت إلى إهمال التنفيذ الحالي في المقام الأول.

هل يمكنك مشاركة سبب استفادتنا أكثر من هذه الطريقة الجديدة على الطريقة القديمة _ فيما يتعلق بتلك الحجة الخاصة_؟

إحدى الفوائد التي يمكنني التفكير فيها بالفعل هي أنه (بالنسبة لـ py3.6 +) يمكننا تحديد ترتيب الإخراج للأعمدة بشكل فردي.

  • تهجئة أسماء المخرجات التي ليست معرّفات Python قبيحة: .agg(**{'output name': (col, func)})

بطريقة ما ، كانت الطريقة القديمة أفضل في هذا الصدد. ولكن كما قلت سابقًا ، طالما أنه من الممكن استخدام بنية **{...} لبناء التجميع ديناميكيًا ، سأكون سعيدًا بما فيه الكفاية.

  • إنها Python 3.6+ فقط ، أو نحتاج إلى بعض الاختراقات القبيحة مقابل 3.5 وما قبلها ، نظرًا لأن ترتيب **kwargs لم يتم الاحتفاظ به مسبقًا

كيف كان يعمل من قبل (ميزة ديكت-أوف-ديكت الحالية)؟ هل تم ضمان الطلب بطريقة ما؟

  • يجب أن يكون aggfunc دالة أحادية. إذا احتاج aggfunc المخصص إلى وسيطات إضافية ، فستحتاج إلى تطبيقه جزئيًا أولاً

فقط لتأكيد فهمي: يمكن أن يكون aggfunc أي استدعاء يقوم بإرجاع قيمة صحيحة؟ (بالإضافة إلى سلسلة aggfungs "المستخدمة غالبًا" مثل 'min' ، 'max' ، إلخ.). هل هناك فرق مقارنة بالسابق؟ (على سبيل المثال ، ألم يكن القيد الأحادي موجودًا بالفعل؟)

وهناك تفاصيل تنفيذية ، لم يتم دعم aggfuncs lambda المتعددة لنفس العمود حتى الآن ، على الرغم من أنه يمكن إصلاح ذلك لاحقًا.

نعم ، هذا أمر مزعج نوعًا ما ، ولكن طالما أنه مجرد قيد مؤقت وهو مفتوح لإصلاح هذا ، فقد ينجح ذلك.

أظن أن معظم الأشخاص المشتركين هنا سيكونون داعمين لبعض البدائل للسلوك المهمل. ما رأي الناس في هذا على وجه التحديد؟

حسنًا ، على أي حال ، أعتقد أن التجميع وإعادة التسمية في خطوة واحدة أمر مهم حقًا للاحتفاظ به. إذا لم يكن السلوك القديم خيارًا حقًا ، فيمكن لهذا البديل أن يفعل.

هل يمكنك مشاركة سبب استفادتنا أكثر من هذه الطريقة الجديدة على الطريقة القديمة فيما يتعلق بهذه الحجة المحددة.

قد أكون أخطأ في التذكر ، لكنني أعتقد أن لدى SeriesGroupby.agg و DataFrameGroupby.agg معاني مختلفة بين المفتاح الخارجي في القاموس (هل هو اختيار عمود أم تسمية إخراج؟). باستخدام بناء الجملة هذا ، يمكننا أن نحصل باستمرار على الكلمة الأساسية التي تعني اسم الإخراج.

بطريقة ما ، كانت الطريقة القديمة أفضل في هذا الصدد.

هل الفرق هو ** ؟ وإلا أعتقد أن نفس القيود مشتركة.

كيف كان يعمل من قبل (ميزة ديكت-أوف-ديكت الحالية)؟ هل تم ضمان الطلب بطريقة ما؟

فرز المفاتيح ، وهو ما أفعله الآن في العلاقات العامة.

فقط لتأكيد فهمي: يمكن أن يكون aggfunc أي استدعاء يقوم بإرجاع قيمة صحيحة؟

هذا هو الفرق

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

مع الاقتراح البديل ، نحن نحتفظ بـ **kwargs لأسماء أعمدة الإخراج. لذلك ستحتاج إلى functools.partitial(aggfunc, myarg='bar') .

حسنًا ، شكرًا ، أعتقد أن النهج المقترح هو 👍 للتكرار الأول (وسيكون حقًا على ما يرام كبديل بمجرد إزالة قيود تطبيق lambda المتعددة)

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات

القضايا ذات الصلة

andreas-thomik picture andreas-thomik  ·  3تعليقات

matthiasroder picture matthiasroder  ·  3تعليقات

scls19fr picture scls19fr  ·  3تعليقات

hiiwave picture hiiwave  ·  3تعليقات

Ashutosh-Srivastav picture Ashutosh-Srivastav  ·  3تعليقات