समूह वस्तु पर बनाम लागू करें


174

निम्नलिखित डेटाफ़्रेम पर विचार करें:

     A      B         C         D
0  foo    one  0.162003  0.087469
1  bar    one -1.156319 -1.526272
2  foo    two  0.833892 -1.666304
3  bar  three -2.026673 -0.322057
4  foo    two  0.411452 -0.954371
5  bar    two  0.765878 -0.095968
6  foo    one -0.654890  0.678091
7  foo  three -1.789842 -1.130922

निम्नलिखित आदेश काम करते हैं:

> df.groupby('A').apply(lambda x: (x['C'] - x['D']))
> df.groupby('A').apply(lambda x: (x['C'] - x['D']).mean())

लेकिन निम्न में से कोई भी काम नहीं:

> df.groupby('A').transform(lambda x: (x['C'] - x['D']))
ValueError: could not broadcast input array from shape (5) into shape (5,3)

> df.groupby('A').transform(lambda x: (x['C'] - x['D']).mean())
 TypeError: cannot concatenate a non-NDFrame object

क्यों? दस्तावेज़ीकरण पर उदाहरण से लगता है कि transformसमूह में कॉल करने से कोई पंक्ति-वार ऑपरेशन प्रक्रिया कर सकता है:

# Note that the following suggests row-wise operation (x.mean is the column mean)
zscore = lambda x: (x - x.mean()) / x.std()
transformed = ts.groupby(key).transform(zscore)

दूसरे शब्दों में, मैंने सोचा था कि परिवर्तन अनिवार्य रूप से एक विशिष्ट प्रकार का लागू होता है (जो कि समग्र नहीं होता है)। मैं गलत कहाँ हूँ?

संदर्भ के लिए, नीचे मूल डेटाफ़्रेम का निर्माण नीचे दिया गया है:

df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar',
                          'foo', 'bar', 'foo', 'foo'],
                   'B' : ['one', 'one', 'two', 'three',
                         'two', 'two', 'one', 'three'],
                   'C' : randn(8), 'D' : randn(8)})

1
फ़ंक्शन को transformएक संख्या, एक पंक्ति, या तर्क के समान आकार वापस करना होगा। यदि यह एक संख्या है तो समूह में सभी तत्वों के लिए संख्या निर्धारित की जाएगी, यदि यह एक पंक्ति है, तो इसे समूह में सभी पंक्तियों में प्रसारित किया जाएगा। आपके कोड में, लैम्ब्डा फ़ंक्शन एक कॉलम लौटाता है जिसे समूह में प्रसारित नहीं किया जा सकता है।
HY

1
धन्यवाद @ हाय, लेकिन मैं उलझन में हूँ। यदि आप उस दस्तावेज़ के उदाहरण को देखते हैं जिसे मैंने ऊपर (यानी zscore) के साथ कॉपी किया है , तो transformएक लंबो फ़ंक्शन प्राप्त होता है जो मानता है कि प्रत्येक xआइटम के भीतर है group, और समूह में प्रति आइटम मान भी लौटाता है । मैं क्या खो रहा हूँ?
अमेलियो वाज़केज़-रीना

अत्यंत विस्तृत समाधान की तलाश करने वालों के लिए, इसे नीचे देखें
टेड पेट्रोउ

@TedPetrou: tl; उस का ड्रिंक: 1) applyपूरे df में transformगुजरता है , लेकिन प्रत्येक कॉलम को एक श्रृंखला के रूप में व्यक्तिगत रूप से पास करता है। 2) applyकिसी भी आकार के आउटपुट (स्केलर / सीरीज / डेटाफ़्रेम / एरे / लिस्ट ...) transformको वापस कर सकता है , जबकि एक सीक्वेंस (1 डी सीरीज़ / एरे / लिस्ट) को ग्रुप की तरह ही वापस करना होगा। इसलिए ओपी की जरूरत apply()नहीं है transform()। यह एक अच्छा सवाल है क्योंकि डॉक्टर ने दोनों मतभेदों को स्पष्ट रूप से नहीं बताया है। (भेद apply/map/applymap, या अन्य चीजों के बीच अंतर ...)
smci

