पंडों DataFrame कई स्तंभों का उपयोग कर कुल कार्य


80

क्या एग्रीगेशन फंक्शन लिखने का एक DataFrame.aggतरीका है, जैसा कि मेथड में इस्तेमाल किया जाता है , जिसमें एग्रीगेट किए जा रहे डेटा के एक से अधिक कॉलम तक पहुंच होगी? विशिष्ट उपयोग के मामलों में भारित औसत, भारित मानक विचलन फंक होंगे।

मैं चाहूंगा कि मैं कुछ लिख सकूं

def wAvg(c, w):
    return ((c * w).sum() / w.sum())

df = DataFrame(....) # df has columns c and w, i want weighted average
                     # of c using w as weight.
df.aggregate ({"c": wAvg}) # and somehow tell it to use w column as weights ...

इस विशिष्ट SO प्रश्न को संबोधित करने वाला अच्छा लेख: pbpython.com/weighted-aiture.html
ptim

जवाबों:


104

हाँ; .apply(...)फ़ंक्शन का उपयोग करें , जिसे प्रत्येक उप- पर बुलाया जाएगा DataFrame। उदाहरण के लिए:

grouped = df.groupby(keys)

def wavg(group):
    d = group['data']
    w = group['weights']
    return (d * w).sum() / w.sum()

grouped.apply(wavg)

इसे कुछ परिचालनों में तोड़ने के लिए अधिक कुशल हो सकता है: (1) वजन का एक स्तंभ बनाएं, (2) उनके भार द्वारा टिप्पणियों को सामान्य करें, (3) भारित टिप्पणियों के समूहबद्ध योग और वजन के एक समूह योग की गणना करें। , (4) वज़न के योग से भारित योग का सामान्यीकरण करें।
कालू

4
क्या होगा यदि हम wavg की गणना कई चर (कॉलम), जैसे df ['वेट'] के अलावा सब कुछ करें?
CPBL

2
@, क्या ऐसा कोई तरीका है जिससे एक बार ऐसा किया जा सके agg()और एक lambdaबिल्ट इन np.average(...weights=...), या भारित साधनों के लिए पांडा में कोई नया मूल समर्थन प्राप्त हो, क्योंकि यह पोस्ट पहली बार सामने आया?
sparc_spread 20

4
@ मैककेनी: अपनी पुस्तक में आप इस दृष्टिकोण का सुझाव देते हैं get_wavg = lambda g: np.average(g['data'], weights = g['weights']):; grouped.apply(wavg) दो विनिमेय हैं?
16:03 बजे लूट

9

मेरा समाधान नथानिएल के समाधान के समान है, केवल यह एक कॉलम के लिए है और मैं हर बार पूरे डेटा फ्रेम को डीप-कॉपी नहीं करता हूं, जो निषेधात्मक रूप से धीमा हो सकता है। समाधान ग्रुपबी (...) पर प्रदर्शन लाभ। लागू करें (...) लगभग 100x (!) है।

def weighted_average(df, data_col, weight_col, by_col):
    df['_data_times_weight'] = df[data_col] * df[weight_col]
    df['_weight_where_notnull'] = df[weight_col] * pd.notnull(df[data_col])
    g = df.groupby(by_col)
    result = g['_data_times_weight'].sum() / g['_weight_where_notnull'].sum()
    del df['_data_times_weight'], df['_weight_where_notnull']
    return result

यदि आप PEP8 का लगातार उपयोग करते हैं और सतही delलाइन को हटाते हैं तो अधिक पठनीय होगा ।
मर्सोज

धन्यवाद! delलाइन वास्तव में ज़रूरत से ज़्यादा नहीं है, के बाद से मैं यथा-स्थान इनपुट DataFrame बदलने के प्रदर्शन में सुधार करने के लिए, तो मैं साफ करने के लिए किया है।
अर्नेस्टस्क्राइब्लर

लेकिन आप परिणाम को अगली पंक्ति में वापस करते हैं जो फ़ंक्शन को समाप्त करता है। एक बार फ़ंक्शन समाप्त हो जाने के बाद, सभी आंतरिक ऑब्जेक्ट को वैसे भी शुद्ध किया जाता है।
MERose

1
लेकिन ध्यान दें कि df कोई आंतरिक वस्तु नहीं है। यह फ़ंक्शन के लिए एक तर्क है, और जब तक आप इसे कभी नहीं असाइन करते हैं ( df = something) यह एक उथली प्रतिलिपि बना रहता है और इन-प्लेस में बदल जाता है। इस स्थिति में, DataFrame में कॉलम जोड़े जाएंगे। इस फ़ंक्शन को कॉपी-पेस्ट करने की कोशिश करें और इसे delलाइन के बिना चलाएं, और देखें कि यह दिए गए DataFrame को कॉलम जोड़कर बदलता है।
अर्नेस्टस्क्राइब्लर

यह सवाल का जवाब नहीं देता है, क्योंकि भारित औसत कई कॉलमों पर किसी भी कुल के लिए एक उदाहरण के रूप में कार्य करता है।
user__42

8

किसी भी समूह की वस्तु से किसी भी संख्या में कुल मूल्यों को वापस करना संभव है apply। बस, एक श्रृंखला लौटाएं और सूचकांक मान नए कॉलम नाम बन जाएंगे।

आइए एक त्वरित उदाहरण देखें:

