مبادئ وتقنيات علم البيانات

الفصل الرابع: التحليل الاستكشافي للبيانات

فهرس الفصل:


مقدمة

في التحليل الاستكشافي للبيانات، إحدى أهم الخطوات في دورة حياة علم البيانات، نقوم بالتلخيص، الرسم البياني وتحويل البيانات لشكل يسهل علينا فهمها بشكل واضح. الإحصائي “جون توكي” عرّف التحليل الاستكشافي للبيانات كالتالي:

“التحليل الاستكشافي للبيانات” هي طريقه، حالة ذات مرونة، ورغبه في أن نرى تلك الأشياء التي نؤمن أنها ليست موجودة في البيانات، وكذلك تلك التي نؤمن بوجودها.

طالب علم البيانات قد يرى هذا التعريف غير كافي، هل طريقة الاستكشاف فقط كافيه لعمل تحليل للبيانات؟ على العكس، نقطة “توكي” هي أنه يجب علينا فهم البيانات قبل التسرع بتطبيق الاختبارات الإحصائية. يستفاد من كلام “توكي” في خوارزميات اتخاذ القرارات هذه الأيام.

في خطوة التحليل الاستكشافي للبيانات نسعى لفهم البيانات بشكل عميق. والإبقاء على “حالة المرونه” لتساعدنا على إيجاد ما نبحث عنه. فهمنا للأدوات الحسابية يساعدنا على البدء بعملية البحث. في هذا الفصل سنتعرف على الأدوات الأكثر تطوراً واستخداماً في تحليل البيانات. على الرغم من اختلاف طريقة التحليل الاستكشافي للبيانات في المجالات المختلفة، غالباً ما نبدأ الاستكشاف بفهم التالي:

  • أنواع البيانات في الأعمدة وتقسيم البيانات في الصفوف.
  • توزيع البيانات الرقمية ومقياس الوسط والانتشار.
  • العلاقات بين البيانات الكمية.

أنواع البيانات

عادةً ما نبدأ التحليل الاستكشافي للبيانات بعرض أنواع البيانات في الجداول. على الرغم من وجود طُرق كثيره لتصنيف أنواع البيانات، في هذا الكتاب سنستخدم ثلاث من أشهر الأنواع:

  • البيانات الاسمية: يقصد بها البيانات التي تعبر عن تصنيفات ليس لها ترتيب معين. مثلاً: الانتماء السياسي ( ديموقراطي، جمهوري، غيرها)، الجنس (ذكر، انثى)، نظام الحاسب ( ويندوز، ماكنتوش، لينكس).
  • البيانات الترتيبية: بيانات بتصنيفات مرتبه. مثل: مقاسات الملابس ( صغير، وسط، كبير)، مقياس ليكرت (معارض، حيادي، متفِق)، مستوى التعليم (ثانوي، بكالريوس، ماجستير). البيانات الاسمية والترتيبية تعتبر أنواع من البيانات التصنيفية. 📝
  • البيانات الكمية: وهي البيانات العددية أو الرقمية. مثل: الطول، السعر، والمسافة.

ونشير لهذه الأنواع بـ أنواع البيانات الإحصائية، أو للتبسيط أنواع البيانات. 📝 📝 📝

أنواع البيانات الحسابية × الإحصائية

تقوم بانداز بتعيين نوع حسابي لكل عمود لدينا ليمثل كيف يتم حفظ البيانات في ذاكرة الحاسب.

مثلاً، لنأخذ الجدول التالي الذي يحتوي على أوزان الأطفال بعد الولادة، عرق الأم، والمستوى التعليمي للأم:

لتحميل البيانات، اضغط هنا.

babies_small = pd.read_csv('babies23.data', delimiter='\s+')[['wt', 'race', 'ed']]
babies_small
ed race wt  
5 8 120 0
5 0 113 1
2 0 128 2
2 1 130 1233
4 0 125 1234
4 0 117 1235
1236 rows × 3 columns

كل عمود في هذا الـ DataFrame هو حسابياً من النوع الكمي. في بياناتنا هذه، النوع int64 يعني أن كل عمود يحتوي على عدد صحيح:

babies_small.dtypes
wt      int64
race    int64
ed      int64
dtype: object