जवाबों:


146

applyऔर के बीच दो प्रमुख अंतरtransform

transformऔर applyग्रुपबी विधियों के बीच दो प्रमुख अंतर हैं ।

  • इनपुट:
    • applyअंतर्निहित फ़ंक्शन के लिए DataFrame के रूप में प्रत्येक समूह के लिए अंतर्निहित सभी स्तंभों को पास करता है।
    • जबकि transformएक के रूप में व्यक्तिगत रूप से प्रत्येक समूह के लिए प्रत्येक स्तंभ गुजरता सीरीज कस्टम कार्य करने के लिए।
  • आउटपुट:
    • कस्टम फ़ंक्शन applyएक स्केलर, या एक श्रृंखला या डेटाफ़्रेम (या संख्यात्मक सरणी या यहां तक ​​कि सूची) वापस कर सकता है
    • कस्टम फ़ंक्शन को transformएक अनुक्रम (एक आयामी श्रृंखला, सरणी या सूची) को समूह के समान लंबाई वापस करना होगा

तो, transformएक समय में सिर्फ एक सीरीज पर applyकाम करता है और एक ही बार में पूरे डाटाफ्रेम पर काम करता है।

कस्टम फ़ंक्शन का निरीक्षण करना

यह करने के लिए पारित कर दिया अपने कस्टम फ़ंक्शन का इनपुट का निरीक्षण करने के काफ़ी मदद कर सकते हैं applyया transform

उदाहरण

आइए कुछ नमूना डेटा बनाएं और समूहों का निरीक्षण करें ताकि आप देख सकें कि मैं किस बारे में बात कर रहा हूं:

import pandas as pd
import numpy as np
df = pd.DataFrame({'State':['Texas', 'Texas', 'Florida', 'Florida'], 
                   'a':[4,5,1,3], 'b':[6,10,3,11]})

     State  a   b
0    Texas  4   6
1    Texas  5  10
2  Florida  1   3
3  Florida  3  11

आइए एक सरल कस्टम फ़ंक्शन बनाएं जो अंतर्निहित उत्तीर्ण वस्तु के प्रकार को प्रिंट करता है और फिर एक त्रुटि उठाता है ताकि निष्पादन को रोका जा सके।

def inspect(x):
    print(type(x))
    raise

अब इस फंक्शन को ग्रुपबी applyऔर transformमेथड्स दोनों में पास करते हैं, यह देखने के लिए कि इसमें क्या ऑब्जेक्ट पास किया गया है:

df.groupby('State').apply(inspect)

<class 'pandas.core.frame.DataFrame'>
<class 'pandas.core.frame.DataFrame'>
RuntimeError

जैसा कि आप देख सकते हैं, एक DataFrame inspectफ़ंक्शन में पारित किया गया है। आप सोच रहे होंगे कि टाइप, DataFrame, दो बार प्रिंट आउट क्यों हुआ। पंडों ने पहले समूह को दो बार चलाया। यह यह निर्धारित करने के लिए करता है कि गणना करने के लिए एक तेज़ तरीका है या नहीं। यह एक मामूली विवरण है जिसके बारे में आपको चिंता नहीं करनी चाहिए।

अब, एक ही बात के साथ करते हैं transform

df.groupby('State').transform(inspect)
<class 'pandas.core.series.Series'>
<class 'pandas.core.series.Series'>
RuntimeError

यह एक श्रृंखला पारित की जाती है - एक पूरी तरह से अलग पंडों की वस्तु।

तो, transformकेवल एक बार में एक ही श्रृंखला के साथ काम करने की अनुमति है। यह है नहीं असंभव यह एक ही समय में दो कॉलम पर कार्रवाई करने के लिए। इसलिए, यदि हम अपने कस्टम फ़ंक्शन के अंदर aसे कॉलम को आज़माते और घटाते हैं, तो हमें bएक त्रुटि मिलेगी transform। निचे देखो:

