ডেটা ক্লিনিং

“Be happy for this moment. This moment is your life.”― OMAR KHAYYAM

যেকোনো ডেটা সায়েন্স প্রজেক্ট এর অন্যতম গুরুত্বপূর্ণ অংশ হচ্ছে ডেটা ক্লিনিং করা। বাস্তব জীবনে আমরা যে সকল ডেটা সেট পেয়ে থাকি তার অধিকাংশই বিভিন্ন গার্বেজ ভ্যালু দ্বারা পূর্ণ থাকে এই অবস্থায় এরকম ডেটা সেট পরবর্তী এক্সপ্লোরাটরি ডাটা এনালাইসিস অথবা মেশিন লার্নিং এর জন্য উপযুক্ত নয় অর্থাৎ পরবর্তী ধাপ সমূহর আগেই আমাদেরকে ডেটাসেট ক্লিন এবং প্রসেস করে নিতে হবে।

এ পর্যায়ে আমরা দেখবো কীভাবে ধাপে ধাপে একটি ডেটাসেটকে ক্লিন করতে হয় । ডেটাসেটকে ক্লিন করার জন্য বিভিন্ন পদ্ধতি রয়েছে। মূলত বিভিন্ন ধরনের সমস্যার জন্য আমরা আলাদা আলাদা পদ্ধতি প্রয়োগ করবো।

আইবিএম অ্যানালাইটিকসের ভাষ্য মতে একজন ডেটা সাইন্টিস্টকে তার মোট সময়ের শতকরা 80 ভাগ সময় তিনি ডেটা প্রসেসিং এর পিছনে ব্যয় করতে হয় সুতরাং সহজেই বোঝা যাচ্ছে ডেটা সাইন্স পাইপলাইনে ডেটা ক্লিনিং এবং প্রসেসিং কতখানি গুরুত্বপূর্ণ।

লোডিং রিনেম

প্রথমেই আমরা ডেটা সেটটি আমাদের নোটবুকে লোড করে নিচ্ছি।

import pandas as pd
import seaborn as sns
import numpy as np

df = pd.read_csv('https://raw.githubusercontent.com/fazlyrabbi77/DataProcessing/master/real-estate.csv')
df

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

আমাদের ডেটাসেট থেকে সহজেই বোঝা যায় এখানে বেশ কিছু NaN ভ্যালু রয়েছে, এছাড়া অনেক গারবেজ ভ্যালুও রয়েছে। এই অবস্থায় এই ডেটাসেট ডেটা মডেলিং এর জন্য অনুপযুক্ত। তাই পরবর্তী ধাপে যাওয়ার আগে আমাদের এই ডেটাসেটকে ক্লিন করে উপযোগী করে তুলতে হবে।

অনেক সময় আমাদের ডেটাসেটের ফিচার বা কলাম এর নাম পরিবর্তন করা প্রয়োজন হয় সেক্ষেত্রে নিচের ফাংশনের মাধ্যমে আমরা কলাম রিনেম করতে পারি। inplace=True ব্যাবহার না করলে সেটি আবার আগের অবস্থায় ফিরে যাবে, তাই এই পরিবর্তনটিকে সংরক্ষনের জন্য আমরা inplace=True ব্যাবহার করেছি।

#Renaming Columns with inplace
df.rename(columns = {"NUM_BEDROOMS": "BEDROOMS",  "NUM_BATH":"BATH"},inplace=True) 
df.head()

NaN ভ্যালু চেক করা

ডেটাসেটে কোন NaN ভ্যালু আছে কিনা সেটা জানা অত্যন্ত জরুরি। df.isnull( ).values.any( ) এর আউটপুট True এর মানে আমাদের ডেটাসেটে NaN ভ্যালু রয়েছে। df.isnull( ).values.sum( ) এর মাধ্যমে মাধ্যমে দেখতে পাই আমাদের ৮ টি NaN ভ্যালু রয়েছে। সর্বশেষে আমার দেখে নিলাম আমাদের কোন কলামে কয়টি NaN ভ্যালু রয়েছে।

df.isnull().values.any()

True

df.isnull().values.sum()

8

এখন আমরা দেখতে চাচ্ছি কোন রো'তে কয়টি NaN ভ্যালু আছে । সেটা দেখার জন্য আমরা নিচের কোড রান করবো।

#Showing the null values as per attributes
null_columns=df.columns[df.isnull().any()]
df[null_columns].isnull().sum()
print(df[df.isnull().any(axis=1)][null_columns].head())

NaN ভ্যালু হ্যান্ডেলিং

