Pandas: معالجة القيم المفقودة

آخر تحديث: 2026-08-26

القيم المفقودة (NaN) هي العدو الأول لتنظيف البيانات — البيانات في العالم الحقيقي ليست مثالية أبدًا. عطل في المستشعر يترك قراءات درجة الحرارة مفقودة، ومستخدم يتجاوز حقلاً اختياريًا فتفقد قيمة العمر، ودمج نظام يمحو المعرفات. تقدم Pandas سلسلة أدوات متكاملة للكشف عن القيم المفقودة، وإسقاطها، وملئها، والتقريب لها. يرشدك هذا القسم خلال كل واحدة منها ويشرح كيفية العمل بأمان ضمن نمط النسخ عند الكتابة (Copy-on-Write).

⚠️ ملاحظة: يجب تشغيل الكود أدناه في بيئة Python محلية.

1. ما ستتعلمه


2. الفجوة في بيانات تمارين كارول

(1) نقطة الضعف: 30% من بيانات معدل ضربات القلب مفقودة

سجّل جهاز تتبع لياقة كارول بيانات لمعدل ضربات القلب لمدة 30 يومًا، لكن الإشارة انقطعت (NaN) في 9 أيام منها أثناء تمارينها:

PYTHON
import pandas as pd
import numpy as np

hr = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=30),
    'heart_rate': [72, 75, np.nan, 78, np.nan, 80, 82, np.nan,
                   85, 88, np.nan, 90, 87, np.nan, 83, 80,
                   np.nan, 78, 76, np.nan, 74, 72, np.nan, 70,
                   68, np.nan, 65, 63, np.nan, 60]
})
print(f"Missing: {hr['heart_rate'].isnull().sum()} / {len(hr)}")
# Missing: 9 / 30
TEXT 📖 للعرض فقط
> **الخرج:** شغّل هذا في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبتة مسبقًا — ثبّتها محليًا (`pip install pandas`) للمتابعة. قد تختلف القيم الفعلية قليًّا حسب إصدار pandas الخاص بك.

(2) الحل: أربع استراتيجيات، لكل منها حالة استخدام خاصة

الاستراتيجية الطريقة متى نستخدمها
الكشف isnull / notnull لفهم توزيع القيم المفقودة
الحذف dropna عند قلة القيم المفقودة، والصفوف غير مهمة
التعبئة fillna عند وجود قيمة افتراضية معقولة
الاستكمال interpolate لبيانات السلاسل الزمنية ذات الاتجاه الواضح

3. طبيعة NaN

(1) NaN لا يساوي NaN

▶ مثال: السلوك الخاص لـ NaN (الصعوبة ⭐)

PYTHON
import numpy as np

# NaN لا يساوي نفسه - هذا هو معيار IEEE 754
print(np.nan == np.nan)   # False!
print(np.nan != np.nan)   # True!

# استخدم np.isnan() للكشف عن NaN
print(np.isnan(np.nan))   # True

# يتعامل Pandas مع هذا داخليًا - isnull() تعمل بشكل صحيح
import pandas as pd
s = pd.Series([1, np.nan, 3, None, 5])
print(s.isnull())
# 0    False
# 1     True
# 2    False
# 3     True
# 4    False
TEXT 📖 للعرض فقط
> **الناتج:** قم بتشغيل هذا في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبتًا مسبقًا — قم بتثبيته محليًا (`pip install pandas`) للمتابعة. قد تختلف القيم الفعليّة قليًّا حسب إصدار pandas الخاص بك.

(2) أنواع القيم المفقودة في Pandas

النوع التمثيل المفقود مثال
float64 np.nan قيمة الفاصلة العائمة المفقودة الافتراضية
int64 لا يمكن تمثيل NaN أصليًّا → يتم التحويل تلقائيًّا إلى float64 [1, None, 3] → float64
كائن None / np.nan أعمدة من نوع مختلط
سلسلة pd.NA سلسلة قابلة لل_Null في Pandas 1.x+
Int64 pd.NA عدد صحيح قابل لل_Null (حرف كبير I)
datetime64 NaT قيمة وقت مفقودة
💡 نصيحة: الأنواع القابلة لل_Null في Pandas 2.x (حرف كبير، مثل Int64، boolean، وstring) تستخدم pd.NA بدلاً من np.nan، مما يحل مشكلة عدم قدرة أعمدة الأعداد الصحيحة على تمثيل القيم المفقودة. قم بالتحويل عبر: df['col'].astype('Int64').