def subtract_two(x):
    return x['a'] - x['b']

df.groupby('State').transform(subtract_two)
KeyError: ('a', 'occurred at index a')

हमें एक KeyError मिलती है क्योंकि पांडा श्रृंखला सूचकांक को खोजने का प्रयास कर रहा है aजो मौजूद नहीं है। आप इस ऑपरेशन को पूरा कर सकते हैं applyक्योंकि इसमें पूरा DataFrame है:

df.groupby('State').apply(subtract_two)

State     
Florida  2   -2
         3   -8
Texas    0   -2
         1   -5
dtype: int64

आउटपुट एक श्रृंखला है और मूल सूचकांक के रूप में रखा गया है, लेकिन हम सभी स्तंभों तक पहुंच रखते हैं।


पारित पंडों वस्तु को प्रदर्शित करना

यह कस्टम फ़ंक्शन के भीतर पूरे पांडा ऑब्जेक्ट को प्रदर्शित करने के लिए और भी अधिक मदद कर सकता है, इसलिए आप ठीक से देख सकते हैं कि आप किसके साथ काम कर रहे हैं। आप मॉड्यूल से फ़ंक्शन printका उपयोग करना पसंद करते हैं, ताकि आप डेटा का उपयोग HTML में ज्यूपिटर नोटबुक में अच्छी तरह से आउटपुट प्राप्त कर सकें।displayIPython.display

from IPython.display import display
def subtract_two(x):
    display(x)
    return x['a'] - x['b']

स्क्रीनशॉट: यहां छवि विवरण दर्ज करें


ट्रांसफ़ॉर्म को एकल आयामी अनुक्रम को समूह के समान आकार में वापस करना होगा

दूसरा अंतर यह है कि transformएकल आयामी अनुक्रम को समूह के समान आकार में वापस करना चाहिए। इस विशेष उदाहरण में, प्रत्येक समूह में दो पंक्तियाँ होती हैं, इसलिए transformदो पंक्तियों का क्रम वापस करना चाहिए। यदि ऐसा नहीं होता है तो एक त्रुटि उठाई जाती है:

def return_three(x):
    return np.array([1, 2, 3])

df.groupby('State').transform(return_three)
ValueError: transform must return a scalar value for each group

त्रुटि संदेश वास्तव में समस्या का विवरणात्मक नहीं है। आपको समूह के समान लंबाई वाला अनुक्रम वापस करना होगा। तो, इस तरह एक समारोह काम करेगा:

def rand_group_len(x):
    return np.random.rand(len(x))

df.groupby('State').transform(rand_group_len)

          a         b
0  0.962070  0.151440
1  0.440956  0.782176
2  0.642218  0.483257
3  0.056047  0.238208

एकल स्केलर ऑब्जेक्ट को वापस करना भी काम करता है transform

यदि आप अपने कस्टम फ़ंक्शन से सिर्फ एक स्केलर वापस करते हैं, तो transform समूह में प्रत्येक पंक्ति के लिए इसका उपयोग करेंगे:

def group_sum(x):
    return x.sum()

df.groupby('State').transform(group_sum)

   a   b
0  9  16
1  9  16
2  4  14
3  4  14

3
npपरिभाषित नहीं है। मुझे लगता है कि यदि आप import numpy as npअपने जवाब में शामिल करते हैं तो शुरुआती लोग सराहना करेंगे ।
कास्वेद

187

जैसा कि मैंने .transformऑपरेशन के साथ इसी तरह से उलझन में महसूस किया बनाम .applyमुझे कुछ जवाब मिले, इस मुद्दे पर कुछ प्रकाश डाला। यह उत्तरउदाहरण के लिए बहुत मददगार था।

अब तक मेरा टेकआउट एक दूसरे से अलग-थलग (स्तंभों) के .transformसाथ काम (या सौदा) करेगा । इसका मतलब यह है कि आपके पिछले दो कॉल में:Series

