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

۴۹۹۰ بازدید
آخرین به‌روزرسانی: ۲۲ اسفند ۱۴۰۲
زمان مطالعه: ۱۸ دقیقه
پاکسازی داده (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 پرداخته می‌شود. در ابتدا، کار با ایمپورت کردن ماژول‌های مورد نیاز (یعنی دو کتابخانه یاد شده) آغاز می‌شود.

1>>> import pandas as pd
2>>> import numpy as np

حذف ستون‌ها در یک DataFrame

در اغلب پروژه‌های تحلیل داده و داده‌کاوی، پژوهشگران با این مساله مواجه هستند که همه دسته‌های داده‌های موجود مفید نیستند. برای مثال، ممکن است یک مجموعه داده از اطلاعات دانش‌آموزان شامل نام، مقطع تحصیلی، نام والدین و آدرس آن‌ها وجود داشته باشد، اما هدف تحلیل مقطع تحصیلی دانش‌آموزان باشد. در این مثال، آدرس منزل یا نام والدین در تحلیل‌ها فاقد اهمیت محسوب می‌شوند.

نگهداری این اطلاعات بلااستفاده صرفا فضا اشغال کرده و سرعت تحلیل را در زمان اجرا کاهش می‌دهد. Pandas یک راهکار کاربردی برای حذف ستون‌ها یا سطرهای ناخواسته از دیتافریم (DataFrame) با تابع ()drop فراهم می‌کند. در ادامه مثال کوچکی از چگونگی حذف تعدادی از ستون‌ها از یک دیتافریم نشان داده شده است. ابتدا، یک دیتافریم از فایل CSV مربوط به مجموعه داده BL-Flickr-Images-Book.csv ساخته می‌شود. در این مثال، مسیر مربوطه به  pd.read_csv پاس داده می‌شود، بدین معنا که مجموعه داده‌ها در پوشه‌ای با نام Datasets در دایرکتوری در حال کار فعلی قرار دارند.

1>>> df = pd.read_csv('Datasets/BL-Flickr-Images-Book.csv')
2>>> df.head()
3
4    Identifier             Edition Statement      Place of Publication  \
50         206                           NaN                    London
61         216                           NaN  London; Virtue & Yorston
72         218                           NaN                    London
83         472                           NaN                    London
94         480  A new edition, revised, etc.                    London
10
11  Date of Publication              Publisher  \
120         1879 [1878]       S. Tinsley & Co.
131                1868           Virtue & Co.
142                1869  Bradbury, Evans & Co.
153                1851          James Darling
164                1857   Wertheim & Macintosh
17
18                                               Title     Author  \
190                  Walter Forbes. [A novel.] By A. A      A. A.
201  All for Greed. [A novel. The dedication signed...  A., A. A.
212  Love the Avenger. By the author of “All for Gr...  A., A. A.
223  Welsh Sketches, chiefly ecclesiastical, to the...  A., E. S.
234  [The World in which I live, and my place in it...  A., E. S.
24
25                                   Contributors  Corporate Author  \
260                               FORBES, Walter.               NaN
271  BLAZE DE BURY, Marie Pauline Rose - Baroness               NaN
282  BLAZE DE BURY, Marie Pauline Rose - Baroness               NaN
293                   Appleyard, Ernest Silvanus.               NaN
304                           BROOME, John Henry.               NaN
31
32   Corporate Contributors Former owner  Engraver Issuance type  \
330                     NaN          NaN       NaN   monographic
341                     NaN          NaN       NaN   monographic
352                     NaN          NaN       NaN   monographic
363                     NaN          NaN       NaN   monographic
374                     NaN          NaN       NaN   monographic
38
39                                          Flickr URL  \
400  http://www.flickr.com/photos/britishlibrary/ta...
411  http://www.flickr.com/photos/britishlibrary/ta...
422  http://www.flickr.com/photos/britishlibrary/ta...
433  http://www.flickr.com/photos/britishlibrary/ta...
444  http://www.flickr.com/photos/britishlibrary/ta...
45
46                            Shelfmarks
470    British Library HMNTS 12641.b.30.
481    British Library HMNTS 12626.cc.2.
492    British Library HMNTS 12625.dd.1.
503    British Library HMNTS 10369.bbb.15.
514    British Library HMNTS 9007.d.28.

سپس با استفاده از متد ()head پنج ورودی اول پرینت می‌شوند، و بر همین اساس قابل مشاهده است که برخی ستون‌ها اطلاعات مازادی فراهم می‌کنند که امکان دارد به وصف کتابخانه کمک کنند، اما نقشی در توصیف خود کتاب‌ها ندارند که از این جمله می‌توان به Edition Statement، Corporate Author، Corporate Contributors،  Former owner، Engraver، Issuance type و Shelfmarks اشاره کرد. می‌توان این ستون‌ها را به شکل زیر حذف کرد.

1>>> to_drop = ['Edition Statement',
2...            'Corporate Author',
3...            'Corporate Contributors',
4...            'Former owner',
5...            'Engraver',
6...            'Contributors',
7...            'Issuance type',
8...            'Shelfmarks']
9
10>>> df.drop(to_drop, inplace=True, axis=1)

در بالا، لیستی تعریف شد که اسامی همه ستون‌هایی که هدف حذف آن‌ها است تعیین شده‌اند. سپس، از تابع ()drop روی اشیا تعیین شده استفاده می‌شود، و برای پارامتر inplace مقدار True و پارامتر axis برابر با ۱ قرار داده می‌شوند. این به Pandas می‌گوید که پژوهشگر تمایل دارد تغییراتی را به طور مستقیم روی اشیا انجام داده و در این راستا باید مقادیری از ستون‌های شی حذف شوند. با بازرسی مجدد دیتافریم (DataFrame)، به وضوح مشهود است که ستون‌ها حذف شده‌اند.

1>>> df.head()
2   Identifier      Place of Publication Date of Publication  \
30         206                    London         1879 [1878]
41         216  London; Virtue & Yorston                1868
52         218                    London                1869
63         472                    London                1851
74         480                    London                1857
8
9               Publisher                                              Title  \
100       S. Tinsley & Co.                  Walter Forbes. [A novel.] By A. A
111           Virtue & Co.  All for Greed. [A novel. The dedication signed...
122  Bradbury, Evans & Co.  Love the Avenger. By the author of “All for Gr...
133          James Darling  Welsh Sketches, chiefly ecclesiastical, to the...
144   Wertheim & Macintosh  [The World in which I live, and my place in it...
15
16      Author                                         Flickr URL
170      A. A.  http://www.flickr.com/photos/britishlibrary/ta...
181  A., A. A.  http://www.flickr.com/photos/britishlibrary/ta...
192  A., A. A.  http://www.flickr.com/photos/britishlibrary/ta...
203  A., E. S.  http://www.flickr.com/photos/britishlibrary/ta...
214  A., E. S.  http://www.flickr.com/photos/britishlibrary/ta...

از سوی دیگر، می‌توان ستون‌ها را با پاس دادن آن‌ها به پارامتر columns به طور مستقیم حذف کرد، به جای آنکه برچسب‌ها را برای حذف و axis که Pandas باید در آن به دنبال برچسب‌ها باشد را به طور جداگانه تعیین کرد.

1>>> df.drop(columns=to_drop, inplace=True)

این «نحو» (syntax) بصری‌تر و دارای خوانایی بالاتر است. آنچه در اینجا انجام می‌شود کاملا واضح است.

تغییر Index دیتافریم

در کتابخانه Pandas پایتون، Index عملکرد آرایه NumPy را گسترش می‌دهد تا امکان برچسب‌زنی و بخش‌بندی متنوع‌تر داده‌ها فراهم شود. در بسیاری از موارد، استفاده از یک فیلد شناسایی دارای مقدار یکتا برای داده به عنوان اندیس آن می‌تواند مفید باشد. برای مثال، در مجموعه داده استفاده شده در بخش بعدی، می‌توان انتظار داشت هنگامی که یک کتابدار به دنبال یک رکورد می‌گردد، یک شناساگر یکتا را برای کتاب وارد کند (مقادیر در ستون Identifier):

1>>> df['Identifier'].is_unique
2True

اکنون اندیس موجود در این ستون با استفاده از set_index جایگزین می‌شود:

1>>> df = df.set_index('Identifier')
2>>> df.head()
3                Place of Publication Date of Publication  \
4206                           London         1879 [1878]
5216         London; Virtue & Yorston                1868
6218                           London                1869
7472                           London                1851
8480                           London                1857
9
10                        Publisher  \
11206              S. Tinsley & Co.
12216                  Virtue & Co.
13218         Bradbury, Evans & Co.
14472                 James Darling
15480          Wertheim & Macintosh
16
17                                                        Title     Author  \
18206                         Walter Forbes. [A novel.] By A. A      A. A.
19216         All for Greed. [A novel. The dedication signed...  A., A. A.
20218         Love the Avenger. By the author of “All for Gr...  A., A. A.
21472         Welsh Sketches, chiefly ecclesiastical, to the...  A., E. S.
22480         [The World in which I live, and my place in it...  A., E. S.
23
24                                                   Flickr URL
25206         http://www.flickr.com/photos/britishlibrary/ta...
26216         http://www.flickr.com/photos/britishlibrary/ta...
27218         http://www.flickr.com/photos/britishlibrary/ta...
28472         http://www.flickr.com/photos/britishlibrary/ta...
29480         http://www.flickr.com/photos/britishlibrary/ta...

نکته: بر خلاف کلید اصلی در SQL، یک Index در Pandas هیچ تضمینی بر یکتا بودن ندارد، اگرچه انجام اندیس‌گذاری‌ها و ادغام‌های زیاد منجر به افزایش سرعت در زمان اجرا می‌شوند.

می‌توان به هر رکورد به شیوه‌ای ساده و با بهره‌گیری از [ ]loc دسترسی داشت. با وجود آنکه [ ]loc ممکن است به معنای واقعی کلمه بصری نباشد، اما امکان اندیس‌گذاری مبتنی بر برچسب (label-based indexing) را فراهم می‌کند که در واقع برچسب‌گذاری سطرها یا رکوردها بدون در نظر گرفتن موقعیت آن‌ها است.

1>>> df.loc[206]
2Place of Publication                                               London
3Date of Publication                                           1879 [1878]
4Publisher                                                S. Tinsley & Co.
5Title                                   Walter Forbes. [A novel.] By A. A
6Author                                                              A. A.
7Flickr URL              http://www.flickr.com/photos/britishlibrary/ta...
8Name: 206, dtype: object

به عبارت دیگر، 206 اولین برچسب اندیس است. برای «دسترسی» (access) به این مقدار با استفاده از موقعیت (position) آن می‌توان از [df.iloc[0 استفاده کرد که اندیس‌گذاری مبتنی بر موقعیت را امکان‌پذیر می‌کند. پیش از این، اندیس یک «RangeIndex» (اعداد صحیح از صفر آغاز می‌شدند، مشابه با range توکار در پایتون) بود. با پاس دادن یک نام به set_index، اندیس به مقدار موجود در Identifier تغییر پیدا می‌کند. نکته شایان توجه آن است که متغیر مجدد به شی بازگردانده شده توسط متد (...)df = df.set_index تخصیص داده شده است. این کار به این دلیل صورت می‌گیرد که به طور پیش‌فرض، متد یک کپی ویرایش شده از شی را باز گردانده و تغییرات را به طور مستقیم روی شی انجام نمی‌دهد. می‌توان با تنظیم پارامتر inplace از این کار اجتناب کرد.

1df.set_index('Identifier', inplace=True)

مرتب‌سازی فیلدهای داده

تاکنون، ستون‌های غیر لازم حذف شدند و اندیس دیتافریم (DataFrame) به چیز معقول‌تری تغییر کرد. در این بخش، ستون‌های مشخصی حذف می‌شوند و سایر موارد به یک فرمت واحد در می‌آیند تا ضمن ارائه درک بهتری از مجموعه داده از انسجام نیز پیروی کند. مشخصا، Date of Publication و Place of Publication به دلیل آنکه مورد نیاز نیستند حذف خواهند شد. پس از انجام بازرسی، مشخص شد که همه انواع داده‌ها از نوع داده object dtype هستند که تقریبا مشابه با str در پایتون محلی است. این مورد هر فیلدی که نمی‌تواند به خوبی به عنوان عددی یا داده دسته‌ای فیت شود را انباشته می‌کند. این کار هنگامی معنادار است که پژوهشگر با داده‌هایی کار کند که در اصل دسته‌ای از رشته‌های کثیف هستند.

1>>> df.get_dtype_counts()
2object    6

یکی از فیلدهایی که تبدیل آن به مقدار عددی دارای معنا خواهد بود، تاریخ انتشار است. بنابراین، می‌توان محاسبات زیر را روی آن‌ها انجام داد.

1>>> df.loc[1905:, 'Date of Publication'].head(10)
2Identifier
31905           1888
41929    1839, 38-54
52836        [1897?]
62854           1865
72956        1860-63
82957           1873
93017           1866
103131           1899
114598           1814
124884           1820
13Name: Date of Publication, dtype: object

یک کتاب خاص ممکن است تنها یک تاریخ انتشار داشته باشد. بنابراین انجام کارهای زیر مورد نیاز است.

  • حذف داده‌های اضافی در آکولادها در هر جایی که [1879] 1878 وجود داشت.
  • تبدیل رنج داده‌ها به تاریخ شروع آن‌ها، هرجا که 63-1860، 1839 و 54-38 وجود دارد.
  • تاریخ‌هایی که کاربر درباره آن‌ها مطمئن نیست به طور کامل حذف و با مقدار NaN در کتابخانه NumPy جایگزین می‌شوند، برای مثال [?1897]
  • تبدیل رشته NaN به مقدار NaN در NumPy

با سنتز کرده این الگوها، می‌توان از مزایای عبارت با قاعده مجرد برای استخراج سال انتشار استفاده کرد.

1regex = r'^(\d{4})'

عبارت با قاعده بالا به معنای یافتن هر چهار رقم در آغاز رشته است که برای پیدا کردن سال انتشار کافی محسوب می‌شود. آنچه در بالا ارائه شد «رشته خام» (raw string) است (بدین معنا که بَک‌اِسلَش (backslash) دیگر یک کاراکتر فرار محسوب نمی‌شود)، که در واقع یک اقدام استاندارد با عبارات باقاعده است. «d\» هر رقمی را نشان می‌دهد، و {4} این قانون را چهار بار تکرار می‌کند. کاراکتر ^ شروع رشته‌ها را مطابقت می‌دهد و پرانتزها مشخص کننده یک گروه طبقه‌بندی شده هستند که به Pandas سیگنال می‌دهد که کاربر قصد دارد آن بخش از regex را استخراج کند (هدف آن است که ^ از مواردی که در آن جمله با ] آغاز می‌شود اجتناب کند). اکنون می‌توان دید که با اجرای این regex در مجموعه داده چه اتفاقی می‌افتد.

1>>> extr = df['Date of Publication'].str.extract(r'^(\d{4})', expand=False)
2>>> extr.head()
3Identifier
4206    1879
5216    1868
6218    1869
7472    1851
8480    1857
9Name: Date of Publication, dtype: object

نکته: افرادی که با regex آشنایی ندارند می‌توانند در رابطه با نتیجه خروجی عبارت بالا در این وب‌سایت به بررسی بپردازند.

به لحاظ فنی این ستون همچنان دارای object dtype است، اما می‌توان به سادگی نسخه عددی آن را با pd.to_numeric دریافت کرد.

1>>> df['Date of Publication'] = pd.to_numeric(extr)
2>>> df['Date of Publication'].dtype
3dtype('float64')

این نتیجه تقریبا یکی در هر ده مورد مقدار ناموجود است که هزینه کوچکی محسوب می‌شود که اکنون باید پرداخته شود تا امکان انجام محاسبات در دیگر مقادیر باقیمانده وجود داشته باشد.

1>>> df['Date of Publication'].isnull().sum() / len(df)
20.11717147339205986

بسیار عالی، کار مورد نظر انجام شد.

ترکیب متدهای str با NumPy برای پاکسازی ستون‌ها

مساله‌ای که در بالا ممکن است توجه افراد زیادی را به خود جلب کرده باشد استفاده از df['Date of Publication'].str است. این مشخصه راهی برای دسترسی سریع به عملیات رشته‌ها در Pandas محسوب می‌شود که به طور گسترده عملیات موجود برای رشته‌ها یا عبارات باقاعده کامپایل شده در پایتون محلی مانند ()split()، .replace. و ()capitalize. را تقلید می‌کند.

برای پاکسازی فیلد Place of Publication، می‌توان متد str در کتابخانه Pandas را با np.where در NumPy ترکیب کرد که در اصل شکل بُرداری شده ماکرو ()IF در نرم‌افزار «اکسل» (Excel) به شمار می‌آید. نحو این دستور در پایتون به صورت زیر است.

1>>> np.where(condition, then, else)

در اینجا، condition نیز یک شی آرایه مانند یا «ماسک بولین» (boolean mask) محسوب می‌شود. then مقداری که اگر condition برابر با True ارزیابی شود و else مقداری است که در غیر این صورت مورد استفاده قرار می‌گیرد.

اساسا، ()where. هر مولفه در شی که برای condition مورد استفاده قرار می‌گیرد را دریافت کرده، بررسی می‌کند که حاصل ارزیابی آن مولفه برابر با True در زمینه شرط است یا خیر، و یک ndarray باز می‌گرداند که بسته به آنکه کدام شرط اعمال شود دربرگیرنده then یا else است. این دستور می‌تواند در یک عبارت ترکیبی if-then تودرتو نوشته شود که امکان محاسبه مقادیر بر پایه شرایط گوناگون را فراهم می‌کند.

1>>> np.where(condition1, x1, 
2        np.where(condition2, x2, 
3            np.where(condition3, x3, ...)))

از این دو تابع برای پاکسازی Place of Publication استفاده می‌شود، زیرا این ستون دارای اشیای رشته‌ای است که محتوای آن به صورت زیر نمایش داده می‌شود.

1>>> df['Place of Publication'].head(10)
2Identifier
3206                                  London
4216                London; Virtue & Yorston
5218                                  London
6472                                  London
7480                                  London
8481                                  London
9519                                  London
10667     pp. 40. G. Bryan & Co: Oxford, 1898
11874                                 London]
121143                                 London
13Name: Place of Publication, dtype: object

قابل مشاهده است که در برخی از سطرها محل انتشار به وسیله دیگر اطلاعات غیرلازم احاطه شده. اگر به مقادیر موجود با دقت بیشتر نگاه شود، می‌توان به وضوح مشاهده کرد که این شرایط تنها برای برخی سطرهایی که موقعیت انتشار آن‌ها «London» یا «Oxford» است وجود دارد. در ادامه دو ورودی مشخص بررسی می‌شوند.

1>>> df.loc[4157862]
2Place of Publication                                  Newcastle-upon-Tyne
3Date of Publication                                                  1867
4Publisher                                                      T. Fordyce
5Title                   Local Records; or, Historical Register of rema...
6Author                                                        T.  Fordyce
7Flickr URL              http://www.flickr.com/photos/britishlibrary/ta...
8Name: 4157862, dtype: object
9
10>>> df.loc[4159587]
11Place of Publication                                  Newcastle upon Tyne
12Date of Publication                                                  1834
13Publisher                                                Mackenzie & Dent
14Title                   An historical, topographical and descriptive v...
15Author                                               E. (Eneas) Mackenzie
16Flickr URL              http://www.flickr.com/photos/britishlibrary/ta...
17Name: 4159587, dtype: object

این دو کتاب در یک محل منتشر شده‌اند، اما در نام محل یکی از آن‌ها خط فاصله وجود دارد و در دیگری این خط وجود ندارد. برای پاکسازی این ستون در یک حرکت، می‌توان از دستور ()str.contains برای دریافت ماسک‌های بولین استفاده کرد. ستون را می‌توان به صورت زیر پاک کرد.

1>>> pub = df['Place of Publication']
2>>> london = pub.str.contains('London')
3>>> london[:5]
4Identifier
5206    True
6216    True
7218    True
8472    True
9480    True
10Name: Place of Publication, dtype: bool
11
12>>> oxford = pub.str.contains('Oxford')

دستور بالا با np.where به صورت زیر ترکیب می‌شود.

1df['Place of Publication'] = np.where(london, 'London',
2                                      np.where(oxford, 'Oxford',
3                                               pub.str.replace('-', ' ')))
4
5>>> df['Place of Publication'].head()
6Identifier
7206    London
8216    London
9218    London
10472    London
11480    London
12Name: Place of Publication, dtype: object

در اینجا تابع np.where در یک ساختار تو در تو فراخوانی می‌شود، با condition که سری‌هایی از بولین‌های مشاهده شده با ()str.contains است. متد ()contains به طرز مشابهی با in keyword توکار استفاده شده برای کشف وقوع یک موجودیت در تکرارپذیری (یا زیررشته در رشته) به کار می‌رود.

جایگزینی که استفاده می‌شود در واقع رشته‌ای است که محل مورد انتظار انتشار را نشان می‌دهد. همچنین، با استفاده از دستور ()str.replace خط تیره‌ها با فاصله (space) جایگزین و مجددا به ستون دیتافریم (DataFrame) تخصیص داده می‌شوند. اگرچه داده‌های کثیف زیادی در این مجموعه داده وجود دارند ولی در حال حاضر تنها دو ستون مورد بررسی قرار می‌گیرند. ابتدا پنج ورودی اول بررسی می‌شوند و مشخص است که مجموعه داده در حال حاضر نسبت به زمانی که هنوز کار پاکسازی آغاز نشده بود بسیار واضح‌تر و شفاف‌تر هستند.

1>>> df.head()
2           Place of Publication Date of Publication              Publisher  \
3206                      London                1879        S. Tinsley & Co.
4216                      London                1868           Virtue & Co.
5218                      London                1869  Bradbury, Evans & Co.
6472                      London                1851          James Darling
7480                      London                1857   Wertheim & Macintosh
8
9                                                        Title    Author  \
10206                         Walter Forbes. [A novel.] By A. A        AA
11216         All for Greed. [A novel. The dedication signed...   A. A A.
12218         Love the Avenger. By the author of “All for Gr...   A. A A.
13472         Welsh Sketches, chiefly ecclesiastical, to the...   E. S A.
14480         [The World in which I live, and my place in it...   E. S A.
15
16                                                   Flickr URL
17206         http://www.flickr.com/photos/britishlibrary/ta...
18216         http://www.flickr.com/photos/britishlibrary/ta...
19218         http://www.flickr.com/photos/britishlibrary/ta...
20472         http://www.flickr.com/photos/britishlibrary/ta...
21480         http://www.flickr.com/photos/britishlibrary/ta...

نکته: در این لحظه، Place of Publication می‌تواند کاندیدای خوبی برای تبدیل به Categorical dtype باشد، زیرا می‌توان مجموعه یکتا و به اندازه کافی کوچکی از شهرها را با اعداد صحیح رمزنگاری کرد. (میزان حافظه مورد استفاده برای Categorical متناسب با تعداد دسته‌ها به علاوه طول داده است. یک شی dtype ثابتی است که طول داده را چند برابر می‌کند.)

پاکسازی کل مجموعه داده با استفاده از تابع applymap

در یک موقعیت خاص، می‌توان مشاهده کرد که «dirt» تنها در یک ستون محلی نشده، بلکه بیشتر گسترش یافته است. نمونه‌هایی نیز وجود دارد که در آن‌ها اعمال یک تابع سفارشی‌سازی شده به هر سلول یا مولفه در دیتافریم می‌تواند مفید واقع شود. متد ()applymap. در کتابخانه Pandas مشابه تابع محلی ()map است و به سادگی یک تابع را بر همه دیگر عناصر موجود در دیتافریم اعمال می‌کند. مثال زیر در همین راستا بسیار قابل توجه است و در آن یک DataFrame از فایل مجموعه داده «university_towns.txt» ساخته شده.

1$ head Datasets/univerisity_towns.txt
2Alabama[edit]
3Auburn (Auburn University)[1]
4Florence (University of North Alabama)
5Jacksonville (Jacksonville State University)[2]
6Livingston (University of West Alabama)[2]
7Montevallo (University of Montevallo)[2]
8Troy (Troy University)[2]
9Tuscaloosa (University of Alabama, Stillman College, Shelton State)[3][4]
10Tuskegee (Tuskegee University)[5]
11Alaska[edit]

قابل ملاحظه است که اسامی ایالت‌ها به صورت دوره‌ای با شهر دانشگاه در آن ایالت دنبال شده‌اند: StateA TownA1 TownA2 StateB TownB1 TownB2 و .... اگر به شیوه‌ای که نام ایالت‌ها در فایل نوشته شده توجه شود، قابل مشاهده است که همه آن‌ها دارای زیر رشته «[edit]» هستند. می‌توان از مزایای این الگو با ساخت لیستی از تاپل‌های (state, city) و پوشش‌دهی آن لیست در یک دیتافریم بهره‌مند شد.

1>>> university_towns = []
2>>> with open('Datasets/university_towns.txt') as file:
3...     for line in file:
4...         if '[edit]' in line:
5...             # Remember this `state` until the next is found
6...             state = line
7...         else:
8...             # Otherwise, we have a city; keep `state` as last-seen
9...             university_towns.append((state, line))
10
11>>> university_towns[:5]
12[('Alabama[edit]\n', 'Auburn (Auburn University)[1]\n'),
13 ('Alabama[edit]\n', 'Florence (University of North Alabama)\n'),
14 ('Alabama[edit]\n', 'Jacksonville (Jacksonville State University)[2]\n'),
15 ('Alabama[edit]\n', 'Livingston (University of West Alabama)[2]\n'),
16 ('Alabama[edit]\n', 'Montevallo (University of Montevallo)[2]\n')]

می‌توان این لیست را در دیتافریم پوشش داد و ستون‌ها را با عنوان «State» و «RegionName» تنظیم کرد. Pandas هر عنصر در لیست را دریافت کرده و State را روی مقدار سمت چپ و RegionName را روی مقدار سمت راست تنظیم می‌کند. مجموعه داده حاصل مشابه زیر خواهد بود:

1>>> towns_df = pd.DataFrame(university_towns,
2...                         columns=['State', 'RegionName'])
3
4>>> towns_df.head()
5 State                                         RegionName
60  Alabama[edit]\n                    Auburn (Auburn University)[1]\n
71  Alabama[edit]\n           Florence (University of North Alabama)\n
82  Alabama[edit]\n  Jacksonville (Jacksonville State University)[2]\n
93  Alabama[edit]\n       Livingston (University of West Alabama)[2]\n
104  Alabama[edit]\n         Montevallo (University of Montevallo)[2]

می‌توان این رشته‌ها را در حلقه بالا پاک‌سازی کرد، و Pandas انجام این کار را ساده می‌کند. در این راستا، تنها نام ایالت‌ها و شهرها مورد نیاز است و می‌توان هر چیز دیگری را پاک کرد. در حالیکه امکان استفاده از متد ()str. در Pandas وجود دارد، می‌توان از ()applymap برای نگاشت یک چیز قابل فراخوانی در پایتون (Python callable) برای هر مولفه در دیتافریم بهره برد. در کد بالا از عبارت element استفاده شد، اما واقعا معنی این کار چیست؟؟ مجموعه داده «الکی» زیر مفروض است.

1        0           1
20    Mock     Dataset
31  Python     Pandas
42    Real     Python
53   NumPy     Clean

در این مثال، هر سلول (Mock، Dataset، Python، Pandas و دیگر موارد) یک مولفه است. بنابراین، ()applymap یک تابع را بر هر یک از این موارد به طور مستقل اعمال می‌کند. اکنون تابع بیان شده تعریف می‌شود.

1>>> def get_citystate(item):
2...     if ' (' in item:
3...         return item[:item.find(' (')]
4...     elif '[' in item:
5...         return item[:item.find('[')]
6...     else:
7...         return item

()applymap. کتابخانه Pandas تنها یک پارامتر را دریافت می‌کند که تابعی است (قابل فراخوانی) که باید بر هر عنصر اعمال شود.

1>>> towns_df =  towns_df.applymap(get_citystate)

ابتدا، یک تابع پایتون که یک مولفه را از دیتافریم به عنوان پارامتر خود دریافت می‌کند تعریف می‌شود. درون تابع، بررسی‌هایی به منظور تعیین اینکه ) یا ] وجود دارد یا خیر انجام و بسته به بررسی‌ها، مقادیر توسط تابع بازگردانده می‌شوند. در نهایت، تابع ()applymap در شی فراخونی می‌شود. اکنون دیتافریم تمیزتر است.

1>>> towns_df.head()
2     State    RegionName
30  Alabama        Auburn
41  Alabama      Florence
52  Alabama  Jacksonville
63  Alabama    Livingston
74  Alabama    Montevallo

متد ()applymap هر مولفه را از دیتافریم گرفته و آن را به تابع پاس می‌دهد، این در حالیست که مقادیر قبلی با مقدار بازگردانده شده جایگزین می‌شوند. به همین سادگی!

نکته: در حالیکه متد applymap. ساده و دارای کاربرد گسترده‌ای است، می‌تواند زمان اجرای قابل توجهی برای مجموعه داده‌های بزرگ داشته باشد، زیرا یک چیز قابل فراخوانی توسط پایتون را برای هر مولفه نگاشت می‌کند. در برخی موارد، این راهکار که Cython یا NumPY را به کار گیرد (که به نوبه خود تماس‌ها را در C انجام می‌دهد) می‌تواند برای انجام عملیات برداری‌سازی موثرتر باشد.

تغییر نام ستون‌ها و گذر از سطرها

اغلب، مجموعه داده‌هایی که افراد با آن کار می‌کنند دارای اسامی برای ستون‌ها هستند که معنای آن‌ها به سادگی قابل درک نیست، و یا حاوی اطلاعات غیر مهمی مانند تعریف عبارات موجود در مجموعه داده و پانویس‌ها در چند سطر اول و آخر هستند. در این شرایط، داده‌کاو معمولا تمایل به تغییر نام ستون‌ها و گذر از سطرهای خاص و رسیدن به اطلاعات لازم با برچسب‌های صحیح و معنادار دارد. برای آنکه چگونگی انجام این کار شفاف شود، پنج سطر اول مجموعه داده «olympics.csv» با دستور head نمایش داده می‌شوند.

1$ head -n 5 Datasets/olympics.csv
20,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
3,? Summer,01 !,02 !,03 !,Total,? Winter,01 !,02 !,03 !,Total,? Games,01 !,02 !,03 !,Combined total
4Afghanistan (AFG),13,0,0,2,2,0,0,0,0,0,13,0,0,2,2
5Algeria (ALG),12,5,2,8,15,3,0,0,0,0,15,5,2,8,15
6Argentina (ARG),23,18,24,28,70,18,0,0,0,0,41,18,24,28,70

اکنون مجموعه داده در دیتافریم Pandas خوانده می‌شود.

1>>> olympics_df = pd.read_csv('Datasets/olympics.csv')
2>>> olympics_df.head()
3                   0         1     2     3     4      5         6     7     8  \
40                NaN  ? Summer  01 !  02 !  03 !  Total  ? Winter  01 !  02 !
51  Afghanistan (AFG)        13     0     0     2      2         0     0     0
62      Algeria (ALG)        12     5     2     8     15         3     0     0
73    Argentina (ARG)        23    18    24    28     70        18     0     0
84      Armenia (ARM)         5     1     2     9     12         6     0     0
9
10      9     10       11    12    13    14              15
110  03 !  Total  ? Games  01 !  02 !  03 !  Combined total
121     0      0       13     0     0     2               2
132     0      0       15     5     2     8              15
143     0      0       41    18    24    28              70
154     0      0       11     1     2     9              12

این مجموعه داده حقیقتا کثیف است! ستون‌ها فرم رشته‌ای از اعداد صحیح اندیس‌گذاری شده از صفر هستند. سطری که باید «سرآیند» (Header) باشد (سطری که برای نام‌گذاری ستون‌ها مورد استفاده قرار می‌گیرد) در [olympics_df.iloc[0 قرار دارد. این مساله به این دلیل به وقوع می‌پیوندد که فایل CSV موجود با ۰، ۱، ۲، ...، ۱۵ آغاز شده است. همچنین، اگر منبع مجموعه داده مذکور بررسی شود، می‌توان ملاحظه کرد که NaN بالا باید چیزی شبیه «Country» باشد و ? Summer باید نمایانگر «Summer Games» و !01 باید «Gold» باشد و به همین صورت. بنابراین، نیاز به انجام دو کار است:

  • حذف یک سطر و تنظیم هِدِر به عنوان اولین سطر (دارای اندیس ۰)
  • تغییر نام ستون‌ها

می‌توان در حال خواندن فایل CSV با پاس دادن برخی پارامترها به تابع ()read_csv از سطرها گذر و هدِر را تنظیم کرد. این تابع پارامترهای دلخواه زیادی را دریافت می‌کند، اما در این مورد تنها به یک مورد (header) برای حذف سطر ۰ نیاز است.

1>>> olympics_df = pd.read_csv('Datasets/olympics.csv', header=1)
2>>> olympics_df.head()
3          Unnamed: 0  ? Summer  01 !  02 !  03 !  Total  ? Winter  \
40        Afghanistan (AFG)        13     0     0     2      2         0
51            Algeria (ALG)        12     5     2     8     15         3
62          Argentina (ARG)        23    18    24    28     70        18
73            Armenia (ARM)         5     1     2     9     12         6
84  Australasia (ANZ) [ANZ]         2     3     4     5     12         0
9
10   01 !.1  02 !.1  03 !.1  Total.1  ? Games  01 !.2  02 !.2  03 !.2  \
110       0       0       0        0       13       0       0       2
121       0       0       0        0       15       5       2       8
132       0       0       0        0       41      18      24      28
143       0       0       0        0       11       1       2       9
154       0       0       0        0        2       3       4       5
16
17   Combined total
180               2
191              15
202              70
213              12
224              12

اکنون سطر صحیحی به عنوان هِدِر قرار گرفت و همه سطرهای غیر لازم حذف شدند. این نکته که چگونه Pandas نام ستون‌های حاوی اسامی کشورها را از NaN به  Unnamed: 0 تغییر داد قابل توجه است. برای تغییر نام ستون‌ها، از متد ()rename دیتافریم استفاده می‌شود که امکان برچسب زدن یک محور برپایه نگاشت را فراهم می‌کند (در این مورد یک dict). اکنون کار با تعریف یک دیکشنری که اسامی ستون‌های کنونی را (به عنوان کلیدی) به موارد قابل استفاده‌تر تبدیل می‌کند آغاز خواهد شد (مقادیر دیکشنری).

1>>> new_names =  {'Unnamed: 0': 'Country',
2...               '? Summer': 'Summer Olympics',
3...               '01 !': 'Gold',
4...               '02 !': 'Silver',
5...               '03 !': 'Bronze',
6...               '? Winter': 'Winter Olympics',
7...               '01 !.1': 'Gold.1',
8...               '02 !.1': 'Silver.1',
9...               '03 !.1': 'Bronze.1',
10...               '? Games': '# Games',
11...               '01 !.2': 'Gold.2',
12...               '02 !.2': 'Silver.2',
13...               '03 !.2': 'Bronze.2'}

تابع ()rename در شی فراخوانی می‌شود.

1>>> olympics_df.rename(columns=new_names, inplace=True)

تنظیم inplace به مقدار True مشخص می‌کند که تغییرات به طور مستقیم روی شی انجام شوند. اکنون این مساله با قطعه کد زیر بررسی می‌شود.

1>>> olympics_df.head()
2                   Country  Summer Olympics  Gold  Silver  Bronze  Total  \
30        Afghanistan (AFG)               13     0       0       2      2
41            Algeria (ALG)               12     5       2       8     15
52          Argentina (ARG)               23    18      24      28     70
63            Armenia (ARM)                5     1       2       9     12
74  Australasia (ANZ) [ANZ]                2     3       4       5     12
8
9   Winter Olympics  Gold.1  Silver.1  Bronze.1  Total.1  # Games  Gold.2  \
100                0       0         0         0        0       13       0
111                3       0         0         0        0       15       5
122               18       0         0         0        0       41      18
133                6       0         0         0        0       11       1
144                0       0         0         0        0        2       3
15
16   Silver.2  Bronze.2  Combined total
170         0         2               2
181         2         8              15
192        24        28              70
203         2         9              12
214         4         5              12

خلاصه مطلب

در این راهنما، چگونگی حذف اطلاعات غیر لازم از یک مجموعه داده با استفاده از تابع ()drop و چگونگی تنظیم اندیس برای مجموعه داده به نوعی که آیتم‌های آن به سادگی قابل ارجاع باشند بیان شد. علاوه بر این، چگونگی پاک کردن فیلدهای object با اکسسور ()str. و نحوه پاکسازی کل مجموعه داده با استفاده از متد ()applymap مورد بررسی قرار گرفت. در پایان، چگونگی گذر از سطرها در فایل CSV و تغییر نام ستون‌ها با استفاده از متد rename() تشریح شد. تسلط بر چگونگی پاک‌سازی داده‌ها بسیار مهم است، زیرا این کار بخش مهم و بزرگی از پروژه‌های علم داده محسوب می‌شود.

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

^^

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
Real Python
۱ دیدگاه برای «پاکسازی داده (Data Cleaning) در پایتون با استفاده از NumPy و Pandas — راهنمای جامع»

سلام وقت بخیر
اگر دیتایی داشته باشیم و بخوایم مثلا فقط دیتا شهر تهران به نمایش نمایش بده یا برعکس همه دیتا نمایش بده به جز تهران از چه کدی میشه استفاده کرد؟

نظر شما چیست؟

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