Pandas: معالجة البيانات وتحليلها في Python
دليل شامل لمكتبة Pandas لتحميل البيانات الجدولية وتنظيفها وتصفيتها وتجميعها وتصديرها في Python: Series وDataFrame وgroupby وmerge والتواريخ.
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
خبير في التدريب والتقنيات
مدرّب متخصص في التقنيات المتقدمة والذكاء الاصطناعي، تدعمه شركة GNeurone Inc.
الشهادات:
- AWS Certified Solutions Architect – Professional
- شهادات Google Cloud
- Microsoft Certified: DevOps Engineer Expert
- Certified Kubernetes Administrator (CKA)
- CompTIA Security+
التعليقات
التعليقات مدعومة عبر GitHub Discussions
سجّل الدخول باستخدام GitHub للمشاركة في النقاش