df.groupby('A').transform(lambda x: (x['C'] - x['D']))
df.groupby('A').transform(lambda x: (x['C'] - x['D']).mean())

तुम ने पूछा था .transform दो कॉलमों से मान लेने के लिए और 'यह' वास्तव में दोनों को एक ही समय पर 'देखने' के लिए नहीं है (इसलिए बोलने के लिए)। transformएक-एक करके डेटाफ़्रेम कॉलमों को देखेंगे और बार- len(input_column)बार दोहराए जाने वाले स्केलरों की एक श्रृंखला (या श्रृंखला का समूह) वापस करेंगे ।

तो यह स्केलर, जिसका उपयोग किया जाना चाहिए .transform बनाने के लिए Seriesहै कुछ कमी समारोह का एक परिणाम के एक इनपुट पर लागूSeries (और एक समय में केवल एक श्रृंखला / स्तंभ)।

इस उदाहरण पर विचार करें (अपने डेटाफ़्रेम पर):

zscore = lambda x: (x - x.mean()) / x.std() # Note that it does not reference anything outside of 'x' and for transform 'x' is one column.
df.groupby('A').transform(zscore)

निकलेगा:

       C      D
0  0.989  0.128
1 -0.478  0.489
2  0.889 -0.589
3 -0.671 -1.150
4  0.034 -0.285
5  1.149  0.662
6 -1.404 -0.907
7 -0.509  1.653

जो बिल्कुल वैसा ही है जैसे कि आप इसे एक समय में केवल एक कॉलम पर उपयोग करेंगे:

df.groupby('A')['C'].transform(zscore)

उपज:

0    0.989
1   -0.478
2    0.889
3   -0.671
4    0.034
5    1.149
6   -1.404
7   -0.509

ध्यान दें कि .applyअंतिम उदाहरण में ( df.groupby('A')['C'].apply(zscore)) ठीक उसी तरह से काम करेगा, लेकिन यदि आप डेटाफ्रेम पर इसका उपयोग करने की कोशिश करते हैं तो यह विफल हो जाएगा:

df.groupby('A').apply(zscore)

त्रुटि देता है:

ValueError: operands could not be broadcast together with shapes (6,) (2,)

तो और कहाँ .transformउपयोगी है? सबसे सरल मामला मूल डेटाफ़्रेम में कमी फ़ंक्शन के परिणामों को असाइन करने का प्रयास कर रहा है।

df['sum_C'] = df.groupby('A')['C'].transform(sum)
df.sort('A') # to clearly see the scalar ('sum') applies to the whole column of the group

उपज:

     A      B      C      D  sum_C
1  bar    one  1.998  0.593  3.973
3  bar  three  1.287 -0.639  3.973
5  bar    two  0.687 -1.027  3.973
4  foo    two  0.205  1.274  4.373
2  foo    two  0.128  0.924  4.373
6  foo    one  2.113 -0.516  4.373
7  foo  three  0.657 -1.179  4.373
0  foo    one  1.270  0.201  4.373

के साथ एक ही कोशिश कर रहा .applyदेना होगा NaNsमें sum_C। क्योंकि .applyएक कम हो जाएगा Series, जो यह नहीं जानता कि कैसे वापस प्रसारित किया जाए:

df.groupby('A')['C'].apply(sum)

दे रही है:

A
bar    3.973
foo    4.373

ऐसे मामले भी हैं जब .transformडेटा को फ़िल्टर करने के लिए उपयोग किया जाता है:

df[df.groupby(['B'])['D'].transform(sum) < -1]

     A      B      C      D
3  bar  three  1.287 -0.639
7  foo  three  0.657 -1.179

मुझे उम्मीद है कि यह थोड़ा और स्पष्टता जोड़ता है।


4
हे भगवान। अंतर इतना सूक्ष्म है।
दावेई

3
.transform()लापता मूल्यों को भरने के लिए भी इस्तेमाल किया जा सकता है। खासकर यदि आप समूह का मतलब या समूह सांख्यिकीय को NaNउस समूह के मूल्यों में प्रसारित करना चाहते हैं । दुर्भाग्य से, पांडा प्रलेखन मेरे लिए भी उपयोगी नहीं था।
साइबर-गणित

