apply
, আপনার সুবিধার্থী সুবিধার প্রয়োজন নেই
আমরা ওপি-তে প্রশ্নগুলি একের পর এক সম্বোধন করে শুরু করি।
"যদি apply
খুব খারাপ হয় তবে এটি কেন এপিআইতে আছে?"
DataFrame.apply
এবং Series.apply
হয় সুবিধার ফাংশন DataFrame এবং সিরিজ সংজ্ঞাসমূহ যথাক্রমে অবজেক্ট। apply
কোনও ডেটাফ্রেমে ট্রান্সফর্মেশন / সমষ্টি প্রযোজ্য যে কোনও ব্যবহারকারীর সংজ্ঞায়িত ফাংশন গ্রহণ করে। apply
কার্যকরভাবে একটি সিলভার বুলেট যা কোনও বিদ্যমান পান্ডাস ফাংশন যা করতে পারে না তা করে।
কিছু কাজ apply
করতে পারে:
- কোনও ডেটাফ্রেম বা সিরিজে কোনও ব্যবহারকারী-সংজ্ঞায়িত ফাংশন চালান
- কোনও ডেটা ফ্রেমে সারি-ভিত্তিক (
axis=1
) বা কলাম-ভিত্তিক ( axis=0
) ফাংশন প্রয়োগ করুন
- ফাংশন প্রয়োগ করার সময় সূচী প্রান্তিককরণ সম্পাদন করুন
- ব্যবহারকারী-সংজ্ঞায়িত ফাংশনগুলির সাথে সমষ্টি সম্পাদন করুন (তবে আমরা সাধারণত পছন্দ করি
agg
বা transform
এই ক্ষেত্রে)
- উপাদান অনুসারে রূপান্তরগুলি সম্পাদন করুন
- মূল সারিগুলিতে একত্রিত ফলাফলগুলি সম্প্রচার করুন (
result_type
যুক্তিটি দেখুন)।
- ব্যবহারকারী-সংজ্ঞায়িত ফাংশনগুলিতে স্থান দিতে অবস্থানগত / কীওয়ার্ড আর্গুমেন্ট গ্রহণ করুন।
...অন্যদের মধ্যে. আরও তথ্যের জন্য ডকুমেন্টেশনে সারি বা কলাম ভিত্তিক ফাংশন অ্যাপ্লিকেশনটি দেখুন ।
সুতরাং, এই সমস্ত বৈশিষ্ট্য সহ, apply
খারাপ কেন ? এটা কারণ apply
হয় ধীর । পান্ডস আপনার ফাংশনটির প্রকৃতি সম্পর্কে কোনও অনুমান করে না এবং তাই পুনরাবৃত্তভাবে আপনার ক্রিয়াকে প্রতিটি সারি / কলামে প্রয়োজনীয় হিসাবে প্রয়োগ করে । অতিরিক্তভাবে, উপরের সমস্ত পরিস্থিতি পরিচালনা করার অর্থ apply
প্রতিটি পুনরাবৃত্তিতে কিছু বড় ওভারহেড অন্তর্ভুক্ত। আরও apply
অনেক বেশি মেমরি গ্রহণ করে যা মেমোরি সীমাবদ্ধ অ্যাপ্লিকেশনগুলির জন্য একটি চ্যালেঞ্জ।
খুব কম পরিস্থিতি রয়েছে যেখানে apply
ব্যবহারের জন্য উপযুক্ত (নীচের দিকের আরও কিছু)। আপনার ব্যবহার করা উচিত কিনা তা আপনি যদি নিশ্চিত না হন তবে apply
আপনার সম্ভবত এটি করা উচিত নয়।
আসুন পরবর্তী প্রশ্নে সম্বোধন করা যাক।
"কখন এবং কখন আমার কোড- apply
ফ্রি করা উচিত ?"
পুনঃব্যবহারের জন্য, এখানে কয়েকটি সাধারণ পরিস্থিতি রয়েছে যেখানে আপনি যে কোনও কল থেকে মুক্তি পেতে চাইবেন apply
।
সংখ্যার ডেটা
আপনি যদি অঙ্কের ডেটা নিয়ে কাজ করছেন তবে ইতিমধ্যে সম্ভবত একটি ভেক্টরাইজড সিথন ফাংশন রয়েছে যা আপনি যা করতে চেষ্টা করছেন ঠিক তা করে (যদি না হয় তবে দয়া করে স্ট্যাক ওভারফ্লোতে কোনও প্রশ্ন জিজ্ঞাসা করুন বা গিটহাবের উপর একটি বৈশিষ্ট্য অনুরোধ খুলুন)।
apply
একটি সাধারণ সংযোজন ক্রিয়াকলাপের পারফরম্যান্সের বিপরীতে ।
df = pd.DataFrame({"A": [9, 4, 2, 1], "B": [12, 7, 5, 4]})
df
A B
0 9 12
1 4 7
2 2 5
3 1 4
<! - ->
df.apply(np.sum)
A 16
B 28
dtype: int64
df.sum()
A 16
B 28
dtype: int64
পারফরম্যান্স অনুসারে, এর তুলনা নেই, সাইথোনাইজড সমতুল্য অনেক দ্রুত। কোনও গ্রাফের দরকার নেই কারণ খেলনা ডেটার জন্যও পার্থক্য সুস্পষ্ট।
%timeit df.apply(np.sum)
%timeit df.sum()
2.22 ms ± 41.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
471 µs ± 8.16 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
এমনকি যদি আপনি raw
তর্ক দিয়ে কাঁচা অ্যারে পাস করা সক্ষম করে থাকেন তবে এটি এখনও দ্বিগুণ হয়ে যায়।
%timeit df.apply(np.sum, raw=True)
840 µs ± 691 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
আরেকটি উদাহরণ:
df.apply(lambda x: x.max() - x.min())
A 8
B 8
dtype: int64
df.max() - df.min()
A 8
B 8
dtype: int64
%timeit df.apply(lambda x: x.max() - x.min())
%timeit df.max() - df.min()
2.43 ms ± 450 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
1.23 ms ± 14.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
সাধারণভাবে, সম্ভব হলে ভেক্টরাইজড বিকল্পগুলি সন্ধান করুন।
স্ট্রিং / রেজেক্স
পান্ডাস বেশিরভাগ পরিস্থিতিতে "ভেক্টরাইজড" স্ট্রিং ফাংশন সরবরাহ করে তবে এমন বিরল ঘটনা রয়েছে যেখানে এই ফাংশনগুলি ... "প্রয়োগ" হয় না, তাই কথা বলতে হবে।
একটি সাধারণ সমস্যা হ'ল একই সারির অন্য কলামে কলামের মান উপস্থিত আছে কিনা তা পরীক্ষা করা।
df = pd.DataFrame({
'Name': ['mickey', 'donald', 'minnie'],
'Title': ['wonderland', "welcome to donald's castle", 'Minnie mouse clubhouse'],
'Value': [20, 10, 86]})
df
Name Value Title
0 mickey 20 wonderland
1 donald 10 welcome to donald's castle
2 minnie 86 Minnie mouse clubhouse
এটি "ডোনাল্ড" এবং "মিনি" তাদের নিজ নিজ "শিরোনাম" কলামগুলিতে উপস্থিত হওয়ায় এটি দ্বিতীয় এবং তৃতীয় সারিতে ফিরে আসা উচিত।
প্রয়োগ ব্যবহার করে, এটি ব্যবহার করে করা হবে
df.apply(lambda x: x['Name'].lower() in x['Title'].lower(), axis=1)
0 False
1 True
2 True
dtype: bool
df[df.apply(lambda x: x['Name'].lower() in x['Title'].lower(), axis=1)]
Name Title Value
1 donald welcome to donald's castle 10
2 minnie Minnie mouse clubhouse 86
তবে তালিকা বোঝার সাহায্যে আরও ভাল সমাধান বিদ্যমান a
df[[y.lower() in x.lower() for x, y in zip(df['Title'], df['Name'])]]
Name Title Value
1 donald welcome to donald's castle 10
2 minnie Minnie mouse clubhouse 86
<! - ->
%timeit df[df.apply(lambda x: x['Name'].lower() in x['Title'].lower(), axis=1)]
%timeit df[[y.lower() in x.lower() for x, y in zip(df['Title'], df['Name'])]]
2.85 ms ± 38.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
788 µs ± 16.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
এখানে লক্ষ্য করার বিষয়টি হ'ল পুনরাবৃত্ত রুটিনগুলি দ্রুত apply
ওভারহেডের কারণে দ্রুত হয় । আপনার যদি NaNs এবং অবৈধ ডাইপগুলি হ্যান্ডেল করার প্রয়োজন হয় তবে আপনি কাস্টম ফাংশনটি ব্যবহার করে এটি তৈরি করতে পারেন তারপরে তালিকার বোধগমের ভিতরে যুক্তি দিয়ে কল করতে পারেন।
তালিকা বোধগম্যকরণকে কখন একটি ভাল বিকল্প হিসাবে বিবেচনা করা উচিত সে সম্পর্কে আরও তথ্যের জন্য, আমার লেখার ব্যবস্থাটি দেখুন: পান্ডাসে লুপগুলি কি আসলেই খারাপ? আমার কখন যত্ন করা উচিত? ।
দ্রষ্টব্য
তারিখ এবং তারিখের সময় অপারেশনগুলিতেও ভেক্টরাইজড সংস্করণ রয়েছে। সুতরাং, উদাহরণস্বরূপ, আপনার পছন্দ করা উচিত pd.to_datetime(df['date'])
, ওভার, বলুন df['date'].apply(pd.to_datetime)
,।
এ আরও পড়ুন
ডক্স ।
একটি সাধারণ ক্ষতি: তালিকার বিস্ফোরক কলাম
s = pd.Series([[1, 2]] * 3)
s
0 [1, 2]
1 [1, 2]
2 [1, 2]
dtype: object
মানুষ ব্যবহার করতে প্রলুব্ধ হয় apply(pd.Series)
। পারফরম্যান্সের ক্ষেত্রে এটি ভয়াবহ ।
s.apply(pd.Series)
0 1
0 1 2
1 1 2
2 1 2
একটি ভাল বিকল্প হ'ল কলামটি অনুকূল করা এবং এটি পিডি.ডাটাফ্রেমে পাস করা।
pd.DataFrame(s.tolist())
0 1
0 1 2
1 1 2
2 1 2
<! - ->
%timeit s.apply(pd.Series)
%timeit pd.DataFrame(s.tolist())
2.65 ms ± 294 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
816 µs ± 40.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
শেষ অবধি,
"এমন কোনও পরিস্থিতি আছে যেখানে apply
ভাল?"
প্রয়োগ একটি সুবিধার ফাংশন, তাই হয় পরিস্থিতিতে যেখানে ওভারহেড ক্ষমা করার তুচ্ছ যথেষ্ট। এটি কার্যত কতবার ফাংশন ডাকা হয় তার উপর নির্ভর করে।
সিরিজগুলির জন্য ভেক্টরাইজড ফাংশনগুলি, তবে ডেটা ফ্রেমগুলি নয়
আপনি যদি একাধিক কলামে স্ট্রিং অপারেশন প্রয়োগ করতে চান তবে কী হবে? আপনি যদি একাধিক কলাম ডেটটাইমে রূপান্তর করতে চান? এই ফাংশনগুলি কেবল সিরিজের জন্য ভেক্টরাইজড, সুতরাং আপনার রূপান্তর / পরিচালনা করতে চান এমন প্রতিটি কলামের উপরে সেগুলি প্রয়োগ করতে হবে ।
df = pd.DataFrame(
pd.date_range('2018-12-31','2019-01-31', freq='2D').date.astype(str).reshape(-1, 2),
columns=['date1', 'date2'])
df
date1 date2
0 2018-12-31 2019-01-02
1 2019-01-04 2019-01-06
2 2019-01-08 2019-01-10
3 2019-01-12 2019-01-14
4 2019-01-16 2019-01-18
5 2019-01-20 2019-01-22
6 2019-01-24 2019-01-26
7 2019-01-28 2019-01-30
df.dtypes
date1 object
date2 object
dtype: object
এটি একটি গ্রহণযোগ্য কেস apply
:
df.apply(pd.to_datetime, errors='coerce').dtypes
date1 datetime64[ns]
date2 datetime64[ns]
dtype: object
নোট করুন এটি এটিকে বোঝায় stack
, বা কেবল একটি স্পষ্ট লুপ ব্যবহার করে। এই সমস্ত বিকল্প ব্যবহার করার চেয়ে কিছুটা দ্রুত apply
, তবে পার্থক্য ক্ষমা করার পক্ষে যথেষ্ট ছোট।
%timeit df.apply(pd.to_datetime, errors='coerce')
%timeit pd.to_datetime(df.stack(), errors='coerce').unstack()
%timeit pd.concat([pd.to_datetime(df[c], errors='coerce') for c in df], axis=1)
%timeit for c in df.columns: df[c] = pd.to_datetime(df[c], errors='coerce')
5.49 ms ± 247 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
3.94 ms ± 48.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
3.16 ms ± 216 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.41 ms ± 1.71 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
স্ট্রিং অপারেশন, বা বিভাগে রূপান্তর হিসাবে আপনি অন্যান্য ক্রিয়াকলাপের জন্য অনুরূপ কেস তৈরি করতে পারেন।
u = df.apply(lambda x: x.str.contains(...))
v = df.apply(lambda x: x.astype(category))
v / s
u = pd.concat([df[c].str.contains(...) for c in df], axis=1)
v = df.copy()
for c in df:
v[c] = df[c].astype(category)
এবং আরও ...
সিরিজ রূপান্তর করা str
: astype
বনামapply
এটি API এর একটি idiosyncrasy মত মনে হচ্ছে। apply
একটি সিরিজে পূর্ণসংখ্যাকে স্ট্রিংয়ে রূপান্তর করতে ব্যবহার করার চেয়ে তুলনীয় (এবং কখনও কখনও দ্রুত) astype
।
গ্রাফটি perfplot
লাইব্রেরি ব্যবহার করে চক্রান্ত করা হয়েছিল ।
import perfplot
perfplot.show(
setup=lambda n: pd.Series(np.random.randint(0, n, n)),
kernels=[
lambda s: s.astype(str),
lambda s: s.apply(str)
],
labels=['astype', 'apply'],
n_range=[2**k for k in range(1, 20)],
xlabel='N',
logx=True,
logy=True,
equality_check=lambda x, y: (x == y).all())
ভাসমান সহ আমি দেখতে পাচ্ছি astype
ধারাবাহিকভাবে তত দ্রুত বা তার চেয়ে কিছুটা দ্রুত apply
। সুতরাং এটি এই পরীক্ষার ডেটাটি পূর্ণসংখ্যার প্রকারের সাথে করতে হবে।
GroupBy
শৃঙ্খলাবদ্ধ রূপান্তর সঙ্গে অপারেশন
GroupBy.apply
এখন অবধি আলোচনা করা হয়নি, তবে GroupBy.apply
বিদ্যমান GroupBy
ফাংশনগুলি না করে এমন কোনও কিছু পরিচালনা করার জন্য এটি একটি পুনরাবৃত্তিমূলক সুবিধার ফাংশন ।
একটি সাধারণ প্রয়োজনীয়তা হ'ল একটি গ্রুপবাই এবং তারপরে দুটি প্রধান ক্রিয়াকলাপ যেমন "লেগড সিউসাম":
df = pd.DataFrame({"A": list('aabcccddee'), "B": [12, 7, 5, 4, 5, 4, 3, 2, 1, 10]})
df
A B
0 a 12
1 a 7
2 b 5
3 c 4
4 c 5
5 c 4
6 d 3
7 d 2
8 e 1
9 e 10
<! - ->
আপনার এখানে দুটি ধারাবাহিকভাবে কলের প্রয়োজন হবে:
df.groupby('A').B.cumsum().groupby(df.A).shift()
0 NaN
1 12.0
2 NaN
3 NaN
4 4.0
5 9.0
6 NaN
7 3.0
8 NaN
9 1.0
Name: B, dtype: float64
ব্যবহার করে apply
, আপনি এএ সিঙ্গেল কল এ সংক্ষিপ্ত করতে পারেন।
df.groupby('A').B.apply(lambda x: x.cumsum().shift())
0 NaN
1 12.0
2 NaN
3 NaN
4 4.0
5 9.0
6 NaN
7 3.0
8 NaN
9 1.0
Name: B, dtype: float64
পারফরম্যান্সের পরিমাণ নির্ধারণ করা খুব কঠিন কারণ এটি ডেটার উপর নির্ভর করে। তবে সাধারণভাবে, apply
যদি একটি groupby
কল হ্রাস করা হয় তবে এটি একটি গ্রহণযোগ্য সমাধান (কারণ groupby
এটি বেশ ব্যয়বহুলও)।
অন্যান্য Caveats
উপরে বর্ণিত সাবধানবাণীগুলি বাদে, এটিও উল্লেখযোগ্য যে apply
প্রথম সারিতে (বা কলাম) দু'বার চালিত হয়। ফাংশনটির কোনও পার্শ্ব প্রতিক্রিয়া রয়েছে কিনা তা নির্ধারণের জন্য এটি করা হয়। যদি তা না হয় apply
তবে ফলাফলটি মূল্যায়নের জন্য দ্রুতগতি ব্যবহার করতে সক্ষম হতে পারে, অন্যথায় এটি ধীর বাস্তবায়নে ফিরে আসে।
df = pd.DataFrame({
'A': [1, 2],
'B': ['x', 'y']
})
def func(x):
print(x['A'])
return x
df.apply(func, axis=1)
A B
0 1 x
1 2 y
এই আচরণটি GroupBy.apply
পান্ডাস সংস্করণ <0.25 এও দেখা যায় (এটি 0.25 এর জন্য ঠিক করা হয়েছিল, আরও তথ্যের জন্য এখানে দেখুন ))
returns.add(1).apply(np.log)
বনামnp.log(returns.add(1)
একটি ক্ষেত্রেapply
এটি সাধারণত প্রান্তিক দ্রুততর হবে, যা নীচে নীচে জেপিপির চিত্রের ডানদিকে সবুজ বাক্স।