4. الكشف باستخدام isnull / notnull

(1) الكشف الأساسي

▶ مثال

TEXT 📖 للعرض فقط
> **Output:** Run this in a local Python environment (pandas 2.x). The Piston server does not have pandas pre-installed — install it locally (`pip install pandas`) to follow along. Actual values may vary slightly depending on your pandas version.

: كشف القيم المفقودة (الصعوبة ⭐)

PYTHON
import pandas as pd
import numpy as np

df = pd.DataFrame({
    'name': ['Alice', 'Bob', 'Charlie', 'Carol', 'David'],
    'age': [28, np.nan, 25, 30, np.nan],
    'salary': [75000, 92000, np.nan, 88000, 105000],
    'department': ['Sales', 'Engineering', np.nan, 'Sales', 'Management']
})

# Per-element detection
print(df.isnull())
#     name    age  salary  department
# 0  False  False   False       False
# 1  False   True   False       False
# 2  False  False    True        True
# 3  False  False   False       False
# 4  False   True   False       False

# Per-column count
print(df.isnull().sum())
# name          0
# age           2
# salary        1
# department    1

# Total missing
print(f"Total missing: {df.isnull().sum().sum()}")  # 4

# Not-null (inverse)
print(df.notnull().sum())  # count of non-missing per column
TEXT 📖 للعرض فقط
> **Output:** قم بتشغيل هذا في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبتًا مسبقًا — قم بتثبيته محليًا (`pip install pandas`) لمتابعة التوضيح. القيم الفعلية قد تختلف قليلاً حسب إصدار pandas الخاص بك.

(2) تصور وتحليل القيم المفقودة

▶ مثال

TEXT 📖 للعرض فقط
> **Output:** قم بتشغيل هذا في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبتًا مسبقًا — قم بتثبيته محليًا (`pip install pandas`) لمتابعة التوضيح. القيم الفعلية قد تختلف قليلاً حسب إصدار pandas الخاص بك.