NaN ভ্যালু যা আমরা পেয়েছি এবার সেগুলোকে ট্রিটমেন্ট করারা পালা। NaN ভ্যালুগুলো যেসব রো'তে রয়েছে আমরা সেগুলোকে ডিলিট করে দিতে পারি, কিন্তু ডেটাসেট যদি ছোট হয়ে থাকে সেক্ষত্রে আমাদের অনেক ডেটা হারাতে হবে, তাই এটা কোন কার্যকারী পদ্ধতি নয়। আমরা বিভিন্ন উপায়ে চেষ্টা করবো NaN ভ্যালুগুলোকে সম্ভাব্য যৌক্তিক কোন ভ্যালু দ্বারা পূরণ করে দিতে। NaN ভ্যালু হ্যান্ডেল করার অনেক পদ্ধতি আছে , আমারা ধাপে ধাপে সেগুলো দেখার চয়েসটা করবো।

আমরা আগেই দেখেছিলাম PID কলামে একটি NaN ভ্যালু রয়েছে। যে রো'তে নাল ভ্যালু রয়েছে তার আগের রো এর PIDএর মান 1000040000 এবং পরের রো এর PID এর1000060000 মান। সুতরাং আমারা কমন সেন্স দিয়ে বুঝে নিলাম এই রোএর NaN ভ্যালুর মান হবে 100005000 । আমরা fillna মেথডের মাধ্যমে সহজেই PID কলামের NaN ভ্যালু পূর্ণ করে দিলাম।

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

# Filling null values with specific value
df['PID'].fillna(100005000,inplace=True)
df

রো বা কলাম মুছে ফেলাঃ সম্পূর্ণ কলামটি মুছে ফেলতে চাইলে df.drop(['PID'], axis=1,inplace=True) কোডটি রান করতে হবে। এখানে inplace=True না দিলে পরিবর্তনটি স্থায়ী হবে না। axis=1 দ্বারা বোঝানো হয়েছে কলামকে।

আমরা চাইলে রো মুছে ফেলতে পারি। df.drop([7,8]) কোডের মাধ্যমে রো ৭ এবং ৮ নাম্বার রো মুছে ফেলা যাবে। এক্ষেত্রেও inplace=True না দিলে পরিবর্তনটি স্থায়ী হবে না।

আমাদের ডেটাসেটে ST_NUM কলামে দুটি NaN ভ্যালু আছে। আমারা এবার দেখবো কিভাবে রো নাম্বার ভিত্তিক NaN ভ্যালু পূরণ করা যায়। 2 নাম্বার রো'তে ST_NUM NaN হলেও ST_NAME দেয়া রয়েছে LEXINGTON। 1 নাম্বার রো'তে যেহেতু LEXINGTON এর ST_NUM দেয়া আছে 197 , সুতরাং উক্ত NaN ভ্যালু 197 দ্বারা পূরণ করবো। 6 নাম্বার রো এর ST_NUM এর NaN ভ্যালুর ক্ষেত্রে আমরা জেনে নিয়েছি যে WASHINGTON স্টিট এর ST_NUM হোল 208।

# Row wise data filling
df.loc[2,'ST_NUM'] = 197
df.loc[6,'ST_NUM'] = 208
df

আনওয়ান্টেন্ড ভ্যালু হ্যান্ডেলিং

OWN_OCCUPIED কলামে NaN ছাড়াও 12 নামের একটি আনওয়ান্টেন্ড ভ্যালু রয়েছে , যা এই কলামের ভ্যালুর ডোমেইনের বাইরে। এই কলামের ভ্যালুগুলো অবশ্যই Y অথবা N হবে। নিচের পদ্ধতিতে আমরা এই কলামের আনওয়ান্টেন্ড (এক্ষেত্রে নিউম্যারিক্যাল) ভ্যালুকে NaN এ কনভার্ট করবো।

#unwanted value treatment
cnt=0
for row in df['OWN_OCCUPIED']:
    try:
        int(row)
        df.loc[cnt, 'OWN_OCCUPIED']=np.nan
    except ValueError:
        pass
    cnt+=1
df

BEDROOMS, BATH এবং SQ_FT এই তিনটি কলামে শুধুমাত্র নিউম্যারিক্যাল ভ্যালু থাকার কথা কিন্তু আমরা দেখতে পাচ্ছি এই কলাম গুলোতে কিছু আনওয়ান্টেড নন-নিউম্যারিক্যাল ভ্যালু রয়েছে। সুতরাং আমরা প্রথমেই এদেরকে NaN এ কনভার্ট করবো।