ولكن، لن يكون مفهوماً العمل مع هذه الأعمدة كبيانات إحصائية كمية. لفهم أنواع البيانات لدينا، يجب علينا دائماً العودة إلى قاموس البيانات. قاموس البيانات هو شرح تفصيلي يأتي مع البيانات يحتوي على تفاصيل وشرح أكثر لمحتوى الأعمدة. مثلاً، قاموس البيانات للبيانات التي نعمل عليها الآن هو كالتالي: 📝

wt -  الوزن عند الولادة بالأونصة (999 غير معروف)
race - عرق الأم
    0-5= بيضاء
    6= مكسيكيه
    7= سوداء
    8= آسيويه
    9= منوعه
    99= غير معروف
ed - مستوى تعليم الأم 
   0= اقل من السنه الثامنه, 
   1= من السنة الثامنه حتى 12 - لم تتخرج, 
   2= خريجة ثانويه
   3= ثانويه + مهنيه,
   4= ثانويه + جامعه
   5= خريجة جامعه
   6&7= ثانويه مهنيه غير معروفه
   9= غير معروف

رغم أن قيم الأعمدة الثلاثة wt، race و ed محفوظه كأعداد صحيحه في بانداز، العمود race يحتوي على بيانات أسميه والعمود ed يحتوي على بيانات ترتيبية.

بالأصح، يجب علينا الحذر دائماً حتى مع العمود wt. عند محاولة إيجاد معدل متوسط هذا العمود لن نحصل على بيانات دقيقة لأن العمود يحتوي على قيم بالرقم 999 والتي تعني قيم أوزان غير معروفة للطفل. إذا تركناه كما هو، القيم غير المعروفة ستجعل متوسط الأوزان لدينا أعلى مما يكون.

أهمية أنواع البيانات

أنواع البيانات توجهنا للطريقة الصحيحة للقيام بالعمليات على البيانات ورسمها. مثلاً، الفرق يعتبر مهم عندنا يحدث في البيانات الكمية على العكس عندما يكون في البيانات الترتيبية. هذا يعني في بيانات أوزان الأطفال بعد الولادة المتوسط للوزن يعتبر له معنى على عكس متوسط مستوى التعليم.

بانداز لن تكترث عندما نحاول إيجاد المتوسط للقيم في عمود مستوى التعليم:

babies_small['ed'].mean()
2.9215210355987056

هذه القيمة لا تقدم لنا أي معلومات. يمكن أن نستبدل القيم في العمود ed بمعانيها النصية. مثلاً، يمكن ان نستبدل 0 ب “أقل من السنة الثامنة” و 1 ب “من السنة الثامنة حتى 12 - لم تتخرج” وهكذا.

رغم أن الفرق في البيانات الترتيبية ليس له معنى، نتيجته قد تقدم لنا بعض المعلومات. مثلاً، يمكننا القول أن أم بقيمة ed=5 (خريجة جامعه) أعلى تعليماً من أم بقيمة ed=2 (خريجة ثانوية).

على العكس، لا يؤثر علينا الفرق في البيانات الاسمية. الأم التي قيمة العرق فيها race=6 (مكسيكية) و أم race=7 (سوداء) ببساطة لديهم أعراق مختلفة.

مثال: تدخين الأم وصحة الطفل الرضيع

مؤسسة صحة وتنمية الطفل (CHDS) قامت بإجراء أبحاث لفترات طويلة عن كيف أن الصحة والأمراض تنتقل بين الأجيال.

في إحدى الدراسات، قامت المؤسسة بجمع بيانات من عدة أجيال بين 1960 و 1967 لسيدات في سان فرانسيسكو - إيست باي. على الرغم أن المؤسسة تطالب بتقديم طلب للحصول على هذه البيانات، جزء من هذه البيانات تم إتاحته بواسطة قسم الإحصاء (رابط). قمنا بتحميل البيانات وقراءتها في بانداز:

لتحميل البيانات، اضغط هنا.

babies = pd.read_csv('babies.data', delimiter='\s+')
babies
smoke weight height age parity gestation bwt  
0 100 62 27 0 284 120 0
0 135 64 33 0 282 113 1
1 115 64 28 0 279 128 2
1 150 65 30 0 291 130 1233
0 110 65 21 1 281 125 1234
0 129 65 38 0 297 117 1235
1236 rows × 7 columns

