0%
Pandas: معالجة البيانات وتحليلها في Python

Pandas: معالجة البيانات وتحليلها في Python

دليل شامل لمكتبة Pandas لتحميل البيانات الجدولية وتنظيفها وتصفيتها وتجميعها وتصديرها في Python: Series وDataFrame وgroupby وmerge والتواريخ.

I

InSkillCoach

· دقيقة

Pandas: معالجة البيانات وتحليلها في Python

Pandas هي المكتبة المرجعية لمعالجة البيانات الجدولية في Python. بُنيت فوق NumPy، وتوفر بنيتي بيانات أساسيتين هما Series وDataFrame، إضافةً إلى مجموعة متكاملة من الأدوات لتحميل الملفات وتنظيف القيم المفقودة والتصفية والتجميع والدمج وتصدير البيانات. يغطي هذا الدليل سير العمل الكامل للتحليل: من قراءة ملف CSV حتى تصدير النتائج، مروراً بالأخطاء الشائعة التي ينبغي تجنبها.

Series وDataFrame: البنيتان الأساسيتان

Series: عمود ذو تسميات

الـ Series هي مصفوفة أحادية البعد ترتبط كل قيمة فيها بتسمية فهرس.

import pandas as pd

s = pd.Series([15, 12, 17, 9], index=["Alice", "Bob", "Chloé", "David"])
print(s)
# Alice    15
# Bob      12
# Chloé    17
# David     9
# dtype: int64

# Accès par étiquette ou par position
print(s["Chloé"])       # 17
print(s.iloc[0])        # 15

# Les opérations sont vectorisées, comme avec NumPy
print(s.mean())         # 13.25
print(s + 1)            # chaque note augmentée de 1

DataFrame: جدول ثنائي الأبعاد

الـ DataFrame هو مجموعة من الأعمدة (وهي Series) تتشارك فهرس الصفوف نفسه.

df = pd.DataFrame({
    "nom": ["Alice", "Bob", "Chloé", "David"],
    "age": [25, 32, 28, 41],
    "ville": ["Paris", "Lyon", "Paris", "Nantes"],
    "salaire": [42000, 38000, 51000, 46000],
})
print(df)
#      nom  age   ville  salaire
# 0  Alice   25   Paris    42000
# 1    Bob   32    Lyon    38000
# 2  Chloé   28   Paris    51000
# 3  David   41  Nantes    46000

# Une colonne est une Series
print(df["age"].dtype)    # int64
print(df.shape)           # (4, 4)
print(df.columns.tolist())  # ['nom', 'age', 'ville', 'salaire']

قراءة الملفات باستخدام read_csv

تُعد read_csv أكثر دوال Pandas استخداماً. وتغطي خياراتها شبه كامل صيغ CSV التي تصادفها في الواقع العملي.

# Lecture simple
df = pd.read_csv("ventes.csv")

# Options courantes
df = pd.read_csv(
    "ventes.csv",
    sep=";",                     # séparateur (point-virgule fréquent en France)
    decimal=",",                 # virgule décimale
    encoding="utf-8",            # ou "latin-1" pour d'anciens fichiers
    usecols=["date", "produit", "montant"],   # ne charger que certaines colonnes
    dtype={"produit": "string"}, # forcer un type
    parse_dates=["date"],        # convertir en datetime dès la lecture
    na_values=["N/A", "inconnu"],  # valeurs à traiter comme manquantes
    nrows=1000,                  # limiter le nombre de lignes (utile pour explorer)
)

تقرأ Pandas أيضاً العديد من الصيغ الأخرى: read_excel وread_json وread_parquet وread_sql. وبالنسبة إلى أحجام البيانات الكبيرة، فإن صيغة Parquet أسرع وأكثر إحكاماً من CSV بشكل ملحوظ.

الفحص: head وinfo وdescribe

الأوامر الثلاثة الأولى التي ينبغي تشغيلها على أي مجموعة بيانات جديدة:

# Aperçu des premières lignes
print(df.head())        # 5 premières lignes (df.tail() pour les dernières)

# Structure : types, valeurs non nulles, mémoire
df.info()
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 4 entries, 0 to 3
# Data columns (total 4 columns):
#  #   Column   Non-Null Count  Dtype
# ---  ------   --------------  -----
#  0   nom      4 non-null      object
#  1   age      4 non-null      int64
#  2   ville    4 non-null      object
#  3   salaire  4 non-null      int64

