تعلمت في الدرس السابق قراءة البيانات وتنظيفها باستخدام NumPy وPandas. لكن الجدول النظيف لا يكشف كل شيء؛ فقد تبدو الأرقام طبيعية عند قراءتها صفًا بعد صف، بينما يكشف رسم واحد تجمعًا غير متوقع، أو قيمة شاذة، أو علاقة لا تستحق بناء نموذج عليها.
إذا كنت جديدًا في البرمجة، فابدأ أولًا من درس أساسيات Python لطالب الذكاء الاصطناعي، ثم عد إلى هذا الدرس لتطبيق الرسوم البيانية عمليًا.
تصوير البيانات ليس مرحلة لتجميل التقرير بعد انتهاء التحليل، بل أداة فحص تسبق تعلم الآلة. الرسم الجيد يساعدك على اكتشاف المشكلة، بينما قد يخفيها رسم سيئ أو يقود القارئ إلى استنتاج غير صحيح.
في هذا الدرس ستستخدم Matplotlib وSeaborn لتحليل بيانات طلاب، وستتعلم اختيار الرسم بناءً على السؤال، لا بناءً على شكله الجذاب.
ماذا ستتعلم في هذا الدرس؟
بنهاية الدرس ستكون قادرًا على:
- فهم دور Matplotlib ودور Seaborn.
- اختيار الرسم المناسب لنوع السؤال والبيانات.
- فحص توزيع القيم باستخدام المدرج التكراري.
- اكتشاف القيم الشاذة باستخدام الرسم الصندوقي.
- دراسة العلاقة بين متغيرين باستخدام الرسم المبعثر.
- استخدام الألوان لتمييز المجموعات دون إرباك القارئ.
- قراءة خريطة الارتباط دون ادعاء وجود علاقة سببية.
- إنشاء لوحة فحص صغيرة وحفظها كصورة.
- كتابة استنتاج مهني يفصل بين الملاحظة والتفسير.
لماذا نستخدم Matplotlib وSeaborn معًا؟
Matplotlib للتحكم في تفاصيل الرسم
Matplotlib مكتبة مرنة لإنشاء الرسوم الثابتة والمتحركة والتفاعلية في Python. تمنحك تحكمًا مباشرًا في حجم الشكل، والمحاور، والعناوين، والألوان، وترتيب عدة رسوم داخل لوحة واحدة.
يُستخدم الاختصار التالي عند استيرادها:
import matplotlib.pyplot as plt
Seaborn للاستكشاف الإحصائي السريع
Seaborn مبنية على Matplotlib، وتوفر واجهة مرتفعة المستوى لإنشاء رسوم إحصائية واضحة من جداول Pandas.
تتعامل مباشرة مع أسماء أعمدة DataFrame، وتضيف كثيرًا من العناوين ومفاتيح الألوان تلقائيًا.
يُستخدم الاختصار التالي:
import seaborn as sns
القاعدة العملية هي: استخدم Seaborn لإنشاء الرسم الإحصائي بسرعة، ثم استخدم Matplotlib لضبط الحجم والعنوان والمحاور والحفظ.
تجهيز بيئة العمل
إذا كنت تستخدم Google Colab، فغالبًا تكون المكتبات الأساسية متاحة. وللتثبيت محليًا نفّذ:
pip install pandas matplotlib seaborn
ثم استورد المكتبات:
import pandas as pdimport matplotlib.pyplot as pltimport seaborn as sns
اختر مظهرًا موحدًا للرسوم:
sns.set_theme(style="whitegrid")
سنستخدم عناوين إنجليزية داخل الكود حتى لا تواجه مشكلة اتصال الحروف العربية في بعض البيئات. تستطيع لاحقًا تثبيت خط عربي وضبط إعداداته.
بيانات المختبر
أنشئ جدولًا صغيرًا يمثل بيانات مجموعة من الطلاب:
data = { "student_id": [ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112 ], "study_hours": [ 2, 3, 5, 7, 8, 9, 4, 6, 10, 1, 5, 15 ], "attendance": [ 55, 65, 72, 80, 90, 95, 68, 85, 92, 50, 75, 88 ], "assignments": [ 3, 4, 6, 7, 9, 10, 5, 8, 10, 2, 6, 9 ], "score": [ 48, 52, 61, 69, 78, 88, 57, 74, 91, 44, 66, 82 ], "passed": [ "No", "No", "Yes", "Yes", "Yes", "Yes", "No", "Yes", "Yes", "No", "Yes", "Yes" ]}df = pd.DataFrame(data)print(df.head())print(df.info())
هذا جدول تعليمي صغير، ولا يصلح لاستخلاص حكم عام عن الطلاب أو اتخاذ قرار حقيقي بشأنهم.
عند استخدام بيانات حقيقية، احذف الأسماء وأرقام الهوية وأي معلومات حساسة، وراجع دليل حماية صورك وملفاتك قبل رفعها إلى تطبيقات الذكاء الاصطناعي .
قاعدة دليل التقنية: اكتب السؤال قبل الرسم
قبل تنفيذ أي أمر رسم، اكتب السؤال الذي تريد الإجابة عنه.
أمثلة:
- كيف تتوزع ساعات الدراسة؟
- هل توجد قيمة بعيدة عن بقية القيم؟
- هل ترتبط ساعات الدراسة بالنتيجة؟
- هل تختلف نسبة الحضور بين الناجحين وغير الناجحين؟
- ما المتغيرات العددية التي تتحرك معًا؟
إذا لم تستطع كتابة السؤال بجملة واضحة، فمن المحتمل أنك ترسم لأن الأداة متاحة، لا لأن الرسم يخدم التحليل.
اختبار سلامة البيانات قبل الرسم
لا تبدأ الرسم قبل فحص الجدول:
print(df.shape)print(df.dtypes)print(df.isna().sum())print("Duplicate rows:", df.duplicated().sum())
تحقق أيضًا من الحدود المنطقية:
إذا واجهت قيمًا مفقودة أو أعمدة غير مرتبة قبل الرسم، فارجع إلى درس قراءة البيانات باستخدام NumPy وPandas لتجهيز الجدول بصورة صحيحة.
print(df["attendance"].between(0, 100).all())print((df["study_hours"] >= 0).all())print(df["passed"].value_counts())
الرسم لا يصلح الخطأ الموجود في البيانات. إذا كانت نسبة الحضور 900 بدل 90، فقد يتغير مقياس المحور ويصبح بقية الرسم مضغوطًا بصورة تخفي التفاصيل.
الرسم الأول: المدرج التكراري لفهم التوزيع
المدرج التكراري يقسم القيم إلى نطاقات ويعرض عدد المشاهدات داخل كل نطاق. استخدمه عندما تريد معرفة شكل توزيع متغير عددي.
plt.figure(figsize=(8, 5))sns.histplot( data=df, x="study_hours", bins=6, kde=True, color="#1769aa")plt.title("Distribution of Study Hours")plt.xlabel("Study Hours")plt.ylabel("Number of Students")plt.tight_layout()plt.show()
ماذا نبحث عنه؟
- أين تتركز معظم القيم؟
- هل التوزيع متوازن أم مائل إلى جهة؟
- هل توجد فجوات؟
- هل تظهر قيمة بعيدة عن بقية الطلاب؟
- هل عدد المشاهدات كافٍ لتفسير شكل التوزيع؟
المعامل bins يحدد عدد النطاقات. تغيير عدد النطاقات قد يغير الانطباع البصري، لذلك لا تختَر الرقم الذي يجعل القصة أكثر إثارة؛ جرّب أكثر من قيمة وتأكد من ثبات الملاحظة.
أما kde=True فيضيف منحنى ناعمًا تقريبيًا. لا تعتمد على شكله كثيرًا عندما تكون العينة صغيرة مثل بيانات هذا المختبر.
الرسم الثاني: الرسم الصندوقي لاكتشاف القيم الشاذة
يساعد الرسم الصندوقي في تلخيص التوزيع وإظهار القيم التي تبدو بعيدة عن بقية البيانات.
plt.figure(figsize=(8, 4))sns.boxplot( data=df, x="study_hours", color="#4db6ac")plt.title("Study Hours Box Plot")plt.xlabel("Study Hours")plt.tight_layout()plt.show()
قد تظهر قيمة 15 ساعة بعيدة عن غالبية القيم. لكن ظهورها كنقطة شاذة لا يعني أنها خاطئة.
قبل حذفها اسأل:
- هل حدث خطأ عند إدخالها؟
- هل الوحدة ساعات يومية أم أسبوعية؟
- هل الطالب حالة حقيقية تختلف عن الآخرين؟
- هل تؤثر القيمة بقوة في المتوسط أو النموذج؟
- هل أملك دليلًا يبرر التعديل؟
القيمة الشاذة إشارة تستحق التحقيق، وليست أمرًا تلقائيًا بالحذف.
مقارنة المجموعات بالرسم الصندوقي
يمكن مقارنة نسبة الحضور بين الطلاب الناجحين وغير الناجحين:
plt.figure(figsize=(8, 5))sns.boxplot( data=df, x="passed", y="attendance", hue="passed", palette="Set2", legend=False)plt.title("Attendance by Pass Status")plt.xlabel("Passed")plt.ylabel("Attendance Percentage")plt.tight_layout()plt.show()
إذا ظهرت نسبة الحضور أعلى لدى مجموعة الناجحين، فاكتب:
«في هذه العينة، تبدو قيم الحضور أعلى لدى الطلاب الناجحين».
لا تكتب:
«الحضور المرتفع يسبب النجاح».
الرسم يعرض علاقة داخل البيانات، لكنه لا يثبت السبب؛ فقد توجد عوامل أخرى لم يسجلها الجدول.
الرسم الثالث: الرسم المبعثر لدراسة العلاقة
الرسم المبعثر مناسب لدراسة العلاقة بين متغيرين عدديين.
سنرسم ساعات الدراسة مقابل النتيجة:
plt.figure(figsize=(8, 5))sns.scatterplot( data=df, x="study_hours", y="score", hue="passed", s=100, palette={ "Yes": "#2e7d32", "No": "#c62828" })plt.title("Study Hours vs Score")plt.xlabel("Study Hours")plt.ylabel("Final Score")plt.tight_layout()plt.show()
كيف تقرأ الرسم؟
ابحث عن:
- الاتجاه العام للنقاط.
- وجود تجمعات منفصلة.
- نقاط بعيدة عن النمط.
- تداخل المجموعات.
- نطاق لا توجد فيه بيانات كافية.
قد تلاحظ أن النتيجة ترتفع غالبًا مع زيادة ساعات الدراسة. هذه ملاحظة أولية تحتاج إلى اختبار إحصائي وبيانات أكبر، وليست برهانًا على السببية.
لا تحوّل كل معلومة إلى لون أو حجم
تسمح Seaborn بتمثيل متغيرات إضافية باستخدام:
hueللون.sizeللحجم.styleلشكل النقطة.
لكن جمعها كلها في رسم واحد قد يجعل القراءة أصعب.
يمكن إضافة عدد الواجبات على هيئة حجم للنقاط:
sns.scatterplot( data=df, x="study_hours", y="score", hue="passed", size="assignments", sizes=(50, 250))plt.show()
هذا مفيد إذا كان عدد الواجبات مهمًا للسؤال. أما إذا لم تستطع شرح معنى اللون والحجم والشكل بسرعة، فاحذف أحدها وأنشئ رسمًا ثانيًا أبسط.
الرسم الرابع: الأعمدة للمقارنات الواضحة
لمقارنة متوسط الدرجات بين مجموعتين يمكن استخدام الأعمدة:
plt.figure(figsize=(7, 5))sns.barplot( data=df, x="passed", y="score", hue="passed", palette="Blues", legend=False, errorbar=None)plt.title("Average Score by Pass Status")plt.xlabel("Passed")plt.ylabel("Average Score")plt.ylim(0, 100)plt.tight_layout()plt.show()
ضبط المحور من صفر إلى مئة يجعل المقارنة مناسبة لطبيعة الدرجات. قطع المحور عند رقم مرتفع قد يضخم الفارق بصريًا.
انتبه أيضًا إلى أن عرض المتوسط وحده قد يخفي تشتت القيم. لذلك من الأفضل غالبًا قراءة الرسم العمودي مع الرسم الصندوقي أو عرض النقاط الأصلية.
متى نستخدم الرسم الخطي؟
استخدم الرسم الخطي عندما يكون المحور الأفقي ذا ترتيب حقيقي، مثل:
- الأيام والأسابيع.
- السنوات.
- خطوات التدريب.
- عدد الدورات التدريبية للنموذج.
لا تستخدم خطًا يصل بين الطلاب 101 و102 و103 لمجرد أن أرقامهم مرتبة؛ رقم الطالب معرّف، وليس تسلسلًا زمنيًا.
مثال مناسب لنتيجة نموذج عبر دورات التدريب:
epochs = [1, 2, 3, 4, 5]loss = [0.82, 0.61, 0.47, 0.39, 0.35]plt.figure(figsize=(8, 5))plt.plot(epochs, loss, marker="o")plt.title("Training Loss")plt.xlabel("Epoch")plt.ylabel("Loss")plt.xticks(epochs)plt.tight_layout()plt.show()
انخفاض الخط لا يعني دائمًا تحسن النموذج على بيانات جديدة؛ ستحتاج لاحقًا إلى مقارنة خطأ التدريب بخطأ التحقق.
الرسم الخامس: خريطة الارتباط
يمكن حساب الارتباط بين الأعمدة العددية وعرضه بخريطة حرارية:
numeric_df = df[ [ "study_hours", "attendance", "assignments", "score" ]]correlation = numeric_df.corr()plt.figure(figsize=(8, 6))sns.heatmap( correlation, annot=True, cmap="coolwarm", vmin=-1, vmax=1, fmt=".2f")plt.title("Correlation Heatmap")plt.tight_layout()plt.show()
تتراوح قيمة الارتباط بين -1 و1:
- قيمة قريبة من 1: المتغيران يتحركان غالبًا في الاتجاه نفسه.
- قيمة قريبة من -1: يتحركان غالبًا في اتجاهين متعاكسين.
- قيمة قريبة من صفر: لا تظهر علاقة خطية قوية.
ما الذي لا تخبرك به خريطة الارتباط؟
- لا تثبت أن متغيرًا يسبب الآخر.
- قد لا تكشف العلاقة غير الخطية.
- قد تتأثر بالقيم الشاذة.
- قد تنتج علاقة مرتفعة بسبب عامل ثالث.
- قد تكون مضللة في عينة صغيرة.
- لا تفسر معنى المتغيرات أو طريقة جمعها.
لا تختَر خصائص النموذج بناءً على لون الخريطة وحده.
أنشئ لوحة فحص بدل عشرات الرسوم المنفصلة
يمكن وضع عدة رسوم في لوحة واحدة:
fig, axes = plt.subplots( 2, 2, figsize=(12, 9))sns.histplot( data=df, x="study_hours", bins=6, ax=axes[0, 0], color="#1769aa")axes[0, 0].set_title("Study Hours Distribution")sns.boxplot( data=df, x="study_hours", ax=axes[0, 1], color="#4db6ac")axes[0, 1].set_title("Study Hours Outliers")sns.scatterplot( data=df, x="study_hours", y="score", hue="passed", ax=axes[1, 0])axes[1, 0].set_title("Hours vs Score")sns.boxplot( data=df, x="passed", y="attendance", ax=axes[1, 1], color="#90caf9")axes[1, 1].set_title("Attendance by Status")plt.tight_layout()plt.show()
الهدف من اللوحة ليس حشد أكبر عدد ممكن من الرسوم، بل جمع مجموعة صغيرة تجيب عن أسئلة مترابطة.
حفظ الرسم بجودة مناسبة
احفظ الشكل قبل plt.show():
plt.savefig( "student_data_report.png", dpi=150, bbox_inches="tight")plt.show()
dpi=150مناسب غالبًا للتقارير والشاشات.bbox_inches="tight"يقلل المساحات البيضاء الزائدة.- استخدم اسم ملف يوضح محتوى الرسم بدل
image1.png.
إذا احتجت خلفية بيضاء:
plt.savefig( "student_data_report.png", dpi=150, bbox_inches="tight", facecolor="white")
كيف تمنع الرسم من تضليل القارئ؟
لا تقطع المحور دون سبب واضح
بدء محور الأعمدة من قيمة مرتفعة قد يجعل فرقًا صغيرًا يبدو ضخمًا. إذا اضطررت إلى قطع المحور، وضّح ذلك ولا تخفِ علامات التدريج.
لا تستخدم الأبعاد الثلاثية للزينة
الرسوم ثلاثية الأبعاد قد تجعل مقارنة الأطوال والمساحات أصعب دون إضافة معلومة مفيدة.
لا تستخدم ألوانًا كثيرة
استخدم لونًا محايدًا لمعظم البيانات ولونًا بارزًا للنقطة المهمة. لا تجعل كل عمود بلون مختلف إذا لم تحمل الألوان معنى.
لا تعتمد على اللون وحده
قد يصعب على بعض القراء تمييز ألوان معينة. استخدم عناوين واضحة، وتباينًا مناسبًا، وأشكالًا أو تسميات إضافية عند الحاجة.
لا تستخدم الرسم الدائري لفئات كثيرة
عندما تتعدد الفئات، يكون الرسم العمودي أسهل في المقارنة من شرائح دائرية متقاربة.
لا تخفِ النقاط الأصلية دائمًا
المتوسط قد يبدو مقنعًا بينما تخفيه قيم متباعدة. اعرض التوزيع أو النقاط عندما يكون حجم البيانات مناسبًا.
لا تعرض منحنى ناعمًا بوصفه حقيقة
المنحنى الناعم تقدير يعتمد على إعدادات معينة. وضّح أنه تمثيل تقريبي، خصوصًا عندما تكون العينة صغيرة.
قالب تفسير أي رسم
بعد كل رسم، اكتب أربع جمل:
- السؤال: ما الذي أردت معرفته؟
- الملاحظة: ماذا يظهر الرسم دون مبالغة؟
- التفسيرات المحتملة: ما الأسباب التي قد تشرح النتيجة؟
- الخطوة التالية: ما الفحص أو البيانات الإضافية المطلوبة؟
مثال:
«أردنا فحص العلاقة بين ساعات الدراسة والنتيجة. تظهر العينة اتجاهًا موجبًا عامًا مع وجود قيمة بعيدة نسبيًا. قد يكون ذلك مرتبطًا بجودة الدراسة أو الحضور أو عوامل غير مسجلة. نحتاج إلى عينة أكبر وفحص القيم الشاذة قبل استخدام ساعات الدراسة في نموذج تنبؤي».
هذا الأسلوب يفصل ما رأيته عما تعتقد أنه السبب.
مشروع تطبيقي: تقرير فحص بصري
أنشئ تقريرًا من صفحة واحدة عن بيانات الطلاب، ويتضمن:
- وصفًا لمعنى كل صف.
- عدد الصفوف والأعمدة.
- رسمًا لتوزيع متغير عددي.
- رسمًا صندوقيًا لاكتشاف القيم الشاذة.
- رسمًا مبعثرًا لعلاقة متغيرين.
- مقارنة واحدة بين مجموعتين.
- خريطة ارتباط للأعمدة العددية.
- خمس جمل تلخص أهم الملاحظات.
- تنبيهًا إلى حدود البيانات وعدم إثبات السببية.
- ملف الكود وصورة التقرير النهائي.
لا تستخدم بيانات طلاب حقيقية تتضمن أسماء أو أرقامًا جامعية أو معلومات حساسة. استخدم بيانات صناعية، أو بيانات عامة بترخيص واضح، أو أزل ما يكشف الهوية بعد الحصول على التصريح اللازم.
أخطاء شائعة لدى المبتدئين
- رسم كل الأعمدة دون تحديد سؤال.
- اعتبار الرسم الجميل دليلًا على صحة البيانات.
- حذف القيمة الشاذة لمجرد ظهورها خارج الصندوق.
- استخدام الرسم الخطي لبيانات ليس لها ترتيب زمني.
- كتابة «يسبب» عندما لا يظهر الرسم إلا ارتباطًا.
- ازدحام الرسم بالألوان والأحجام والتسميات.
- عدم كتابة وحدة القياس على المحور.
- مقارنة مجموعات تختلف كثيرًا في عدد المشاهدات دون توضيح.
- بناء استنتاج قوي من عينة صغيرة.
- حفظ الرسم دون عنوان أو اسم ملف مفهوم.
- نشر رسم يحتوي على بيانات شخصية أو حساسة.
قائمة فحص قبل اعتماد الرسم
- هل كتبت السؤال قبل إنشاء الرسم؟
- هل اخترت الرسم المناسب لنوع المتغيرات؟
- هل فحصت القيم المفقودة والتكرار والحدود؟
- هل تحمل الألوان معنى واضحًا؟
- هل المحاور والعناوين والوحدات مكتوبة؟
- هل المقياس البصري عادل وغير مضلل؟
- هل فحصت القيم الشاذة بدل حذفها تلقائيًا؟
- هل فرّقت بين الارتباط والسببية؟
- هل يستطيع قارئ آخر فهم الرسم دون قراءة الكود؟
- هل أزلت البيانات الحساسة؟
- هل حفظت الكود الذي أنشأ الرسم؟
- هل تستطيع إعادة إنتاج النتيجة نفسها؟
ولمعرفة الخطوة التي تسبق هذا الدرس وما ستتعلمه بعده، راجع خريطة تعلم الذكاء الاصطناعي لطالب الجامعة.
أسئلة شائعة
هل أتعلم Matplotlib أم Seaborn أولًا؟
تعلّم أساسيات الشكل والمحاور والعناوين والحفظ في Matplotlib، ثم استخدم Seaborn لإنشاء الرسوم الإحصائية من DataFrame. ستستخدم المكتبتين معًا في معظم التطبيقات.
ما الفرق بين histplot وbarplot؟
histplot يعرض توزيع متغير عددي عبر نطاقات، بينما barplot يقارن قيمة ملخصة، مثل المتوسط، بين فئات منفصلة.
هل القيمة الشاذة تعني وجود خطأ؟
لا. قد تكون خطأ إدخال، أو حالة حقيقية نادرة، أو معلومة مهمة. تحقق من المصدر والسياق قبل تعديلها.
هل الارتباط المرتفع يعني أن أحد المتغيرين يسبب الآخر؟
لا. الارتباط يقيس نمط حركة المتغيرات، ولا يثبت السببية أو يستبعد وجود عامل ثالث.
متى أستخدم الرسم الخطي؟
عندما توجد استمرارية أو علاقة مرتبة، مثل الزمن أو دورات تدريب النموذج. لا تستخدمه لمجرد أن الصفوف مرتبة في الجدول.
لماذا يختلف الرسم عند تغيير bins؟
لأن المدرج التكراري يجمع القيم داخل نطاقات. تغيير عدد النطاقات يغيّر مستوى التفاصيل وقد يؤثر في الانطباع البصري.
هل يمكن أن يحل الرسم محل الاختبار الإحصائي؟
لا. الرسم يساعدك على الاستكشاف وصياغة الأسئلة واكتشاف المشكلات، ثم تأتي الاختبارات والتحليلات المناسبة لتقييم الفرضيات.
هل أستخدم الألوان نفسها في جميع الرسوم؟
حافظ على معنى ثابت للألوان داخل التقرير؛ فإذا كان الأخضر يعني النجاح في رسم، فلا تجعله يعني الفشل في رسم آخر.
الخلاصة
تصوير البيانات مرحلة فحص، وليس زينة تضاف بعد انتهاء المشروع. ابدأ بالسؤال، وافحص جودة الجدول، ثم اختر الرسم الذي يجيب عنه بأقل قدر من التعقيد.
استخدم المدرج التكراري لفهم التوزيع، والرسم الصندوقي لفحص القيم الشاذة، والرسم المبعثر لدراسة العلاقات، والأعمدة لمقارنة الفئات، والخريطة الحرارية للحصول على نظرة أولية إلى الارتباطات.
لكن لا تجعل الرسم يقرر نيابة عنك. راجع مصدر البيانات، وحجم العينة، وطريقة القياس، والتفسيرات البديلة، وفرّق دائمًا بين الملاحظة والسبب.
بعد إتقان هذه المرحلة تصبح مستعدًا لدراسة الإحصاء التطبيقي والانتقال تدريجيًا إلى بناء أول نموذج تعلم آلي يمكن تقييمه بطريقة صحيحة.
ويمكنك متابعة بقية الدروس والمسارات التعليمية من قسم طلاب الحاسب في دليل التقنية.
مصادر رسمية
- دليل البداية السريعة في Matplotlib:
https://matplotlib.org/stable/users/explain/quick_start.html - مقدمة Seaborn:
https://seaborn.pydata.org/tutorial/introduction.html - دليل تصوير التوزيعات:
https://seaborn.pydata.org/tutorial/distributions.html - دليل تصوير العلاقات:
https://seaborn.pydata.org/tutorial/relational.html


شاركنا رأيك