لفهم أنواع البيانات في الأعمدة، نعود دائماً لقاموس البيانات:

العمود الوصف
bwt الوزن عند الولادة بالأونصة (999 غير معروف)
gestation مدة الحمل بالأيام (999 غير معروف)
parity هل أول مولود؟ 0=أول مولود، 9= غير معروف
age عمر الأم بالسنوات
height طول الأم بالبوصة (99 غير معروف)
weight وزن الأم قبل الولادة بالباوند (999 غير معروف)
smoke حالة تدخين الأم: 0=لا تدخن الآن، 1=تدخن الآن، 9=غير معروف

رغم أن القاموس لا يبين أنواع البيانات، يمكننا رؤية أن بعض الأعمدة تحتوي على بيانات أسميه رغم أن القيم الموجودة فيها عبارة عن أرقام. بناءًا فقط على وصف العمود يمكننا معاملة العامودين parity و smoke كبيانات أسميه والبقية كرقميه.

نتمنى دائماً ان يقدم لنا قاموس البيانات كافة المعلومات المهمة عن كل عمود، لكن من الأفضل دائماً التأكد من البيانات عن طريق فحصها. مثلاً، لا يبدو واضحاً دائماً إذا كانت القيم في عمود العمر تحتوي على أرقام (21، 30، 41) أو نطاقات (21-29، 30-40، 41+).

بيانات babies يبدو أنها تحتوي على أعداد صحيحة فقط:

babies
smoke weight height age parity gestation bwt  
0 100 62 27 0 284 120 0
0 135 64 33 0 282 113 1
1 115 64 28 0 279 128 2
1 150 65 30 0 291 130 1233
0 110 65 21 1 281 125 1234
0 129 65 38 0 297 117 1235
1236 rows × 7 columns

لنأخذ لمحه سريعة عن قيم عمود العمر age، يمكننا استخدام دالة series.value_counts()a والتي تظهر لنا عدد مرات تكرار القيم المختلفة:

# تعرض العمر يسار وعدد مرات تكرارة على اليمين
babies['age'].value_counts()
23    93
26    90
24    86
      ..
45     1
44     1
15     1
Name: age, Length: 31, dtype: int64

يمكن أن نرى بأن الكثير من الأمهات في بياناتنا يبلغ أعمارهم بين 23 و 26 سنه. أيضاً، لا نرى أعمار بقيم كسرية (مثلاً 24.5)، يدل ذلك أن العمود age يحتوي فقط على أرقام صحيحه.

بشكل تلقائي، تقوم بانداز بتقييد عدد النتائج التي تظهر لنا. ولعرض المزيد من الأسطر يمكننا القيام بالتالي:

from IPython.display import display

# في هذا الحقل، اظهرنا 10 اسطر. لعرض عدد اسطر اكبر
# قم بتغير الرقم 10 في الكود البرمجي التالي إلى رقم اعلى
with pd.option_context('display.max_rows', 10):
    display(babies['age'].value_counts())
23    93
26    90
24    86
27    85
22    79
      ..
42     4
99     2
45     1
44     1
15     1
Name: age, Length: 31, dtype: int64

بعد القيام بمراجعة كل الأعمدة بنفس الطريقة السابقة. نقوم بتحديد نوع كل عمود بناءًا على قاموس البيانات ومراجعتنا للبيانات:

العمود الوصف نوع البيانات
bwt الوزن عند الولادة بالأونصة (999 غير معروف) كمية
gestation مدة الحمل بالايام (999 غير معروف) كمية
parity هل أول مولود؟ 0=أول مولود، 9= غير معروف أسميه
age عمر الأم بالسنوات كمية
height طول الأم بالبوصة (99 غير معروف) كمية
weight وزن الأم قبل الولادة بالباوند (999 غير معروف) كمية
smoke حالة تدخين الأم: 0=لا تدخن الآن، 1=تدخن الآن، 9=غير معروف أسميه

مثال: المخالفات الصحية في مطاعم سان فرانسيسكو

تقوم مدينة سان فرانسيسكو - كاليفورنيا دورياً بفحص المطاعم للتحقق من المخالفات الصحية. في كل عملية فحص، يحصل المطعم على تقييم من 0 إلى 100 بناءًا على عدد وأنواع المخالفات التي سجلت عليه. المطاعم، التقييم والمخالفات جميعها متوفرة بشكل عام في موقع DataSF (الرابط)، الملف يحتوي على جميع الفحوص من شهر يناير 2016.