# Statistiques descriptives des colonnes numériques
print(df.describe())
#              age       salaire
# count   4.000000      4.000000
# mean   31.500000  44250.000000
# std     6.952218   5560.275773
# min    25.000000  38000.000000
# max    41.000000  51000.000000

# Comptage des valeurs distinctes d'une colonne
print(df["ville"].value_counts())
# Paris     2
# Lyon      1
# Nantes    1

التحديد باستخدام loc وiloc

أداتا وصول متكاملتان: loc تحدد بحسب التسميات، وiloc بحسب المواضع الصحيحة.

# loc[lignes, colonnes] par étiquettes
print(df.loc[0, "nom"])                  # Alice
print(df.loc[0:2, ["nom", "ville"]])     # lignes 0 à 2 INCLUSES, deux colonnes

# iloc[lignes, colonnes] par positions
print(df.iloc[0, 0])                     # Alice
print(df.iloc[0:2, 0:2])                 # lignes 0 et 1 (borne exclue), colonnes 0 et 1

# Toutes les lignes, une colonne
print(df.loc[:, "salaire"])

# Dernière ligne
print(df.iloc[-1])

انتبه إلى اختلاف الاصطلاح: loc[0:2] تشمل الحد 2 (منطق التسميات)، بينما iloc[0:2] تستثنيه (منطق المواضع، كما في قوائم Python).

التصفية المنطقية

# Une condition
seniors = df[df["age"] > 30]
print(seniors)
#      nom  age   ville  salaire
# 1    Bob   32    Lyon    38000
# 3  David   41  Nantes    46000

# Conditions combinées : & (et), | (ou), ~ (non), parenthèses obligatoires
cible = df[(df["ville"] == "Paris") & (df["salaire"] >= 45000)]
print(cible)
#      nom  age  ville  salaire
# 2  Chloé   28  Paris    51000

# Appartenance à une liste
grandes_villes = df[df["ville"].isin(["Paris", "Lyon"])]

# Filtrage sur du texte
noms_a = df[df["nom"].str.startswith("A")]

# query : syntaxe alternative lisible
resultat = df.query("age > 30 and salaire < 47000")

الأعمدة المحسوبة

# Nouvelle colonne à partir des existantes
df["salaire_mensuel"] = df["salaire"] / 12

# Colonne conditionnelle avec numpy.where
import numpy as np
df["categorie"] = np.where(df["age"] < 30, "junior", "senior")

# Transformation par fonction (à réserver aux cas non vectorisables)
df["initiale"] = df["nom"].apply(lambda n: n[0])

# assign : créer des colonnes en chaînant les opérations
df2 = df.assign(
    salaire_k=df["salaire"] / 1000,
    age_en_2030=df["age"] + 4,
)
print(df2[["nom", "salaire_k", "age_en_2030"]].head(2))
#      nom  salaire_k  age_en_2030
# 0  Alice       42.0           29
# 1    Bob       38.0           36

فضّل دائماً العمليات المتجهية (df["a"] + df["b"] وnp.where و.str و.dt): فاستخدام apply مع lambda غالباً ما يكون أبطأ بمقدار 10 إلى 100 مرة.

القيم المفقودة: isna وfillna وdropna

df_nan = pd.DataFrame({
    "produit": ["A", "B", "C", "D"],
    "prix": [10.0, None, 25.0, None],
    "stock": [5, 3, None, 8],
})

# Détecter les valeurs manquantes
print(df_nan.isna())
print(df_nan.isna().sum())     # nombre de NaN par colonne
# produit    0
# prix       2
# stock      1

# Remplacer par une valeur fixe
df_rempli = df_nan.fillna({"prix": 0.0, "stock": 0})

# Remplacer par une statistique
df_nan["prix"] = df_nan["prix"].fillna(df_nan["prix"].mean())
print(df_nan["prix"])
# 0    10.0
# 1    17.5
# 2    25.0
# 3    17.5

# Supprimer les lignes contenant au moins un NaN
df_propre = df_nan.dropna()

# Supprimer seulement si une colonne précise est manquante
df_propre = df_nan.dropna(subset=["stock"])

اختر الاستراتيجية بحسب السياق: حذف الصفوف يؤدي إلى فقدان المعلومات، وتعويض القيم المفقودة بالمتوسط قد يُحدث انحيازاً في التحليل. وثّق اختيارك دائماً.

