تحليل البيانات بلغة البايثون – تطبيق عملي

1 3٬886

تحليل البيانات بلغة البايثون من المهام التي يسأل عنها العديد من الأشخاص المهتمون في مجال تحليل البيانات. السبب في ذلك يعود للشهرة الواسعة والقوة التي تتمتع فيه لغة البايثون في هذا المجال البحثي والتطبيقي النشط. أهلا بكم في بايثونات في هذا المقال السريع والذي نتحدث فيه عن مجموعة بيانات تُسمى “Medical Appointment No Shows” وسنقدم خلال المقال مستند Jupyter يشرح بالتفصيل أهم العمليات التحليلية بلغة البايثون، فأهلا وسهلا بكم.

عن المشروع

في هذا المشروع، قمت بدراسة مجموعة بيانات تحتوي على ما يقرب من 100 ألف من سجلات المواعيد الطبية من نظام الصحة العامة البرازيلي المعروف باسم SUS. تم تحميل مجموعة بيانات No Show Appointments وحفظتها باسم “noshowappointments.csv”. في هذا المشروع، يركز التحليل على إيجاد الاتجاهات التي تؤثر على المرضى للتحقق مما إذا كان المرضى سيحضرون إلى المواعيد المحجوزة معهم مسبقا أم لا.

يمكن العثور على وصف المشكلة الأصلي ومجموعة البيانات هنا.

في هذا التحليل ، سيتم الرد على الأسئلة التالية:

1) هل للعمر أي تأثير أو علاقة بحالة الحضور/عدم الحضور للمواعيد؟

2) هل للجمع بين العمر والجنس تأثير أو علاقة بحالة الحضور/عدم الحضور للمواعيد؟

في البداية نقوم باستيراد المكتبات اللازمة للعمل:


#import needed packages for reading and manipulating CSV File, Basic EDA and Visualization
%matplotlib inline
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

معالجة البيانات

في هذا الجزء من العمل سنجهز البيانات للاستخدام والمعالجة ثم نستكشفها ونعالجها.

الخصائص العامة

في الشيفرات البرمجية (الأكواد) التالية، أريد استكشاف مجموعة البيانات وتقييمها بعمق لفهمها بعمق وإيجاد الإجابة على الأسئلة التفصيلية التالية:

  • كم عدد العينات في مجموعة البيانات؟
  • كم عدد الأعمدة في مجموعة البيانات؟
  • ما نوع (أنواع البيانات) في المتغيرات؟
  • هل هناك حاجة لهندسة البيانات؟ مثل تحويل نوع البيانات أو إنشاء المزيد من الأعمدة ببيانات مفيدة؟
  • هل هناك أي تكرارات؟
  • هل هناك أي قيمة مفقودة؟
  • هل هناك أي صفوف مكررة في مجموعة البيانات
  • كم عدد القيم الفريدة غير الفارغة في مجموعة البيانات؟
  • ما هي هذه القيم الفريدة وأهميتها بالنسبة لكل منها؟
# Load and read no show appointments data and print out a few lines 

appointment_df= pd.read_csv('/Users/lubnaalhenaki/Downloads/noshowappointments.csv')

#print few lines to explor the data

appointment_df.head()

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

# Create the summary report ( Find any anomalies in the data)
appointment_df.describe()

الشيفرة البرمجية التالية تُجيب على سؤال:

  • كم عدد العينات في مجموعة البيانات؟
  • كم عدد الأعمدة في مجموعة البيانات؟
appointment_df.shape #There are 110527 records and 14 columns in the dataset.

النتيجة هي:

(110527, 14)

تقييم البيانات

الدالة التالية تُجيب على الأسئلة التفصيلية التي طُرحت في الأعلى من السؤال الثالث وحتى النهاية:

appointment_df.info() #structure of the data

النتيجة:

<class 'pandas.core.frame.DataFrame'>

RangeIndex: 110527 entries, 0 to 110526

Data columns (total 14 columns):

PatientId 110527 non-null float64