df = pd.DataFrame({'group':['a','a','b','b'],
                   'd1':[5,10,100,30],
                   'd2':[7,1,3,20],
                   'weights':[.2,.8, .4, .6]},
                 columns=['group', 'd1', 'd2', 'weights'])
df

  group   d1  d2  weights
0     a    5   7      0.2
1     a   10   1      0.8
2     b  100   3      0.4
3     b   30  20      0.6

एक कस्टम फ़ंक्शन परिभाषित करें जिसे पास किया जाएगा apply। यह स्पष्ट रूप से एक DataFrame को स्वीकार करता है - मतलब dataपैरामीटर एक DataFrame है। ध्यान दें कि यह कई स्तंभों का उपयोग कैसे करता है, जो समूह aggविधि से संभव नहीं है :

def weighted_average(data):
    d = {}
    d['d1_wa'] = np.average(data['d1'], weights=data['weights'])
    d['d2_wa'] = np.average(data['d2'], weights=data['weights'])
    return pd.Series(d)

applyहमारे कस्टम फ़ंक्शन के साथ ग्रुपबी विधि को कॉल करें :

df.groupby('group').apply(weighted_average)

       d1_wa  d2_wa
group              
a        9.0    2.2
b       58.0   13.2

आप अन्य जवाबों में बताए अनुसार नए डेटाफ्रेम कॉलम में भारित योगों को पहले से निर्धारित करके बेहतर प्रदर्शन प्राप्त कर सकते applyहैं।


4

निम्नलिखित (वेस मैककिनी के उत्तर पर आधारित) वास्तव में वही है जो मैं देख रहा था। मुझे यह जानकर खुशी होगी कि अगर ऐसा करने का एक सरल तरीका है pandas

def wavg_func(datacol, weightscol):
    def wavg(group):
        dd = group[datacol]
        ww = group[weightscol] * 1.0
        return (dd * ww).sum() / ww.sum()
    return wavg


def df_wavg(df, groupbycol, weightscol):
    grouped = df.groupby(groupbycol)
    df_ret = grouped.agg({weightscol:sum})
    datacols = [cc for cc in df.columns if cc not in [groupbycol, weightscol]]
    for dcol in datacols:
        try:
            wavg_f = wavg_func(dcol, weightscol)
            df_ret[dcol] = grouped.apply(wavg_f)
        except TypeError:  # handle non-numeric columns
            df_ret[dcol] = grouped.agg({dcol:min})
    return df_ret

फ़ंक्शन df_wavg()एक डेटाफ़्रेम लौटाता है जिसे "ग्रुपबी" कॉलम द्वारा समूहीकृत किया जाता है, और जो वज़न कॉलम के लिए भार का योग देता है। अन्य कॉलम या तो भारित औसत हैं या, यदि गैर-संख्यात्मक हैं, तो min()फ़ंक्शन का उपयोग एकत्रीकरण के लिए किया जाता है।


4

मैं यह बहुत कुछ करता हूं और निम्नलिखित काफी उपयोगी पाया गया है:

def weighed_average(grp):
    return grp._get_numeric_data().multiply(grp['COUNT'], axis=0).sum()/grp['COUNT'].sum()
df.groupby('SOME_COL').apply(weighed_average)

यह सभी संख्यात्मक स्तंभों के भारित औसत की गणना करेगा dfऔर गैर-संख्यात्मक लोगों को छोड़ देगा।


यह तेजी से धधक रहा है! अच्छा काम!
शाय बेन-सासन

यदि आपके पास कई कॉलम हैं, तो यह वास्तव में मीठा है। अच्छा लगा!
क्रिस

@santon, उत्तर के लिए धन्यवाद। क्या आप अपने समाधान का एक उदाहरण दे सकते हैं? मुझे आपके समाधान का उपयोग करने का प्रयास करते समय एक त्रुटि 'KeyError:' COUNT 'मिली।
एलन

@ सभी को कॉलम के नाम का उपयोग करना चाहिए जो कि आपके पास भारित औसत के लिए उपयोग करने के लिए गिना जाता है।
सैनटन

4

इसके माध्यम से पूरा groupby(...).apply(...)करना गैर-निष्पादित है। यहाँ एक समाधान है जो मैं हर समय उपयोग करता हूं (अनिवार्य रूप से कुल्लू के तर्क का उपयोग करके)।

def grouped_weighted_average(self, values, weights, *groupby_args, **groupby_kwargs):
   """
    :param values: column(s) to take the average of
    :param weights_col: column to weight on
    :param group_args: args to pass into groupby (e.g. the level you want to group on)
    :param group_kwargs: kwargs to pass into groupby
    :return: pandas.Series or pandas.DataFrame
    """

    if isinstance(values, str):
        values = [values]

    ss = []
    for value_col in values:
        df = self.copy()
        prod_name = 'prod_{v}_{w}'.format(v=value_col, w=weights)
        weights_name = 'weights_{w}'.format(w=weights)

        df[prod_name] = df[value_col] * df[weights]
        df[weights_name] = df[weights].where(~df[prod_name].isnull())
        df = df.groupby(*groupby_args, **groupby_kwargs).sum()
        s = df[prod_name] / df[weights_name]
        s.name = value_col
        ss.append(s)
    df = pd.concat(ss, axis=1) if len(ss) > 1 else ss[0]
    return df

pandas.DataFrame.grouped_weighted_average = grouped_weighted_average

1
जब आप गैर-प्रदर्शनकारी कहते हैं। अंतर कितना है? इसे मापा है?
बाउंसर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.