groupby والتجميعات

نمط «التقسيم والتطبيق والدمج» (split-apply-combine): تقسيم البيانات إلى مجموعات، وتطبيق عملية حسابية، ثم إعادة دمج النتائج.

ventes = pd.DataFrame({
    "region": ["Nord", "Sud", "Nord", "Sud", "Nord"],
    "produit": ["A", "A", "B", "B", "A"],
    "montant": [100, 150, 200, 120, 80],
})

# Agrégation simple
print(ventes.groupby("region")["montant"].sum())
# region
# Nord    380
# Sud     270

# Plusieurs statistiques à la fois
print(ventes.groupby("region")["montant"].agg(["sum", "mean", "count"]))
#         sum        mean  count
# region
# Nord    380  126.666667      3
# Sud     270  135.000000      2

# Groupement sur plusieurs colonnes
print(ventes.groupby(["region", "produit"])["montant"].sum())
# region  produit
# Nord    A          180
#         B          200
# Sud     A          150
#         B          120

# Agrégations nommées : colonnes de sortie explicites
resume = ventes.groupby("region").agg(
    total=("montant", "sum"),
    panier_moyen=("montant", "mean"),
)
print(resume)
#         total  panier_moyen
# region
# Nord      380    126.666667
# Sud       270    135.000000

ولتحويل نتيجة مجمّعة إلى جدول متقاطع، تكون pivot_table غالباً أكثر مباشرة:

tableau = ventes.pivot_table(index="region", columns="produit",
                             values="montant", aggfunc="sum", fill_value=0)
print(tableau)
# produit    A    B
# region
# Nord     180  200
# Sud      150  120

دمج الجداول: merge وconcat

merge: الربط على طريقة SQL

clients = pd.DataFrame({
    "client_id": [1, 2, 3],
    "nom": ["Alice", "Bob", "Chloé"],
})
commandes = pd.DataFrame({
    "client_id": [1, 1, 2, 4],
    "montant": [50, 30, 70, 20],
})

# Jointure interne : uniquement les correspondances
interne = pd.merge(clients, commandes, on="client_id", how="inner")
print(interne)
#    client_id    nom  montant
# 0          1  Alice       50
# 1          1  Alice       30
# 2          2    Bob       70

# Jointure gauche : tous les clients, même sans commande
gauche = pd.merge(clients, commandes, on="client_id", how="left")
# Chloé apparaît avec montant NaN

# how accepte aussi "right" et "outer"

concat: تكديس الجداول

janvier = pd.DataFrame({"produit": ["A", "B"], "ventes": [10, 20]})
fevrier = pd.DataFrame({"produit": ["A", "B"], "ventes": [15, 25]})

# Empilement vertical avec réindexation
annee = pd.concat([janvier, fevrier], ignore_index=True)
print(annee)
#   produit  ventes
# 0       A      10
# 1       B      20
# 2       A      15
# 3       B      25

الفرز

# Tri sur une colonne
print(df.sort_values("age"))

# Tri décroissant sur plusieurs colonnes
print(df.sort_values(["ville", "salaire"], ascending=[True, False]))

# Tri sur l'index
print(df.sort_index())

# Les n plus grandes valeurs (plus rapide qu'un tri complet)
print(df.nlargest(2, "salaire")[["nom", "salaire"]])
#      nom  salaire
# 2  Chloé    51000
# 3  David    46000

التواريخ: to_datetime وresample

journal = pd.DataFrame({
    "date": ["2026-01-05", "2026-01-12", "2026-02-03", "2026-02-20", "2026-03-01"],
    "ca": [120, 80, 200, 150, 90],
})

# Conversion en datetime
journal["date"] = pd.to_datetime(journal["date"])
print(journal["date"].dtype)     # datetime64[ns]

# L'accesseur .dt expose les composantes
journal["mois"] = journal["date"].dt.month
journal["jour_semaine"] = journal["date"].dt.day_name()

# resample : agrégation temporelle (nécessite un index datetime)
serie = journal.set_index("date")["ca"]
mensuel = serie.resample("ME").sum()     # "ME" = fin de mois (pandas >= 2.2)
print(mensuel)
# date
# 2026-01-31    200
# 2026-02-28    350
# 2026-03-31     90

# Filtrer une plage de dates
recentes = journal[journal["date"] >= "2026-02-01"]

