ما هو تحليل البيانات؟ دليل عملي لتصوير البيانات باستخدام بايثون وNumpy وPandas وMatplotlib وSeaborn
مقدمة: لماذا يُعد تحليل البيانات مهارة أساسية اليوم؟
تحليل البيانات هو عملية استكشاف البيانات وفحصها واستخلاص المعاني منها باستخدام الأساليب الإحصائية والتمثيل البصري. والغاية من ذلك ليست جمع الأرقام فقط، بل تحويلها إلى معرفة تساعد على اتخاذ قرارات أدق، وفهم الأنماط، واكتشاف العلاقات الخفية بين المتغيرات.
في الواقع، يجمع تحليل البيانات بين جانبين مهمين:
- جانب علمي يعتمد على الإحصاء، وهياكل البيانات، والمكتبات البرمجية المتخصصة.
- وجانب عملي تحليلي يعتمد على طرح الأسئلة الصحيحة وتفسير النتائج بصورة منطقية.
في هذا الدليل ستتعرّف إلى الأساسيات التي يحتاجها أي مبتدئ أو ممارس يريد فهم مسار العمل في تحليل البيانات باستخدام لغة Python، ومكتبات Numpy وPandas وMatplotlib وSeaborn.

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

مثال بسيط: التنبؤ بإنتاج محصول اعتماداً على المناخ
لنفترض أننا نريد تقدير إنتاج التفاح بالاعتماد على ثلاثة عوامل: درجة الحرارة، ومعدل الأمطار، والرطوبة. يمكن تقريب العلاقة من خلال معادلة خطية بسيطة:
yield_of_apples = w1 * temperature + w2 * rainfall + w3 * humidity
هذه المعادلة لا تمثل الواقع بالكامل، لكنها نموذج أولي مفيد لتوضيح الفكرة. بعد تحليل البيانات التاريخية قد نختار قيماً تقريبية للأوزان:
w1, w2, w3 = 0.3, 0.2, 0.5
وبعد ذلك يمكن إدخال بيانات منطقة معيّنة:
kanto_temp = 73
kanto_rainfall = 67
kanto_humidity = 43
kanto_yield_apples = kanto_temp * w1 + kanto_rainfall * w2 + kanto_humidity * w3
print("The expected yield of apples in Kanto region is {} tons per hectare.".format(kanto_yield_apples))
هذه الطريقة تعمل، لكنها تصبح مرهقة حين نريد التوسع والتعامل مع عدة مناطق أو آلاف السجلات.
كيف نحول القوائم إلى مصفوفات باستخدام Numpy؟
بدلاً من تمثيل بيانات كل منطقة في متغيرات منفصلة، يمكننا وضعها داخل قائمة:
kanto = [73, 67, 43]
johto = [91, 88, 64]
hoenn = [87, 134, 58]
sinnoh = [102, 43, 37]
unova = [69, 96, 70]
weights = [w1, w2, w3]
ثم يمكن إنشاء دالة لحساب الناتج:
def crop_yield(region, weights):
result = 0
for x, w in zip(region, weights):
result += x * w
return result
لكن في Numpy تصبح العملية أبسط وأسرع. أولاً نثبت المكتبة ثم نستوردها:
!pip install numpy --upgrade --quiet
import numpy as np
بعدها نحوّل القوائم إلى مصفوفات:
kanto = np.array([73, 67, 43])
weights = np.array([0.3, 0.2, 0.5])
وبذلك يمكننا تنفيذ الضرب النقطي مباشرة عبر الدالة np.dot():
np.dot(kanto, weights)
كما يمكن الوصول إلى النتيجة نفسها عبر ضرب العناصر ثم جمعها:
(kanto * weights).sum()
لماذا تُعد مصفوفات Numpy أفضل من القوائم؟
- تسمح بكتابة تعبيرات رياضية أوضح وأقصر.
- توفر أداءً أعلى بكثير عند العمل على بيانات ضخمة.
- تدعم عمليات متجهية ومصفوفية جاهزة دون الحاجة إلى حلقات يدوية كثيرة.
فعند مقارنة for loop مع np.dot() على مليون عنصر، تكون Numpy أسرع بفارق كبير، وهذا فارق حاسم في التطبيقات الواقعية.
المصفوفات متعددة الأبعاد في Numpy
عند التعامل مع أكثر من سجل، يمكن جمع البيانات كلها في مصفوفة ثنائية الأبعاد:
climate_data = np.array([
[73, 67, 43],
[91, 88, 64],
[87, 134, 58],
[102, 43, 37],
[69, 96, 70]
])
كل صف يمثل منطقة، وكل عمود يمثل متغيراً مناخياً. ويمكن معرفة شكل المصفوفة عبر الخاصية .shape:
climate_data.shape
weights.shape

