টিএলডিআর; না, for
লুপ কম্বল "খারাপ" নয়, কমপক্ষে, সর্বদা নয়। এটি সম্ভবত আরও সঠিক যে কিছু ভেক্টরাইজড ক্রিয়াকলাপগুলি পুনরাবৃত্তির চেয়ে ধীর , বনাম যে কিছু ভেক্টরাইজড ক্রিয়াকলাপগুলির চেয়ে পুনরাবৃত্তি দ্রুত হয় তা বলার অপেক্ষা রাখে না। আপনার কোড থেকে সর্বাধিক পারফরম্যান্স পাওয়ার কী এবং কখন তা জানা। সংক্ষেপে, এটি এমন পরিস্থিতি যেখানে ভেক্টরাইজড পান্ডাস ফাংশনগুলির বিকল্প বিবেচনা করা উপযুক্ত:
- যখন আপনার ডেটা ছোট হয় (... আপনি যা করছেন তার উপর নির্ভর করে),
- যখন সঙ্গে তার আচরণ
object
/ মিশ্র dtypes
str
/ রেজেক্স অ্যাকসেসর ফাংশনগুলি ব্যবহার করার সময়
আসুন স্বতন্ত্রভাবে এই পরিস্থিতিগুলি পরীক্ষা করে দেখি।
ছোট ডেটাতে ইন্টেরেশন ভি / এস ভেক্টরাইজেশন
পান্ডাস তার এপিআই নকশায় একটি "কনভেনশন ওভার কনফিগারেশন" পদ্ধতির অনুসরণ করে । এর অর্থ হ'ল একই এপিআইতে বিস্তৃত ডেটা এবং কেস ব্যবহারের ক্ষেত্রে ফিট করা হয়েছে।
যখন কোনও পান্ডাস ফাংশন বলা হয়, কাজ নিশ্চিত করার জন্য নিম্নলিখিত জিনিসগুলি (অন্যদের মধ্যে) অভ্যন্তরীণভাবে ফাংশনটি দ্বারা পরিচালনা করা উচিত
- সূচি / অক্ষ সারিবদ্ধতা
- মিশ্র ডেটাটাইপগুলি পরিচালনা করছে
- নিখোঁজ ডেটা হ্যান্ডলিং
প্রায় প্রতিটি ফাংশনকে এগুলি বিবিধ এক্সটেন্টগুলির সাথে মোকাবেলা করতে হবে এবং এটি একটি উপরি উপস্থাপন করে । ওভারহেড সংখ্যাযুক্ত ফাংশনগুলির জন্য কম (উদাহরণস্বরূপ Series.add
), যখন স্ট্রিং ফাংশনগুলির জন্য এটি আরও বেশি উচ্চারণ করা হয় (উদাহরণস্বরূপ Series.str.replace
)।
for
অন্যদিকে লুপগুলি তত দ্রুত হয় আপনি ভাবেন। আরও ভাল যা তালিকার বোধগম্যতা (যা for
লুপগুলির মাধ্যমে তালিকা তৈরি করে ) আরও তত দ্রুততর কারণ তারা তালিকা তৈরির জন্য পুনরাবৃত্তিমূলক প্রক্রিয়াটি অনুকূলিতকরণ করেছে।
তালিকা বোঝার প্যাটার্ন অনুসরণ
[f(x) for x in seq]
যেখানে seq
পান্ডাস সিরিজ বা ডেটাফ্রেম কলাম রয়েছে। বা, একাধিক কলামের উপরে অপারেট করার সময়,
[f(x, y) for x, y in zip(seq1, seq2)]
কোথায় seq1
এবং seq2
কলাম হয়।
সংখ্যা তুলনা
একটি সাধারণ বুলিয়ান ইনডেক্সিং অপারেশন বিবেচনা করুন। Series.ne
( !=
)) এবং এর বিরুদ্ধে তালিকার বোধগম্য পদ্ধতিটি সময়সীমার করা হয়েছে query
। ফাংশনগুলি এখানে:
# Boolean indexing with Numeric value comparison.
df[df.A != df.B] # vectorized !=
df.query('A != B') # query (numexpr)
df[[x != y for x, y in zip(df.A, df.B)]] # list comp
সরলতার জন্য, আমি perfplot
এই পোস্টে সমস্ত টাইমিট পরীক্ষা চালানোর জন্য প্যাকেজটি ব্যবহার করেছি । উপরোক্ত অপারেশনের সময় নীচে রয়েছে:
তালিকা বোধগম্য query
মাঝারি আকারের এন এর জন্য প্রসারিত করে, এবং এমনকি ভেক্টরাইজড ক্ষুদ্র এন এর সাথে তুলনা করার সমান হয় না। দুর্ভাগ্যক্রমে, তালিকা বোধগম্য স্কেরির রৈখিকভাবে স্কেল করে, সুতরাং এটি বৃহত্তর এন এর পক্ষে খুব বেশি পারফরম্যান্স লাভের প্রস্তাব দেয় না N
দ্রষ্টব্য
এটি উল্লেখ করার মতো যে তালিকা বোধগম্যতার বেশিরভাগ সুবিধা সূচক প্রান্তিককরণ সম্পর্কে চিন্তা না করার কারণে আসে, তবে এর অর্থ আপনার কোডটি যদি সূচীকরণ সারিবদ্ধকরণের উপর নির্ভরশীল হয় তবে এটি ভঙ্গ হবে। কিছু ক্ষেত্রে, অন্তর্নিহিত NumPy অ্যারেগুলির উপর ভেক্টরিযুক্ত অপারেশনগুলি "উভয় বিশ্বের সেরা" হিসাবে আনা হিসাবে বিবেচনা করা যেতে পারে, যা পান্ডাস ফাংশনগুলির সমস্ত অবিবাহিত ওভারহেড ছাড়াই ভেক্টরাইজেশনের অনুমতি দেয় । এর অর্থ আপনি উপরের ক্রিয়াকলাপটি আবার লিখতে পারেন
df[df.A.values != df.B.values]
যা পান্ডা এবং তালিকার উপলব্ধি সমতুল্য উভয়কেই ছাড়িয়ে যায়:
NumPy ভেক্টরাইজেশন এই পোস্টের আওতার বাইরে, তবে পারফরম্যান্সের বিষয়টি বিবেচনা করলে তা অবশ্যই বিবেচ্য।
মান গণনা
অন্য একটি উদাহরণ গ্রহণ করছে - এবার অন্য ভ্যানিলা পাইথন নির্মাণের সাথে যা লুপের চেয়ে দ্রুতcollections.Counter
। একটি সাধারণ প্রয়োজন হ'ল মান গণনা করা এবং অভিধান হিসাবে ফলাফলটি ফেরত দেওয়া। এই সঙ্গে সম্পন্ন করা হয় value_counts
, np.unique
এবং Counter
:
# Value Counts comparison.
ser.value_counts(sort=False).to_dict() # value_counts
dict(zip(*np.unique(ser, return_counts=True))) # np.unique
Counter(ser) # Counter
ফলাফলগুলি আরও Counter
স্পষ্টভাবে প্রকাশিত হয়, ছোট এন (~ 3500) এর বৃহত্তর পরিসরের জন্য উভয় ভেক্টরাইজড পদ্ধতিতে জিতেছে।
আরও ট্রিভিয়া নোট করুন (সৌজন্যে @ ব্যবহারকারী 2357112)। Counter
একটি সঙ্গে বাস্তবায়িত হয় সি বেগবর্ধক , এটা এখনও কাজ করতে হবে, যাতে যখন পাইথন অন্তর্নিহিত সি datatypes পরিবর্তে বস্তুর সঙ্গে, এটা এখনও দ্রুত একটি চেয়ে for
লুপ। পাইথন শক্তি!
অবশ্যই, এখান থেকে সরিয়ে নেওয়া হ'ল পারফরম্যান্সটি আপনার ডেটা এবং ব্যবহারের ক্ষেত্রে নির্ভর করে। এই উদাহরণগুলির মূল বক্তব্য হ'ল বৈধ বিকল্প হিসাবে এই সমাধানগুলি বাতিল না করার জন্য আপনাকে বোঝানো। এগুলি যদি এখনও আপনাকে আপনার প্রয়োজনীয় পারফরম্যান্স না দেয় তবে সর্বদা সিথন এবং নাম্বা থাকে । আসুন এই পরীক্ষাটি মিশ্রণে যুক্ত করি।
from numba import njit, prange
@njit(parallel=True)
def get_mask(x, y):
result = [False] * len(x)
for i in prange(len(x)):
result[i] = x[i] != y[i]
return np.array(result)
df[get_mask(df.A.values, df.B.values)] # numba
নুম্বা খুব শক্তিশালী ভেক্টরাইজড কোডটিতে লুপী পাইথন কোডের জেআইটি সংকলন সরবরাহ করে। কীভাবে নাম্বার কাজ করা যায় তা বোঝার মধ্যে একটি শেখার বক্রতা জড়িত।
মিশ্র / object
dtyype সঙ্গে অপারেশন
স্ট্রিং-ভিত্তিক তুলনা
প্রথম বিভাগ থেকে ফিল্টারিং উদাহরণের পুনর্বিবেচনা, যদি কলামগুলির তুলনা করা হচ্ছে স্ট্রিংগুলি? উপরের একই 3 টি ফাংশনটি বিবেচনা করুন তবে ইনপুট দিয়ে ডেটাফ্রেমকে স্ট্রিংয়ে ফেলেছে।
# Boolean indexing with string value comparison.
df[df.A != df.B] # vectorized !=
df.query('A != B') # query (numexpr)
df[[x != y for x, y in zip(df.A, df.B)]] # list comp
তো, কী বদলে গেল? এখানে লক্ষণীয় বিষয় হ'ল স্ট্রিং অপারেশনগুলি ভেক্টরাইজ করা সহজাতভাবে কঠিন। পান্ডস স্ট্রিংকে অবজেক্ট হিসাবে বিবেচনা করে এবং বস্তুর সমস্ত ক্রিয়াকলাপ ধীর, লুপী প্রয়োগে ফিরে আসে।
এখন, যেহেতু এই লুপী প্রয়োগটি উপরে উল্লিখিত সমস্ত ওভারহেড দ্বারা বেষ্টিত রয়েছে, সেগুলি একই স্কেল করা সত্ত্বেও এই সমাধানগুলির মধ্যে একটি ধ্রুবক মাত্রার পার্থক্য রয়েছে।
পরিবর্তনীয় / জটিল বস্তুগুলির ক্রিয়াকলাপে যখন আসে তখন কোনও তুলনা হয় না। তালিকান বোধগম্যতা ডিক্টস এবং তালিকাগুলি সম্পর্কিত সমস্ত ক্রিয়াকলাপকে ছাপিয়ে যায়।
কী দ্বারা অভিধান মান (গুলি) অ্যাক্সেস করা
এখানে দুটি ক্রিয়াকলাপের সময় রয়েছে যা অভিধানের কলাম থেকে একটি মান বের করে: map
এবং তালিকাটি উপলব্ধি করে। "কোড স্নিপেটস" শিরোনামে সেটআপটি পরিশিষ্টে রয়েছে in
# Dictionary value extraction.
ser.map(operator.itemgetter('value')) # map
pd.Series([x.get('value') for x in ser]) # list comprehension
3 টি ক্রিয়াকলাপের অবস্থানগত তালিকা সূচীকরণের সময়গুলি কলামের তালিকা (ব্যতিক্রমগুলি পরিচালনা করে) map
, str.get
অ্যাক্সেসর পদ্ধতি এবং তালিকাটি বোঝার থেকে 0 তম উপাদানটি বের করে :
# List positional indexing.
def get_0th(lst):
try:
return lst[0]
# Handle empty lists and NaNs gracefully.
except (IndexError, TypeError):
return np.nan
ser.map(get_0th) # map
ser.str[0] # str accessor
pd.Series([x[0] if len(x) > 0 else np.nan for x in ser]) # list comp
pd.Series([get_0th(x) for x in ser]) # list comp safe
দ্রষ্টব্য
সূচকটি যদি গুরুত্বপূর্ণ হয় তবে আপনি এটি করতে চান:
pd.Series([...], index=ser.index)
সিরিজটির পুনর্গঠন করার সময়।
তালিকা সমতলকরণ
একটি চূড়ান্ত উদাহরণ হ'ল সমতলকরণের তালিকা। এটি অন্য একটি সাধারণ সমস্যা এবং এটি নির্ধারণ করে যে খাঁটি অজগরটি এখানে কতটা শক্তিশালী।
# Nested list flattening.
pd.DataFrame(ser.tolist()).stack().reset_index(drop=True) # stack
pd.Series(list(chain.from_iterable(ser.tolist()))) # itertools.chain
pd.Series([y for x in ser for y in x]) # nested list comp
উভয় itertools.chain.from_iterable
এবং নেস্টেড তালিকা অনুধাবন খাঁটি অজগর কাঠামো এবং stack
সমাধানের চেয়ে অনেক ভাল স্কেল ।
এই সময়গুলি এই সত্যের দৃ strong় ইঙ্গিত দেয় যে পান্ডাস মিশ্রিত টাইপগুলির সাথে কাজ করার জন্য সজ্জিত নয় এবং সম্ভবত এটি করার জন্য আপনার ব্যবহার করা থেকে বিরত থাকতে হবে। যেখানেই সম্ভব, ডেটা পৃথক কলামে স্কেলার মান হিসাবে (ints / floats / স্ট্রিং) উপস্থিত থাকা উচিত।
শেষ অবধি, এই সমাধানগুলির প্রয়োগযোগ্যতা আপনার ডেটার উপর ব্যাপকভাবে নির্ভর করে। সুতরাং, সর্বোত্তম কাজটি হ'ল কী হবে তা ঠিক করার আগে আপনার ডেটাতে এই ক্রিয়াকলাপগুলি পরীক্ষা করা। লক্ষ্য করুন আমি কীভাবে apply
এই সমাধানগুলিতে সময় নিই না , কারণ এটি গ্রাফটি স্কু করবে (হ্যাঁ, এটি এত ধীর)।
রেজেক্স অপারেশন এবং .str
অ্যাকসেসর পদ্ধতি
পান্ডাস যেমন Regex অপারেশন প্রয়োগ করতে পারেন str.contains
, str.extract
এবং str.extractall
সেইসাথে অন্যান্য "ভেক্টরকৃত" STRING অপারেশন (যেমন, str.split
, str.find ,
str.translate`, ইত্যাদি) স্ট্রিং কলাম উপর। এই ফাংশনগুলি তালিকা বোধের চেয়ে ধীর এবং এটিকে অন্য যে কোনও কিছুর চেয়ে বেশি সুবিধাজনক ফাংশন হিসাবে বোঝানো হচ্ছে।
একটি রেইজেক্স প্যাটার্নটি প্রাক-সংকলন করা এবং আপনার ডেটা দিয়ে পুনরাবৃত্তি করা সাধারণত খুব দ্রুত হয় re.compile
(এছাড়াও পাইথনের রে ডক্লাইপাইলটি ব্যবহার করা কি উপযুক্ত? )। লিস্টের সমষ্টিটি str.contains
দেখতে এরকম কিছু দেখাচ্ছে:
p = re.compile(...)
ser2 = pd.Series([x for x in ser if p.search(x)])
বা,
ser2 = ser[[bool(p.search(x)) for x in ser]]
আপনার যদি NaNs পরিচালনা করতে হয় তবে আপনি এর মতো কিছু করতে পারেন
ser[[bool(p.search(x)) if pd.notnull(x) else False for x in ser]]
str.extract
( গোষ্ঠীবিহীন) এর সমপরিমান তালিকাটি দেখতে এরকম কিছু দেখতে পাবে:
df['col2'] = [p.search(x).group(0) for x in df['col']]
আপনার যদি কোনও ম্যাচ এবং NaN হ্যান্ডেল করার প্রয়োজন হয় তবে আপনি একটি কাস্টম ফাংশন (এখনও দ্রুত!) ব্যবহার করতে পারেন:
def matcher(x):
m = p.search(str(x))
if m:
return m.group(0)
return np.nan
df['col2'] = [matcher(x) for x in df['col']]
matcher
ফাংশন খুব প্রসার্য হয়। প্রয়োজন অনুসারে প্রতিটি ক্যাপচার গ্রুপের জন্য একটি তালিকা ফিরিয়ে আনতে এটি লাগানো যেতে পারে। ম্যাচারের অবজেক্টের group
বা groups
গুণাবলীটি কেবল অনুসন্ধান করুন ।
জন্য str.extractall
পরিবর্তন p.search
করতে p.findall
।
স্ট্রিং এক্সট্রাকশন
একটি সাধারণ ফিল্টারিং অপারেশন বিবেচনা করুন। ধারণাটি হ'ল 4 অঙ্কগুলি উত্তোলন করা উচিত যদি এটির আগে কোনও বড় অক্ষরের অক্ষর থাকে।
# Extracting strings.
p = re.compile(r'(?<=[A-Z])(\d{4})')
def matcher(x):
m = p.search(x)
if m:
return m.group(0)
return np.nan
ser.str.extract(r'(?<=[A-Z])(\d{4})', expand=False) # str.extract
pd.Series([matcher(x) for x in ser]) # list comprehension
আরও উদাহরণ
সম্পূর্ণ প্রকাশ - আমি নীচে তালিকাভুক্ত এই পোস্টগুলির লেখক (অংশ বা সম্পূর্ণ) in
উপসংহার
উপরের উদাহরণগুলি থেকে দেখানো হয়েছে, ছোট ছোট সারি ডেটাফ্রেমস, মিশ্রিত ডেটাটাইপস এবং নিয়মিত প্রকাশের সাথে কাজ করার সময় পুনরাবৃত্তিটি জ্বলে।
আপনি যে স্পিডআপ পান তা আপনার ডেটা এবং আপনার সমস্যার উপর নির্ভর করে, তাই আপনার মাইলেজটি আলাদা হতে পারে। করণীয় হ'ল সর্বোত্তম বিষয় হ'ল সতর্কতার সাথে পরীক্ষা চালানো এবং দেখুন অর্থ প্রদানের চেষ্টাটি উপযুক্ত কিনা।
"ভেক্টরাইজড" ফাংশনগুলি তাদের সরলতা এবং পঠনযোগ্যতার সাথে জ্বলজ্বল করে, সুতরাং যদি পারফরম্যান্স সমালোচনা না করে তবে অবশ্যই আপনার সেগুলি পছন্দ করা উচিত।
অন্য পক্ষের নোট, নির্দিষ্ট স্ট্রিং অপারেশনগুলি সীমাবদ্ধতাগুলি মোকাবেলা করে যা নুমপি ব্যবহারের পক্ষে। এখানে দুটি উদাহরণ রয়েছে যেখানে সাবধানে NumPy ভেক্টরাইজেশন অজগরকে ছাপিয়েছে:
উপরন্তু, কখনও কখনও মাধ্যমে অন্তর্নিহিত অ্যারে উপর অপারেটিং .values
যেমন সিরিজ অর DataFrames উপর উল্টোদিকে সবচেয়ে স্বাভাবিক পরিস্থিতিতে জন্য একটি সুস্থ যথেষ্ট speedup দিতে পারে (দেখুন নোট মধ্যে সাংখ্যিক তুলনা উপরে অধ্যায়)। সুতরাং, উদাহরণস্বরূপ df[df.A.values != df.B.values]
তাত্ক্ষণিক কর্মক্ষমতা বাড়িয়ে তোলে df[df.A != df.B]
। ব্যবহার .values
প্রতিটি পরিস্থিতিতে উপযুক্ত নাও হতে পারে তবে এটি একটি দরকারী হ্যাক।
উপরে উল্লিখিত হিসাবে, এই সমাধানগুলি বাস্তবায়নের সমস্যার জন্য মূল্যবান কিনা তা সিদ্ধান্ত নেওয়া আপনার পক্ষে নির্ভর করে।
পরিশিষ্ট: কোড স্নিপেটস
import perfplot
import operator
import pandas as pd
import numpy as np
import re
from collections import Counter
from itertools import chain
# Boolean indexing with Numeric value comparison.
perfplot.show(
setup=lambda n: pd.DataFrame(np.random.choice(1000, (n, 2)), columns=['A','B']),
kernels=[
lambda df: df[df.A != df.B],
lambda df: df.query('A != B'),
lambda df: df[[x != y for x, y in zip(df.A, df.B)]],
lambda df: df[get_mask(df.A.values, df.B.values)]
],
labels=['vectorized !=', 'query (numexpr)', 'list comp', 'numba'],
n_range=[2**k for k in range(0, 15)],
xlabel='N'
)
# Value Counts comparison.
perfplot.show(
setup=lambda n: pd.Series(np.random.choice(1000, n)),
kernels=[
lambda ser: ser.value_counts(sort=False).to_dict(),
lambda ser: dict(zip(*np.unique(ser, return_counts=True))),
lambda ser: Counter(ser),
],
labels=['value_counts', 'np.unique', 'Counter'],
n_range=[2**k for k in range(0, 15)],
xlabel='N',
equality_check=lambda x, y: dict(x) == dict(y)
)
# Boolean indexing with string value comparison.
perfplot.show(
setup=lambda n: pd.DataFrame(np.random.choice(1000, (n, 2)), columns=['A','B'], dtype=str),
kernels=[
lambda df: df[df.A != df.B],
lambda df: df.query('A != B'),
lambda df: df[[x != y for x, y in zip(df.A, df.B)]],
],
labels=['vectorized !=', 'query (numexpr)', 'list comp'],
n_range=[2**k for k in range(0, 15)],
xlabel='N',
equality_check=None
)
# Dictionary value extraction.
ser1 = pd.Series([{'key': 'abc', 'value': 123}, {'key': 'xyz', 'value': 456}])
perfplot.show(
setup=lambda n: pd.concat([ser1] * n, ignore_index=True),
kernels=[
lambda ser: ser.map(operator.itemgetter('value')),
lambda ser: pd.Series([x.get('value') for x in ser]),
],
labels=['map', 'list comprehension'],
n_range=[2**k for k in range(0, 15)],
xlabel='N',
equality_check=None
)
# List positional indexing.
ser2 = pd.Series([['a', 'b', 'c'], [1, 2], []])
perfplot.show(
setup=lambda n: pd.concat([ser2] * n, ignore_index=True),
kernels=[
lambda ser: ser.map(get_0th),
lambda ser: ser.str[0],
lambda ser: pd.Series([x[0] if len(x) > 0 else np.nan for x in ser]),
lambda ser: pd.Series([get_0th(x) for x in ser]),
],
labels=['map', 'str accessor', 'list comprehension', 'list comp safe'],
n_range=[2**k for k in range(0, 15)],
xlabel='N',
equality_check=None
)
# Nested list flattening.
ser3 = pd.Series([['a', 'b', 'c'], ['d', 'e'], ['f', 'g']])
perfplot.show(
setup=lambda n: pd.concat([ser2] * n, ignore_index=True),
kernels=[
lambda ser: pd.DataFrame(ser.tolist()).stack().reset_index(drop=True),
lambda ser: pd.Series(list(chain.from_iterable(ser.tolist()))),
lambda ser: pd.Series([y for x in ser for y in x]),
],
labels=['stack', 'itertools.chain', 'nested list comp'],
n_range=[2**k for k in range(0, 15)],
xlabel='N',
equality_check=None
)
# Extracting strings.
ser4 = pd.Series(['foo xyz', 'test A1234', 'D3345 xtz'])
perfplot.show(
setup=lambda n: pd.concat([ser4] * n, ignore_index=True),
kernels=[
lambda ser: ser.str.extract(r'(?<=[A-Z])(\d{4})', expand=False),
lambda ser: pd.Series([matcher(x) for x in ser])
],
labels=['str.extract', 'list comprehension'],
n_range=[2**k for k in range(0, 15)],
xlabel='N',
equality_check=None
)
pd.Series
এবংpd.DataFrame
এখন পুনরাবৃত্তি থেকে নির্মাণ সমর্থন। এর অর্থ যে কোনও একটি প্রথমে একটি তালিকা তৈরির প্রয়োজনের চেয়ে (পাইয়ে বোঝার সাহায্যে) একটি পাইথন জেনারেটরটি কেবল কনস্ট্রাক্টর ফাংশনে পাস করতে পারে যা অনেক ক্ষেত্রে ধীর হতে পারে। তবে জেনারেটরের আউটপুটটির আকার আগে নির্ধারণ করা যায় না। আমি নিশ্চিত না যে কত সময় / স্মৃতি ওভারহেডের কারণ ঘটবে।