: تحليل أنماط القيم المفقودة (الصعوبة ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

df = pd.DataFrame({
    'name': ['Alice', 'Bob', 'Charlie', 'Carol', 'David', 'Eve'],
    'age': [28, np.nan, 25, 30, np.nan, 27],
    'salary': [75000, 92000, np.nan, 88000, 105000, np.nan],
    'department': ['Sales', 'Engineering', np.nan, 'Sales', 'Management', 'HR']
})

# Which rows have ANY missing?
rows_with_missing = df[df.isnull().any(axis=1)]
print(f"Rows with missing values: {len(rows_with_missing)}")
# 3 rows (Bob, Charlie, David)

# Which columns have missing?
cols_with_missing = df.columns[df.isnull().any()].tolist()
print(f"Columns with missing: {cols_with_missing}")
# ['age', 'salary', 'department']

# Missing percentage per column
missing_pct = (df.isnull().sum() / len(df) * 100).round(1)
print(missing_pct[missing_pct > 0])
# age          33.3%
# salary       33.3%
# department   16.7%
TEXT 📖 للعرض فقط
> **Output:** قم بتشغيل هذا في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبتًا مسبقًا — قم بتثبيته محليًا (`pip install pandas`) لمتابعة التوضيح. القيم الفعلية قد تختلف قليلاً حسب إصدار pandas الخاص بك.

5. إسقاط القيم المفقودة باستخدام dropna

(1) الإسقاط الأساسي

▶ مثال

TEXT 📖 للعرض فقط
> **الخرج:** شغّل هذا في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبت مسبقًا — قم بتثبيته محليًا (`pip install pandas`) للمتابعة. قد تختلف القيم الفعلية قليًلاً حسب إصدار pandas الخاص بك.

: الإسقاط باستخدام dropna (الصعوبة ⭐)

PYTHON
import pandas as pd
import numpy as np

df = pd.DataFrame({
    'name': ['Alice', 'Bob', 'Charlie', 'Carol', 'David'],
    'age': [28, np.nan, 25, 30, np.nan],
    'salary': [75000, 92000, np.nan, 88000, 105000]
})

# Drop rows with ANY missing (default)
print(df.dropna())
#     name   age    salary
# 0  Alice  28.0   75000.0
# 3  Carol  30.0   88000.0

# Drop rows where ALL values are missing
print(df.dropna(how='all'))

# Drop columns with any missing
print(df.dropna(axis=1))
#      name
# 0   Alice
# 1     Bob
# ...

# Drop rows with threshold (keep rows with >= N non-missing)
print(df.dropna(thresh=2))  # at least 2 non-NaN values
#      name   age    salary
# 0   Alice  28.0   75000.0
# 1     Bob   NaN   92000.0
# 3   Carol  30.0   88000.0
# 4   David   NaN  105000.0
TEXT 📖 للعرض فقط
> **الخرج:** شغّل هذا في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبت مسبقًا — قم بتثبيته محليًا (`pip install pandas`) للمتابعة. قد تختلف القيم الفعلية قليًلاً حسب إصدار pandas الخاص بك.

(2) تفاصيل معاملات dropna

المعامل القيمة الافتراضية الوصف
axis 0 0 = إسقاط الصفوف، 1 = إسقاط الأعمدة
how 'any' 'any' = إسقاط إذا كان هناك أي قيمة مفقودة، 'all' = إسقاط فقط إذا كانت جميع القيم مفقودة
thresh None الاحتفاظ بالصفوف التي تحتوي على N قيمة غير مفقودة على الأقل
subset None التحقق فقط من الأعمدة المحددة

▶ مثال

TEXT 📖 للعرض فقط
> **الخرج:** شغّل هذا في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبت مسبقًا — قم بتثبيته محليًا (`pip install pandas`) للمتابعة. قد تختلف القيم الفعلية قليًلاً حسب إصدار pandas الخاص بك.

: التحقق على مستوى عمود معين باستخدام dropna (الصعوبة ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

df = pd.DataFrame({
    'name': ['Alice', 'Bob', 'Charlie', 'Carol', 'David'],
    'age': [28, np.nan, 25, 30, np.nan],
    'salary': [75000, 92000, np.nan, 88000, 105000],
    'department': ['Sales', 'Engineering', 'Marketing', 'Sales', 'Management']
})

# Only drop rows where 'salary' is missing
# (keep rows with missing 'age' — we can impute age)
print(df.dropna(subset=['salary']))
#      name   age    salary  department
# 0   Alice  28.0   75000.0       Sales
# 1     Bob   NaN   92000.0  Engineering
# 3   Carol  30.0   88000.0       Sales
# 4   David   NaN  105000.0  Management
TEXT 📖 للعرض فقط
> **الخرج:** شغّل هذا في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبت مسبقًا — قم بتثبيته محليًا (`pip install pandas`) للمتابعة. قد تختلف القيم الفعلية قليًلاً حسب إصدار pandas الخاص بك.

6. ملء القيم المفقودة باستخدام fillna

(1) الملء بقيمة ثابتة

▶ مثال

TEXT 📖 للعرض فقط
> **الناتج:** شغّل هذا في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبّت مسبقاً — قم بتثبيته محلياً (`pip install pandas`) للمتابعة. قد تختلف القيم الفعلية قليلاً حسب إصدار pandas لديك.

: الملء بقيم ثابتة وإحصائية باستخدام fillna (الصعوبة ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

df = pd.DataFrame({
    'name': ['Alice', 'Bob', 'Charlie', 'Carol', 'David'],
    'age': [28, np.nan, 25, 30, np.nan],
    'salary': [75000, 92000, np.nan, 88000, 105000]
})

# Fill with constant
df_const = df.fillna({'age': 0, 'salary': 0})
print(df_const)

# Fill with mean (most common for numeric)
df_mean = df.copy()
df_mean['age'] = df_mean['age'].fillna(df_mean['age'].mean())
df_mean['salary'] = df_mean['salary'].fillna(df_mean['salary'].median())
print(df_mean)
# age: filled with 27.67, salary: filled with 88000.0

# Fill with forward fill (use previous value)
df_ffill = df.fillna(method='ffill')
print(df_ffill)
TEXT 📖 للعرض فقط
> **الناتج:** شغّل هذا في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبّت مسبقاً — قم بتثبيته محلياً (`pip install pandas`) للمتابعة. قد تختلف القيم الفعلية قليلاً حسب إصدار pandas لديك.
⚠️ ملاحظة: في Pandas 2.x، fillna(method=...) مُهمل — استخدم df.ffill() / df.bfill() بدلاً منه.

(2) الملء الأمامي / الخلفي

▶ مثال

TEXT 📖 للعرض فقط
> **الناتج:** شغّل هذا في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبّت مسبقاً — قم بتثبيته محلياً (`pip install pandas`) للمتابعة. قد تختلف القيم الفعلية قليلاً حسب إصدار pandas لديك.

: ffill و bfill (الصعوبة ⭐)

PYTHON
import pandas as pd
import numpy as np

# Time-series data — ffill makes sense here
ts = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=8),
    'temperature': [22.5, np.nan, np.nan, 23.1, np.nan, 24.0, np.nan, 24.5]
})