كذلك يمكن فحص نوع البيانات عبر .dtype، وهي خاصية مهمة لأن عناصر مصفوفة Numpy يجب أن تكون من نوع متجانس غالباً.
الضرب المصفوفي لحساب النتائج دفعة واحدة
إذا كانت لدينا مصفوفة بحجم 5x3 ومتجه أوزان بطول 3، فيمكن ضربهما مصفوَفياً للحصول على نتائج كل المناطق مرة واحدة:
np.matmul(climate_data, weights)
climate_data @ weights

هذا الأسلوب أكثر احترافية، لأنه يختصر الكثير من التكرار ويجعل الشيفرة أقرب إلى المنطق الرياضي الحقيقي.
قراءة ملفات CSV والتعامل معها باستخدام Numpy
صيغة CSV من أكثر الصيغ شيوعاً لتخزين البيانات الجدولية. يمكن قراءة ملف باستخدام الدالة np.genfromtxt():
import urllib.request
urllib.request.urlretrieve(
'https://hub.jovian.ml/wp-content/uploads/2020/08/climate.csv',
'climate.txt'
)
climate_data = np.genfromtxt('climate.txt', delimiter=',', skip_header=1)
بعد تحميل البيانات يمكن حساب الإنتاج المتوقع كاملاً:
weights = np.array([0.3, 0.2, 0.5])
yields = climate_data @ weights
ثم إلحاق النتائج كعمود جديد باستخدام np.concatenate() بعد إعادة تشكيل المتجه عبر reshape():
climate_results = np.concatenate((climate_data, yields.reshape(10000, 1)), axis=1)

وأخيراً يمكن حفظ النتائج:
np.savetxt(
'climate_results.txt',
climate_results,
fmt='%.2f',
delimiter=',',
header='temperature,rainfall,humidity,yeild_apples',
comments=''
)
عمليات Numpy الأساسية: الحسابات وBroadcasting والمقارنة
العمليات الحسابية
تدعم مصفوفات Numpy عمليات مثل + و- و* و/ و%، سواء مع قيمة مفردة أو مع مصفوفة أخرى متوافقة في الشكل.
arr2 = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 1, 2, 3]])
arr3 = np.array([[11, 12, 13, 14], [15, 16, 17, 18], [19, 11, 12, 13]])
arr2 + 3
arr3 - arr2
arr2 / 2
arr2 * arr3
arr2 % 4
ما هو Broadcasting؟
تسمح هذه الآلية بتنفيذ عمليات بين مصفوفتين مختلفتي الأبعاد إذا كان شكلهما متوافقاً. مثال ذلك:
arr2 = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 1, 2, 3]])
arr4 = np.array([4, 5, 6, 7])
arr2 + arr4
في هذه الحالة تُعامل المصفوفة الأصغر كما لو أنها تكررت لتناسب الشكل الأكبر دون نسخ فعلي في الذاكرة، ما يحقق كفاءة ممتازة.

عمليات المقارنة
يمكن مقارنة العناصر باستخدام == و!= و>= و< وغيرها، وتكون النتيجة مصفوفة من القيم المنطقية:
arr1 = np.array([[1, 2, 3], [3, 4, 5]])
arr2 = np.array([[2, 2, 3], [1, 2, 5]])
arr1 == arr2
(arr1 == arr2).sum()
هذا النوع من العمليات أساسي في التنظيف، والفلترة، والتحقق من الشروط داخل البيانات.
الفهرسة والاقتطاع في مصفوفات Numpy
تدعم Numpy فهرسة متعددة الأبعاد بطريقة مرنة، ما يسمح باستخراج عنصر أو نطاق محدد بسهولة:
arr3 = np.array([
[[11, 12, 13, 14], [13, 14, 15, 19]],
[[15, 16, 17, 21], [63, 92, 36, 18]],
[[98, 32, 81, 23], [17, 18, 19.5, 43]]
])
arr3[1, 1, 2]
arr3[1:, 0:1, :2]
arr3[1:, 1, :3]

