پاکسازی داده (Data Cleaning) در پایتون با استفاده از NumPy و Pandas — راهنمای جامع

«دانشمندان داده» (Data Scientists) حجم زیادی از زمان خود در پروژههای «تحلیل داده» (Data Analysis) را به «پاکسازی دادهها» (Data Cleansing | Data Cleaning) و تبدیل آنها به شکل قابل پردازش اختصاص میدهند. در حقیقت، دانشمندان داده بسیاری بر این باور هستند که گامهای ابتدایی گردآوری و پاکسازی دادهها در فرآیند تحلیل داده و دادهکاوی، ۸۰ درصد از کل کار آنها را تشکیل میدهد.
بنابراین، برای افرادی که تمایل دارند وارد حوزه دادهکاوی و تحلیل داده شوند تسلط بر روشهای مواجهه با دادههای کثیف (Dirty data | Messy Data) الزامی و حائز اهمیت محسوب میشود. منظور از دادههای کثیف دادههایی است که شامل مقادیر ناموجود، فرمتهای ناسازگار، موارد ثبت شده ناقص یا دورافتادگیهای فاقد معنی هستند. در این راهنما، از کتابخانههای Pandas و NumPy زبان برنامهنویسی پایتون به منظور پاکسازی دادهها استفاده خواهد شد. مطالعه مطلب «یادگیری علم داده با پایتون — از صفر تا صد» نیز به افرادی که در آغاز راه یادگیری علم داده با پایتون قرار دارند توصیه میشود.
در مقاله پیش رو سرفصلهای زیر مورد بررسی قرار خواهد گرفت.
- حذف ستونهای غیر لازم از دیتافریم
- تغییر اندیس یک دیتافریم
- استفاده از متدهای ()str. برای پاک کردن ستونها
- استفاده از تابع ()DataFrame.applymap برای پاکسازی کل مجموعه داده به صورت مولفه به مولفه
- نامگذاری مجدد ستونها به منظور ایجاد مجموعه برچسبهای قابل تشخیصتر
- گذر کردن از سطرهای غیر لازم در فایل CSV
مجموعه دادههایی که در مثالهای این نوشتار مورد استفاده قرار گرفتهاند عبارتند از:
- BL-Flickr-Images-Book.csv: یک فایل CSV که شامل اطلاعاتی پیرامون کتابهای موجود در کتابخانه بریتانیا «British Library» است.
- university_towns.txt: یک فایل متنی که در برگیرنده اسامی شهرهای دارای کالج در هر یک از ایالتهای آمریکا است.
- olympics.csv: یک فایل CSV که حاوی اطلاعات خلاصهای پیرامون مشارکت کلیه کشورها در المپیک تابستانی و زمستانی است.
مجموعه دادههای بالا را میتوان از مخزن گیتهاب (GitHub repository) پایتون دانلود کرد. انجام این کار به مخاطبان این مطلب اکیدا توصیه میشود، زیرا میتوانند گام به گام و همراه با مطلب، آنچه آموزش داده شده را تمرین کرده و بیاموزند. در ادامه این مطلب فرض بر این است که مطالعهکنندگان دارای آشنایی مقدماتی با کتابخانههای Pandas و NumPy و به ویژه «سریها» (Series) و «دیتافریمها» (DataFrame)، متدهای متداول قابل اعمال بر آنها و همچنین مقدار NaN در NumPy هستند. افرادی که با مباحث یاد شده آشنایی ندارند میتوانند پیش از ادامه مقاله پیش رو، این مطلب را مطالعه کنند.
اکنون به اصل مطلب، یعنی پاکسازی دادههای کثیف با استفاده از NumPy و Pandas پرداخته میشود. در ابتدا، کار با ایمپورت کردن ماژولهای مورد نیاز (یعنی دو کتابخانه یاد شده) آغاز میشود.
>>> import pandas as pd >>> import numpy as np
حذف ستونها در یک DataFrame
در اغلب پروژههای تحلیل داده و دادهکاوی، پژوهشگران با این مساله مواجه هستند که همه دستههای دادههای موجود مفید نیستند. برای مثال، ممکن است یک مجموعه داده از اطلاعات دانشآموزان شامل نام، مقطع تحصیلی، نام والدین و آدرس آنها وجود داشته باشد، اما هدف تحلیل مقطع تحصیلی دانشآموزان باشد. در این مثال، آدرس منزل یا نام والدین در تحلیلها فاقد اهمیت محسوب میشوند.
نگهداری این اطلاعات بلااستفاده صرفا فضا اشغال کرده و سرعت تحلیل را در زمان اجرا کاهش میدهد. Pandas یک راهکار کاربردی برای حذف ستونها یا سطرهای ناخواسته از دیتافریم (DataFrame) با تابع ()drop فراهم میکند. در ادامه مثال کوچکی از چگونگی حذف تعدادی از ستونها از یک دیتافریم نشان داده شده است. ابتدا، یک دیتافریم از فایل CSV مربوط به مجموعه داده BL-Flickr-Images-Book.csv ساخته میشود. در این مثال، مسیر مربوطه به pd.read_csv پاس داده میشود، بدین معنا که مجموعه دادهها در پوشهای با نام Datasets در دایرکتوری در حال کار فعلی قرار دارند.
>>> df = pd.read_csv('Datasets/BL-Flickr-Images-Book.csv') >>> df.head() Identifier Edition Statement Place of Publication \ 0 206 NaN London 1 216 NaN London; Virtue & Yorston 2 218 NaN London 3 472 NaN London 4 480 A new edition, revised, etc. London Date of Publication Publisher \ 0 1879 [1878] S. Tinsley & Co. 1 1868 Virtue & Co. 2 1869 Bradbury, Evans & Co. 3 1851 James Darling 4 1857 Wertheim & Macintosh Title Author \ 0 Walter Forbes. [A novel.] By A. A A. A. 1 All for Greed. [A novel. The dedication signed... A., A. A. 2 Love the Avenger. By the author of “All for Gr... A., A. A. 3 Welsh Sketches, chiefly ecclesiastical, to the... A., E. S. 4 [The World in which I live, and my place in it... A., E. S. Contributors Corporate Author \ 0 FORBES, Walter. NaN 1 BLAZE DE BURY, Marie Pauline Rose - Baroness NaN 2 BLAZE DE BURY, Marie Pauline Rose - Baroness NaN 3 Appleyard, Ernest Silvanus. NaN 4 BROOME, John Henry. NaN Corporate Contributors Former owner Engraver Issuance type \ 0 NaN NaN NaN monographic 1 NaN NaN NaN monographic 2 NaN NaN NaN monographic 3 NaN NaN NaN monographic 4 NaN NaN NaN monographic Flickr URL \ 0 http://www.flickr.com/photos/britishlibrary/ta... 1 http://www.flickr.com/photos/britishlibrary/ta... 2 http://www.flickr.com/photos/britishlibrary/ta... 3 http://www.flickr.com/photos/britishlibrary/ta... 4 http://www.flickr.com/photos/britishlibrary/ta... Shelfmarks 0 British Library HMNTS 12641.b.30. 1 British Library HMNTS 12626.cc.2. 2 British Library HMNTS 12625.dd.1. 3 British Library HMNTS 10369.bbb.15. 4 British Library HMNTS 9007.d.28.
سپس با استفاده از متد ()head پنج ورودی اول پرینت میشوند، و بر همین اساس قابل مشاهده است که برخی ستونها اطلاعات مازادی فراهم میکنند که امکان دارد به وصف کتابخانه کمک کنند، اما نقشی در توصیف خود کتابها ندارند که از این جمله میتوان به Edition Statement، Corporate Author، Corporate Contributors، Former owner، Engraver، Issuance type و Shelfmarks اشاره کرد. میتوان این ستونها را به شکل زیر حذف کرد.
>>> to_drop = ['Edition Statement', ... 'Corporate Author', ... 'Corporate Contributors', ... 'Former owner', ... 'Engraver', ... 'Contributors', ... 'Issuance type', ... 'Shelfmarks'] >>> df.drop(to_drop, inplace=True, axis=1)
در بالا، لیستی تعریف شد که اسامی همه ستونهایی که هدف حذف آنها است تعیین شدهاند. سپس، از تابع ()drop روی اشیا تعیین شده استفاده میشود، و برای پارامتر inplace مقدار True و پارامتر axis برابر با ۱ قرار داده میشوند. این به Pandas میگوید که پژوهشگر تمایل دارد تغییراتی را به طور مستقیم روی اشیا انجام داده و در این راستا باید مقادیری از ستونهای شی حذف شوند. با بازرسی مجدد دیتافریم (DataFrame)، به وضوح مشهود است که ستونها حذف شدهاند.
>>> df.head() Identifier Place of Publication Date of Publication \ 0 206 London 1879 [1878] 1 216 London; Virtue & Yorston 1868 2 218 London 1869 3 472 London 1851 4 480 London 1857 Publisher Title \ 0 S. Tinsley & Co. Walter Forbes. [A novel.] By A. A 1 Virtue & Co. All for Greed. [A novel. The dedication signed... 2 Bradbury, Evans & Co. Love the Avenger. By the author of “All for Gr... 3 James Darling Welsh Sketches, chiefly ecclesiastical, to the... 4 Wertheim & Macintosh [The World in which I live, and my place in it... Author Flickr URL 0 A. A. http://www.flickr.com/photos/britishlibrary/ta... 1 A., A. A. http://www.flickr.com/photos/britishlibrary/ta... 2 A., A. A. http://www.flickr.com/photos/britishlibrary/ta... 3 A., E. S. http://www.flickr.com/photos/britishlibrary/ta... 4 A., E. S. http://www.flickr.com/photos/britishlibrary/ta...
از سوی دیگر، میتوان ستونها را با پاس دادن آنها به پارامتر columns به طور مستقیم حذف کرد، به جای آنکه برچسبها را برای حذف و axis که Pandas باید در آن به دنبال برچسبها باشد را به طور جداگانه تعیین کرد.
>>> df.drop(columns=to_drop, inplace=True)
این «نحو» (syntax) بصریتر و دارای خوانایی بالاتر است. آنچه در اینجا انجام میشود کاملا واضح است.
تغییر Index دیتافریم
در کتابخانه Pandas پایتون، Index عملکرد آرایه NumPy را گسترش میدهد تا امکان برچسبزنی و بخشبندی متنوعتر دادهها فراهم شود. در بسیاری از موارد، استفاده از یک فیلد شناسایی دارای مقدار یکتا برای داده به عنوان اندیس آن میتواند مفید باشد. برای مثال، در مجموعه داده استفاده شده در بخش بعدی، میتوان انتظار داشت هنگامی که یک کتابدار به دنبال یک رکورد میگردد، یک شناساگر یکتا را برای کتاب وارد کند (مقادیر در ستون Identifier):
>>> df['Identifier'].is_unique True
اکنون اندیس موجود در این ستون با استفاده از set_index جایگزین میشود:
>>> df = df.set_index('Identifier') >>> df.head() Place of Publication Date of Publication \ 206 London 1879 [1878] 216 London; Virtue & Yorston 1868 218 London 1869 472 London 1851 480 London 1857 Publisher \ 206 S. Tinsley & Co. 216 Virtue & Co. 218 Bradbury, Evans & Co. 472 James Darling 480 Wertheim & Macintosh Title Author \ 206 Walter Forbes. [A novel.] By A. A A. A. 216 All for Greed. [A novel. The dedication signed... A., A. A. 218 Love the Avenger. By the author of “All for Gr... A., A. A. 472 Welsh Sketches, chiefly ecclesiastical, to the... A., E. S. 480 [The World in which I live, and my place in it... A., E. S. Flickr URL 206 http://www.flickr.com/photos/britishlibrary/ta... 216 http://www.flickr.com/photos/britishlibrary/ta... 218 http://www.flickr.com/photos/britishlibrary/ta... 472 http://www.flickr.com/photos/britishlibrary/ta... 480 http://www.flickr.com/photos/britishlibrary/ta...
نکته: بر خلاف کلید اصلی در SQL، یک Index در Pandas هیچ تضمینی بر یکتا بودن ندارد، اگرچه انجام اندیسگذاریها و ادغامهای زیاد منجر به افزایش سرعت در زمان اجرا میشوند.
میتوان به هر رکورد به شیوهای ساده و با بهرهگیری از [ ]loc دسترسی داشت. با وجود آنکه [ ]loc ممکن است به معنای واقعی کلمه بصری نباشد، اما امکان اندیسگذاری مبتنی بر برچسب (label-based indexing) را فراهم میکند که در واقع برچسبگذاری سطرها یا رکوردها بدون در نظر گرفتن موقعیت آنها است.
>>> df.loc[206] Place of Publication London Date of Publication 1879 [1878] Publisher S. Tinsley & Co. Title Walter Forbes. [A novel.] By A. A Author A. A. Flickr URL http://www.flickr.com/photos/britishlibrary/ta... Name: 206, dtype: object
به عبارت دیگر، 206 اولین برچسب اندیس است. برای «دسترسی» (access) به این مقدار با استفاده از موقعیت (position) آن میتوان از [df.iloc[0 استفاده کرد که اندیسگذاری مبتنی بر موقعیت را امکانپذیر میکند. پیش از این، اندیس یک «RangeIndex» (اعداد صحیح از صفر آغاز میشدند، مشابه با range توکار در پایتون) بود. با پاس دادن یک نام به set_index، اندیس به مقدار موجود در Identifier تغییر پیدا میکند. نکته شایان توجه آن است که متغیر مجدد به شی بازگردانده شده توسط متد (...)df = df.set_index تخصیص داده شده است. این کار به این دلیل صورت میگیرد که به طور پیشفرض، متد یک کپی ویرایش شده از شی را باز گردانده و تغییرات را به طور مستقیم روی شی انجام نمیدهد. میتوان با تنظیم پارامتر inplace از این کار اجتناب کرد.
df.set_index('Identifier', inplace=True)
مرتبسازی فیلدهای داده
تاکنون، ستونهای غیر لازم حذف شدند و اندیس دیتافریم (DataFrame) به چیز معقولتری تغییر کرد. در این بخش، ستونهای مشخصی حذف میشوند و سایر موارد به یک فرمت واحد در میآیند تا ضمن ارائه درک بهتری از مجموعه داده از انسجام نیز پیروی کند. مشخصا، Date of Publication و Place of Publication به دلیل آنکه مورد نیاز نیستند حذف خواهند شد. پس از انجام بازرسی، مشخص شد که همه انواع دادهها از نوع داده object dtype هستند که تقریبا مشابه با str در پایتون محلی است. این مورد هر فیلدی که نمیتواند به خوبی به عنوان عددی یا داده دستهای فیت شود را انباشته میکند. این کار هنگامی معنادار است که پژوهشگر با دادههایی کار کند که در اصل دستهای از رشتههای کثیف هستند.
>>> df.get_dtype_counts() object 6
یکی از فیلدهایی که تبدیل آن به مقدار عددی دارای معنا خواهد بود، تاریخ انتشار است. بنابراین، میتوان محاسبات زیر را روی آنها انجام داد.
>>> df.loc[1905:, 'Date of Publication'].head(10) Identifier 1905 1888 1929 1839, 38-54 2836 [1897?] 2854 1865 2956 1860-63 2957 1873 3017 1866 3131 1899 4598 1814 4884 1820 Name: Date of Publication, dtype: object
یک کتاب خاص ممکن است تنها یک تاریخ انتشار داشته باشد. بنابراین انجام کارهای زیر مورد نیاز است.
- حذف دادههای اضافی در آکولادها در هر جایی که [1879] 1878 وجود داشت.
- تبدیل رنج دادهها به تاریخ شروع آنها، هرجا که 63-1860، 1839 و 54-38 وجود دارد.
- تاریخهایی که کاربر درباره آنها مطمئن نیست به طور کامل حذف و با مقدار NaN در کتابخانه NumPy جایگزین میشوند، برای مثال [?1897]
- تبدیل رشته NaN به مقدار NaN در NumPy
با سنتز کرده این الگوها، میتوان از مزایای عبارت با قاعده مجرد برای استخراج سال انتشار استفاده کرد.
regex = r'^(\d{4})'
عبارت با قاعده بالا به معنای یافتن هر چهار رقم در آغاز رشته است که برای پیدا کردن سال انتشار کافی محسوب میشود. آنچه در بالا ارائه شد «رشته خام» (raw string) است (بدین معنا که بَکاِسلَش (backslash) دیگر یک کاراکتر فرار محسوب نمیشود)، که در واقع یک اقدام استاندارد با عبارات باقاعده است. «d\» هر رقمی را نشان میدهد، و {4} این قانون را چهار بار تکرار میکند. کاراکتر ^ شروع رشتهها را مطابقت میدهد و پرانتزها مشخص کننده یک گروه طبقهبندی شده هستند که به Pandas سیگنال میدهد که کاربر قصد دارد آن بخش از regex را استخراج کند (هدف آن است که ^ از مواردی که در آن جمله با ] آغاز میشود اجتناب کند). اکنون میتوان دید که با اجرای این regex در مجموعه داده چه اتفاقی میافتد.
>>> extr = df['Date of Publication'].str.extract(r'^(\d{4})', expand=False) >>> extr.head() Identifier 206 1879 216 1868 218 1869 472 1851 480 1857 Name: Date of Publication, dtype: object
نکته: افرادی که با regex آشنایی ندارند میتوانند در رابطه با نتیجه خروجی عبارت بالا در این وبسایت به بررسی بپردازند.
به لحاظ فنی این ستون همچنان دارای object dtype است، اما میتوان به سادگی نسخه عددی آن را با pd.to_numeric دریافت کرد.
>>> df['Date of Publication'] = pd.to_numeric(extr) >>> df['Date of Publication'].dtype dtype('float64')
این نتیجه تقریبا یکی در هر ده مورد مقدار ناموجود است که هزینه کوچکی محسوب میشود که اکنون باید پرداخته شود تا امکان انجام محاسبات در دیگر مقادیر باقیمانده وجود داشته باشد.
>>> df['Date of Publication'].isnull().sum() / len(df) 0.11717147339205986
بسیار عالی، کار مورد نظر انجام شد.
ترکیب متدهای str با NumPy برای پاکسازی ستونها
مسالهای که در بالا ممکن است توجه افراد زیادی را به خود جلب کرده باشد استفاده از df['Date of Publication'].str است. این مشخصه راهی برای دسترسی سریع به عملیات رشتهها در Pandas محسوب میشود که به طور گسترده عملیات موجود برای رشتهها یا عبارات باقاعده کامپایل شده در پایتون محلی مانند ()split()، .replace. و ()capitalize. را تقلید میکند.
برای پاکسازی فیلد Place of Publication، میتوان متد str در کتابخانه Pandas را با np.where در NumPy ترکیب کرد که در اصل شکل بُرداری شده ماکرو ()IF در نرمافزار «اکسل» (Excel) به شمار میآید. نحو این دستور در پایتون به صورت زیر است.
>>> np.where(condition, then, else)
در اینجا، condition نیز یک شی آرایه مانند یا «ماسک بولین» (boolean mask) محسوب میشود. then مقداری که اگر condition برابر با True ارزیابی شود و else مقداری است که در غیر این صورت مورد استفاده قرار میگیرد.
اساسا، ()where. هر مولفه در شی که برای condition مورد استفاده قرار میگیرد را دریافت کرده، بررسی میکند که حاصل ارزیابی آن مولفه برابر با True در زمینه شرط است یا خیر، و یک ndarray باز میگرداند که بسته به آنکه کدام شرط اعمال شود دربرگیرنده then یا else است. این دستور میتواند در یک عبارت ترکیبی if-then تودرتو نوشته شود که امکان محاسبه مقادیر بر پایه شرایط گوناگون را فراهم میکند.
>>> np.where(condition1, x1, np.where(condition2, x2, np.where(condition3, x3, ...)))
از این دو تابع برای پاکسازی Place of Publication استفاده میشود، زیرا این ستون دارای اشیای رشتهای است که محتوای آن به صورت زیر نمایش داده میشود.
>>> df['Place of Publication'].head(10) Identifier 206 London 216 London; Virtue & Yorston 218 London 472 London 480 London 481 London 519 London 667 pp. 40. G. Bryan & Co: Oxford, 1898 874 London] 1143 London Name: Place of Publication, dtype: object
قابل مشاهده است که در برخی از سطرها محل انتشار به وسیله دیگر اطلاعات غیرلازم احاطه شده. اگر به مقادیر موجود با دقت بیشتر نگاه شود، میتوان به وضوح مشاهده کرد که این شرایط تنها برای برخی سطرهایی که موقعیت انتشار آنها «London» یا «Oxford» است وجود دارد. در ادامه دو ورودی مشخص بررسی میشوند.
>>> df.loc[4157862] Place of Publication Newcastle-upon-Tyne Date of Publication 1867 Publisher T. Fordyce Title Local Records; or, Historical Register of rema... Author T. Fordyce Flickr URL http://www.flickr.com/photos/britishlibrary/ta... Name: 4157862, dtype: object >>> df.loc[4159587] Place of Publication Newcastle upon Tyne Date of Publication 1834 Publisher Mackenzie & Dent Title An historical, topographical and descriptive v... Author E. (Eneas) Mackenzie Flickr URL http://www.flickr.com/photos/britishlibrary/ta... Name: 4159587, dtype: object
این دو کتاب در یک محل منتشر شدهاند، اما در نام محل یکی از آنها خط فاصله وجود دارد و در دیگری این خط وجود ندارد. برای پاکسازی این ستون در یک حرکت، میتوان از دستور ()str.contains برای دریافت ماسکهای بولین استفاده کرد. ستون را میتوان به صورت زیر پاک کرد.
>>> pub = df['Place of Publication'] >>> london = pub.str.contains('London') >>> london[:5] Identifier 206 True 216 True 218 True 472 True 480 True Name: Place of Publication, dtype: bool >>> oxford = pub.str.contains('Oxford')
دستور بالا با np.where به صورت زیر ترکیب میشود.
df['Place of Publication'] = np.where(london, 'London', np.where(oxford, 'Oxford', pub.str.replace('-', ' '))) >>> df['Place of Publication'].head() Identifier 206 London 216 London 218 London 472 London 480 London Name: Place of Publication, dtype: object
در اینجا تابع np.where در یک ساختار تو در تو فراخوانی میشود، با condition که سریهایی از بولینهای مشاهده شده با ()str.contains است. متد ()contains به طرز مشابهی با in keyword توکار استفاده شده برای کشف وقوع یک موجودیت در تکرارپذیری (یا زیررشته در رشته) به کار میرود.
جایگزینی که استفاده میشود در واقع رشتهای است که محل مورد انتظار انتشار را نشان میدهد. همچنین، با استفاده از دستور ()str.replace خط تیرهها با فاصله (space) جایگزین و مجددا به ستون دیتافریم (DataFrame) تخصیص داده میشوند. اگرچه دادههای کثیف زیادی در این مجموعه داده وجود دارند ولی در حال حاضر تنها دو ستون مورد بررسی قرار میگیرند. ابتدا پنج ورودی اول بررسی میشوند و مشخص است که مجموعه داده در حال حاضر نسبت به زمانی که هنوز کار پاکسازی آغاز نشده بود بسیار واضحتر و شفافتر هستند.
>>> df.head() Place of Publication Date of Publication Publisher \ 206 London 1879 S. Tinsley & Co. 216 London 1868 Virtue & Co. 218 London 1869 Bradbury, Evans & Co. 472 London 1851 James Darling 480 London 1857 Wertheim & Macintosh Title Author \ 206 Walter Forbes. [A novel.] By A. A AA 216 All for Greed. [A novel. The dedication signed... A. A A. 218 Love the Avenger. By the author of “All for Gr... A. A A. 472 Welsh Sketches, chiefly ecclesiastical, to the... E. S A. 480 [The World in which I live, and my place in it... E. S A. Flickr URL 206 http://www.flickr.com/photos/britishlibrary/ta... 216 http://www.flickr.com/photos/britishlibrary/ta... 218 http://www.flickr.com/photos/britishlibrary/ta... 472 http://www.flickr.com/photos/britishlibrary/ta... 480 http://www.flickr.com/photos/britishlibrary/ta...
نکته: در این لحظه، Place of Publication میتواند کاندیدای خوبی برای تبدیل به Categorical dtype باشد، زیرا میتوان مجموعه یکتا و به اندازه کافی کوچکی از شهرها را با اعداد صحیح رمزنگاری کرد. (میزان حافظه مورد استفاده برای Categorical متناسب با تعداد دستهها به علاوه طول داده است. یک شی dtype ثابتی است که طول داده را چند برابر میکند.)
پاکسازی کل مجموعه داده با استفاده از تابع applymap
در یک موقعیت خاص، میتوان مشاهده کرد که «dirt» تنها در یک ستون محلی نشده، بلکه بیشتر گسترش یافته است. نمونههایی نیز وجود دارد که در آنها اعمال یک تابع سفارشیسازی شده به هر سلول یا مولفه در دیتافریم میتواند مفید واقع شود. متد ()applymap. در کتابخانه Pandas مشابه تابع محلی ()map است و به سادگی یک تابع را بر همه دیگر عناصر موجود در دیتافریم اعمال میکند. مثال زیر در همین راستا بسیار قابل توجه است و در آن یک DataFrame از فایل مجموعه داده «university_towns.txt» ساخته شده.
$ head Datasets/univerisity_towns.txt Alabama[edit] Auburn (Auburn University)[1] Florence (University of North Alabama) Jacksonville (Jacksonville State University)[2] Livingston (University of West Alabama)[2] Montevallo (University of Montevallo)[2] Troy (Troy University)[2] Tuscaloosa (University of Alabama, Stillman College, Shelton State)[3][4] Tuskegee (Tuskegee University)[5] Alaska[edit]
قابل ملاحظه است که اسامی ایالتها به صورت دورهای با شهر دانشگاه در آن ایالت دنبال شدهاند: StateA TownA1 TownA2 StateB TownB1 TownB2 و .... اگر به شیوهای که نام ایالتها در فایل نوشته شده توجه شود، قابل مشاهده است که همه آنها دارای زیر رشته «[edit]» هستند. میتوان از مزایای این الگو با ساخت لیستی از تاپلهای (state, city) و پوششدهی آن لیست در یک دیتافریم بهرهمند شد.
>>> university_towns = [] >>> with open('Datasets/university_towns.txt') as file: ... for line in file: ... if '[edit]' in line: ... # Remember this `state` until the next is found ... state = line ... else: ... # Otherwise, we have a city; keep `state` as last-seen ... university_towns.append((state, line)) >>> university_towns[:5] [('Alabama[edit]\n', 'Auburn (Auburn University)[1]\n'), ('Alabama[edit]\n', 'Florence (University of North Alabama)\n'), ('Alabama[edit]\n', 'Jacksonville (Jacksonville State University)[2]\n'), ('Alabama[edit]\n', 'Livingston (University of West Alabama)[2]\n'), ('Alabama[edit]\n', 'Montevallo (University of Montevallo)[2]\n')]
میتوان این لیست را در دیتافریم پوشش داد و ستونها را با عنوان «State» و «RegionName» تنظیم کرد. Pandas هر عنصر در لیست را دریافت کرده و State را روی مقدار سمت چپ و RegionName را روی مقدار سمت راست تنظیم میکند. مجموعه داده حاصل مشابه زیر خواهد بود:
>>> towns_df = pd.DataFrame(university_towns, ... columns=['State', 'RegionName']) >>> towns_df.head() State RegionName 0 Alabama[edit]\n Auburn (Auburn University)[1]\n 1 Alabama[edit]\n Florence (University of North Alabama)\n 2 Alabama[edit]\n Jacksonville (Jacksonville State University)[2]\n 3 Alabama[edit]\n Livingston (University of West Alabama)[2]\n 4 Alabama[edit]\n Montevallo (University of Montevallo)[2]\n
میتوان این رشتهها را در حلقه بالا پاکسازی کرد، و Pandas انجام این کار را ساده میکند. در این راستا، تنها نام ایالتها و شهرها مورد نیاز است و میتوان هر چیز دیگری را پاک کرد. در حالیکه امکان استفاده از متد ()str. در Pandas وجود دارد، میتوان از ()applymap برای نگاشت یک چیز قابل فراخوانی در پایتون (Python callable) برای هر مولفه در دیتافریم بهره برد. در کد بالا از عبارت element استفاده شد، اما واقعا معنی این کار چیست؟؟ مجموعه داده «الکی» زیر مفروض است.
0 1 0 Mock Dataset 1 Python Pandas 2 Real Python 3 NumPy Clean
در این مثال، هر سلول (Mock، Dataset، Python، Pandas و دیگر موارد) یک مولفه است. بنابراین، ()applymap یک تابع را بر هر یک از این موارد به طور مستقل اعمال میکند. اکنون تابع بیان شده تعریف میشود.
>>> def get_citystate(item): ... if ' (' in item: ... return item[:item.find(' (')] ... elif '[' in item: ... return item[:item.find('[')] ... else: ... return item
()applymap. کتابخانه Pandas تنها یک پارامتر را دریافت میکند که تابعی است (قابل فراخوانی) که باید بر هر عنصر اعمال شود.
>>> towns_df = towns_df.applymap(get_citystate)
ابتدا، یک تابع پایتون که یک مولفه را از دیتافریم به عنوان پارامتر خود دریافت میکند تعریف میشود. درون تابع، بررسیهایی به منظور تعیین اینکه ) یا ] وجود دارد یا خیر انجام و بسته به بررسیها، مقادیر توسط تابع بازگردانده میشوند. در نهایت، تابع ()applymap در شی فراخونی میشود. اکنون دیتافریم تمیزتر است.
>>> towns_df.head() State RegionName 0 Alabama Auburn 1 Alabama Florence 2 Alabama Jacksonville 3 Alabama Livingston 4 Alabama Montevallo
متد ()applymap هر مولفه را از دیتافریم گرفته و آن را به تابع پاس میدهد، این در حالیست که مقادیر قبلی با مقدار بازگردانده شده جایگزین میشوند. به همین سادگی!
نکته: در حالیکه متد applymap. ساده و دارای کاربرد گستردهای است، میتواند زمان اجرای قابل توجهی برای مجموعه دادههای بزرگ داشته باشد، زیرا یک چیز قابل فراخوانی توسط پایتون را برای هر مولفه نگاشت میکند. در برخی موارد، این راهکار که Cython یا NumPY را به کار گیرد (که به نوبه خود تماسها را در C انجام میدهد) میتواند برای انجام عملیات برداریسازی موثرتر باشد.
تغییر نام ستونها و گذر از سطرها
اغلب، مجموعه دادههایی که افراد با آن کار میکنند دارای اسامی برای ستونها هستند که معنای آنها به سادگی قابل درک نیست، و یا حاوی اطلاعات غیر مهمی مانند تعریف عبارات موجود در مجموعه داده و پانویسها در چند سطر اول و آخر هستند. در این شرایط، دادهکاو معمولا تمایل به تغییر نام ستونها و گذر از سطرهای خاص و رسیدن به اطلاعات لازم با برچسبهای صحیح و معنادار دارد. برای آنکه چگونگی انجام این کار شفاف شود، پنج سطر اول مجموعه داده «olympics.csv» با دستور head نمایش داده میشوند.
$ head -n 5 Datasets/olympics.csv 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 ,? Summer,01 !,02 !,03 !,Total,? Winter,01 !,02 !,03 !,Total,? Games,01 !,02 !,03 !,Combined total Afghanistan (AFG),13,0,0,2,2,0,0,0,0,0,13,0,0,2,2 Algeria (ALG),12,5,2,8,15,3,0,0,0,0,15,5,2,8,15 Argentina (ARG),23,18,24,28,70,18,0,0,0,0,41,18,24,28,70
اکنون مجموعه داده در دیتافریم Pandas خوانده میشود.
>>> olympics_df = pd.read_csv('Datasets/olympics.csv') >>> olympics_df.head() 0 1 2 3 4 5 6 7 8 \ 0 NaN ? Summer 01 ! 02 ! 03 ! Total ? Winter 01 ! 02 ! 1 Afghanistan (AFG) 13 0 0 2 2 0 0 0 2 Algeria (ALG) 12 5 2 8 15 3 0 0 3 Argentina (ARG) 23 18 24 28 70 18 0 0 4 Armenia (ARM) 5 1 2 9 12 6 0 0 9 10 11 12 13 14 15 0 03 ! Total ? Games 01 ! 02 ! 03 ! Combined total 1 0 0 13 0 0 2 2 2 0 0 15 5 2 8 15 3 0 0 41 18 24 28 70 4 0 0 11 1 2 9 12
این مجموعه داده حقیقتا کثیف است! ستونها فرم رشتهای از اعداد صحیح اندیسگذاری شده از صفر هستند. سطری که باید «سرآیند» (Header) باشد (سطری که برای نامگذاری ستونها مورد استفاده قرار میگیرد) در [olympics_df.iloc[0 قرار دارد. این مساله به این دلیل به وقوع میپیوندد که فایل CSV موجود با ۰، ۱، ۲، ...، ۱۵ آغاز شده است. همچنین، اگر منبع مجموعه داده مذکور بررسی شود، میتوان ملاحظه کرد که NaN بالا باید چیزی شبیه «Country» باشد و ? Summer باید نمایانگر «Summer Games» و !01 باید «Gold» باشد و به همین صورت. بنابراین، نیاز به انجام دو کار است:
- حذف یک سطر و تنظیم هِدِر به عنوان اولین سطر (دارای اندیس ۰)
- تغییر نام ستونها
میتوان در حال خواندن فایل CSV با پاس دادن برخی پارامترها به تابع ()read_csv از سطرها گذر و هدِر را تنظیم کرد. این تابع پارامترهای دلخواه زیادی را دریافت میکند، اما در این مورد تنها به یک مورد (header) برای حذف سطر ۰ نیاز است.
>>> olympics_df = pd.read_csv('Datasets/olympics.csv', header=1) >>> olympics_df.head() Unnamed: 0 ? Summer 01 ! 02 ! 03 ! Total ? Winter \ 0 Afghanistan (AFG) 13 0 0 2 2 0 1 Algeria (ALG) 12 5 2 8 15 3 2 Argentina (ARG) 23 18 24 28 70 18 3 Armenia (ARM) 5 1 2 9 12 6 4 Australasia (ANZ) [ANZ] 2 3 4 5 12 0 01 !.1 02 !.1 03 !.1 Total.1 ? Games 01 !.2 02 !.2 03 !.2 \ 0 0 0 0 0 13 0 0 2 1 0 0 0 0 15 5 2 8 2 0 0 0 0 41 18 24 28 3 0 0 0 0 11 1 2 9 4 0 0 0 0 2 3 4 5 Combined total 0 2 1 15 2 70 3 12 4 12
اکنون سطر صحیحی به عنوان هِدِر قرار گرفت و همه سطرهای غیر لازم حذف شدند. این نکته که چگونه Pandas نام ستونهای حاوی اسامی کشورها را از NaN به Unnamed: 0 تغییر داد قابل توجه است. برای تغییر نام ستونها، از متد ()rename دیتافریم استفاده میشود که امکان برچسب زدن یک محور برپایه نگاشت را فراهم میکند (در این مورد یک dict). اکنون کار با تعریف یک دیکشنری که اسامی ستونهای کنونی را (به عنوان کلیدی) به موارد قابل استفادهتر تبدیل میکند آغاز خواهد شد (مقادیر دیکشنری).
>>> new_names = {'Unnamed: 0': 'Country', ... '? Summer': 'Summer Olympics', ... '01 !': 'Gold', ... '02 !': 'Silver', ... '03 !': 'Bronze', ... '? Winter': 'Winter Olympics', ... '01 !.1': 'Gold.1', ... '02 !.1': 'Silver.1', ... '03 !.1': 'Bronze.1', ... '? Games': '# Games', ... '01 !.2': 'Gold.2', ... '02 !.2': 'Silver.2', ... '03 !.2': 'Bronze.2'}
تابع ()rename در شی فراخوانی میشود.
>>> olympics_df.rename(columns=new_names, inplace=True)
تنظیم inplace به مقدار True مشخص میکند که تغییرات به طور مستقیم روی شی انجام شوند. اکنون این مساله با قطعه کد زیر بررسی میشود.
>>> olympics_df.head() Country Summer Olympics Gold Silver Bronze Total \ 0 Afghanistan (AFG) 13 0 0 2 2 1 Algeria (ALG) 12 5 2 8 15 2 Argentina (ARG) 23 18 24 28 70 3 Armenia (ARM) 5 1 2 9 12 4 Australasia (ANZ) [ANZ] 2 3 4 5 12 Winter Olympics Gold.1 Silver.1 Bronze.1 Total.1 # Games Gold.2 \ 0 0 0 0 0 0 13 0 1 3 0 0 0 0 15 5 2 18 0 0 0 0 41 18 3 6 0 0 0 0 11 1 4 0 0 0 0 0 2 3 Silver.2 Bronze.2 Combined total 0 0 2 2 1 2 8 15 2 24 28 70 3 2 9 12 4 4 5 12
خلاصه مطلب
در این راهنما، چگونگی حذف اطلاعات غیر لازم از یک مجموعه داده با استفاده از تابع ()drop و چگونگی تنظیم اندیس برای مجموعه داده به نوعی که آیتمهای آن به سادگی قابل ارجاع باشند بیان شد. علاوه بر این، چگونگی پاک کردن فیلدهای object با اکسسور ()str. و نحوه پاکسازی کل مجموعه داده با استفاده از متد ()applymap مورد بررسی قرار گرفت. در پایان، چگونگی گذر از سطرها در فایل CSV و تغییر نام ستونها با استفاده از متد rename() تشریح شد. تسلط بر چگونگی پاکسازی دادهها بسیار مهم است، زیرا این کار بخش مهم و بزرگی از پروژههای علم داده محسوب میشود.
اگر مطلب بالا برای شما مفید بوده، آموزشهای زیر نیز به شما پیشنهاد میشود:
- گنجینه آموزشهای برنامه نویسی پایتون (Python)
- مجموعه آموزشهای آمار، احتمالات و دادهکاوی
- مجموعه آموزشهای یادگیری ماشین و بازشناسی الگو
- مجموعه آموزشهای شبکههای عصبی مصنوعی
- مجموعه آموزشهای هوش محاسباتی
- آموزش برنامهنویسی R و نرمافزار R Studio
- مجموعه آموزشهای برنامه نویسی متلب (MATLAB)
^^
سلام وقت بخیر
اگر دیتایی داشته باشیم و بخوایم مثلا فقط دیتا شهر تهران به نمایش نمایش بده یا برعکس همه دیتا نمایش بده به جز تهران از چه کدی میشه استفاده کرد؟