# Forward fill (propagate last valid value)
print(ts.assign(temperature_ffill=ts['temperature'].ffill()))
# NaN → use previous non-NaN value

# Backward fill (propagate next valid value)
print(ts.assign(temperature_bfill=ts['temperature'].bfill()))
# NaN → use next non-NaN value

# Limit: only fill N consecutive NaN
print(ts.assign(temperature_limited=ts['temperature'].ffill(limit=1)))
# Only fill 1 consecutive NaN, leave the rest
TEXT 📖 للعرض فقط
> **الناتج:** شغّل هذا في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبّت مسبقاً — قم بتثبيته محلياً (`pip install pandas`) للمتابعة. قد تختلف القيم الفعلية قليلاً حسب إصدار pandas لديك.

7. الاستكمال (التوليف) باستخدام interpolate

(1) الاستكمال الخطي

▶ مثال

TEXT 📖 للعرض فقط
> **المخرجات:** قم بتشغيل هذا في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبتة مسبقاً — قم بتثبيتها محلياً (`pip install pandas`) لمتابعة الشرح. قد تتغير القيم الفعلي قليلاً حسب إصدار pandas لديك.

: الاستكمال الخطي باستخدام interpolate (الصعوبة ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

# بيانات معدل ضربات قلب كارول — الاستكمال الخطي هو الطبيعي
hr = pd.DataFrame({
    'time_min': list(range(10)),
    'heart_rate': [72, 75, np.nan, 78, np.nan, 80, 82, np.nan, 85, 88]
})

# الاستكمال الخطي (الافتراضي)
hr['hr_linear'] = hr['heart_rate'].interpolate()
print(hr[['time_min', 'heart_rate', 'hr_linear']])
# time 2: (75+78)/2 = 76.5
# time 4: (78+80)/2 = 79.0
# time 7: (82+85)/2 = 83.5

# الاستكمال بناءً على الفهرس مقابل الاستكمال الزمني
hr_ts = pd.DataFrame({
    'heart_rate': [72, 75, np.nan, 78, np.nan, 80]
}, index=pd.to_timedelta([0, 5, 15, 20, 40, 45], unit='min'))

# الاستكمال الموزون زمنياً
hr_ts['hr_time'] = hr_ts['heart_rate'].interpolate(method='time')
print(hr_ts)
TEXT 📖 للعرض فقط
> **المخرجات:** قم بتشغيل هذا في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبتة مسبقاً — قم بتثبيتها محلياً (`pip install pandas`) لمتابعة الشرح. قد تتغير القيم الفعلي قليلاً حسب إصدار pandas لديك.

(2) مقارنة طرق الاستكمال

الطريقة طريقة متى تُستخدم
خطي 'linear' بيانات مأخوذة بانتظام، اتجاهات سلسة
موزون زمنياً 'time' فترات زمنية غير منتظمة
موزون بالفهرس 'index' تباعد فهرسي غير منتظم
تربيعي 'quadratic' اتجاهات منحنية
تكعيبي 'cubic' منحنيات سلسة
أقرب جار 'nearest' بيانات منفصلة / متدرجة
سبلاين 'spline' استكمال سلس (يتطلب معامل الترتيب)

▶ مثال

TEXT 📖 للعرض فقط
> **المخرجات:** قم بتشغيل هذا في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبتة مسبقاً — قم بتثبيتها محلياً (`pip install pandas`) لمتابعة الشرح. قد تتغير القيم الفعلي قليلاً حسب إصدار pandas لديك.

: مقارنة طرق الاستكمال (الصعوبة ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

s = pd.Series([0.0, np.nan, np.nan, np.nan, np.nan, 5.0, 10.0])

print("linear:    ", s.interpolate(method='linear').tolist())
# [0.0, ~1.67, ~3.33, 5.0, ~6.67, 5.0, 10.0]
# لكن هذا يتضمن حدود NaN.

print("linear (trim):", s.dropna().interpolate(method='linear').tolist())
# يوضح الاستكمال الخطي فقط.

# التربيعي يحتاج قيمة واحدة غير NaN على كل جانب من الحدود؛ هنا نستخدم 7 نقاط بيانات
print("quadratic:", s.interpolate(method='quadratic').tolist())
# يناسب منحنياً تربيعياً؛ يتطلب على الأقل 3 نقاط غير NaN
TEXT 📖 للعرض فقط
> **المخرجات:** قم بتشغيل هذا في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبتة مسبقاً — قم بتثبيتها محلياً (`pip install pandas`) لمتابعة الشرح. قد تتغير القيم الفعلي قليلاً حسب إصدار pandas لديك.

8. النسخ عند الكتابة والعمليات على القيم المفقودة

(1) الكتابة بأمان ضمن نظام النسخ عند الكتابة

▶ مثال

TEXT 📖 للعرض فقط
> **الناتج:** قم بتشغيل هذا في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبتًا مسبقًا — قم بتثبيته محليًا (`pip install pandas`) لاتباع المثال. قد تختلف القيم الفعلي قليلاً حسب إصدار pandas لديك.

: عمليات آمنة ضمن نظام النسخ عند الكتابة (الصعوبة ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

pd.options.mode.copy_on_write = True  # Pandas 3.x default

df = pd.DataFrame({
    'name': ['Alice', 'Bob', 'Charlie'],
    'age': [28, np.nan, 25],
    'salary': [75000, 92000, np.nan]
})

# ✅ Safe: assign result back
df['age'] = df['age'].fillna(df['age'].mean())

# ✅ Safe: use inplace (works with CoW)
df.fillna({'salary': 0}, inplace=True)

# ❌ Dangerous (pre-CoW): chained assignment
# subset = df[df['age'].notnull()]
# subset['salary'] = 100  # May not modify df!

# ✅ Safe with CoW: loc
df.loc[df['age'].notnull(), 'salary'] = df.loc[df['age'].notnull(), 'salary'] * 1.1
print(df)
TEXT 📖 للعرض فقط
> **الناتج:** قم بتشغيل هذا في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبتًا مسبقًا — قم بتثبيته محليًا (`pip install pandas`) لاتباع المثال. قد تختلف القيم الفعلي قليلاً حسب إصدار pandas لديك.

9. مثال شامل: سير العمل الكامل للتعامل مع القيم المفقودة

(5) ▶ شجرة اتخاذ القرار للتعامل مع القيم المفقودة

100%
graph TB
    A[Detect missing values] --> B{Missing rate?}
    B -->|<5%| C[Drop with dropna]
    B -->|5-30%| D{Data type?}
    D -->|Numeric| E[fillna with mean/median]
    D -->|Categorical| F[fillna with mode]
    D -->|Time-series| G[interpolate]
    B -->|>50%| H[Consider dropping the column]
TEXT 📖 للعرض فقط
> **الخرج:** قم بتشغيل هذا في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبّتة مسبقًا — قم بتثبيتها محليًا (`pip install pandas`) للمتابعة. قد تختلف القيم الفعليّة قليلًا حسب إصدار pandas لديك.

▶ مثال

TEXT 📖 للعرض فقط
> **الخرج:** قم بتشغيل هذا في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبّتة مسبقًا — قم بتثبيتها محليًا (`pip install pandas`) للمتابعة. قد تختلف القيم الفعليّة قليلًا حسب إصدار pandas لديك.

: سير عمل تنظيف القيم المفقودة الكامل (الصعوبة ⭐⭐⭐)

PYTHON
import pandas as pd
import numpy as np

# ============================================
# Comprehensive example: Full missing-value
# handling pipeline
# ============================================

# 1. Load data with missing values
np.random.seed(42)
df = pd.DataFrame({
    'customer_id': range(1, 21),
    'name': [f'Customer_{i}' for i in range(1, 21)],
    'age': [25, np.nan, 35, 40, np.nan, 28, np.nan, 50, 33, np.nan,
            29, np.nan, 45, 38, np.nan, 27, np.nan, 42, 31, np.nan],
    'purchase_amount': [120, 85, np.nan, 200, 150, np.nan, 90, 310,
                        np.nan, 60, 175, np.nan, 250, 140, np.nan,
                        95, 180, np.nan, 110, 75],
    'category': ['Electronics', np.nan, 'Home', 'Electronics', 'Clothing',
                 'Home', np.nan, 'Electronics', 'Clothing', np.nan',
                 'Home', 'Electronics', 'Clothing', np.nan, 'Home',
                 'Electronics', np.nan, 'Home', 'Clothing', 'Electronics']
})

# 2. Analyze missing pattern
print("=== Missing Analysis ===")
missing_report = pd.DataFrame({
    'count': df.isnull().sum(),
    'pct': (df.isnull().sum() / len(df) * 100).round(1)
})
print(missing_report[missing_report['count'] > 0])

# 3. Drop rows where critical fields are missing
df = df.dropna(subset=['customer_id', 'name'])

# 4. Fill numeric columns
df['age'] = df['age'].fillna(df['age'].median())
df['purchase_amount'] = df['purchase_amount'].interpolate(method='linear')

# 5. Fill categorical column
df['category'] = df['category'].fillna(df['category'].mode()[0])

# 6. Verify: no more missing
print(f"\n=== After Cleaning ===")
print(f"Remaining missing: {df.isnull().sum().sum()}")
print(df.head(10))
TEXT 📖 للعرض فقط
> **الخرج:** قم بتشغيل هذا في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبّتة مسبقًا — قم بتثبيتها محليًا (`pip install pandas`) للمتابعة. قد تختلف القيم الفعليّة قليلًا حسب إصدار pandas لديك.

❓ أسئلة شائعة

س ما الفرق بين NaN و None؟
ج NaN (np.nan) هي القيمة المفقودة في الأرقام العشرية حسب معيار IEEE 754، و NaN ≠ NaN. بينما None هي القيمة المفقودة على مستوى كائنات بايثون، و None == None. في Pandas، يتم كشف كليهما بواسطة isnull(). عندما يحتوي عمود من عدد صحيح على NaN، يتم تحويله تلقائياً إلى float64؛ باستخدام النوع Int64 القابل لل-فارغ (بحرف I كبير) يتم الحفاظ على نوع عدد صحيح، مع تمثيل القيم المفقودة بواسطة pd.NA.
س كيف أختار بين dropna و fillna؟
ج إذا كان أقل من 5% من البيانات مفقودة وغير مهمة → استخدم dropna للحذف. إذا كان بين 5-30% مفقوداً ويوجد قيمة افتراضية معقولة → استخدم fillna (المتوسط/الوسيط/المنوال). للبيانات الزمنية → استخدم الاستكمال (interpolation). إذا كان أكثر من 50% مفقوداً → فكّر في حذف العمود. المبدأ الرئيسي: الحذف يفقد المعلومات، والحشو يُدخل انحيازاً، والاستكمال يفترض وجود اتجاه.
س لماذا يسبب fillna(طريقة='ffill') خطأً؟
ج في Pandas 2.x تم إهمال المعامل طريقة الخاص بـ fillna. استخدم df.ffill() (تعبئة للأمام) و df.bfill() (تعبئة للخلف) بدلاً من ذلك. ffill مناسب للبيانات الزمنية (باستخدام آخر قيمة صالحة)، لكنه غير مناسب للبيانات المرتبة عشوائياً (يمكن أن ينشر قيماً خاطئة لمسافات بعيدة).
س أي طريقة استكمال هي الأفضل؟
ج الافتراضي (linear) هو الأكثر تنوعاً. للسلاسل الزمنية، استخدم طريقة='time' (موزون بمسافة الوقت). للبيانات ذات الاتجاه الواضح، استخدم quadratic/cubic. للبيانات المتغيرة بشكل متقطع، استخدم nearest. spline جيد للتنعيم لكنه يتطلب ضبط معامل الدرجة. ابدأ مع linear، وقم بالتبديل فقط إذا لم تكن النتائج جيدة بما فيه الكفاية.
س هل fillna(inplace=True) لا يزال آمناً؟
ج تحت وضع النسخ عند الكتابة (Copy-on-Write) في Pandas 3.x، خيار inplace لا يزال آمناً — حيث يقوم بتعديل DataFrame الأصلي مباشرة. لكن الأسلوب الأكثر توصيةً هو إعادة التعيين لمتغير باستخدام df = df.fillna(...), وهو أكثر وضوحاً دلالياً. يضمن وضع CoW أن كلا النهجين ينتجان نفس النتيجة.
س كيف أتحقق مما إذا كان عمود يحتوي على NaN؟
ج الأسرع: df['col'].isnull().any() تعيد True/False. للعد: df['col'].isnull().sum() تعيد عدد القيم المفقودة. للنسبة المئوية: df['col'].isnull().mean() * 100. للتحقق من الجدول كاملاً: df.isnull().any().any() تخبرك ما إذا كان DataFrame يحتوي على أي NaN على الإطلاق.
س لماذا يتحول العمود من عدد صحيح إلى float بمجرد ظهور NaN؟
ج النوع int64 في NumPy لا يمكنه تمثيل NaN (كل بت يمثل رقماً صالحاً). ليس أمام Pandas خيار سوى تحويل العمود كاملاً إلى float64 لاستيعاب np.nan. الحل: استخدم النوع القابل لل-فارغ df['col'].astype('Int64') (بحرف I كبير), والذي يمثل القيم المفقودة بـ pd.NA مع الحفاظ على دلالات العدد الصحيح.

📖 ملخص


📝 تمارين

  1. أساسي (صعوبة ⭐): قم بإنشاء DataFrame يحتوي على NaN. استخدم isnull().sum() لعد القيم المفقودة في كل عمود، ثم احذف الصفوف المفقودة باستخدام dropna()، وقارن التغيير في عدد الصفوف.
  2. متوسط (صعوبة ⭐⭐): قم بإنشاء DataFrame سلاسل زمنية (10 صفوف مع 3 قيم NaN). قم بملئها باستخدام ffill() / bfill() / interpolate(طريقة='linear') على التوالي، وقارن الاختلافات بين النتائج الثلاثة.
  3. تحدي (صعوبة ⭐⭐⭐): قم بمحاكاة بيانات 50 عميل (العمر/الراتب/الفئة كل منها مفقود بنسبة 15%) وأنجز سير عمل التنظيف الكامل: تحليل القيم المفقودة → dropna(subset=[أعمدة رئيسية]) → ملء العمر بالمتوسط → الاستيفاء للراتب → ملء الفئة بالوسيط → التحقق من عدم وجود قيم مفقودة.

← Previous: Data I/O · Next: Handling Duplicate Data →

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%