استخراج (Extraction) و تبدیل (Transformation) داده ها در پایتون — راهنمای جامع
«استخراج» (Extraction)، فیلتر و «تبدیل داده» (Transformation) از «دیتافریمها» (DataFrame) به منظور رسیدن به دادههایی که امکان تحلیل آنها وجود دارد مساله بسیار مهمی در «علم داده» (Data Science) است. کتابخانه «pandas» روشهایی زیادی دارد که این فرآیند را کارآمد و بصری میکنند. در این مطلب، این روشها به همراه نمونه کدهای پیادهسازی و مثالهایی برای آنها ارائه شدهاند. در مثالهای بیان شده در این نوشته یک دیتافریم (DataFrame) نمونه با اعداد تصادفی به عنوان یک مثال جهت تشریح مطالب مورد استفاده قرار گرفته است.
1import pandas as pd
2import numpy as np
3cols = ['col0', 'col1', 'col2', 'col3', 'col4']
4rows = ['row0', 'row1', 'row2', 'row3', 'row4']
5data = np.random.randint(0, 100, size=(5, 5))
6df = pd.DataFrame(data, columns=cols, index=rows)df.head()
col0 col1 col2 col3 col4 row0 24 78 42 7 96 row1 40 4 80 12 84 row2 83 17 80 26 15 row3 92 68 58 93 33 row4 78 63 35 70 95
اندیسگذاری دیتافریمها
جهت استخراج داده از دیتافریم Pandas میتوان از «اندیسگذاری مستقیم» (direct indexing) یا «اکسسورها» (accessors) استفاده کرد. برای انتخاب سطرها و ستونهای لازم میتوان از برچسب آنها استفاده کرد.
1df['col1']['row1']
4
توجه به این نکته ضروری است که در این نوع از اندیسگذاری، ابتدا برچسب ستون و سپس سطر تعیین میشود. حقیقتی که باید به آن توجه شود این است که مجموعه داده مورد استفاده در اینجا بسیار کوچک به شمار میآید، در حالیکه در مسائل جهان واقعی با مجموعه دادههای فوقالعاده سنگینتری کار میشود. بنابراین انتخاب دادهها با اکسسورهای loc. و iloc. بهتر خواهد بود. تفاوت بین این دو در آن است که loc. برچسبها و iloc. اندیسها را میپذیرد. همچنین، هنگامی که از اکسسور برای اولین بار استفاده میشود، سطرها و ستونها نیز تعیین میشوند. بنابراین برای انتخاب یک مقدار یکتا با استفاده از اکسسورها میتوان به صورت زیر عمل کرد.
1df.loc['row4', 'col2']
35
1df.iloc[4, 2]
35
با استفاده از اندیسگذاری، میتوان یک مقدار یکتا، «سری» (Series) یا دیتافریم را از یک دیتافریم انتخاب کرد. چگونگی انتخاب یک مقدار در بالا نشان داده شده است. برای انتخاب چند ستون، تنها کافی است لیست تو در توی برچسبها پاس داده شود تا دیتافریم در خروجی دریافت شود.
1df_new = df[['col1','col2']]
2df_new.head(3)
col1 col2 row0 78 42 row1 4 80 row2 17 80
برای انتخاب سطرهای مشخص، اندیسهای آن را افزوده تا در خروجی دیتافریم دریافت شوند. این روش «slicing» (برشزنی) نامیده میشود و جزئیات بیشتر پیرامون آن در ادامه ارائه شده است.
1df_new = df[['col1','col2']][1:4]
2df_new.head(3)
col1 col2 row1 4 80 row2 17 80 row3 68 58
برای انتخاب یک «سری» (Series)، باید یک ستون تنها با همه یا بازهای از سطرها انتخاب شود. هر خط از کد خروجی مشابهی خواهد داشت.
1df['col0']
2df.loc[:,'col0']
3df.iloc[:, 0]
row0 24 row1 40 row2 83 row3 92 row4 78 Name: col0, dtype: int32
«دو نقطه» (colon) به معنی آن است که همه سطرها یا همه ستونها انتخاب شوند، ([:,:]df.loc یا [:,:]df.iloc) همه مقادیر را باز میگرداند. اکنون نیاز به slicing یعنی انتخاب بازه مشخصی از دادهها است. برای برش زدن یک سری (slicing ) کافی است یک بازه از سطرهایی که قصد انتخاب آنها وجود دارد با استفاده از اندیس آنها اضافه شوند.
1df['col3'][2:5]
row2 26 row3 93 row4 70 Name: col3, dtype: int32
در رابطه با بازهدهی در پایتون این نکته را نباید فراموش کرد که خروجی عنصر ابتدایی بازه را شامل میشود و عنصر انتهایی را شامل نمیشود. بنابراین، کد بالا سطرهایی با اندیسهای ۵، ۶، ۷، ۸ و ۹ را باز میگرداند. (و اندیسها از صفر آغاز میشوند.)
Slicing دیتافریمها به همین شکل و تنها با یک تفاوت جزئی عمل میکند. هنگام استفاده از loc. (برچسبها) ابتدا و انتهای بازه را شامل میشود. برای مثال، انتخاب سطرها از برچسب «row1» به برچسب «row4» یا از اندیس سطر ۱ تا اندیس ۴ و همه ستونها با قطعه کد زیر انجام میشود.
1df.loc['row1':'row4', :]
col0 col1 col2 col3 col4 row1 40 4 80 12 84 row2 83 17 80 26 15 row3 92 68 58 93 33 row4 78 63 35 70 95
1df.iloc[1:4, :]
col0 col1 col2 col3 col4 row1 40 4 80 12 84 row2 83 17 80 26 15 row3 92 68 58 93 33
اولین خط از کد بالا سطرهای row3 ،row2 ،row1 و row4 را انتخاب کرده است. در حالیکه، دومین خط کد، تنها row3 و row2 ، row1 را انتخاب کرده. چند مثال بیشتر در همین رابطه در ادامه وجود دارد. انتخاب ستونها از برچسب «col1» تا برچسب «col4» یا از اندیس ستون ۱ تا اندیس ۴ و همه سطرها با قطعه کد زیر انجام میشود.
1df.loc[:, 'col1':'col4']
col1 col2 col3 col4 row0 78 42 7 96 row1 4 80 12 84 row2 17 80 26 15 row3 68 58 93 33 row4 63 35 70 95
1df.iloc[:, 1:4]
col1 col2 col3 row0 78 42 7 row1 4 80 12 row2 17 80 26 row3 68 58 93 row4 63 35 70
انتخاب سطرها از برچسب «row1» تا برچسب «row4» یا از اندیس سطر ۱ تا ۴ و ستونها از برچسب ستون «col1» تا «col4» یا از اندیس ۱ تا اندیس ۴ با قطعه کد زیر انجام میشود.
1df.loc['row1':'row4', 'col1':'col4']
col1 col2 col3 col4 row1 4 80 12 84 row2 17 80 26 15 row3 68 58 93 33 row4 63 35 70 95
1df.iloc[1:4,1:4]
col1 col2 col3 row1 4 80 12 row2 17 80 26 row3 68 58 93
از یک لیست برای انتخاب ستونها یا سطرهای مشخصی که در بازه قرار ندارند استفاده میشود.
1df.loc['row2':'row4', ['col1','col3']]
col1 col3 row2 17 26 row3 68 93 row4 63 70
1df.iloc[[2,4], 0:4]
col0 col1 col2 col3 row2 83 17 80 26 row4 78 63 35 70
فیلتر کردن دیتافریمها
فیلتر کردن یک ابزار عمومیتر برای انتخاب بخشی از دادهها بر اساس مشخصههای دادهها و نه اندیس یا برچسب آنها است. دیتافریمها دارای چندین متد برای فیلتر کردن هستند. ایده اساسی همه این متدها یک «سری بولی» (Boolean Series) است. df[‘col1’] > 20 (با این فرض که col1 از نوع صحیح است) یک سری بولی را باز میگرداند که در آن این شرط صحت دارد. خروجی متد ()head. در زیر قرار گرفته، بنابراین نیازی نیست برای تطبیق خروجیها صفحه مرورگر را بالا و پایین کرد.
col0 col1 col2 col3 col4 row0 24 78 42 7 96 row1 40 4 80 12 84 row2 83 17 80 26 15 row3 92 68 58 93 33 row4 78 63 35 70 95
بنابراین، برای انتخاب بخشی از DataFrame که در آن مقادیر col1 از ۲۰ بزرگتر هستند از کد زیر استفاده میشود.
1df[df['col1'] > 20]
2# assigning variable also works
3condition = df['col1'] > 20
4df[condition]
col0 col1 col2 col3 col4 row0 24 78 42 7 96 row3 92 68 58 93 33 row4 78 63 35 70 95
میتوان این فیلترها را با استفاده از عملگرهای منطقی استاندارد ترکیب کرد (و: &، یا: |، نقیض: ~). چگونگی استفاده از پرانتزها در کد زیر قابل توجه است.
1df[(df['col1'] > 25) & (df['col3'] < 30)] # logical and
col0 col1 col2 col3 col4 row0 24 78 42 7 96
1df[(df['col1'] > 25) | (df['col3'] < 30)] # logical or
col0 col1 col2 col3 col4 row0 24 78 42 7 96 row1 40 4 80 12 84 row2 83 17 80 26 15 row3 92 68 58 93 33 row4 78 63 35 70 95
1df[~(df['col1'] > 25)] # logical not
col0 col1 col2 col3 col4 row1 40 4 80 12 84 row2 83 17 80 26 15
کار با مقادیر ۰ و NaN
تقریبا در کلیه مواقع مجموعه دادهها دارای مقادیر صفر یا NaN هستند و کاربر قطعا نیاز دارد بداند این مقادیر کجا قرار دارند. در مجموعه دادهای که برای مثالهای این نوشته استفاده شده مقادیر ۰ و NaN وجود ندارند، بنابراین برای شبیهتر شدن به یک مجموعه داده واقعی باید کمی دستکاری شود.
1df.iloc[3, 3] = 0
2df.iloc[1, 2] = np.nan
3df.iloc[4, 0] = np.nan
4df['col5'] = 0
5df['col6'] = np.NaN
6df.head()
col0 col1 col2 col3 col4 col5 col6 row0 24.0 78 42.0 7 96 0 NaN row1 40.0 4 NaN 12 84 0 NaN row2 83.0 17 80.0 26 15 0 NaN row3 92.0 68 58.0 0 33 0 NaN row4 NaN 63 35.0 70 95 0 NaN
برای انتخاب ستونهایی که هیچ مقدار صفری ندارند میتوان از متد ()all. استفاده کرد (همه دادهها نمایش داده میشوند).
1df.loc[:, df.all()]
col0 col1 col2 col4 col6 row0 24.0 78 42.0 96 NaN row1 40.0 4 NaN 84 NaN row2 83.0 17 80.0 15 NaN row3 92.0 68 58.0 33 NaN row4 NaN 63 35.0 95 NaN
اگر هدف پیدا کردن ستونی باشد که دستکم یک مقدار غیر صفر (هر مقداری) در آن قرار دارد، کد زیر کارآمد است.
1df.loc[:, df.any()]
col0 col1 col2 col3 col4 row0 24.0 78 42.0 7 96 row1 40.0 4 NaN 12 84 row2 83.0 17 80.0 26 15 row3 92.0 68 58.0 0 33 row4 NaN 63 35.0 70 95
برای انتخاب ستونهای دارای حداقل یک مقدار NaN از کد زیر استفاده میشود.
1df.loc[:, df.isnull().any()]
col0 col2 col6 row0 24.0 42.0 NaN row1 40.0 NaN NaN row2 83.0 80.0 NaN row3 92.0 58.0 NaN row4 NaN 35.0 NaN
برای انتخاب ستونهای بدون NaN کد زیر قابل استفاده است.
1df.loc[:, df.notnull().all()]
col1 col3 col4 col5 row0 78 7 96 0 row1 4 12 84 0 row2 17 26 15 0 row3 68 0 33 0 row4 63 70 95 0
میتوان سطرهای دارای NaN را به طور کامل حذف کرد، اما این کار شروع یک بازی خطرناک است، زیرا حذف کردن معمولا راه حل مناسبی نیست. برای برخورد با چنین مقادیری باید دادهها را فهمید و با چنین سطرهایی به شکلی هوشمندانه برخورد کرد. و اما برای افرادی که تمایل دارند چگونگی حذف چنین سطرهایی را بیاموزند، باید گفت قطعه کد زیر این کار را انجام میدهد.
1df.dropna(how='all', axis=1) # if all values in a column are NaN it will be dropped
col0 col1 col2 col3 col4 col5 row0 24.0 78 42.0 7 96 0 row1 40.0 4 NaN 12 84 0 row2 83.0 17 80.0 26 15 0 row3 92.0 68 58.0 0 33 0 row4 NaN 63 35.0 70 95 0
1df.dropna(how='any', axis=1) # if any value in a row is NaN it will be dropped
col1 col3 col4 col5 row0 78 7 96 0 row1 4 12 84 0 row2 17 26 15 0 row3 68 0 33 0 row4 63 70 95 0
این متدها دیتافریم اصلی را ویرایش نمیکنند، بنابراین برای ادامه کار با دادههای فیلتر شده نیاز به تخصیص آن به یک دیتافریم جدید یا رگرسیون زدن روی آنچه موجود است وجود دارد.
1df = df.dropna(how='any', axis=1)
زیبایی فیلتر کردن در آن است که میتوان مقادیر یک ستون را بر پایه ستون دیگری انتخاب و یا ویرایش کرد. برای مثال، میتوان مقادیری از col1 را که col2 آنها بیشتر از ۳۵ است انتخاب و مقادیر آنها را با افزودن ۵ به هر یک، به روز رسانی کرد.
1# Find a column based on another
2df['col1'][df['col2'] > 35]
row0 78 row2 17 row3 68 Name: col1, dtype: int32
1# Modify a column based on another
2df['col1'][df['col2'] > 35] += 5
3df['col1']
row0 83 row1 4 row2 22 row3 73 row4 63 Name: col1, dtype: int32
تبدیل دیتافریمها
پس از آنکه دادهها انتخاب و یا فیلتر شدند، زمان تبدیل آنها فرا میرسد. بهترین راه برای انجام این کار استفاده از متدهای به ارث رفته برای دیتافریمها یا توابع سراسری numpy است که کل یک ستون داده را به صورت آگاهانه از عناصر تبدیل میکنند. مثالی از این مورد تابع ()floordiv. کتابخانه pandas (از مستندات این تابع: تقسیم صحیح دیتافریم و دیگر موارد، به صورت آگاه از عناصر) یا تابع ()floor_divide. کتابخانه numpy (از مستدات این تابع: بزرگترین مقدار صحیحی که کوچکتر یا مساوی تقسیم ورودیها است را باز میگرداند) است. اگر این توابع موجود نبودند، کاربر میتواند کد خود را بنویسد و با متد ()apply. از آن استفاده کند.
1def some_func(x):
2 return x * 2
3df.apply(some_func) -- # update each entry of a DataFrame without any loops
4# lambda also works
5df.apply(lambda n: n*2) -- # the same
این توابع تبدیلات را باز نمیگرداند، بنابراین باید آن را به طور ضمنی ذخیره کرد.
1df['new_col'] = df['col4'].apply(lambda n: n*2)
2df.head()
col0 col1 col2 col3 col4 col5 col6 new_col row0 24.0 83 42.0 7 96 0 NaN 192 row1 40.0 4 NaN 12 84 0 NaN 168 row2 83.0 22 80.0 26 15 0 NaN 30 row3 92.0 73 58.0 0 33 0 NaN 66 row4 NaN 63 35.0 70 95 0 NaN 190
اگر اندیس یک رشته باشد، دارای اکسسور str. است که کاربر را قادر میسازد کل اندیس را به طور یکباره ویرایش کند.
1df.index.str.upper()
Index(['ROW0', 'ROW1', 'ROW2', 'ROW3', 'ROW4'], dtype='object')
همچنین، نمیتوان از متد ()apply. روی اندیس استفاده کرد (جایگزین آن ()map. است).
1df.index = df.index.map(str.lower)
Index(['row0', 'row1', 'row2', 'row3', 'row4'], dtype='object')
اما ()map. روی ستونها نیز به خوبی قابل استفاده است. کد زیر مثالی برای این موضوع محسوب میشود.
1# Create the dictionary: red_vs_blue
2red_vs_blue = {0:'blue', 12:'red'}
3
4# Use the dictionary to map the 'col3' column to the new column df['color']
5df['color'] = df['col3'].map(red_vs_blue)
6df.head()
col0 col1 col2 col3 col4 col5 col6 new_col color row0 24.0 83 42.0 7 96 0 NaN 192 NaN row1 40.0 4 NaN 12 84 0 NaN 168 red row2 83.0 22 80.0 26 15 0 NaN 30 NaN row3 92.0 73 58.0 0 33 0 NaN 66 blue row4 NaN 63 35.0 70 95 0 NaN 190 NaN
عملگرهای حسابی روی سریها و دیتافریمها به طور مستقیم عمل میکنند. عبارت زیر یک ستون جدید را خواهد ساخت که در آن هر مقدار با اندیس n برابر است با مجموع مقادیر با اندیس n از «col3» تا «col7».
1df['col7'] = df['col3'] + df['col4']
2df.head()
col0 col1 col2 col3 col4 col5 col6 new_col color col7 row0 24.0 83 42.0 7 96 0 NaN 192 NaN 103 row1 40.0 4 NaN 12 84 0 NaN 168 red 96 row2 83.0 22 80.0 26 15 0 NaN 30 NaN 41 row3 92.0 73 58.0 0 33 0 NaN 66 blue 33 row4 NaN 63 35.0 70 95 0 NaN 190 NaN 165
اگر مطلب بالا برای شما مفید بوده، آموزشهای زیر نیز به شما پیشنهاد میشود:
- گنجینه آموزشهای برنامه نویسی پایتون (Python)
- مجموعه آموزشهای آمار، احتمالات و دادهکاوی
- مجموعه آموزشهای یادگیری ماشین و بازشناسی الگو
- مجموعه آموزشهای هوش محاسباتی
- مجموعه آموزشهای شبکههای عصبی مصنوعی
- آموزش برنامهنویسی R و نرمافزار R Studio
- مجموعه آموزشهای برنامه نویسی متلب (MATLAB)
- معرفی منابع آموزش ویدئویی هوش مصنوعی به زبان فارسی و انگلیسی
- آموزش پایتون (Python) — مجموعه مقالات جامع وبلاگ فرادرس
^^
عالی بود بانو. ممنون