मैं आखिरी मामले में सोचता हूं, .groupby().filter()वही काम करता है। आपकी व्याख्या के लिए धन्यवाद, .apply()और .transform()मुझे बहुत भ्रमित करता है।
जियाजियांग

यह बताता है कि df.groupby().transform()उप समूह df के लिए काम क्यों नहीं किया जा सकता है, मुझे हमेशा त्रुटि मिलती है ValueError: transform must return a scalar value for each groupक्योंकि transformस्तंभों को एक-एक करके देखता है
jerrytim

मुझे डेटा को फ़िल्टर करने के लिए उपयोग किया जाने वाला अंतिम उदाहरण .transform वास्तव में पसंद आया। श्रेष्ठ सुंदर!
ऋषि जैन

13

मैं अंतर बताने के लिए बहुत ही सरल स्निपेट का उपयोग करने जा रहा हूं:

test = pd.DataFrame({'id':[1,2,3,1,2,3,1,2,3], 'price':[1,2,3,2,3,1,3,1,2]})
grouping = test.groupby('id')['price']

DataFrame इस तरह दिखता है:

    id  price   
0   1   1   
1   2   2   
2   3   3   
3   1   2   
4   2   3   
5   3   1   
6   1   3   
7   2   1   
8   3   2   

इस तालिका में 3 ग्राहक आईडी हैं, प्रत्येक ग्राहक ने तीन लेनदेन किए और हर बार 1,2,3 डॉलर का भुगतान किया।

अब, मैं प्रत्येक ग्राहक द्वारा किए गए न्यूनतम भुगतान का पता लगाना चाहता हूं। इसे करने के दो तरीके हैं:

  1. का उपयोग करते हुए apply :

    grouping.min ()

रिटर्न इस तरह दिखता है:

id
1    1
2    1
3    1
Name: price, dtype: int64

pandas.core.series.Series # return type
Int64Index([1, 2, 3], dtype='int64', name='id') #The returned Series' index
# lenght is 3
  1. का उपयोग करते हुए transform :

    grouping.transform (न्यूनतम)

रिटर्न इस तरह दिखता है:

0    1
1    1
2    1
3    1
4    1
5    1
6    1
7    1
8    1
Name: price, dtype: int64

pandas.core.series.Series # return type
RangeIndex(start=0, stop=9, step=1) # The returned Series' index
# length is 9    

दोनों विधियाँ एक Seriesवस्तु को लौटाती हैं, लेकिन lengthपहले वाले की संख्या 3 है औरlength अंक दूसरे का 9 है।

यदि आप जवाब देना चाहते हैं What is the minimum price paid by each customer, तो applyविधि चुनने के लिए अधिक उपयुक्त है।

यदि आप उत्तर देना चाहते हैं What is the difference between the amount paid for each transaction vs the minimum payment, तो आप उपयोग करना चाहते हैं transform, क्योंकि:

test['minimum'] = grouping.transform(min) # ceates an extra column filled with minimum payment
test.price - test.minimum # returns the difference for each row

Apply यहाँ केवल इसलिए काम नहीं करता क्योंकि यह आकार 3 की एक श्रृंखला देता है, लेकिन मूल df की लंबाई 9. है। आप इसे आसानी से मूल df में वापस एकीकृत नहीं कर सकते।


3
मुझे लगता है कि यह एक महान जवाब है! प्रश्न पूछे जाने के चार साल से अधिक समय बाद उत्तर देने के लिए धन्यवाद!
बेंजामिन डबरू

4
tmp = df.groupby(['A'])['c'].transform('mean')

के समान ही

tmp1 = df.groupby(['A']).agg({'c':'mean'})
tmp = df['A'].map(tmp1['c'])

या

tmp1 = df.groupby(['A'])['c'].mean()
tmp = df['A'].map(tmp1)
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.