فهم هذه الجزئية مهم جداً لأنه يختصر كثيراً من الوقت عند انتقاء أجزاء محددة من البيانات.
طرق إضافية لإنشاء مصفوفات في Numpy
توفر المكتبة دوال مفيدة لإنشاء مصفوفات جاهزة:
np.zeros((3, 2))
np.ones([2, 2, 3])
np.eye(3)
np.random.rand(5)
np.random.randn(2, 3)
np.full([2, 3], 42)
np.arange(10, 90, 3)
np.linspace(3, 27, 9)
هذه الأدوات مهمة عند بناء عينات تجريبية، أو تهيئة أوزان، أو إعداد بيانات لاختبار الخوارزميات.
تحليل البيانات الجدولية باستخدام Pandas
إذا كانت Numpy مناسبة جداً للعمليات العددية والمصفوفية، فإن Pandas هي الخيار المثالي للعمل مع البيانات الجدولية التي تحتوي على صفوف وأعمدة وأسماء حقول.

قراءة ملف CSV إلى DataFrame
!pip install pandas --upgrade --quiet
import pandas as pd
from urllib.request import urlretrieve
urlretrieve(
'https://hub.jovian.ml/wp-content/uploads/2020/09/italy-covid-daywise.csv',
'italy-covid-daywise.csv'
)
covid_df = pd.read_csv('italy-covid-daywise.csv')
يتم تخزين البيانات هنا داخل كائن DataFrame، وهو البنية الأشهر في Pandas.
استكشاف البنية العامة للبيانات
يمكن فهم الملف سريعاً عبر الأوامر التالية:
covid_df.info()
covid_df.describe()
covid_df.columns
covid_df.shape
هذه الأساليب تمنحك نظرة أولية مهمة حول أنواع الأعمدة، وعدد القيم غير الفارغة، والإحصاءات الأساسية، وأبعاد الجدول.
الوصول إلى الأعمدة والصفوف والقيم
covid_df['new_cases']
covid_df.at[246, 'new_cases']
covid_df.loc[243]
covid_df.head(5)
covid_df.tail(4)
covid_df.sample(10)
يمثل كل عمود كائناً من نوع Series، وهو مناسب للعمليات التحليلية السريعة على مستوى عمود واحد.
التعامل مع القيم المفقودة NaN
في البيانات الواقعية ستواجه قيماً مفقودة أو غير متاحة. في هذا المثال، بعض قيم new_tests غير موجودة، ولذلك تظهر كـ NaN وليس 0. والفرق بينهما جوهري:
0يعني أن القيمة موجودة وتساوي صفراً.NaNيعني أن القيمة غير معروفة أو مفقودة.
يمكن إيجاد أول قيمة صالحة باستخدام:
covid_df.new_tests.first_valid_index()
تحليل البيانات داخل Pandas
حساب المؤشرات الأساسية
total_cases = covid_df.new_cases.sum()
total_deaths = covid_df.new_deaths.sum()
death_rate = covid_df.new_deaths.sum() / covid_df.new_cases.sum()
هذه العمليات البسيطة توضح مدى قوة Pandas في الاشتغال على الأعمدة كما لو كانت كيانات تحليلية مستقلة.
الاستعلام عن البيانات باستخدام الشروط
إذا أردنا استخراج الأيام التي تجاوز فيها عدد الإصابات 1000 حالة:
high_cases_df = covid_df[covid_df.new_cases > 1000]
كما يمكن صياغة شروط أكثر تعقيداً تشمل أكثر من عمود أو ناتج عملية حسابية.
إضافة أعمدة جديدة وإزالتها
covid_df['positive_rate'] = covid_df.new_cases / covid_df.new_tests
covid_df.drop(columns=['positive_rate'], inplace=True)
هذه الخطوة شائعة جداً عند اشتقاق مؤشرات جديدة من الأعمدة الأصلية.
ترتيب الصفوف حسب القيم
covid_df.sort_values('new_cases', ascending=False).head(10)
الفرز يساعد على تحديد الأيام الأعلى إصابة أو الأكثر وفيات أو الأقل اختبارات، وهو مدخل أساسي لاستكشاف الشذوذ والقمم والهبوطات.
معالجة التواريخ في Pandas
الأعمدة الزمنية تمنح التحليل بعداً إضافياً. في البداية يكون عمود التاريخ من نوع object، لذلك يجب تحويله:
covid_df['date'] = pd.to_datetime(covid_df.date)
بعد ذلك يمكن استخراج أجزاء التاريخ:
covid_df['year'] = pd.DatetimeIndex(covid_df.date).year
covid_df['month'] = pd.DatetimeIndex(covid_df.date).month
covid_df['day'] = pd.DatetimeIndex(covid_df.date).day
covid_df['weekday'] = pd.DatetimeIndex(covid_df.date).weekday
هذه المعلومات مفيدة جداً عند تحليل الأداء شهرياً أو أسبوعياً أو مقارنة أيام الأسبوع ببعضها.
التجميع والدمج والتراكم في Pandas
التجميع باستخدام groupby()
covid_month_df = covid_df.groupby('month')[['new_cases', 'new_deaths', 'new_tests']].sum()
التجميع الشهري أو السنوي يسهّل رؤية الاتجاهات الكبرى بدلاً من التشتت في البيانات اليومية.
المجاميع التراكمية
covid_df['total_cases'] = covid_df.new_cases.cumsum()
covid_df['total_deaths'] = covid_df.new_deaths.cumsum()
covid_df['total_tests'] = covid_df.new_tests.cumsum() + 935310
المجموع التراكمي مفيد لفهم تطور الحالة بمرور الزمن وليس فقط قراءة كل يوم بشكل معزول.
دمج البيانات من مصادر متعددة
غالباً لا تكفيك مجموعة بيانات واحدة. مثلاً، لحساب الإصابات لكل مليون نسمة، تحتاج إلى بيانات السكان:
locations_df = pd.read_csv('locations.csv')
covid_df['location'] = 'Italy'
merged_df = covid_df.merge(locations_df, on='location')
ثم يمكن اشتقاق مؤشرات إضافية:
merged_df['cases_per_million'] = merged_df.total_cases * 1e6 / merged_df.population
merged_df['deaths_per_million'] = merged_df.total_deaths * 1e6 / merged_df.population
merged_df['tests_per_million'] = merged_df.total_tests * 1e6 / merged_df.population
إعادة كتابة النتائج إلى ملف
result_df = merged_df[[
'date', 'new_cases', 'total_cases', 'new_deaths', 'total_deaths',
'new_tests', 'total_tests', 'cases_per_million',
'deaths_per_million', 'tests_per_million'
]]
result_df.to_csv('results.csv', index=None)
حفظ النتائج خطوة مهنية مهمة، خاصة عند تسليم مخرجات التحليل أو إعادة استخدامها لاحقاً.
تصوير البيانات باستخدام Matplotlib وSeaborn
تصوير البيانات ليس مجرد تحسين بصري، بل هو وسيلة لاكتشاف الأنماط والاتجاهات والعلاقات التي قد لا تظهر بوضوح داخل الجداول.