AppointmentID 110527 non-null int64

Gender 110527 non-null object

ScheduledDay 110527 non-null object

AppointmentDay 110527 non-null object

Age 110527 non-null int64

Neighbourhood 110527 non-null object

Scholarship 110527 non-null int64

Hipertension 110527 non-null int64

Diabetes 110527 non-null int64

Alcoholism 110527 non-null int64

Handcap 110527 non-null int64

SMS_received 110527 non-null int64

No-show 110527 non-null object

dtypes: float64(1), int64(8), object(5)

memory usage: 11.8+ MB

appointment_df.isnull().sum() #There is no missing values in the above dataset
PatientId 0

AppointmentID 0

Gender 0

ScheduledDay 0

AppointmentDay 0

Age 0

Neighbourhood 0

Scholarship 0

Hipertension 0

Diabetes 0

Alcoholism 0

Handcap 0

SMS_received 0

No-show 0

dtype: int64
appointment_df.columns
Index(['PatientId', 'AppointmentID', 'Gender', 'ScheduledDay',

'AppointmentDay', 'Age', 'Neighbourhood', 'Scholarship', 'Hipertension',

'Diabetes', 'Alcoholism', 'Handcap', 'SMS_received', 'No-show'],

dtype='object')
appointment_df.dtypes
PatientId float64

AppointmentID int64

Gender object

ScheduledDay object

AppointmentDay object

Age int64

Neighbourhood object

Scholarship int64

Hipertension int64

Diabetes int64

Alcoholism int64

Handcap int64

SMS_received int64

No-show object

dtype: object
# checking all possible values on each columns
print(appointment_df.Gender.unique())
print(sorted(appointment_df.Age.unique()))
print(sorted(appointment_df.Neighbourhood.unique()))
print(appointment_df.Scholarship.unique())
print(appointment_df.Hipertension.unique())
print(appointment_df.Diabetes.unique())
print(appointment_df.Alcoholism.unique())
print(appointment_df.Handcap.unique())
print(appointment_df.SMS_received.unique())
print(appointment_df['No-show'].unique())

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

الجدول التالي يُوضح تفاصيل كل عمود ونوع البيانات الموجودة فيه. جميع الأعمدة هنا هي independent variables وعمود التصنيف او depedent variable هو No-Show:

لفحص اذا كان هناك سجلات مُكررة أم لا نستخدم السطر التالي:

sum(appointment_df.duplicated()) #there is no duplicate rows in the dataset

والنتيجة هي 0، حيث لا يُوجد سجلات مكررة في البيانات. للتعرف اذا كانت البيانات متوزانة ام لا، نقوم بإحصاء عدد القيم المتاحة للعمود المستقل No-Show كما يلي:

appointment_df['No-show'].value_counts() #this dataset is unbalanced

No 88208

Yes 22319

Name: No-show, dtype: int64

إليكم بعض الاستنتاجات السريعة:

  • هناك 13 متغيرًا مستقلاً ومتغيرًا واحدًا تابعًا (no_show) في مجموعة البيانات.
  • لا تحتوي مجموعة البيانات على أي قيم مفقودة (NaNs).
  • يجب تغيير نوع العمودين Schedule_day وointment_day إلى datetime.
  • مجموعة البيانات غير متوازنة
  • لم يتم تحديد موعد الساعة (وهو ما يعادل 00:00:00). لن نتمكن من تحليل ما إذا كانت ساعة الموعد لها علاقة بعدم الحضور.

تنظيف البيانات

