Logo

المعمل الثالث: المعالجة المسبقة للبيانات والتجميع على بيانات واقعية باستخدام أطر بيانات Pandas

24 دقيقة قراءة
شرائح الدرس
1 / 15

المعمل رقم 3: معالجة البيانات وتجميعها على بيانات حقيقية باستخدام Pandas

من إطارات البيانات الخام إلى مقارنة بين K-Means وSOM على مجموعة بيانات Iris

الفصل الدراسي: خريف 2025 الجامعة: كلية الحاسبات والمعلومات - جامعة المنصورة


أهداف المعمل

بعد إتمام هذا المعمل، سيكون الطالب قادراً على:

  1. تحميل واستكشاف مجموعات بيانات حقيقية (Load and Explore Real Datasets): استخدام pandas لتحميل مجموعات البيانات من مصادر مختلفة وإجراء استكشاف أولي لها
  2. فهم عمليات DataFrame (Understand DataFrame Operations): إتقان العمليات الأساسية في pandas، بما في ذلك الفهرسة (indexing)، والتصفية (filtering)، واختيار البيانات (data selection)
  3. المعالجة المسبقة للبيانات تمهيداً للتجميع (Preprocess Data for Clustering): التعامل مع القيم المفقودة، وترميز المتغيرات الفئوية (categorical variables)، وتطبيع (normalize) الخصائص
  4. تطبيق التجميع على بيانات حقيقية (Apply Clustering to Real Data): استخدام K-Means و SOM على مجموعات بيانات واقعية مع تجهيز مناسب للبيانات
  5. تصور النتائج وتفسيرها (Visualize and Interpret Results): إنشاء تصورات بيانية (visualizations) ذات معنى لفهم أنماط التجميع في البيانات الحقيقية
  6. مقارنة خوارزميات التجميع (Compare Clustering Algorithms): فهم متى نستخدم K-Means ومتى نستخدم SOM بناءً على خصائص مجموعة البيانات

الخطوة 1: استيراد المكتبات المطلوبة

بدايةً، لنستورد كل المكتبات اللازمة لمعالجة البيانات، والتجميع (clustering)، والتصور البياني (visualization).

# Data manipulation and analysis
import pandas as pd
import numpy as np
 
# Visualization
import matplotlib.pyplot as plt
import seaborn as sns
 
# Machine Learning
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score, confusion_matrix
from sklearn_som.som import SOM
 
# Warnings
import warnings
warnings.filterwarnings('ignore')
 
# Set visualization style
plt.style.use('default')
sns.set_palette("husl")
 
print("✓ All libraries imported successfully!")
✓ All libraries imported successfully!

الخطوة 2: مقدمة عن أطر بيانات Pandas (Pandas DataFrames)

ما هو الـ DataFrame؟

الـ DataFrame هو بنية بيانات ثنائية الأبعاد (2-dimensional) ذات تسميات (labeled)، تحتوي على أعمدة يمكن أن تكون من أنواع بيانات مختلفة. تخيّله كجدول في ملف إكسل (spreadsheet) أو كجدول في قاعدة بيانات SQL.

الخصائص الأساسية:

  • صفوف وأعمدة ذات تسميات (labels)
  • يمكن أن تحتوي الأعمدة على أنواع بيانات مختلفة
  • الحجم قابل للتغيير (size-mutable) (يمكن إضافة أو حذف صفوف وأعمدة)
  • دوال قوية لمعالجة البيانات
# Create a simple DataFrame example
sample_data = {
    'Name':  ['Ahmed', 'Fatima', 'Omar', 'Nour'],
    'Age':   [22,       21,       23,     22   ],
    'Grade': [85,       92,       78,     88   ]
}
 
df_sample = pd.DataFrame(sample_data)
 
print("Example DataFrame:")
print(df_sample)
print("\n" + "="*50)
 
# Basic DataFrame operations
print("\nDataFrame Shape (rows, columns):", df_sample.shape)
print("\nColumn Names:", df_sample.columns.tolist())
print("\nData Types:\n", df_sample.dtypes)
print("\n" + "="*50)
 
# Accessing data
print("\nAccessing 'Age' column:")
print(df_sample['Age'])
print("\n" + "="*50)
 
print("\nAccessing first 2 rows:")
print(df_sample.head(2))
Example DataFrame:
     Name  Age  Grade
0   Ahmed   22     85
1  Fatima   21     92
2    Omar   23     78
3    Nour   22     88
 
==================================================
 
DataFrame Shape (rows, columns): (4, 3)
 
Column Names: ['Name', 'Age', 'Grade']
 
Data Types:
 Name     object
Age       int64
Grade     int64
dtype: object
 
==================================================
 
Accessing 'Age' column:
0    22
1    21
2    23
3    22
Name: Age, dtype: int64
 
==================================================
 
Accessing first 2 rows:
     Name  Age  Grade
0   Ahmed   22     85
1  Fatima   21     92

الخطوة 3: تحميل مجموعة بيانات Iris

تُعد مجموعة بيانات Iris (Iris dataset) من أشهر مجموعات البيانات الكلاسيكية في تعلم الآلة، وتحتوي على قياسات لزهور الـ iris من ثلاثة أنواع مختلفة:

  • Setosa
  • Versicolor
  • Virginica

الخصائص (Features):

  1. طول الكأس (Sepal Length) بالسنتيمتر (cm)
  2. عرض الكأس (Sepal Width) بالسنتيمتر (cm)
  3. طول البتلة (Petal Length) بالسنتيمتر (cm)
  4. عرض البتلة (Petal Width) بالسنتيمتر (cm)
# Load the Iris dataset
iris = load_iris()
 
# Create a DataFrame from the dataset
df_iris = pd.DataFrame(data=iris.data, columns=iris.feature_names)
 
# Add the target column (species)
df_iris['species'] = iris.target
 
# Map numeric labels to actual species names
species_map = {0: 'setosa', 1: 'versicolor', 2: 'virginica'}
df_iris['species_name'] = df_iris['species'].map(species_map)
 
print("Iris Dataset Loaded Successfully!")
print("\nDataset Shape:", df_iris.shape)
print("\nFirst 5 rows:")
print(df_iris.head())
Iris Dataset Loaded Successfully!
 