!pip install matplotlib seaborn --upgrade --quiet
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
المخططات الخطية Line Charts
المخطط الخطي من أكثر الرسوم استخداماً لعرض تغير قيمة عبر الزمن.
years = [2010, 2011, 2012, 2013, 2014, 2015]
yield_apples = [0.895, 0.91, 0.919, 0.926, 0.929, 0.931]
plt.plot(years, yield_apples)
plt.xlabel('Year')
plt.ylabel('Yield (tons per hectare)')
ويمكن رسم أكثر من خط ومقارنتهما داخل الشكل نفسه:
years = range(2000, 2012)
apples = [0.895, 0.91, 0.919, 0.926, 0.929, 0.931, 0.934, 0.936, 0.937, 0.9375, 0.9372, 0.939]
oranges = [0.962, 0.941, 0.930, 0.923, 0.918, 0.908, 0.907, 0.904, 0.901, 0.898, 0.9, 0.896]
plt.plot(years, apples, marker='o')
plt.plot(years, oranges, marker='x')
plt.xlabel('Year')
plt.ylabel('Yield (tons per hectare)')
plt.title('Crop Yields in Kanto')
plt.legend(['Apples', 'Oranges'])
تحسين الشكل العام باستخدام Seaborn
sns.set_style('whitegrid')
تمنح هذه الخطوة الرسوم مظهراً أنظف وأسهل للقراءة، وهو أمر مهم عند إعداد تقارير مهنية أو محتوى تعليمي.
المخططات المبعثرة Scatter Plots
تُستخدم المخططات المبعثرة لفهم العلاقة بين متغيرين. مثال مشهور على ذلك مجموعة بيانات iris:
flowers_df = sns.load_dataset('iris')
sns.scatterplot(x=flowers_df.sepal_length, y=flowers_df.sepal_width)
ويمكن تحسين القراءة بإضافة hue لتمييز الفئات:
sns.scatterplot(
x=flowers_df.sepal_length,
y=flowers_df.sepal_width,
hue=flowers_df.species,
s=100
)
هذا النوع من الرسوم مفيد جداً في اكتشاف التجمعات والعلاقات غير الخطية والقيم الشاذة.
المدرجات التكرارية Histograms
إذا أردت فهم توزيع متغير معين، فالمدرج التكراري هو الخيار الأنسب:
plt.hist(flowers_df.sepal_width)
كما يمكن التحكم في عدد الصناديق bins أو حدودها:
plt.hist(flowers_df.sepal_width, bins=5)
plt.hist(flowers_df.sepal_width, bins=np.arange(2, 5, 0.25))
هذا يفيد في فهم مدى تركز القيم، وتحديد القمم، والكشف عن الانحراف في التوزيع.
المخططات الشريطية Bar Charts
المخطط الشريطي مناسب لمقارنة قيم منفصلة أو متوسطات عبر فئات محددة:
years = range(2000, 2006)
apples = [0.35, 0.6, 0.9, 0.8, 0.65, 0.8]
oranges = [0.4, 0.8, 0.9, 0.7, 0.6, 0.8]
plt.bar(years, oranges)
أما في Seaborn فيمكن حساب المتوسطات مباشرة:
tips_df = sns.load_dataset('tips')
sns.barplot(x='day', y='total_bill', data=tips_df)
وبإضافة hue يمكن المقارنة بين مجموعات فرعية مختلفة داخل الفئة نفسها.
الخرائط الحرارية Heatmaps
الخرائط الحرارية ممتازة لعرض البيانات الثنائية الأبعاد مثل الجداول والمحاور الزمنية:
flights_df = sns.load_dataset('flights').pivot('month', 'year', 'passengers')
plt.title('No. of Passengers (1000s)')
sns.heatmap(flights_df, fmt='d', annot=True, cmap='Blues')
تعطيك الخريطة الحرارية فهماً سريعاً لفترات الذروة والانخفاض من خلال اللون قبل حتى قراءة الأرقام.
عرض الصور باستخدام Matplotlib
يمكن كذلك استخدام Matplotlib لعرض الصور باعتبارها مصفوفات رقمية:
from urllib.request import urlretrieve
from PIL import Image
urlretrieve('https://i.imgur.com/SkPbq.jpg', 'chart.jpg')
img = Image.open('chart.jpg')
img_array = np.array(img)
plt.grid(False)
plt.title('A data science meme')
plt.axis('off')
plt.imshow(img)
هذه الفكرة مفيدة خصوصاً في تطبيقات الرؤية الحاسوبية ومعالجة الصور.
رسم عدة مخططات داخل شبكة واحدة
عند إعداد لوحة معلومات صغيرة أو مقارنة أكثر من رسم، يمكن استخدام plt.subplots():
fig, axes = plt.subplots(2, 3, figsize=(16, 8))
هذه التقنية مفيدة جداً في التقارير التحليلية لأنها تتيح جمع أكثر من منظور بصري داخل صفحة واحدة.
أفضل ممارسات عملية لتحليل البيانات وتصويرها
- ابدأ دائماً بفهم طبيعة البيانات قبل القفز إلى الرسوم.
- تحقق من القيم المفقودة والشاذة قبل بناء أي استنتاج.
- استخدم
Numpyللعمليات العددية المكثفة، وPandasللبيانات الجدولية. - اختر نوع الرسم بناءً على السؤال التحليلي، لا على الشكل فقط.
- تجنب ازدحام الرسوم بالألوان أو التفاصيل غير الضرورية.
- احرص على تسمية المحاور والعناوين بوضوح لتسهيل القراءة.
مقارنة سريعة بين المكتبات المستخدمة
| المكتبة | الاستخدام الأساسي | أبرز نقطة قوة |
|---|---|---|
Numpy |
الحوسبة العددية والمصفوفات | سرعة عالية في العمليات الرياضية |
Pandas |
تحليل البيانات الجدولية | سهولة القراءة والفلترة والتجميع |
Matplotlib |
الرسم البياني الأساسي | مرونة كبيرة في التخصيص |
Seaborn |
الرسوم الإحصائية المتقدمة | مظهر أفضل وتكامل ممتاز مع Pandas |
الخلاصة التقنية
تحليل البيانات ليس مجرد كتابة أوامر برمجية، بل هو منهجية تبدأ بفهم السؤال، ثم تجهيز البيانات، ثم تحليلها، وأخيراً عرض النتائج بصرياً بطريقة تدعم القرار. عملياً، يشكل الجمع بين Numpy وPandas وMatplotlib وSeaborn حزمة متكاملة لأي محلل بيانات يعمل بلغة Python. وإذا أتقنت هذه الأدوات، فستمتلك أساساً قوياً للانتقال إلى مراحل أكثر تقدماً مثل التعلم الآلي، ولوحات المعلومات التفاعلية، والتحليلات التنبؤية.