في هذا الجزء من العمل، سأقوم بحل المشكلات الموجودة في مجموعة البيانات من الملاحظات التي حصلنا عليها في الأعلى:

  • الحد الأدنى للعمر هو -1 ، ومن الواضح أن الأشخاص لا يمكن أن يكون لديهم سن -1.
  • نحذف الأعمدة ‘PatientId’ و ‘AppointmentID’ من إطار البيانات لأنهما مجرد بعض الأرقام المولدة من النظام ولا ينبغي استخدامها للتنبؤ بالمتغير التابع، وكذلك لتحسين إمكانية إعادة تعديل قاعدة البيانات.
  • نعيد تسمية الأعمدة لاستخدام أسماء أسهل أثناء الاستكشاف مع إصلاح الأخطاء الإملائية.
  • قم بتغيير قيمة الإعاقة إلى ثنائي (0 و 1) ، لأننا نريد فقط معرفة ما إذا كان المريض يعاني من إعاقة وليس عدد الإعاقات التي يعاني منها.
  • يجب تغيير نوع العمودين Schedule_day وointment_day إلى datetime.
  • نظرًا لأن موعد الموعد يحتوي على 00:00:00 في طابعه الزمني، فسوف نتجاهله ونزيله.

# After discussing the structure of the data and any problems that need to be
# cleaned, perform those cleaning steps in the second part of this section.

appointment_df.query('Age == -1') ## show all recordes that meet the condiction
appointment_df.loc[appointment_df.Age == -1,'Age'] = 0 #reassign value to zero
print("Unique Values in `Age` => {}".format(np.sort(appointment_df.Age.unique()))) # to ensure the value change correctly

وستكون النتيجة كما يلي:


Unique Values in `Age` => [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
90 91 92 93 94 95 96 97 98 99 100 102 115]

#Drop 'PatientId' and 'AppointmentID' from appointment dataframe
appointment_df.drop(columns=['AppointmentID','PatientId'],axis=1,inplace=True)

#Quick sure of removing columns (before is 14 and now is 12)
appointment_df.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 110527 entries, 0 to 110526
Data columns (total 12 columns):
Gender 110527 non-null object
ScheduledDay 110527 non-null object
AppointmentDay 110527 non-null object
Age 110527 non-null int64
Neighbourhood 110527 non-null object
Scholarship 110527 non-null int64
Hipertension 110527 non-null int64
Diabetes 110527 non-null int64
Alcoholism 110527 non-null int64
Handcap 110527 non-null int64
SMS_received 110527 non-null int64
No-show 110527 non-null object
dtypes: int64(7), object(5)
memory usage: 10.1+ MB

#Rename the columns which have incorrect spelling mistakes - this will helps us create columns in easy to understand way
appointment_df.rename(columns={'No-show': 'noshow','Hipertension':'Hypertension','Handcap':'Handicap'}, inplace=True)
appointment_df.rename(columns= lambda x: x.lower(), inplace= True)
appointment_df.head(2)

# remove the level values from handcap variable
appointment_df.handicap=appointment_df.handicap.map({0:0,1:1,2:1,3:1,4:1})
appointment_df.handicap.unique() #to ensure we only get binary value

والنتيجة هي:

array([0, 1])
# Convert columns types
appointment_df['scheduledday'] = pd.to_datetime(appointment_df['scheduledday']).dt.date.astype('datetime64[ns]')
appointment_df['appointmentday'] = pd.to_datetime(appointment_df['appointmentday']).dt.date.astype('datetime64[ns]')

# Check if the type is now datetime
appointment_df.info()
# Print Unique Values for 'AppointmentDay'
print("Unique Values in Appointment Day are: {}".format(np.sort(appointment_df.appointmentday.dt.strftime('%Y-%m-%d').unique())))

وستكون النتيجة:


Unique Values in Appointment Day are: ['2016-04-29' '2016-05-02' '2016-05-03' '2016-05-04' '2016-05-05'
'2016-05-06' '2016-05-09' '2016-05-10' '2016-05-11' '2016-05-12'
'2016-05-13' '2016-05-14' '2016-05-16' '2016-05-17' '2016-05-18'
'2016-05-19' '2016-05-20' '2016-05-24' '2016-05-25' '2016-05-30'
'2016-05-31' '2016-06-01' '2016-06-02' '2016-06-03' '2016-06-06'
'2016-06-07' '2016-06-08']