قمنا بتحميل جزء من البيانات للمتغير scores:

لتحميل البيانات، اضغط هنا.

scores = pd.read_csv('SFRestaurants.csv')
scores
risk_category violation_description inspection_score business_name  
Low Risk Unclean or degraded floors walls or ceilings 86 All stars Donuts 0
Low Risk Wiping cloths not clean or properly stored or … 92 Soo Fong Restaurant 1
Moderate Risk Moderate risk vermin infestation 86 Dar Bar Pakistani/Indian Cusine 2
High Risk Unclean hands or improper use of gloves 71 USA Power Market 52795
Moderate Risk Inadequate and inaccessible handwashing facili… 81 Thai Cottage Restaurant 52796
Moderate Risk Inadequate and inaccessible handwashing facili… 90 St Mary’s Cathedral/Convention Center 52797
52798 rows × 4 columns

في هذه البيانات، نحن محظوظين أن لدينا قاموس يقدم لنا معلومات أوضح بكثير عن البيانات:

العمود نوع البيانات الوصف
business_name string الاسم العام للمتجر
inspection_score number نتيجة الفحص، قيمة من 0 إلى 100
violation_description string وصف بسطر واحد للمخالفات
risk_category string (لم يعطى وصف)

لاحظ أن القاموس يشرح أنواع البيانات حسابياً وليس أنواعها بشكل إحصائي. مثلاً، يستخدم string بدلاً من التحديد إذا كانت أسميه أو ترتيبية. ولكن نلاحظ أن العمود risk_category لا يحتوي على وصف.

البيانات النصية ليست دائماً أسميه

العمود violation_description يبين لنا وصف للمخالفات. لكن لم يوضح لنا في القاموس إذا ما علينا معاملة البيانات في هذا العمود كبيانات أسميه. قد يحتوي العمود على بيانات نصيه غير منظمة، أو بيانات أدخلت يدوياً. النصوص غير المنظمة عاده ما تأتي من مصادر بيانات بلغه طبيعية، مثل مقالات الصحف، بحث قوقل، و ردود أسئلة الاستبيانات.

عاده لا نتعامل مع تلك البيانات كبيانات أسميه، البيانات الاسمية عاده تتكون من تصنيفات محددة مسبقاً، على عكس النصوص الغير منظمة. لذا، يجيب علينا التحقق من القيم في العمود violation_description لتحديد ما اذا كانت تحتوي على بيانات نصيه غير منظمة أو بيانات أسميه.

إذا كان العمود يحتوي على بيانات نصيه غير منظمة، فيجب أن نتوقع تكرار قليل لبعض القيم بسبب وجود طرق كثيره لتسجيل نفس المخالفة، مثلاً: unclean floors “أرضيه غير نظيفة”, dirty floors “أرضيه قذرة”, floor needs cleaning “الأرضية تحتاج للتنظيف” وغيرها الكثير. في الجانب الآخر، إذا كان العمود يحتوي على قيم محددة مسبقاً، الكثير منها سيتكرر.

في المثال السابق على البيانات النصية الغير منظمة يوضح الكاتب طرق مختلفة لكتابة مخالفات لها نفس المعنى، يعني هنا أمثله لتكرار نفس المخالفات لكن كُتبت بأكثر من طريقه. لذلك التكرار فيها سيكون أقل بكثير من البيانات الاسمية. في حال أردنا أن تكون هذه البيانات أسميه، فيجب علينا تحديد قيم مسبقة مثلاً unclean floors وتستخدم بدلاً من القيم الثلاثة.

نريد أن نتحقق ما أذا كان العمود يحتوي على قيم مكررة:

with pd.option_context('display.max_rows', 14):
    display(scores['violation_description'].value_counts())
Unclean or degraded floors walls or ceilings                          3668
Unapproved or unmaintained equipment or utensils                      2704
Inadequate and inaccessible handwashing facilities                    2653
Moderate risk food holding temperature                                2588
Inadequately cleaned or sanitized food contact surfaces               2467
Wiping cloths not clean or properly stored or inadequate sanitizer    2121
Foods not protected from contamination                                1929
                                                                      ... 