df['BEDROOMS'] = pd.to_numeric(df['BEDROOMS'], errors='coerce')
df['BATH'] = pd.to_numeric(df['BATH'], errors='coerce')
df['SQ_FT'] = pd.to_numeric(df['SQ_FT'], errors='coerce')
df

মোড ভ্যালুর মাধ্যমে NaN ভ্যালু পূরণ

কোন নির্দিষ্ট মান ছাড়াও কোন ভ্যারিয়েবলের মোডের মান দ্বারাও NaN ভ্যালু পূরণ করা যায়। এর ফলে যে ভ্যালুটি সবথেকে বেশী বার এসেছে সেই ভ্যালুটি দ্বারা NaN ভ্যালু পূরণ হবে।

df['OWN_OCCUPIED'].fillna(df['OWN_OCCUPIED'].mode()[0], inplace=True)
df

মোড ছাড়াও মিন, মিডিয়ান ইত্যাদি দ্বারাও NaN ভ্যালু পূরণ করা যায়।

মিডিয়ানের ব্যবহার

এখন আমরা দেখতে চাই ১,২ ও ৩ বেডরুমের এপার্টমেন্টের সাইজের মিডিয়ান ভ্যালু কত ।

#Group By parameter check
df.groupby('BEDROOMS')['SQ_FT'].median()

এখন বেডরুমের সংখ্যা অনুযায়ী প্রাপ্ত মিডিয়ান এপার্টমেন্টের সাইজ দ্বারা SQ_FT পূর্ণ করে দেয়া হয়েছে।

# Filling Null with group by vparameter
df['SQ_FT'] = df['SQ_FT'].fillna(df.groupby('BEDROOMS')['SQ_FT'].transform('median'))
df['SQ_FT'] = df['SQ_FT'].fillna(df['SQ_FT'].median())
df

এবার এপার্টমেন্টের সাইজ অনুযায়ী ৯৫০ স্কয়ার ফিটের ছোট এপার্টমেন্টেে ১ টি বেডরুম এবং এর ১০০০ স্কয়ার ফিটের বড় এপার্টমেন্টেের ক্ষেত্রে ৩ টি বেডরুম দ্বারা BEDROOM এর NaN পূরণ করা হয়েছে।

df.loc[2,'BEDROOMS'] = 1
df.loc[5,'BEDROOMS'] = 1
df.loc[8,'BEDROOMS'] = 3
df

bfill এবং ffill

BATH ভেরিয়্যাবেলের NaN ভ্যালু সমূহ bfill আমরা মেথডে পূরণ করবো, এই পদ্ধতিতেNaN ভ্যালু তার পরের ভ্যালুর মান নিয়ে স্বয়ংক্রিয় ভাবে পূরণ হয়। অপরদিকে ffill দ্বারা NaN ভ্যালু তার আগের ভ্যালুর মান নিয়ে স্বয়ংক্রিয় ভাবে পূরণ হয়।

df['BATH']=df['BATH'].fillna(method='bfill')
df

টাইপ কনভার্সন

আমদের ডেটাসেটের PID , ST_NUM, BEDROOMS ও SQ_FT ভ্যারিয়েবল সমূহ টাইপ ডেটা ছিল এগুলকে এ কনভার্ট করার জন্য আমরা নিচের কোড রান করবো।

#Converting street number to int
df.PID = df.PID.astype('int64') 
df.ST_NUM = df.ST_NUM.astype('int64') 
df.BEDROOMS = df.BEDROOMS.astype('int64') 
df.BATH = df.BATH.astype('int64') 
df.SQ_FT = df.SQ_FT.astype('int64') 
df

ফাইনালি এখন আমরা যে ডেটাসেটটি পেলাম সেটি একটি ক্লিন ডেটাসেট !

কোথায় পদ্ধতি উপযুক্ত ?

  • ডেটায় আউটলায়ার থাকলে মিন ব্যবহার যৌক্তিক নয়।

  • ক্যাটেগরিক্যাল ডেটার ক্ষেত্রে মিন এবং মিডিয়ান কোনটিই অর্থবহ নয় এক্ষেত্রে মোড ব্যবহার অর্থবহ হতে পারে।

  • কন্টিনিউয়াস ডেটার ক্ষেত্রে মিডিয়ান বেশ কার্যকর ।

  • রিগ্রেশন মডেলের ক্ষেত্রে ০ দ্বারাও মিসিং ভ্যালু পূরণ করাও কখনো কখনো অর্থবহ।

  • টাইম সিরিজ ডেটার ক্ষেত্রে সাধারন পদ্ধতি অর্থবহ নয় এক্ষেত্রে মুভিং এভারেজ বা অন্যান্য পদ্ধতি ব্যবহার করা যেতে পারে।

Last updated