من التفاصيل أعلاه يمكننا أن نرى أن تاريخ الموعد يتراوح من 2016-04-29 إلى 2016-06-08. يمتد يوم الموعد ما يزيد قليلاً عن شهر واحد على عكس اليوم المجدول الذي يمتد حوالي 7 أشهر.

العمليات الاستكشافية على البيانات

بعد تنظيف مجموعة البيانات، في هذا الجزء من العمل، سنستخدم الاخراج الرسومي للبيانات للإجابة على الأسئلة وفهم البيانات بعمق.


# and plot basic histogram charts
appointment_df.hist(figsize=(15, 8));

الاستنتاجات التي حصلنا عليها من الخطوة السابقة:

  • العمر: هناك العديد من الشباب في مجموعة البيانات ولكن بشكل عام يمكننا أن نرى أن هناك قممًا للرضع ثم يبدأ التوزيع في التوحد. ينخفض
  • عدد المرضى بشكل كبير بالنسبة للمرضى الذين تزيد أعمارهم عن 60 عامًا، ويُمكننا أن نرى توزيع منحرف لليمين.
  • إدمان الكحول: معظم المرضى ليسوا مدمنين على الكحول.
  • داء السكري: معظم المرضى ليسوا من مرضى السكر ولكن أكثر من مدمنين على الكحول.
  • الإعاقة: هناك فئات للإعاقة حيث أن معظم الناس ليسوا معاقين.
  • تم استلام الرسائل القصيرة: تلقى معظم المرضى رسالة
  • ارتفاع ضغط الدم: لا يتم تشخيص ارتفاع ضغط الدم لدى معظم المرضى.
appointment_df['noshow'].value_counts().plot.bar(figsize = (3,3), title = 'Showed Up Patients', color = 'b')
plt.xlabel('No Show')
plt.ylabel('Number');

من المعلومات المذكورة أعلاه يمكننا أن نرى أنه من الواضح أن هناك اختلالًا في التوازن. من بين 110500 مريض ، يأتي حوالي 88000 (80٪) من المرضى للزيارة بعد الموعد وحوالي 20000 (20٪) يتخطون مواعيدهم.

تذكير “لا” يعني إذا حضر المريض لموعده ، و “نعم” إذا لم يحضر

appointment_df.Gender.value_counts().plot(kind='bar') 
#show number of female and male, we can observed that female is double male
ax = sns.countplot(x=appointment_df.gender, hue=appointment_df.noshow, data=appointment_df)
ax.set_title("Show/NoShow for Females and Males")
x_ticks_labels=['Female', 'Male']
ax.set_xticklabels(x_ticks_labels)
plt.xlabel('Gender')
plt.ylabel('Number');
plt.show()

من الشكل البياني أعلاه يمكننا أن نرى بوضوح أن المرضى “الإناث” عادة ما يكون لديهم مواعيد أكثر من المرضى “الذكور”. لذلك، قد يكون الجنس عاملاً مهمًا. ولكن إذا نظرنا عن كثب إلى توزيع NoShow عبر Male’s و Female فهو متماثل تقريبًا. لذلك، قد لا يلعب الجنس دورًا مهمًا في تحديد ما إذا كان المريض يأتي في زيارة أم لا.

الاستقصاء والبحث في السؤال الأول: هل للعمر أي تأثير أو علاقة بحالة الحضور/عدم الحضور للمواعيد؟

# Use this, and more code cells, to explore your data. Don't forget to add
# Markdown cells to document your observations and findings.
plt.figure(figsize=(16,2))
plt.xticks(rotation=90)
age = sns.boxplot(x=appointment_df.age,y=appointment_df.noshow).set_title('Age Distribution Split by No-Show Category')

sns.distplot(appointment_df['age'])
plt.show()

من الشكل في الأعلى يمكننا أن نرى أن متوسط العمر حوالي 30 وأن معدل الذكاء بين 18 و 55. أيضًا، يمكننا أن نرى الأشخاص الذين لم يحضروا لمواعيدهم كانوا أصغر سناً. من ناحية أخرى، المرضى الذين حضروا للموعد، يبدو أن الفئة العمرية من 40 إلى 60 قد أبدت بالفعل اهتمامًا بالموعد والحضور عند مقارنتها بالفئات العمرية من 0 إلى 20.

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