Dataset Shape: (150, 6)
 
First 5 rows:
   sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)  \
0                5.1               3.5                1.4               0.2   
1                4.9               3.0                1.4               0.2   
2                4.7               3.2                1.3               0.2   
3                4.6               3.1                1.5               0.2   
4                5.0               3.6                1.4               0.2   
 
   species species_name  
0        0       setosa  
1        0       setosa  
2        0       setosa  
3        0       setosa  
4        0       setosa
# Load the Iris dataset from CSV
# Option 1: From a local file
df_iris = pd.read_csv('iris.csv')
 
# Option 2: From a URL (online source)
# df_iris = pd.read_csv('https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv')
 
print("Iris Dataset Loaded Successfully!")
print("\nDataset Shape:", df_iris.shape)
print("\nFirst 5 rows:")
print(df_iris.head())
 
# Check column names to see what we're working with
print("\nColumn names:", df_iris.columns.tolist())
 
# If the CSV has species names directly (like 'setosa', 'versicolor', 'virginica')
# You may need to rename columns to match what we use later
df_iris.columns = ['sepal length (cm)', 'sepal width (cm)',
                   'petal length (cm)', 'petal width (cm)', 'species_name']
 
# Create numeric species column for later use
species_map_reverse = {'setosa': 0, 'versicolor': 1, 'virginica': 2}
df_iris['species'] = df_iris['species_name'].map(species_map_reverse)
 
print("\nAfter renaming:")
print(df_iris.head())
Iris Dataset Loaded Successfully!
 
Dataset Shape: (150, 5)
 
First 5 rows:
   sepal_length  sepal_width  petal_length  petal_width species
0           5.1          3.5           1.4          0.2  setosa
1           4.9          3.0           1.4          0.2  setosa
2           4.7          3.2           1.3          0.2  setosa
3           4.6          3.1           1.5          0.2  setosa
4           5.0          3.6           1.4          0.2  setosa
 
Column names: ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species']
 
After renaming:
   sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)  \
0                5.1               3.5                1.4               0.2   
1                4.9               3.0                1.4               0.2   
2                4.7               3.2                1.3               0.2   
3                4.6               3.1                1.5               0.2   
4                5.0               3.6                1.4               0.2   
 
  species_name  species  
0       setosa        0  
1       setosa        0  
2       setosa        0  
3       setosa        0  
4       setosa        0

الخطوة 4: التحليل الاستكشافي للبيانات (Exploratory Data Analysis - EDA)

4.1 معلومات أساسية

print("="*60)
print("DATASET INFORMATION")
print("="*60)
 
print("\nBasic Statistics:")
print(df_iris.describe())
 
print("\n" + "="*60)
print("\nData Types:")
print(df_iris.dtypes)
 
print("\n" + "="*60)
print("\nMissing Values:")
print(df_iris.isnull().sum())
 
print("\n" + "="*60)
print("\nSpecies Distribution:")
print(df_iris['species_name'].value_counts())
============================================================
DATASET INFORMATION
============================================================
 
Basic Statistics:
       sepal length (cm)  sepal width (cm)  petal length (cm)  \
count         150.000000        150.000000         150.000000   
mean            5.843333          3.054000           3.758667   
std             0.828066          0.433594           1.764420   
min             4.300000          2.000000           1.000000   
25%             5.100000          2.800000           1.600000   
50%             5.800000          3.000000           4.350000   
75%             6.400000          3.300000           5.100000   
max             7.900000          4.400000           6.900000   
 
       petal width (cm)     species  
count        150.000000  150.000000  
mean           1.198667    1.000000  
std            0.763161    0.819232  
min            0.100000    0.000000  
25%            0.300000    0.000000  
50%            1.300000    1.000000  
75%            1.800000    2.000000  
max            2.500000    2.000000  
 
============================================================
 
Data Types:
sepal length (cm)    float64
sepal width (cm)     float64
petal length (cm)    float64
petal width (cm)     float64
species_name          object
species                int64
dtype: object
 
============================================================
 
Missing Values:
sepal length (cm)    0
sepal width (cm)     0
petal length (cm)    0
petal width (cm)     0
species_name         0
species              0
dtype: int64
 
============================================================
 
Species Distribution:
species_name
setosa        50
versicolor    50
virginica     50
Name: count, dtype: int64

4.2 اختيار البيانات وتصفيتها (Data Selection and Filtering)

توفر أطر بيانات DataFrame دوالاً قوية لاختيار البيانات وتصفيتها.

# Select specific columns
print("Selecting only sepal measurements:")
sepal_data = df_iris[['sepal length (cm)', 'sepal width (cm)']]
print(sepal_data.head()) # 5 rows
 
print("\n" + "="*60)
 
# Filter rows based on condition
print("\nFiltering: Only Setosa species:")
setosa_only = df_iris[df_iris['species_name'] == 'setosa']
print(f"Number of Setosa samples: {len(setosa_only)}")
print(setosa_only.head())
 
print("\n" + "="*60)
 
# Multiple conditions
print("\nFiltering: Setosa with sepal length > 5.0:")
filtered_data = df_iris[(df_iris['species_name'] == 'setosa') & 
                        (df_iris['sepal length (cm)'] > 5.0)]
print(f"Number of matching samples: {len(filtered_data)}")
print(filtered_data.head())
Selecting only sepal measurements:
   sepal length (cm)  sepal width (cm)
0                5.1               3.5
1                4.9               3.0
2                4.7               3.2
3                4.6               3.1
4                5.0               3.6
 
============================================================
 
Filtering: Only Setosa species:
Number of Setosa samples: 50
   sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)  \
0                5.1               3.5                1.4               0.2   
1                4.9               3.0                1.4               0.2   
2                4.7               3.2                1.3               0.2   
3                4.6               3.1                1.5               0.2   
4                5.0               3.6                1.4               0.2   
 
  species_name  species  
0       setosa        0  
1       setosa        0  
2       setosa        0  
3       setosa        0  
4       setosa        0  
 
============================================================
 
Filtering: Setosa with sepal length > 5.0:
Number of matching samples: 22
    sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)  \