Discharge from employee nose mouth or eye                                6
Noncompliance with Gulf Coast oyster regulation                          5
Mobile food facility stored in unapproved location                       4
Mobile food facility with unapproved operating conditions                3
Unreported or unrestricted ill employee with communicable disease        1
Noncompliance with Cottage Food Operation                                1
Mobile food facility HCD insignia unavailable                            1
Name: violation_description, Length: 67, dtype: int64

النتيجة جعلتنا نتأكد أن القيم المحفوظة في هذا العمود تم تحديدها مسبقاً في قائمة للمخالفات المُحتملة. لذا، سنتعامل مع العمود violation_description كعمود يحتوي على بيانات أسميه.

التحقق من الوصف المفقود

رغم أن العمود risk_category لم يتم وصفه في قاموس البيانات، يمكننا التحقق من محتوى العمود لمحاولة فهم معناه. أولاً، نلاحظ أن العمود يحتوي على ثلاث قيم فقط:

scores['risk_category'].value_counts()
Low Risk         19694
Moderate Risk    14712
High Risk         5686
Name: risk_category, dtype: int64

يمكن أن نفهم أن هذه القيم تشرح خطورة المخالفة. يمكننا التحقق من فهمنا عن طريق عرض المخالفات لكل قيمه من القيم الموجودة في العمود risk_category:

def risk_counts(risk):
    return (scores.loc[scores['risk_category'] == risk,
                       'violation_description']
            .value_counts().head())

الكاتب عرّف دالة تستقبل نص وهنا النص إحدى الأنواع الثلاثة من درجات خطورة المخالفة، وينتج عن الدالة DataFrame يحتوي على كل نوع من المخالفات في تلك الدرجة وعدد مرات تكرارها. يمكنك مراجعة الفصل السابق لمعلومات أكثر عن .loc وكيفية إيجاد الأسطر التي تحتوي على قيم معينة.

risk_counts('High Risk')
High risk food holding temperature             1619
Unclean or unsanitary food contact surfaces    1197
Improper cooling methods                        823
Unclean hands or improper use of gloves         755
High risk vermin infestation                    712
Name: violation_description, dtype: int64
risk_counts('Moderate Risk')
Inadequate and inaccessible handwashing facilities         2653
Moderate risk food holding temperature                     2588
Inadequately cleaned or sanitized food contact surfaces    2467
Foods not protected from contamination                     1929
Moderate risk vermin infestation                           1814
Name: violation_description, dtype: int64
risk_counts('Low Risk')
Unclean or degraded floors walls or ceilings                          3668
Unapproved or unmaintained equipment or utensils                      2704
Wiping cloths not clean or properly stored or inadequate sanitizer    2121
Improper food storage                                                 1817
Unclean nonfood contact surfaces                                      1440
Name: violation_description, dtype: int64

وبشكل سريع نلاحظ، أن أنواع المخالفات مقسمه حسب درجة الخطورة. أيضاً، المخالفات من الدرجة High Risk احتمالية تسببها لأمراض أكثر من المخالفات من الدرجة Low Risk. من المحتمل أن يكون مطعماً بمخالفة High risk vermin infestation “خطورة حصول غزو/هجوم حشري” أقل نظافة و احتماليه تعرض مرتاديه لأمراض أكثر من مطعم بمخالفة Unclean nonfood contact surfaces “أسطح غير مخصصه للطعام غير نظيفة”.

بسبب ذلك، قررنا أن العمود risk_category يحتوي على بيانات ترتيبية توضح درجة خطورة كل مخالفه (Low Risk < Moderate Risk < High Risk).

مراجعة قاموس البيانات

العمود نوع البيانات الوصف
business_name string الاسم العام للمتجر
inspection_score number نتيجة الفحص، قيمة من 0 إلى 100
violation_description string وصف بسطر واحد للمخالفات
risk_category string درجة خطورة المخالفة (Low Risk < Moderate Risk < High Risk)

ملخص

عرفنا البيانات الاسمية، الترتيبية والكمية وأهميتها في علم البيانات. شاهدنا أمثله على بيانات، واستعنا بقواميس البيانات وبالبيانات نفسها لتحديد أنواع البيانات في كل عمود. تأكد أن تفرق بين أنواع البيانات الحسابية والإحصائية.