الاستقصاء والبحث في السؤال الثاني: هل للجمع بين العمر والجنس تأثير أو علاقة بحالة الحضور/عدم الحضور للمواعيد؟

sns.catplot(x="gender", y="age", col="noshow", data=appointment_df, height=6, kind="bar", palette="muted", )

# Analysing the distribution among genders with their age whose status for NoShow is "No"
# i.e, they are coming for the appointment.
df = pd.DataFrame()
df['Age'] = range(100) # Setting up for the age limit of 100 only
Male = df.Age.apply(lambda x:len(appointment_df[(appointment_df.age == x) & (appointment_df.gender == 'M') & (appointment_df.noshow == 'No')]))
Female = df.Age.apply(lambda x:len(appointment_df[(appointment_df.age == x) & (appointment_df.gender == 'F') & (appointment_df.noshow == 'No')]))

# multiple line plot
plt.plot( df, Male, marker='o', markerfacecolor='blue', markersize=2, color='skyblue', linewidth=4)
plt.plot( df, Female, marker='', color='olive', linewidth=2)
plt.legend(['Male','Female'])
plt.xlabel('Age')
plt.ylabel('Frequency')
plt.title('Gender based difference Age - For NoShow == "No"');

يمكننا أن نلاحظ من الشكل أعلاه أن المرضى الإناث من الفئة العمرية من 20 إلى 40 لا ينشطون في الحصول على موعد مع الأطباء عند مقارنتهم بالفئة العمرية المماثلة للذكور.

الاستنتاجات

كان الغرض من هذا التحليل إجراء تحليل لقاعدة بيانات الاستشارات الطبية “Medical Appointment No Shows”، والتي تحتوي على أكثر من 100 ألف موعد لم يحضرها ما يقرب من 30 ٪ من المرضى. كان الغرض من هذا التحليل هو جمع بعض الأفكار حول الأسباب المحتملة لهذه المواعيد المفقودة.

أهم الاستنتاجات المثيرة للاهتمام:

  • معظم المرضى ليسوا مدمنين على الكحول.
  • بدأت مواعيد الزيارات بتاريخ 2015/11/10 وانتهت بتاريخ 08/06/2016.
  • هناك العديد من الشباب في مجموعة البيانات (معظمهم من عمر 0) وينخفض ​​عدد المرضى بشكل كبير بالنسبة للمرضى الذين تزيد أعمارهم عن 60 عامًا.
  • يبلغ متوسط ​​عمر المرضى 37 عامًا. 25٪ من المرضى تقل أعمارهم عن 18 عامًا ومعظمهم تحت سن 55.
  • معظم المرضى ليسوا من مرض السكري ولكن أكثر من مدمنين على الكحول.
  • لا يتم تشخيص ارتفاع ضغط الدم لدى معظم المرضى.
  • بالنسبة لجميع المتغيرات الفئوية، تبدو توزيعات الحضور/عدم الحضور للفئات المختلفة متشابهة جدًا. لا يوجد مؤشر واضح على أن أي من هذه المتغيرات لها تأثير أكبر من تأثير الآخرين على خصائص الحضور/عدم الحضور. تؤكد الرسوم البيانية حوالي 20٪ من معدل عدم الحضور لمعظم الفئات.
  • لدينا عدد كبير من المرضى من النساء، على افتراض أن النساء تميل إلى الاهتمام بصحتهن أكثر من الرجل بسبب الاختلاف الهائل من الاستشاريين كما نرى هنا.

محددات

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

بذلك انتهينا من تحليل هذه المجموعة من البيانات، ونرجو أن نكون وفقنا في تقديم الفائدة.

1 Comment
  1. hajer abdullah says

    شكرا لبنى ،البحث جدا ملهم

اترك ردًا

Your email address will not be published.