0                 5.1               3.5                1.4               0.2   
5                 5.4               3.9                1.7               0.4   
10                5.4               3.7                1.5               0.2   
14                5.8               4.0                1.2               0.2   
15                5.7               4.4                1.5               0.4   
 
   species_name  species  
0        setosa        0  
5        setosa        0  
10       setosa        0  
14       setosa        0  
15       setosa        0

4.3 تصور البيانات بيانياً (Visualizing the Data)

# Create a figure with multiple subplots
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
#   0,0 | 0,1
#   1,0 | 1,1
#
# Plot 1: Distribution of sepal length
axes[0, 0].hist(df_iris['sepal length (cm)'], bins=20, color='skyblue', edgecolor='black')
axes[0, 0].set_title('Distribution of Sepal Length', fontsize=12, fontweight='bold')
axes[0, 0].set_xlabel('Sepal Length (cm)')
axes[0, 0].set_ylabel('Frequency')
axes[0, 0].grid(alpha=0.3)
 
# Plot 2: Species count
species_counts = df_iris['species_name'].value_counts()
axes[0, 1].bar(species_counts.index, species_counts.values, color=['#FF6B6B', '#4ECDC4', '#45B7D1'])
axes[0, 1].set_title('Species Distribution', fontsize=12, fontweight='bold')
axes[0, 1].set_xlabel('Species')
axes[0, 1].set_ylabel('Count')
axes[0, 1].grid(alpha=0.3, axis='y')
 
# Plot 3: Scatter plot - Sepal measurements
for species in df_iris['species_name'].unique():
    species_data = df_iris[df_iris['species_name'] == species]
    axes[1, 0].scatter(species_data['sepal length (cm)'], 
                       species_data['sepal width (cm)'],
                       label=species, alpha=0.6, s=50)
axes[1, 0].set_title('Sepal Length vs Width by Species', fontsize=12, fontweight='bold')
axes[1, 0].set_xlabel('Sepal Length (cm)')
axes[1, 0].set_ylabel('Sepal Width (cm)')
axes[1, 0].legend()
axes[1, 0].grid(alpha=0.3)
 
# Plot 4: Scatter plot - Petal measurements
for species in df_iris['species_name'].unique():
    species_data = df_iris[df_iris['species_name'] == species]
    axes[1, 1].scatter(species_data['petal length (cm)'], 
                       species_data['petal width (cm)'],
                       label=species, alpha=0.6, s=50)
axes[1, 1].set_title('Petal Length vs Width by Species', fontsize=12, fontweight='bold')
axes[1, 1].set_xlabel('Petal Length (cm)')
axes[1, 1].set_ylabel('Petal Width (cm)')
axes[1, 1].legend()
axes[1, 1].grid(alpha=0.3)
 
plt.tight_layout()
plt.show()
 
print("✓ Visualizations created successfully!")
✓ Visualizations created successfully!

الخطوة 5: المعالجة المسبقة للبيانات تمهيداً للتجميع (Data Preprocessing for Clustering)

5.1 اختيار الخصائص (Feature Selection)

# Select only the feature columns (exclude target variables)
feature_columns = ['sepal length (cm)', 'sepal width (cm)', 
                   'petal length (cm)', 'petal width (cm)']
 
X = df_iris[feature_columns]
 
print("Selected Features for Clustering:")
print(X.head())
print("\nFeature Matrix Shape:", X.shape)
Selected Features for Clustering:
   sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)
0                5.1               3.5                1.4               0.2
1                4.9               3.0                1.4               0.2
2                4.7               3.2                1.3               0.2
3                4.6               3.1                1.5               0.2
4                5.0               3.6                1.4               0.2
 
Feature Matrix Shape: (150, 4)

5.2 تطبيع الخصائص (Feature Normalization)

لماذا نطبّع البيانات (Why normalize)؟

قد تختلف مقاييس (scales) الخصائص المختلفة. على سبيل المثال:

  • طول الكأس (Sepal length): من 4 إلى 8 سم
  • عرض البتلة (Petal width): من 0.1 إلى 2.5 سم

تعمل خوارزميات التجميع (خصوصاً تلك التي تعتمد على مقاييس المسافة) بشكل أفضل عندما تكون الخصائص على نفس المقياس.

يقوم StandardScaler بتحويل الخصائص عن طريق:

  • طرح المتوسط (centering)
  • القسمة على الانحراف المعياري (scaling)

الصيغة (Formula): z = (x - μ) / σ

# Initialize the StandardScaler
scaler = StandardScaler()
 
# Fit and transform the data
X_scaled = scaler.fit_transform(X)
 
# Convert back to DataFrame for better visualization
X_scaled_df = pd.DataFrame(X_scaled, columns=feature_columns)
 
print("Original Data (first 5 rows):")
print(X.head())
 
print("\n" + "="*60)
print("\nScaled Data (first 5 rows):")
print(X_scaled_df.head())
 
print("\n" + "="*60)
print("\nOriginal Data Statistics:")
print(X.describe())
 
print("\n" + "="*60)
print("\nScaled Data Statistics (Notice mean ≈ 0, std ≈ 1):")
print(X_scaled_df.describe())
Original Data (first 5 rows):
   sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)
0                5.1               3.5                1.4               0.2
1                4.9               3.0                1.4               0.2
2                4.7               3.2                1.3               0.2
3                4.6               3.1                1.5               0.2
4                5.0               3.6                1.4               0.2
 
============================================================
 
Scaled Data (first 5 rows):
   sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)
0          -0.900681          1.032057          -1.341272         -1.312977
1          -1.143017         -0.124958          -1.341272         -1.312977
2          -1.385353          0.337848          -1.398138         -1.312977
3          -1.506521          0.106445          -1.284407         -1.312977
4          -1.021849          1.263460          -1.341272         -1.312977
 
============================================================
 
Original Data Statistics:
       sepal length (cm)  sepal width (cm)  petal length (cm)  \
count         150.000000        150.000000         150.000000   
mean            5.843333          3.054000           3.758667   
std             0.828066          0.433594           1.764420   
min             4.300000          2.000000           1.000000   
25%             5.100000          2.800000           1.600000   
50%             5.800000          3.000000           4.350000   
75%             6.400000          3.300000           5.100000   
max             7.900000          4.400000           6.900000   
 
       petal width (cm)  
count        150.000000  
mean           1.198667  
std            0.763161  
min            0.100000  
25%            0.300000  
50%            1.300000  
75%            1.800000  
max            2.500000  
 
============================================================
 
Scaled Data Statistics (Notice mean ≈ 0, std ≈ 1):
       sepal length (cm)  sepal width (cm)  petal length (cm)  \
count       1.500000e+02      1.500000e+02       1.500000e+02   
mean       -4.736952e-16     -6.631732e-16       3.315866e-16   
std         1.003350e+00      1.003350e+00       1.003350e+00   
min        -1.870024e+00     -2.438987e+00      -1.568735e+00   
25%        -9.006812e-01     -5.877635e-01      -1.227541e+00   
50%        -5.250608e-02     -1.249576e-01       3.362659e-01   
75%         6.745011e-01      5.692513e-01       7.627586e-01   
max         2.492019e+00      3.114684e+00       1.786341e+00   
 
       petal width (cm)  
count      1.500000e+02  
mean      -2.842171e-16  
std        1.003350e+00  
min       -1.444450e+00  
25%       -1.181504e+00  
50%        1.332259e-01  
75%        7.905908e-01  
max        1.710902e+00

الخطوة 6: تطبيق تجميع K-Means (Apply K-Means Clustering)

6.1 تحديد العدد الأمثل للمجموعات (Determine Optimal Number of Clusters)

سنستخدم طريقتين:

  1. طريقة المرفق (Elbow Method): البحث عن نقطة "المرفق" (elbow) في مخطط WCSS
  2. مقياس Silhouette (Silhouette Score): كلما ارتفعت القيمة، دلّ ذلك على تجميع أفضل
# We'll use the Elbow Method to find optimal k
max_k = 10
wcss = []
silhouette_scores = []
 
print("Computing optimal number of clusters...")
 
# Explain when python take the last value and when it doesn't
for k in range(2, max_k + 1):
    kmeans = KMeans(n_clusters=k, init='k-means++', 
                    max_iter=300, n_init=10, random_state=42)
    kmeans.fit(X_scaled)
    wcss.append(kmeans.inertia_)
    
    # Calculate silhouette score
    sil_score = silhouette_score(X_scaled, kmeans.labels_)
    silhouette_scores.append(sil_score)
    
    print(f"  k={k}: WCSS={kmeans.inertia_:.2f}, Silhouette={sil_score:.3f}")
 
print("\n✓ Optimization complete!")
Computing optimal number of clusters...
  k=2: WCSS=223.73, Silhouette=0.580
  k=3: WCSS=140.97, Silhouette=0.459
  k=4: WCSS=114.62, Silhouette=0.385
  k=5: WCSS=91.30, Silhouette=0.347
  k=6: WCSS=81.76, Silhouette=0.341
  k=7: WCSS=71.32, Silhouette=0.329
  k=8: WCSS=62.65, Silhouette=0.340
  k=9: WCSS=55.26, Silhouette=0.343
  k=10: WCSS=50.62, Silhouette=0.340
 
✓ Optimization complete!
# Plot the results
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))
 
# Elbow plot
ax1.plot(range(2, max_k + 1), wcss, marker='o', linewidth=2, markersize=8)
ax1.set_title('Elbow Method for Optimal K', fontsize=14, fontweight='bold')
ax1.set_xlabel('Number of Clusters (K)', fontsize=12)
ax1.set_ylabel('WCSS', fontsize=12)
ax1.axvline(x=3, color='red', linestyle='--', alpha=0.7, label='True k=3')
ax1.legend()
ax1.grid(alpha=0.3)
 
# Silhouette score plot
ax2.plot(range(2, max_k + 1), silhouette_scores, marker='s',
         linewidth=2, markersize=8, color='green')
ax2.set_title('Silhouette Score vs K', fontsize=14, fontweight='bold')
ax2.set_xlabel('Number of Clusters (K)', fontsize=12)
ax2.set_ylabel('Silhouette Score', fontsize=12)
ax2.axvline(x=3, color='red', linestyle='--', alpha=0.7, label='True k=3')
ax2.legend()
ax2.grid(alpha=0.3)
 
plt.tight_layout()
plt.show()
 
# Find best k
best_k_idx = np.argmax(silhouette_scores)
best_k = best_k_idx + 2
print(f"\n✓ Best k based on Silhouette Score: {best_k} (Score: {max(silhouette_scores):.3f})")
 
✓ Best k based on Silhouette Score: 2 (Score: 0.580)

6.2 تدريب النموذج النهائي لـ K-Means (Train Final K-Means Model)

# Train K-Means with optimal k=3
optimal_k = 3
kmeans_final = KMeans(n_clusters=optimal_k, init='k-means++', 
                      max_iter=300, n_init=10, random_state=42)
 
# Fit and predict
kmeans_labels = kmeans_final.fit_predict(X_scaled)
 
# Add predictions to the DataFrame
df_iris['kmeans_cluster'] = kmeans_labels
 
print("K-Means Clustering Complete!")
print(f"\nCluster Distribution:")
print(df_iris['kmeans_cluster'].value_counts().sort_index())
 
print("\n" + "="*60)
print("\nSample predictions:")
print(df_iris[['species_name', 'kmeans_cluster']].head(10))
K-Means Clustering Complete!
 
Cluster Distribution:
kmeans_cluster
0    53
1    50
2    47
Name: count, dtype: int64
 
============================================================
 
Sample predictions:
  species_name  kmeans_cluster
0       setosa               1
1       setosa               1
2       setosa               1
3       setosa               1
4       setosa               1
5       setosa               1
6       setosa               1
7       setosa               1
8       setosa               1
9       setosa               1

6.3 تصور نتائج K-Means بيانياً (Visualize K-Means Results)

# Create visualization
fig, axes = plt.subplots(1, 2, figsize=(16, 6))
 
# Plot 1: True Species
for species_idx, species in enumerate(['setosa', 'versicolor', 'virginica']):
    species_data = df_iris[df_iris['species_name'] == species]
    axes[0].scatter(species_data['sepal length (cm)'], 
                    species_data['petal length (cm)'],
                    label=species, alpha=0.6, s=80)
 
axes[0].set_title('True Species Labels', fontsize=14, fontweight='bold')
axes[0].set_xlabel('Sepal Length (cm)', fontsize=12)
axes[0].set_ylabel('Petal Length (cm)', fontsize=12)
axes[0].legend()
axes[0].grid(alpha=0.3)
 
# Plot 2: K-Means Clusters
scatter = axes[1].scatter(df_iris['sepal length (cm)'], 
                          df_iris['petal length (cm)'],
                          c=df_iris['kmeans_cluster'], 
                          cmap='viridis', alpha=0.6, s=80)
 
# Add centroids
centroids_original = scaler.inverse_transform(kmeans_final.cluster_centers_)
axes[1].scatter(centroids_original[:, 0], centroids_original[:, 2],
                marker='X', s=300, c='red', edgecolor='black', 
                linewidth=2, label='Centroids')
 
axes[1].set_title('K-Means Predicted Clusters', fontsize=14, fontweight='bold')
axes[1].set_xlabel('Sepal Length (cm)', fontsize=12)
axes[1].set_ylabel('Petal Length (cm)', fontsize=12)
axes[1].legend()
axes[1].grid(alpha=0.3)
plt.colorbar(scatter, ax=axes[1], label='Cluster')
 
plt.tight_layout()
plt.show()
 
# Calculate and display performance
sil_score = silhouette_score(X_scaled, kmeans_labels)
print(f"\n✓ Final K-Means Silhouette Score: {sil_score:.3f}")
 
✓ Final K-Means Silhouette Score: 0.459

الخطوة 7: تطبيق الخرائط ذاتية التنظيم (Self-Organizing Maps - SOM)

7.1 تدريب نموذج SOM (Train SOM Model)

سنستخدم مكتبة sklearn_som التي توفر تنفيذاً لخوارزمية SOM متوافقاً مع scikit-learn.

المعاملات (Parameters):

  • m, n: أبعاد الشبكة (Grid dimensions) (سنستخدم 3×1 لثلاث مجموعات)
  • dim: عدد خصائص الإدخال (4 في حالتنا)
  • lr: معدل التعلم (Learning rate)
  • max_iter: عدد تكرارات التدريب (training iterations)
# Initialize SOM with a 3x1 grid
# For 3 clusters, a smaller grid like 1x3 or 2x2 could also work
som_model = SOM(m=3, n=1, dim=4, lr=0.5, max_iter=1000, random_state=42)
 
print("Training Self-Organizing Map...")
print(f"  Grid size: 3×1")
print(f"  Input dimensions: 4 features")
print(f"  Learning rate: 0.5")
print(f"  Max iterations: 1000")
 
# Fit the model
som_model.fit(X_scaled)
 
# Predict cluster assignments
som_labels = som_model.predict(X_scaled)
 
# Add to DataFrame
df_iris['som_cluster'] = som_labels
 
print("\n✓ SOM Training Complete!")
print(f"\nCluster Distribution:")
print(df_iris['som_cluster'].value_counts().sort_index())
 
print("\n" + "="*60)
print("\nSample predictions:")
print(df_iris[['species_name', 'som_cluster']].head(10))
Training Self-Organizing Map...
  Grid size: 3×1
  Input dimensions: 4 features
  Learning rate: 0.5
  Max iterations: 1000
 
✓ SOM Training Complete!
 
Cluster Distribution:
som_cluster
0    70
1    30
2    50
Name: count, dtype: int64
 
============================================================
 
Sample predictions:
  species_name  som_cluster
0       setosa            2
1       setosa            2
2       setosa            2
3       setosa            2
4       setosa            2
5       setosa            2
6       setosa            2
7       setosa            2
8       setosa            2
9       setosa            2

7.2 تصور نتائج SOM بيانياً (Visualize SOM Results)

# Create visualization
fig, axes = plt.subplots(1, 2, figsize=(16, 6))
 
# Plot 1: True Species (for comparison)
for species in ['setosa', 'versicolor', 'virginica']:
    species_data = df_iris[df_iris['species_name'] == species]
    axes[0].scatter(species_data['sepal length (cm)'], 
                    species_data['petal length (cm)'],
                    label=species, alpha=0.6, s=80)
 
axes[0].set_title('True Species Labels', fontsize=14, fontweight='bold')
axes[0].set_xlabel('Sepal Length (cm)', fontsize=12)
axes[0].set_ylabel('Petal Length (cm)', fontsize=12)
axes[0].legend()
axes[0].grid(alpha=0.3)
 
# Plot 2: SOM Clusters
scatter = axes[1].scatter(df_iris['sepal length (cm)'], 
                          df_iris['petal length (cm)'],
                          c=df_iris['som_cluster'], 
                          cmap='plasma', alpha=0.6, s=80)
 
axes[1].set_title('SOM Predicted Clusters', fontsize=14, fontweight='bold')
axes[1].set_xlabel('Sepal Length (cm)', fontsize=12)
axes[1].set_ylabel('Petal Length (cm)', fontsize=12)
axes[1].grid(alpha=0.3)
plt.colorbar(scatter, ax=axes[1], label='Cluster')
 
plt.tight_layout()
plt.show()
 
# Calculate performance
som_sil_score = silhouette_score(X_scaled, som_labels)
print(f"\n✓ SOM Silhouette Score: {som_sil_score:.3f}")
 
✓ SOM Silhouette Score: 0.412

الخطوة 8: مقارنة K-Means بـ SOM

8.1 جدول المقارنة (Comparison Table)

# Create comparison DataFrame
comparison_df = df_iris[['species_name', 'species', 'kmeans_cluster', 'som_cluster']].copy()
 
print("="*60)
print("CLUSTERING COMPARISON")
print("="*60)
 
print("\nFirst 15 samples:")
print(comparison_df.head(15))
 
print("\n" + "="*60)
print("\nCluster Distribution Comparison:")
print("\nTrue Species:")
print(df_iris['species_name'].value_counts().sort_index())
 
print("\nK-Means Clusters:")
print(df_iris['kmeans_cluster'].value_counts().sort_index())
 
print("\nSOM Clusters:")
print(df_iris['som_cluster'].value_counts().sort_index())
============================================================
CLUSTERING COMPARISON
============================================================
 
First 15 samples:
   species_name  species  kmeans_cluster  som_cluster
0        setosa        0               1            2
1        setosa        0               1            2
2        setosa        0               1            2
3        setosa        0               1            2
4        setosa        0               1            2
5        setosa        0               1            2
6        setosa        0               1            2
7        setosa        0               1            2
8        setosa        0               1            2
9        setosa        0               1            2
10       setosa        0               1            2
11       setosa        0               1            2
12       setosa        0               1            2
13       setosa        0               1            2
14       setosa        0               1            2
 
============================================================
 
Cluster Distribution Comparison:
 
True Species:
species_name
setosa        50
versicolor    50
virginica     50
Name: count, dtype: int64
 
K-Means Clusters:
kmeans_cluster
0    53
1    50
2    47
Name: count, dtype: int64
 
SOM Clusters:
som_cluster
0    70
1    30
2    50
Name: count, dtype: int64

8.2 تحليل الجدولة التقاطعية (Cross-Tabulation Analysis)

تساعدنا الجدولة التقاطعية (cross-tabulation) على معرفة مدى نجاح كل خوارزمية تجميع في استرجاع البنية الحقيقية للأنواع (species).

# Create cross-tabulation tables
print("\n" + "="*60)
print("K-MEANS: Species vs Predicted Clusters")
print("="*60)
kmeans_crosstab = pd.crosstab(df_iris['species_name'], 
                                df_iris['kmeans_cluster'],
                                margins=True)
print(kmeans_crosstab)
 
print("\n" + "="*60)
print("SOM: Species vs Predicted Clusters")
print("="*60)
som_crosstab = pd.crosstab(df_iris['species_name'], 
                            df_iris['som_cluster'],
                            margins=True)
print(som_crosstab)
 
# Performance comparison
print("\n" + "="*60)
print("PERFORMANCE METRICS")
print("="*60)
print(f"K-Means Silhouette Score: {sil_score:.3f}")
print(f"SOM Silhouette Score:     {som_sil_score:.3f}")
print(f"\nBetter performing model: {'K-Means' if sil_score > som_sil_score else 'SOM'}")
 
============================================================
K-MEANS: Species vs Predicted Clusters
============================================================
kmeans_cluster   0   1   2  All
species_name                   
setosa           0  50   0   50
versicolor      39   0  11   50
virginica       14   0  36   50
All             53  50  47  150
 
============================================================
SOM: Species vs Predicted Clusters
============================================================
som_cluster    0   1   2  All
species_name                 
setosa         0   0  50   50
versicolor    21  29   0   50
virginica     49   1   0   50
All           70  30  50  150
 
============================================================
PERFORMANCE METRICS
============================================================
K-Means Silhouette Score: 0.459
SOM Silhouette Score:     0.412
 
Better performing model: K-Means

8.3 تصور جنباً إلى جنب (Side-by-Side Visualization)

# Create comprehensive comparison plot
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
#   0,0 | 0,1
#   1,0 | 1,1
# Sepal measurements - K-Means
scatter1 = axes[0, 0].scatter(df_iris['sepal length (cm)'], 
                               df_iris['sepal width (cm)'],
                               c=df_iris['kmeans_cluster'], 
                               cmap='viridis', alpha=0.6, s=80)
axes[0, 0].set_title('K-Means: Sepal Measurements', fontsize=12, fontweight='bold')
axes[0, 0].set_xlabel('Sepal Length (cm)')
axes[0, 0].set_ylabel('Sepal Width (cm)')
axes[0, 0].grid(alpha=0.3)
plt.colorbar(scatter1, ax=axes[0, 0], label='Cluster')
 
# Sepal measurements - SOM
scatter2 = axes[0, 1].scatter(df_iris['sepal length (cm)'], 
                               df_iris['sepal width (cm)'],
                               c=df_iris['som_cluster'], 
                               cmap='plasma', alpha=0.6, s=80)
axes[0, 1].set_title('SOM: Sepal Measurements', fontsize=12, fontweight='bold')
axes[0, 1].set_xlabel('Sepal Length (cm)')
axes[0, 1].set_ylabel('Sepal Width (cm)')
axes[0, 1].grid(alpha=0.3)
plt.colorbar(scatter2, ax=axes[0, 1], label='Cluster')
 
# Petal measurements - K-Means
scatter3 = axes[1, 0].scatter(df_iris['petal length (cm)'], 
                               df_iris['petal width (cm)'],
                               c=df_iris['kmeans_cluster'], 
                               cmap='viridis', alpha=0.6, s=80)
axes[1, 0].set_title('K-Means: Petal Measurements', fontsize=12, fontweight='bold')
axes[1, 0].set_xlabel('Petal Length (cm)')
axes[1, 0].set_ylabel('Petal Width (cm)')
axes[1, 0].grid(alpha=0.3)
plt.colorbar(scatter3, ax=axes[1, 0], label='Cluster')
 
# Petal measurements - SOM
scatter4 = axes[1, 1].scatter(df_iris['petal length (cm)'], 
                               df_iris['petal width (cm)'],
                               c=df_iris['som_cluster'], 
                               cmap='plasma', alpha=0.6, s=80)
axes[1, 1].set_title('SOM: Petal Measurements', fontsize=12, fontweight='bold')
axes[1, 1].set_xlabel('Petal Length (cm)')
axes[1, 1].set_ylabel('Petal Width (cm)')
axes[1, 1].grid(alpha=0.3)
plt.colorbar(scatter4, ax=axes[1, 1], label='Cluster')
 
plt.tight_layout()
plt.show()
 
print("✓ Comprehensive comparison visualizations created!")
✓ Comprehensive comparison visualizations created!

الخطوة 9: العمل مع DataFrames - عمليات متقدمة (Advanced Operations)

9.1 التجميع والتلخيص (Grouping and Aggregation)

يتيح لنا التجميع (grouping) إجراء عمليات على مجموعات جزئية (subsets) من البيانات.

print("="*60)
print("GROUPING AND AGGREGATION")
print("="*60)
 
# Group by species and calculate mean values
species_means = df_iris.groupby('species_name')[feature_columns].mean()
print("\nMean values by species:")
print(species_means)
 
print("\n" + "="*60)
 
# Group by K-Means cluster and calculate mean values
kmeans_cluster_means = df_iris.groupby('kmeans_cluster')[feature_columns].mean()
print("\nMean values by K-Means cluster:")
print(kmeans_cluster_means)
 
print("\n" + "="*60)
 
# Multiple aggregations
cluster_stats = df_iris.groupby('kmeans_cluster')[feature_columns].agg(['mean', 'std', 'min', 'max'])
print("\nDetailed statistics by K-Means cluster:")
print(cluster_stats)
============================================================
GROUPING AND AGGREGATION
============================================================
 
Mean values by species:
              sepal length (cm)  sepal width (cm)  petal length (cm)  \
species_name                                                           
setosa                    5.006             3.418              1.464   
versicolor                5.936             2.770              4.260   
virginica                 6.588             2.974              5.552   
 
              petal width (cm)  
species_name                    
setosa                   0.244  
versicolor               1.326  
virginica                2.026  
 
============================================================
 
Mean values by K-Means cluster:
                sepal length (cm)  sepal width (cm)  petal length (cm)  \
kmeans_cluster                                                           
0                        5.801887          2.673585           4.369811   
1                        5.006000          3.418000           1.464000   
2                        6.780851          3.095745           5.510638   
 
                petal width (cm)  
kmeans_cluster                    
0                       1.413208  
1                       0.244000  
2                       1.972340  
 
============================================================
 
Detailed statistics by K-Means cluster:
               sepal length (cm)                     sepal width (cm)  \
                            mean       std  min  max             mean   
kmeans_cluster                                                          
0                       5.801887  0.405487  4.9  6.6         2.673585   
1                       5.006000  0.352490  4.3  5.8         3.418000   
2                       6.780851  0.490624  5.9  7.9         3.095745   
 
                                   petal length (cm)                      \
                     std  min  max              mean       std  min  max   
kmeans_cluster                                                             
0               0.254310  2.0  3.0          4.369811  0.560420  3.0  5.6   
1               0.381024  2.3  4.4          1.464000  0.173511  1.0  1.9   
2               0.262063  2.5  3.8          5.510638  0.638011  4.4  6.9   
 
               petal width (cm)                      
                           mean       std  min  max  
kmeans_cluster                                       
0                      1.413208  0.311348  1.0  2.4  
1                      0.244000  0.107210  0.1  0.6  
2                      1.972340  0.330811  1.4  2.5

9.2 إنشاء أعمدة جديدة (Creating New Columns)

يمكننا إنشاء خصائص مشتقة (derived features) بناءً على الأعمدة الموجودة.

# Create new derived features
df_iris['sepal_area'] = df_iris['sepal length (cm)'] * df_iris['sepal width (cm)']
df_iris['petal_area'] = df_iris['petal length (cm)'] * df_iris['petal width (cm)']
df_iris['sepal_to_petal_ratio'] = df_iris['sepal_area'] / df_iris['petal_area']
 
print("New derived features created:")
print(df_iris[['sepal_area', 'petal_area', 'sepal_to_petal_ratio']].head(10))
 
print("\n" + "="*60)
print("\nStatistics of derived features by species:")
print(df_iris.groupby('species_name')[['sepal_area', 'petal_area', 'sepal_to_petal_ratio']].mean())
New derived features created:
   sepal_area  petal_area  sepal_to_petal_ratio
0       17.85        0.28             63.750000
1       14.70        0.28             52.500000
2       15.04        0.26             57.846154
3       14.26        0.30             47.533333
4       18.00        0.28             64.285714
5       21.06        0.68             30.970588
6       15.64        0.42             37.238095
7       17.00        0.30             56.666667
8       12.76        0.28             45.571429
9       15.19        0.15            101.266667
 
============================================================
 
Statistics of derived features by species:
              sepal_area  petal_area  sepal_to_petal_ratio
species_name                                              
setosa           17.2088      0.3628             57.443120
versicolor       16.5262      5.7204              2.970577
virginica        19.6846     11.2962              1.765114

9.3 حفظ النتائج (Saving Results)

يمكننا حفظ نتائجنا في ملف CSV لاستخدامها لاحقاً.

# Save the results to a CSV file
output_file = 'iris_clustering_results.csv'
df_iris.to_csv(output_file, index=False)
 
print(f"\n✓ Results saved to '{output_file}'")
 
# Demonstrate how to read it back
print("\nTo read the file later, use:")
print("df_loaded = pd.read_csv('iris_clustering_results.csv')")
print("\nExample of loaded data:")
df_loaded = pd.read_csv(output_file)
print(df_loaded.head())
 
✓ Results saved to 'iris_clustering_results.csv'
 
To read the file later, use:
df_loaded = pd.read_csv('iris_clustering_results.csv')
 
Example of loaded data:
   sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)  \
0                5.1               3.5                1.4               0.2   
1                4.9               3.0                1.4               0.2   
2                4.7               3.2                1.3               0.2   
3                4.6               3.1                1.5               0.2   
4                5.0               3.6                1.4               0.2   
 
  species_name  species  kmeans_cluster  som_cluster  sepal_area  petal_area  \
0       setosa        0               1            2       17.85        0.28   
1       setosa        0               1            2       14.70        0.28   
2       setosa        0               1            2       15.04        0.26   
3       setosa        0               1            2       14.26        0.30   
4       setosa        0               1            2       18.00        0.28   
 
   sepal_to_petal_ratio  
0             63.750000  
1             52.500000  
2             57.846154  
3             47.533333  
4             64.285714

الخطوة 10: الملخص وأهم النقاط المستفادة (Summary and Key Takeaways)

print("="*70)
print(" "*15 + "LAB SUMMARY AND KEY TAKEAWAYS")
print("="*70)
 
print("\n📊 DATASET OVERVIEW:")
print(f"   • Dataset: Iris Flower Dataset")
print(f"   • Samples: {len(df_iris)} flowers")
print(f"   • Features: {len(feature_columns)} measurements")
print(f"   • True Classes: 3 species")
 
print("\n🔧 DATA PREPROCESSING:")
print(f"   • Feature selection using DataFrame column indexing")
print(f"   • Standardization using StandardScaler")
print(f"   • Missing values: None detected")
 
print("\n🎯 K-MEANS CLUSTERING:")
print(f"   • Optimal k: {optimal_k} (determined by Elbow Method)")
print(f"   • Silhouette Score: {sil_score:.3f}")
print(f"   • Cluster sizes: {df_iris['kmeans_cluster'].value_counts().sort_index().tolist()}")
 
print("\n🗺️ SELF-ORGANIZING MAPS:")
print(f"   • Grid size: 3×1")
print(f"   • Silhouette Score: {som_sil_score:.3f}")
print(f"   • Cluster sizes: {df_iris['som_cluster'].value_counts().sort_index().tolist()}")
 
print("\n📈 PERFORMANCE COMPARISON:")
print(f"   • Best model: {'K-Means' if sil_score > som_sil_score else 'SOM'}")
print(f"   • Score difference: {abs(sil_score - som_sil_score):.3f}")
 
print("\n💡 KEY LEARNINGS:")
print("   ✓ DataFrames provide powerful data manipulation capabilities")
print("   ✓ Feature scaling is crucial for distance-based algorithms")
print("   ✓ Both K-Means and SOM can identify natural groupings in data")
print("   ✓ Visualization helps validate clustering results")
print("   ✓ Real-world data requires careful preprocessing")
 
print("\n" + "="*70)
======================================================================
               LAB SUMMARY AND KEY TAKEAWAYS
======================================================================
 
📊 DATASET OVERVIEW:
   • Dataset: Iris Flower Dataset
   • Samples: 150 flowers
   • Features: 4 measurements
   • True Classes: 3 species
 
🔧 DATA PREPROCESSING:
   • Feature selection using DataFrame column indexing
   • Standardization using StandardScaler
   • Missing values: None detected
 
🎯 K-MEANS CLUSTERING:
   • Optimal k: 3 (determined by Elbow Method)
   • Silhouette Score: 0.459
   • Cluster sizes: [53, 50, 47]
 
🗺️ SELF-ORGANIZING MAPS:
   • Grid size: 3×1
   • Silhouette Score: 0.412
   • Cluster sizes: [70, 30, 50]
 
📈 PERFORMANCE COMPARISON:
   • Best model: K-Means
   • Score difference: 0.047
 
💡 KEY LEARNINGS:
   ✓ DataFrames provide powerful data manipulation capabilities
   ✓ Feature scaling is crucial for distance-based algorithms
   ✓ Both K-Means and SOM can identify natural groupings in data
   ✓ Visualization helps validate clustering results
   ✓ Real-world data requires careful preprocessing
 
======================================================================

أسئلة تدريبية (Exercise Questions)

أجب عن الأسئلة التالية بناءً على نتائجك:

1. عمليات DataFrame (DataFrame Operations):

  • Q1.1: كيف يمكنك تصفية الـ DataFrame لعرض الزهور التي يزيد طول البتلة (petal length) فيها عن 4.5 سم فقط؟
  • Q1.2: أنشئ عموداً جديداً باسم total_size يمثل مجموع القياسات الأربعة جميعها.

2. استكشاف البيانات (Data Exploration):

  • Q2.1: ما هو النوع (species) الذي يمتلك أكبر متوسط لمساحة البتلة (petal area)؟
  • Q2.2: استخدم groupby() لإيجاد أقصى طول للكأس (sepal length) في كل مجموعة (cluster) من مجموعات K-Means.

3. تحليل التجميع (Clustering Analysis):

  • Q3.1: ماذا يحدث إذا غيّرت عدد مجموعات K-Means إلى 2 أو 4؟
  • Q3.2: جرّب تدريب SOM بحجم شبكة (grid size) مختلف (مثلاً 2×2). كيف تتغير النتائج؟

4. هندسة الخصائص (Feature Engineering):

  • Q4.1: أنشئ خاصية جديدة: petal_sepal_length_ratio = petal length / sepal length
  • Q4.2: أعد تشغيل K-Means مع تضمين هذه الخاصية الجديدة. هل يحسّن ذلك مقياس Silhouette (Silhouette Score)؟

5. المقارنة (Comparison):

  • Q5.1: متى تُفضّل استخدام K-Means على SOM؟
  • Q5.2: متى يكون استخدام SOM أكثر ملاءمة؟
# Exercise Solutions - Try to solve before running!
 
# Q1.1: Filter flowers with petal length > 4.5
# large_petals = df_iris[df_iris['petal length (cm)'] > 4.5]
# print(large_petals)
 
# Q1.2: Create total_size column
# df_iris['total_size'] = df_iris[feature_columns].sum(axis=1)
# print(df_iris[['species_name', 'total_size']].head())
 
# Q2.1: Species with largest average petal area
# print(df_iris.groupby('species_name')['petal_area'].mean())
 
# Q2.2: Maximum sepal length by cluster
# print(df_iris.groupby('kmeans_cluster')['sepal length (cm)'].max())

الخاتمة (Conclusion)

في هذا المعمل، تعلمت:

  1. ✅ كيفية العمل مع أطر بيانات pandas (DataFrames) لمعالجة البيانات
  2. ✅ خطوات المعالجة المسبقة الأساسية لخوارزميات التجميع
  3. ✅ كيفية تطبيق K-Means و SOM على بيانات واقعية
  4. ✅ تقنيات تقييم نتائج التجميع وتصويرها بياناً
  5. ✅ كيفية مقارنة خوارزميات التجميع المختلفة

تذكّر: مفتاح نجاح عملية التجميع يكمن في:

  • فهم بياناتك من خلال التحليل الاستكشافي للبيانات "Exploratory Data Analysis" (EDA)
  • المعالجة المسبقة الصحيحة (التحجيم (scaling)، والتعامل مع القيم المفقودة)
  • اختيار مقاييس تقييم مناسبة
  • تصور النتائج بياناً للتحقق من صحة الاستنتاجات