استخراج (Extraction) و تبدیل (Transformation) داده ها در پایتون — راهنمای جامع

۲۱۶۶ بازدید
آخرین به‌روزرسانی: ۲۸ خرداد ۱۴۰۱
زمان مطالعه: ۹ دقیقه
استخراج (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 ،row،row1 و row4 را انتخاب کرده است. در حالیکه، دومین خط کد، تنها row3 و row، rowرا انتخاب کرده. چند مثال بیشتر در همین رابطه در ادامه وجود دارد. انتخاب ستون‌ها از برچسب «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

اگر مطلب بالا برای شما مفید بوده، آموزش‌های زیر نیز به شما پیشنهاد می‌شود:

^^

بر اساس رای ۷ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
towards data science
۱ دیدگاه برای «استخراج (Extraction) و تبدیل (Transformation) داده ها در پایتون — راهنمای جامع»

عالی بود بانو. ممنون

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *