কোনও গ্রুপ অবজেক্টে বনাম রূপান্তর প্রয়োগ করুন


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অবশ্যই একটি সংখ্যা, একটি সারি বা আর্গুমেন্টের মতো একই আকারটি ফেরত দিতে হবে। যদি এটি একটি সংখ্যা হয় তবে সংখ্যাটি গ্রুপের সমস্ত উপাদানকে সেট করা হবে, যদি এটি একটি সারি হয় তবে এটি গোষ্ঠীর সমস্ত সারিতে সম্প্রচারিত হবে। আপনার কোডে ল্যাম্বদা ফাংশনটি একটি কলাম দেয় যা গোষ্ঠীতে সম্প্রচারিত হতে পারে না।
HYRY

1
ধন্যবাদ @ হাইওয়াই, তবে আমি বিভ্রান্ত যদি আপনি ডকুমেন্টেশনের উদাহরণটি দেখুন যা আমি উপরে কপি করেছি (অর্থাত্ zscore) সাথে, transformএকটি ল্যাম্বডা ফাংশন গ্রহণ করে যা অনুমান করে যে প্রত্যেকটি xএকটি আইটেমের মধ্যে রয়েছে এবং গ্রুপে প্রতিটি আইটেমওgroup প্রত্যাবর্তন করে। আমি কী মিস করছি?
অ্যামিলিও ওয়াজকেজ-রেইনা

যারা অত্যন্ত বিশদ সমাধানের সন্ধান করছেন তাদের জন্য এটি নীচে দেখুন
টেড পেট্রো

@ টেডপেট্রো: টিএল; ডাঃ এর মধ্যে: 1) applyপুরো ডিএফ এ transformযায় তবে প্রতিটি কলাম পৃথকভাবে সিরিজ হিসাবে পাস করে। 2) applyযে কোনও আকারের আউটপুট (স্কেলার / সিরিজ / ডেটা ফ্রেম / অ্যারে / তালিকা ...) transformফিরিয়ে দিতে পারে , তবে অবশ্যই গ্রুপের সমান দৈর্ঘ্যের একটি সিকোয়েন্স (1 ডি সিরিজ / অ্যারে / তালিকা) প্রদান করতে হবে। এজন্য ওপি দরকার apply()নেই transform()। এটি একটি ভাল প্রশ্ন যেহেতু ডক উভয় পার্থক্য পরিষ্কারভাবে ব্যাখ্যা করেন নি। ( apply/map/applymapবা অন্যান্য জিনিসের মধ্যে পার্থক্যের অনুরূপ ...)
স্মি ডিসি

উত্তর:


146

applyএবং মধ্যে দুটি প্রধান পার্থক্যtransform

গ্রুপ transformএবং applyপদ্ধতিগুলির মধ্যে দুটি প্রধান পার্থক্য রয়েছে ।

  • ইনপুট:
    • applyস্পষ্টভাবে প্রতিটি গ্রুপের জন্য সমস্ত কলাম কাস্টম ফাংশনে ডেটা ফ্রেম হিসাবে পাস করে।
    • যখন 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

আপনি দেখতে পাচ্ছেন, একটি ডেটাফ্রেম inspectফাংশনে প্রবেশ করা হয়েছে । আপনি হয়ত ভাবছেন যে ডেটা ফ্রেম টাইপটি কেন দু'বার মুদ্রিত হয়েছিল? পান্ডস দু'বার প্রথম গ্রুপ চালান। এটি গণনাটি সম্পূর্ণ করার দ্রুত উপায় আছে কিনা তা নির্ধারণ করতে এটি করে। এটি একটি ছোটখাটো বিশদ যা আপনার উচিত হবে না।

এখন, এর সাথে একই জিনিস করা যাক 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')

পান্ডারা সিরিজ সূচকটি খুঁজে পাওয়ার চেষ্টা করছে aযা বিদ্যমান নেই We applyপুরো ডেটা ফ্রেম যেমন রয়েছে তেমনি আপনি এই ক্রিয়াকলাপটি সম্পূর্ণ করতে পারবেন :

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

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

আউটপুটটি একটি সিরিজ এবং মূল সূচকটি রাখা হিসাবে কিছুটা বিভ্রান্তিকর, তবে আমাদের কাছে সমস্ত কলামে অ্যাক্সেস রয়েছে।


পাস হওয়া পান্ডাস অবজেক্টটি প্রদর্শন করা হচ্ছে

এটি কাস্টম ফাংশনটির মধ্যে পুরো পান্ডাস অবজেক্টটি প্রদর্শন করতে আরও অনেক বেশি সহায়তা করতে পারে, তাই আপনি কীটি পরিচালনা করছেন তা আপনি দেখতে পারবেন। আপনি printবিবৃতি ব্যবহার করতে পারেন আমি মডিউল displayথেকে ফাংশনটি ব্যবহার করতে চাই IPython.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()আমাকে অনেক গুলিয়ে ফেলেছে।
Jiaxiang

এটি ব্যাখ্যা করে যে কেন df.groupby().transform()একটি উপ গ্রুপ ডিএফের জন্য কাজ করতে পারে না, আমি সর্বদা ত্রুটি পাই ValueError: transform must return a scalar value for each groupকারণ transformকলামগুলি একে একে দেখে
জেরিটিম

আমি শেষ উদাহরণটি সত্যিই পছন্দ করেছি। ডেটা ফিল্টার করতে ট্রান্সফর্ম ব্যবহার করি। অনেক সুন্দর!
iষি জেইন

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

ডেটাফ্রেমটি দেখতে এমন দেখাচ্ছে:

    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 আকারের একটি সিরিজ দেয় তবে মূল ডিএফের দৈর্ঘ্য 9 হয় it আপনি এটি সহজেই মূল ডিএফ-তে ফিরে সংহত করতে পারবেন না।


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)
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.