মেমরি ব্যবহার এবং পারফরম্যান্সের জন্য পান্ডাস ডেটাফ্রেম অপ্টিমাইজ করার একটি সম্পূর্ণ নির্দেশিকা, যেখানে ডেটা টাইপ, ইনডেক্সিং এবং উন্নত কৌশলগুলি আলোচনা করা হয়েছে।
পান্ডাস ডেটাফ্রেম অপ্টিমাইজেশন: মেমরি ব্যবহার এবং পারফরম্যান্স টিউনিং
পান্ডাস ডেটা ম্যানিপুলেশন এবং বিশ্লেষণের জন্য একটি শক্তিশালী পাইথন লাইব্রেরি। তবে, বড় ডেটাসেটের সাথে কাজ করার সময়, পান্ডাস ডেটাফ্রেমগুলি উল্লেখযোগ্য পরিমাণে মেমরি ব্যবহার করতে পারে এবং ধীর পারফরম্যান্স দেখাতে পারে। এই নিবন্ধটি মেমরি ব্যবহার এবং পারফরম্যান্স উভয়ের জন্য পান্ডাস ডেটাফ্রেম অপ্টিমাইজ করার একটি সম্পূর্ণ নির্দেশিকা প্রদান করে, যা আপনাকে আরও দক্ষতার সাথে বড় ডেটাসেট প্রসেস করতে সক্ষম করবে।
পান্ডাস ডেটাফ্রেমে মেমরির ব্যবহার বোঝা
অপ্টিমাইজেশন কৌশলগুলিতে যাওয়ার আগে, পান্ডাস ডেটাফ্রেমগুলি কীভাবে মেমরিতে ডেটা সঞ্চয় করে তা বোঝা গুরুত্বপূর্ণ। একটি ডেটাফ্রেমের প্রতিটি কলামের একটি নির্দিষ্ট ডেটা টাইপ থাকে, যা তার মানগুলি সঞ্চয় করার জন্য প্রয়োজনীয় মেমরির পরিমাণ নির্ধারণ করে। সাধারণ ডেটা টাইপগুলির মধ্যে রয়েছে:
- int64: ৬৪-বিট ইন্টিজার (ইন্টিজারের জন্য ডিফল্ট)
- float64: ৬৪-বিট ফ্লোটিং-পয়েন্ট সংখ্যা (ফ্লোটিং-পয়েন্ট সংখ্যার জন্য ডিফল্ট)
- object: পাইথন অবজেক্ট (স্ট্রিং এবং মিশ্র ডেটা টাইপের জন্য ব্যবহৃত)
- category: ক্যাটাগরিক্যাল ডেটা (পুনরাবৃত্তিমূলক মানের জন্য কার্যকর)
- bool: বুলিয়ান মান (True/False)
- datetime64: ডেটটাইম মান
object ডেটা টাইপ প্রায়শই সবচেয়ে বেশি মেমরি-ইনটেনসিভ হয় কারণ এটি পাইথন অবজেক্টের পয়েন্টার সঞ্চয় করে, যা ইন্টিজার বা ফ্লোটের মতো প্রিমিটিভ ডেটা টাইপের চেয়ে উল্লেখযোগ্যভাবে বড় হতে পারে। স্ট্রিং, এমনকি ছোটগুলিও, যখন `object` হিসাবে সংরক্ষণ করা হয়, প্রয়োজনের চেয়ে অনেক বেশি মেমরি খরচ করে। একইভাবে, যখন `int32` যথেষ্ট হতো, তখন `int64` ব্যবহার করা মেমরির অপচয় করে।
উদাহরণ: ডেটাফ্রেমের মেমরি ব্যবহার পরীক্ষা করা
আপনি একটি ডেটাফ্রেমের মেমরি ব্যবহার পরীক্ষা করার জন্য memory_usage() পদ্ধতি ব্যবহার করতে পারেন:
import pandas as pd
import numpy as np
data = {
'col1': np.random.randint(0, 1000, 100000),
'col2': np.random.rand(100000),
'col3': ['A', 'B', 'C'] * (100000 // 3 + 1)[:100000],
'col4': ['This is a long string'] * 100000
}
df = pd.DataFrame(data)
memory_usage = df.memory_usage(deep=True)
print(memory_usage)
print(df.dtypes)
deep=True আর্গুমেন্ট নিশ্চিত করে যে অবজেক্টের (যেমন স্ট্রিং) মেমরি ব্যবহার সঠিকভাবে গণনা করা হয়েছে। `deep=True` ছাড়া, এটি কেবল পয়েন্টারগুলির জন্য মেমরি গণনা করবে, মূল ডেটার জন্য নয়।
ডেটা টাইপ অপ্টিমাইজ করা
মেমরি ব্যবহার কমানোর অন্যতম কার্যকর উপায় হল আপনার ডেটাফ্রেম কলামগুলির জন্য সবচেয়ে উপযুক্ত ডেটা টাইপ বেছে নেওয়া। এখানে কিছু সাধারণ কৌশল রয়েছে:
১. নিউমেরিক্যাল ডেটা টাইপ ডাউনকাস্ট করা
যদি আপনার ইন্টিজার বা ফ্লোটিং-পয়েন্ট কলামগুলির জন্য ৬৪-বিট প্রিসিশনের সম্পূর্ণ পরিসরের প্রয়োজন না হয়, তাহলে আপনি সেগুলিকে int32, int16, float32, বা float16 এর মতো ছোট ডেটা টাইপে ডাউনকাস্ট করতে পারেন। এটি বিশেষ করে বড় ডেটাসেটের জন্য মেমরি ব্যবহার উল্লেখযোগ্যভাবে কমাতে পারে।
উদাহরণ: একটি কলাম বিবেচনা করুন যা বয়স উপস্থাপন করে, যা ১২০ এর বেশি হওয়ার সম্ভাবনা নেই। এটিকে `int64` হিসাবে সংরক্ষণ করা অপচয়; `int8` (পরিসর -১২৮ থেকে ১২৭) আরও উপযুক্ত হবে।
def downcast_numeric(df):
"""Downcasts numeric columns to the smallest possible data type."""
for col in df.columns:
if pd.api.types.is_integer_dtype(df[col]):
df[col] = pd.to_numeric(df[col], downcast='integer')
elif pd.api.types.is_float_dtype(df[col]):
df[col] = pd.to_numeric(df[col], downcast='float')
return df
df = downcast_numeric(df.copy())
print(df.memory_usage(deep=True))
print(df.dtypes)
pd.to_numeric() ফাংশনটি downcast আর্গুমেন্টের সাথে ব্যবহার করে স্বয়ংক্রিয়ভাবে ক্ষুদ্রতম সম্ভাব্য ডেটা টাইপ নির্বাচন করা হয় যা কলামের মানগুলিকে উপস্থাপন করতে পারে। `copy()` ব্যবহার করে মূল ডেটাফ্রেম পরিবর্তন করা এড়ানো হয়। ডাউনকাস্ট করার আগে সর্বদা আপনার ডেটার মানের পরিসর পরীক্ষা করুন যাতে আপনি কোনো তথ্য না হারান।
২. ক্যাটাগরিক্যাল ডেটা টাইপ ব্যবহার করা
যদি একটি কলামে সীমিত সংখ্যক ইউনিক মান থাকে, তাহলে আপনি এটিকে একটি category ডেটা টাইপে রূপান্তর করতে পারেন। ক্যাটাগরিক্যাল ডেটা টাইপগুলি প্রতিটি ইউনিক মান কেবল একবার সঞ্চয় করে এবং তারপরে কলামের মানগুলিকে উপস্থাপন করার জন্য ইন্টিজার কোড ব্যবহার করে। এটি মেমরি ব্যবহার উল্লেখযোগ্যভাবে কমাতে পারে, বিশেষ করে সেইসব কলামের জন্য যেখানে পুনরাবৃত্তিমূলক মানের অনুপাত বেশি।
উদাহরণ: দেশের কোড উপস্থাপনকারী একটি কলাম বিবেচনা করুন। যদি আপনি সীমিত সংখ্যক দেশের সাথে কাজ করেন (যেমন, কেবল ইউরোপীয় ইউনিয়নের দেশগুলি), তবে এটিকে স্ট্রিং হিসাবে সংরক্ষণ করার চেয়ে ক্যাটাগরি হিসাবে সংরক্ষণ করা অনেক বেশি কার্যকর হবে।
def optimize_categories(df):
"""Converts object columns with low cardinality to categorical type."""
for col in df.columns:
if df[col].dtype == 'object':
num_unique_values = len(df[col].unique())
num_total_values = len(df[col])
if num_unique_values / num_total_values < 0.5:
df[col] = df[col].astype('category')
return df
df = optimize_categories(df.copy())
print(df.memory_usage(deep=True))
print(df.dtypes)
এই কোডটি পরীক্ষা করে দেখে যে একটি অবজেক্ট কলামে ইউনিক মানের সংখ্যা মোট মানের ৫০% এর কম কিনা। যদি তাই হয়, এটি কলামটিকে একটি ক্যাটাগরিক্যাল ডেটা টাইপে রূপান্তর করে। ৫০% থ্রেশহোল্ডটি ইচ্ছামত নির্ধারণ করা হয়েছে এবং আপনার ডেটার নির্দিষ্ট বৈশিষ্ট্যের উপর ভিত্তি করে এটি সামঞ্জস্য করা যেতে পারে। এই পদ্ধতিটি সবচেয়ে উপকারী যখন কলামে অনেক পুনরাবৃত্তিমূলক মান থাকে।
৩. স্ট্রিংয়ের জন্য অবজেক্ট ডেটা টাইপ এড়িয়ে চলা
আগেই উল্লেখ করা হয়েছে, object ডেটা টাইপ প্রায়শই সবচেয়ে বেশি মেমরি-ইনটেনসিভ, বিশেষ করে যখন স্ট্রিং সংরক্ষণের জন্য ব্যবহৃত হয়। সম্ভব হলে, স্ট্রিং কলামগুলির জন্য object ডেটা টাইপ ব্যবহার করা এড়ানোর চেষ্টা করুন। কম কার্ডিনালিটি সহ স্ট্রিংয়ের জন্য ক্যাটাগরিক্যাল টাইপগুলি পছন্দনীয়। যদি কার্ডিনালিটি বেশি হয়, তবে বিবেচনা করুন যে স্ট্রিংগুলিকে সংখ্যাসূচক কোড দিয়ে উপস্থাপন করা যায় কিনা বা স্ট্রিং ডেটা সম্পূর্ণরূপে এড়ানো যায় কিনা।
যদি আপনাকে কলামে স্ট্রিং অপারেশন করতে হয়, তবে আপনাকে এটিকে একটি অবজেক্ট টাইপ হিসাবে রাখতে হতে পারে, তবে বিবেচনা করুন যে এই অপারেশনগুলি আগে থেকে করা যায় কিনা, তারপরে এটিকে আরও কার্যকর টাইপে রূপান্তর করা যায় কিনা।
৪. তারিখ এবং সময় ডেটা
তারিখ এবং সময় তথ্যের জন্য `datetime64` ডেটা টাইপ ব্যবহার করুন। নিশ্চিত করুন যে রেজোলিউশনটি উপযুক্ত (ন্যানোসেকেন্ড রেজোলিউশন অপ্রয়োজনীয় হতে পারে)। পান্ডাস সময় সিরিজের ডেটা খুব দক্ষতার সাথে পরিচালনা করে।
ডেটাফ্রেম অপারেশন অপ্টিমাইজ করা
ডেটা টাইপ অপ্টিমাইজ করার পাশাপাশি, আপনি পান্ডাস ডেটাফ্রেমের উপর করা অপারেশনগুলি অপ্টিমাইজ করে এর পারফরম্যান্স উন্নত করতে পারেন। এখানে কিছু সাধারণ কৌশল রয়েছে:
১. ভেক্টরাইজেশন
ভেক্টরাইজেশন হল পৃথক উপাদানগুলির উপর পুনরাবৃত্তি করার পরিবর্তে একবারে সম্পূর্ণ অ্যারে বা কলামের উপর অপারেশন সম্পাদন করার প্রক্রিয়া। পান্ডাস ভেক্টরাইজড অপারেশনগুলির জন্য অত্যন্ত অপ্টিমাইজড, তাই সেগুলি ব্যবহার করে পারফরম্যান্স উল্লেখযোগ্যভাবে উন্নত করা যায়। যখনই সম্ভব সুস্পষ্ট লুপ এড়িয়ে চলুন। পান্ডাসের বিল্ট-ইন ফাংশনগুলি সাধারণত সমতুল্য পাইথন লুপের চেয়ে অনেক দ্রুত হয়।
উদাহরণ: প্রতিটি মানের বর্গ গণনা করার জন্য একটি কলামের মাধ্যমে পুনরাবৃত্তি করার পরিবর্তে, pow() ফাংশনটি ব্যবহার করুন:
# Inefficient (using a loop)
import time
start_time = time.time()
results = []
for value in df['col2']:
results.append(value ** 2)
df['col2_squared_loop'] = results
end_time = time.time()
print(f"Loop time: {end_time - start_time:.4f} seconds")
# Efficient (using vectorization)
start_time = time.time()
df['col2_squared_vectorized'] = df['col2'] ** 2
end_time = time.time()
print(f"Vectorized time: {end_time - start_time:.4f} seconds")
ভেক্টরাইজড পদ্ধতিটি সাধারণত লুপ-ভিত্তিক পদ্ধতির চেয়ে কয়েক গুণ দ্রুত হয়।
২. সতর্কতার সাথে `apply()` ব্যবহার করা
apply() পদ্ধতিটি আপনাকে একটি ডেটাফ্রেমের প্রতিটি সারি বা কলামে একটি ফাংশন প্রয়োগ করতে দেয়। তবে, এটি সাধারণত ভেক্টরাইজড অপারেশনগুলির চেয়ে ধীর কারণ এটি প্রতিটি উপাদানের জন্য একটি পাইথন ফাংশন কল করে। apply() কেবল তখনই ব্যবহার করুন যখন ভেক্টরাইজড অপারেশন সম্ভব নয়।
যদি আপনাকে অবশ্যই `apply()` ব্যবহার করতে হয়, তবে আপনি যে ফাংশনটি প্রয়োগ করছেন তা যতটা সম্ভব ভেক্টরাইজ করার চেষ্টা করুন। উল্লেখযোগ্য পারফরম্যান্স উন্নতির জন্য ফাংশনটিকে মেশিন কোডে কম্পাইল করতে Numba-এর `jit` ডেকোরেটর ব্যবহার করার কথা বিবেচনা করুন।
from numba import jit
@jit(nopython=True)
def my_function(x):
return x * 2 # Example function
df['col2_applied'] = df['col2'].apply(my_function)
৩. দক্ষতার সাথে কলাম নির্বাচন করা
একটি ডেটাফ্রেম থেকে কলামগুলির একটি উপসেট নির্বাচন করার সময়, সর্বোত্তম পারফরম্যান্সের জন্য নিম্নলিখিত পদ্ধতিগুলি ব্যবহার করুন:
- সরাসরি কলাম নির্বাচন:
df[['col1', 'col2']](কয়েকটি কলাম নির্বাচনের জন্য দ্রুততম) - বুলিয়ান ইনডেক্সিং:
df.loc[:, [True if col.startswith('col') else False for col in df.columns]](শর্তের ভিত্তিতে কলাম নির্বাচনের জন্য দরকারী)
কলাম নির্বাচনের জন্য রেগুলার এক্সপ্রেশন সহ df.filter() ব্যবহার করা এড়িয়ে চলুন, কারণ এটি অন্যান্য পদ্ধতির চেয়ে ধীর হতে পারে।
৪. জয়েন এবং মার্জ অপ্টিমাইজ করা
ডেটাফ্রেম জয়েন এবং মার্জ করা কম্পিউটেশনালি ব্যয়বহুল হতে পারে, বিশেষ করে বড় ডেটাসেটের জন্য। জয়েন এবং মার্জ অপ্টিমাইজ করার জন্য এখানে কিছু টিপস রয়েছে:
- উপযুক্ত জয়েন কী ব্যবহার করুন: নিশ্চিত করুন যে জয়েন কীগুলির একই ডেটা টাইপ রয়েছে এবং সেগুলি ইনডেক্স করা আছে।
- জয়েনের ধরন উল্লেখ করুন: আপনার প্রয়োজনীয়তার উপর ভিত্তি করে উপযুক্ত জয়েনের ধরন (যেমন,
inner,left,right,outer) ব্যবহার করুন। একটি ইনার জয়েন সাধারণত একটি আউটার জয়েনের চেয়ে দ্রুত হয়। - `join()` এর পরিবর্তে `merge()` ব্যবহার করুন:
merge()ফাংশনটি আরও বহুমুখী এবং প্রায়শইjoin()পদ্ধতির চেয়ে দ্রুত হয়।
উদাহরণ:
df1 = pd.DataFrame({'key': ['A', 'B', 'C', 'D'], 'value1': [1, 2, 3, 4]})
df2 = pd.DataFrame({'key': ['B', 'D', 'E', 'F'], 'value2': [5, 6, 7, 8]})
# Efficient inner join
df_merged = pd.merge(df1, df2, on='key', how='inner')
print(df_merged)
৫. অপ্রয়োজনীয়ভাবে ডেটাফ্রেম কপি করা এড়িয়ে চলা
অনেক পান্ডাস অপারেশন ডেটাফ্রেমের কপি তৈরি করে, যা মেমরি-ইনটেনসিভ এবং সময়সাপেক্ষ হতে পারে। অপ্রয়োজনীয় কপি করা এড়াতে, যখন উপলব্ধ থাকে তখন inplace=True আর্গুমেন্ট ব্যবহার করুন, অথবা একটি অপারেশনের ফলাফল মূল ডেটাফ্রেমে আবার অ্যাসাইন করুন। `inplace=True` নিয়ে খুব সতর্ক থাকুন কারণ এটি ত্রুটি লুকাতে পারে এবং ডিবাগিং কঠিন করে তুলতে পারে। পুনরায় অ্যাসাইন করা প্রায়শই নিরাপদ, যদিও এটি সামান্য কম পারফরম্যান্ট হতে পারে।
উদাহরণ:
# Inefficient (creates a copy)
df_filtered = df[df['col1'] > 500]
# Efficient (modifies the original DataFrame in place - CAUTION)
df.drop(df[df['col1'] <= 500].index, inplace=True)
#SAFER - reassigns, avoids inplace
df = df[df['col1'] > 500]
৬. চাংকিং এবং ইটারেটিং
অত্যন্ত বড় ডেটাসেটগুলির জন্য যা মেমরিতে ফিট করতে পারে না, ডেটা চাংকে প্রসেস করার কথা বিবেচনা করুন। ফাইল থেকে ডেটা পড়ার সময় `chunksize` প্যারামিটার ব্যবহার করুন। চাংকগুলির মাধ্যমে পুনরাবৃত্তি করুন এবং প্রতিটি চাংকের উপর আপনার বিশ্লেষণ আলাদাভাবে সম্পাদন করুন। এর জন্য সতর্ক পরিকল্পনা প্রয়োজন যাতে বিশ্লেষণ সঠিক থাকে, কারণ কিছু অপারেশনের জন্য একবারে পুরো ডেটাসেট প্রসেস করতে হয়।
# Read CSV in chunks
for chunk in pd.read_csv('large_data.csv', chunksize=100000):
# Process each chunk
print(chunk.shape)
৭. প্যারালাল প্রসেসিংয়ের জন্য ডাস্ক ব্যবহার করা
ডাস্ক একটি প্যারালাল কম্পিউটিং লাইব্রেরি যা পান্ডাসের সাথে নির্বিঘ্নে সংহত হয়। এটি আপনাকে বড় ডেটাফ্রেম প্যারালালে প্রসেস করতে দেয়, যা পারফরম্যান্স উল্লেখযোগ্যভাবে উন্নত করতে পারে। ডাস্ক ডেটাফ্রেমকে ছোট ছোট পার্টিশনে বিভক্ত করে এবং সেগুলি একাধিক কোর বা মেশিনে বিতরণ করে।
import dask.dataframe as dd
# Create a Dask DataFrame
ddf = dd.read_csv('large_data.csv')
# Perform operations on the Dask DataFrame
ddf_filtered = ddf[ddf['col1'] > 500]
# Compute the result (this triggers the parallel computation)
result = ddf_filtered.compute()
print(result.head())
দ্রুত লুকআপের জন্য ইনডেক্সিং
একটি কলামে একটি ইনডেক্স তৈরি করা লুকআপ এবং ফিল্টারিং অপারেশনগুলিকে উল্লেখযোগ্যভাবে দ্রুত করতে পারে। পান্ডাস একটি নির্দিষ্ট মানের সাথে মিলে যাওয়া সারিগুলি দ্রুত সনাক্ত করতে ইনডেক্স ব্যবহার করে।
উদাহরণ:
# Set 'col3' as the index
df = df.set_index('col3')
# Faster lookup
value = df.loc['A']
print(value)
# Reset the index
df = df.reset_index()
তবে, খুব বেশি ইনডেক্স তৈরি করা মেমরি ব্যবহার বাড়াতে পারে এবং রাইট অপারেশনগুলিকে ধীর করে দিতে পারে। কেবল সেই কলামগুলিতে ইনডেক্স তৈরি করুন যা প্রায়শই লুকআপ বা ফিল্টারিংয়ের জন্য ব্যবহৃত হয়।
অন্যান্য বিবেচ্য বিষয়
- হার্ডওয়্যার: যদি আপনি নিয়মিতভাবে বড় ডেটাসেটের সাথে কাজ করেন তবে আপনার হার্ডওয়্যার (CPU, RAM, SSD) আপগ্রেড করার কথা বিবেচনা করুন।
- সফ্টওয়্যার: নিশ্চিত করুন যে আপনি পান্ডাসের সর্বশেষ সংস্করণ ব্যবহার করছেন, কারণ নতুন সংস্করণগুলিতে প্রায়শই পারফরম্যান্সের উন্নতি অন্তর্ভুক্ত থাকে।
- প্রোফাইলিং: আপনার কোডের পারফরম্যান্সের বাধাগুলি সনাক্ত করতে প্রোফাইলিং সরঞ্জামগুলি (যেমন,
cProfile,line_profiler) ব্যবহার করুন। - ডেটা স্টোরেজ ফরম্যাট: CSV এর পরিবর্তে Parquet বা Feather এর মতো আরও কার্যকর ডেটা স্টোরেজ ফরম্যাট ব্যবহার করার কথা বিবেচনা করুন। এই ফরম্যাটগুলি কলামনার এবং প্রায়শই সংকুচিত থাকে, যার ফলে ফাইলের আকার ছোট হয় এবং রিড/রাইট সময় দ্রুত হয়।
উপসংহার
দক্ষতার সাথে বড় ডেটাসেটের সাথে কাজ করার জন্য মেমরি ব্যবহার এবং পারফরম্যান্সের জন্য পান্ডাস ডেটাফ্রেম অপ্টিমাইজ করা অত্যন্ত গুরুত্বপূর্ণ। উপযুক্ত ডেটা টাইপ নির্বাচন করে, ভেক্টরাইজড অপারেশন ব্যবহার করে এবং আপনার ডেটাকে কার্যকরভাবে ইনডেক্স করে, আপনি মেমরি খরচ উল্লেখযোগ্যভাবে কমাতে এবং পারফরম্যান্স উন্নত করতে পারেন। পারফরম্যান্সের বাধাগুলি সনাক্ত করতে আপনার কোড প্রোফাইল করতে ভুলবেন না এবং অত্যন্ত বড় ডেটাসেটের জন্য চাংকিং বা ডাস্ক ব্যবহার করার কথা বিবেচনা করুন। এই কৌশলগুলি প্রয়োগ করে, আপনি ডেটা বিশ্লেষণ এবং ম্যানিপুলেশনের জন্য পান্ডাসের সম্পূর্ণ সম্ভাবনাকে আনলক করতে পারেন।