إذا لم تكن الصيغة قياسية، فحدّدها صراحةً: pd.to_datetime(s, format="%d/%m/%Y") يتفادى الالتباس بين اليوم والشهر.

تصدير النتائج

# CSV (index=False pour ne pas écrire la colonne d'index)
df.to_csv("resultats.csv", index=False, sep=";", encoding="utf-8")

# Excel (nécessite openpyxl)
df.to_excel("resultats.xlsx", sheet_name="Analyse", index=False)

# Parquet : rapide, compact, types préservés (nécessite pyarrow)
df.to_parquet("resultats.parquet")

# JSON
df.to_json("resultats.json", orient="records", force_ascii=False)

الأخطاء الشائعة: SettingWithCopyWarning

يظهر هذا التحذير عندما تعدّل نتيجة تحديد لا تعرف Pandas إن كانت عرضاً (view) أم نسخة (copy).

# MAUVAIS : indexation chaînée, la modification peut être perdue
sous_ensemble = df[df["ville"] == "Paris"]
sous_ensemble["salaire"] = 0        # SettingWithCopyWarning

# BON : modifier le DataFrame original en une seule opération loc
df.loc[df["ville"] == "Paris", "salaire"] = 0

# BON : travailler sur une copie explicite et assumée
sous_ensemble = df[df["ville"] == "Paris"].copy()
sous_ensemble["salaire"] = 0        # aucun warning, df original intact

أخطاء شائعة أخرى:

  • المقارنة مع NaN: التعبير df["col"] == np.nan يعيد دائماً False؛ استخدم df["col"].isna().
  • أنواع object الصامتة: يتحول عمود الأرقام الذي يحتوي على سلسلة نصية إلى النوع object؛ تحقق باستخدام df.dtypes وحوّل باستخدام pd.to_numeric(s, errors="coerce").
  • inplace=True: لا ينصح به فريق Pandas؛ فضّل إعادة الإسناد df = df.dropna().

أفضل الممارسات

  • افحص بشكل منهجي كل مجموعة بيانات جديدة باستخدام head وinfo وdescribe قبل أي تحويل.
  • استخدم loc/iloc صراحةً في كل عملية كتابة، وتجنب الفهرسة المتسلسلة (df[...][...] = ...).
  • اعتمد المعالجة المتجهية: تجنب apply والحلقات عندما تتوفر عملية تُطبَّق على العمود بأكمله.
  • حلّل التواريخ عند القراءة باستخدام parse_dates بدلاً من تحويلها لاحقاً.
  • سمِّ تجميعاتك باستخدام agg(total=("montant", "sum")) للحصول على أعمدة إخراج واضحة.
  • فضّل Parquet على CSV للملفات الوسيطة: أسرع وأكثر إحكاماً ودون فقدان في الأنواع.
  • سلسل التحويلات بشكل مقروء باستخدام assign وquery وpipe بدلاً من تكثير المتغيرات الوسيطة.

الخاتمة

تغطي Pandas دورة حياة تحليل البيانات بأكملها: تحميل متين باستخدام read_csv، واستكشاف باستخدام info وdescribe، وتنظيف القيم المفقودة، والتصفية المنطقية، والأعمدة المحسوبة، والتجميعات باستخدام groupby، والربط باستخدام merge، والتعامل الدقيق مع التواريخ، والتصدير إلى صيغ متعددة. وبفهم الفرق بين loc وiloc وتجنب الفهرسة المتسلسلة، تتخلص من الغالبية العظمى من الأخطاء الدقيقة. أما الخطوات الطبيعية التالية فهي تصوير DataFrames الخاصة بك باستخدام Matplotlib والتحليل الإحصائي باستخدام SciPy، وهما مكتبتان تتكاملان بشكل أصيل مع Pandas.

InSkillCoach

نبذة عن InSkillCoach

خبير في التدريب والتقنيات

مدرّب متخصص في التقنيات المتقدمة والذكاء الاصطناعي، تدعمه شركة GNeurone Inc.

الشهادات:

  • AWS Certified Solutions Architect – Professional
  • شهادات Google Cloud
  • Microsoft Certified: DevOps Engineer Expert
  • Certified Kubernetes Administrator (CKA)
  • CompTIA Security+
681
266

التعليقات

التعليقات مدعومة عبر GitHub Discussions

سجّل الدخول باستخدام GitHub للمشاركة في النقاش

تم نسخ الرابط!