«دانشمندان داده» (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() تشریح شد. تسلط بر چگونگی پاک‌سازی داده‌ها بسیار مهم است، زیرا این کار بخش مهم و بزرگی از پروژه‌های علم داده محسوب می‌شود.

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

^^

بر اساس رای 8 نفر
آیا این مطلب برای شما مفید بود؟
شما قبلا رای داده‌اید!
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.

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

نظر شما چیست؟

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