بايثونتحليل البياناتعلم البيانات

كيفية طرح عمودين في إطار بيانات بانداس

دليل أكاديمي شامل يشرح كيفية طرح عمودين في إطار بيانات بانداس (Pandas DataFrame) باستخدام العمليات المتجهة، والتعامل مع القيم المفقودة وتحسين الأداء الحسابي.

تاريخ النشر

تُعد مكتبة بانداس (Pandas) الركيزة الأساسية لتحليل ومعالجة البيانات المهيكلة في لغة بايثون (Python)، حيث توفر هياكل بيانات متقدمة تجمع بين مرونة لغات البرمجة عالية المستوى وكفاءة العمليات الحسابية منخفضة المستوى المكتوبة بلغة C. ومن بين العمليات الجبرية الأساسية التي يحتاجها كل مهندس بيانات ومحلل إحصائي تبرز عملية طرح عمودين داخل إطار البيانات (DataFrame). ورغم بساطة المفهوم الرياضي للطرح، إلا أن تطبيقه البرمجي في بيئات الإنتاج والبيانات الضخمة ينطوي على تعقيدات تقنية ترتبط بمحاذاة الفهارس، وإدارة الذاكرة المؤقتة، وتوافق الأنواع البيانية، وتفادي الأخطاء الصامتة التي قد تؤثر في النتائج الإحصائية النهائية.

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

سواء كنت تقوم ببناء خطوط معالجة متقدمة لتعلم الآلة، أو تحسب مؤشرات مالية معقدة، أو تجري تحليلات مقارنة للسلاسل الزمنية، فإن الفهم العميق للآليات الداخلية لمكتبة بانداس أثناء إجراء العمليات الحسابية على الأعمدة يمنحك القدرة على كتابة برمجيات فائقة السرعة، خالية من التحذيرات الشائعة، ومحصنة ضد التشوهات البيانية غير المقصودة.

1. مقدمة نظرية حول العمليات الحسابية المتجهة في مكتبة بانداس

1.1 مفهوم العمليات الموجهة (Vectorized Operations) وأهميتها

تقوم المعالجة الموجهة (Vectorization) في مكتبة بانداس على تنفيذ العمليات الحسابية والمنطقية على مصفوفات كاملة من البيانات دفعة واحدة، دون الحاجة إلى كتابة حلقات تكرارية صريحة (Explicit Loops) بلغة بايثون مثل حلقات for أو while. يتيح هذا النمط المعماري نقل عبء المعالجة من مفسر بايثون الديناميكي بطيء التنفيذ إلى روتين مجمع ومكتوب بلغة C أو Fortran عالي الكفاءة، وهو ما يحقق قفزات هائلة في سرعة المعالجة الحسابية.

تعتمد مكتبة بانداس بشكل مباشر على مكتبة نامباي (NumPy) كطبقة خلفية لتخزين البيانات في مصفوفات متصلة متجاورة في الذاكرة العشوائية (Contiguous Memory Buffers). هذا التجاور في الذاكرة يسمح للمعالجات الحديثة بتطبيق تعليمات متقدمة مثل التعليمات المتعددة للبيانات المفردة (SIMD – Single Instruction, Multiple Data)، حيث يمكن لسجل المعالج الواحد تنفيذ عملية الطرح على عدة أعداد عشرية أو صحيحة في نبضة ساعة واحدة، مما يقلل زمن التنفيذ بنسبة تتجاوز أحياناً مئات الأضعاف مقارنة بالحلقات التقليدية.

إلى جانب السرعة، تساهم العمليات المتجهة في خفض استهلاك الذاكرة الإجمالي والحد من ظاهرة التشتت الذاكري (Memory Fragmentation). فعند تطبيق عملية الطرح المتجه، يتم تخصيص مصفوفة ناتجة بحجم محدد سلفاً، مما يلغي التكاليف الإدارية الإضافية (Overhead) الخاصة بإنشاء كائنات بايثون المستقلة لكل عنصر من عناصر العمود، وهو أمر بالغ الأهمية عند التعامل مع البيانات الضخمة التي تتجاوز سعتها الجيجابايت.

1.2 الهيكل البنائي لكائنات Series وعلاقتها بالأعمدة

يتشكل إطار البيانات (DataFrame) في بانداس أساساً من مجموعة من الأعمدة المتراصة، حيث يمثل كل عمود كائناً أحادي البعد يعرف بالسلسلة (Series). تتميز السلسلة باحتوائها على محورين رئيسيين: مصفوفة البيانات المتجانسة ومصفوفة التسميات التوضيحية التي تسمى الفهرس (Index). وتفرض هذه البنية تجانساً نوعياً صارماً داخل العمود الواحد، بحيث تشترك جميع عناصره في نفس النوع البياني المخزن، مما يعزز موثوقية العمليات الحسابية التراكمية.

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

من الناحية الرياضية، تخضع عملية طرح عمودين لنفس خصائص الجبر الخطي للمصفوفات؛ فهي عملية تبديلية معكوسة الإشارة، وغير تجميعية، وتتطلب تطابقاً بعدياً ونوعياً لإنتاج فضاء شعاعي متسق. يوفر فهم هذه الطبيعة الهيكلية الأساس الضروري لتجنب المفاجآت البرمجية عند دمج سلاسل ذات فهارس متباينة أو محتوية على تسميات غير فريدة.

2. الصيغة الأساسية لطرح عمودين باستخدام المعامل الحسابي المباشر (-)

2.1 طريقة الأقواس المعقوفة (Bracket Notation)

تُعد طريقة الأقواس المعقوفة الصيغة القياسية والأكثر أماناً لإجراء العمليات الحسابية بين الأعمدة في بانداس. تأخذ الصياغة النموذجية الشكل البرمجي المباشر: df['C'] = df['A'] - df['B']. في هذا التعبير، يتم استدعاء العمودين المسجلين تحت المفتاحين ‘A’ و ‘B’، وتطبيق معامل الطرح الحسابي بين مصفوفتيهما المتجهتين، ثم إسناد النتيجة إلى عمود جديد تماماً يحمل الاسم ‘C’ ضمن إطار البيانات الأصلي.

تتميز هذه الطريقة بمرونتها وقدرتها على استيعاب أسماء الأعمدة المعقدة، مثل الأسماء التي تحتوي على مسافات بيضاء، أو رموز خاصة، أو التي تبدأ بأرقام، أو تطابق كلمات محجوزة في لغة بايثون. على سبيل المثال، التعبير df['صافي الربح'] = df['إجمالي الإيراد'] - df['تكلفة الإنتاج'] لا يمكن تنفيذه بسلاسة إلا عبر استخدام الأقواس المعقوفة.

علاوة على ذلك، تضمن هذه الصياغة التحقق التلقائي من توافق الأبعاد ومحاذاة الفهارس قبل توليد مصفوفة الناتج. إذا كان العمود الناتج غير موجود مسبقاً، ستقوم بانداس بإنشائه ديناميكياً وإضافته إلى نهاية هيكل الأعمدة، بينما إذا كان الاسم موجوداً بالفعل، فسيتم استبدال قيمه القديمة بالقيم الناتجة عن عملية الطرح بطريقة آمنة وواضحة برمجياً.

2.2 طريقة النقطة (Dot Notation) وحدود استخدامها

توفر بايثون أسلوباً بديلاً للوصول إلى الأعمدة كخصائص لكائن إطار البيانات باستخدام النقطة، مثل: df['C'] = df.A - df.B. تبدو هذه الطريقة جذابة للوهلة الأولى نظراً لإيجازها وسهولة كتابتها، إلا أنها تنطوي على محددات هيكلية خطيرة ومخاطر برمجية تستوجب الحذر الشديد عند الاعتماد عليها في البرمجيات الاحترافية.

تكمن المشكلة الأساسية في تعارض أسماء الأعمدة مع التوابع والخصائص المدمجة في كائن DataFrame. فإذا كان العمود يحمل اسماً مثل ‘count’ أو ‘min’ أو ‘shape’ أو ‘index’، فإن استخدام df.count سيستدعي الدالة البرمجية المدمجة لحساب عدد العناصر بدلاً من استرجاع بيانات العمود، مما يتسبب في أخطاء فادحة من نوع TypeError عند محاولة تطبيق معامل الطرح الحسابي.

بالإضافة إلى ذلك، لا يمكن استخدام طريقة النقطة لإنشاء عمود جديد؛ حيث إن محاولة تنفيذ df.C = df.A - df.B ستؤدي في الغالب إلى إنشاء خاصية جديدة لكائن بايثون دون تسجيلها كعمود رسمي داخل جدول البيانات، مما يمنع إمكانية الوصول إليها لاحقاً عبر الفهارس القياسية. لذلك، تقتضي أفضل ممارسات الكود النظيف (Clean Code) حصر استخدام النقطة في القراءة السريعة للأعمدة ذات الأسماء البسيطة والاعتماد الكلي على الأقواس المعقوفة عند التعديل والإنشاء.

2.3 إنشاء عمود الناتج وتحديث إطار البيانات

عند تنفيذ عملية الطرح المباشر وتعيين الناتج لعمود جديد، تقوم مكتبة بانداس بتحديث مصفوفة الكتلة الداخلية (BlockManager) المسؤولة عن تنظيم الذاكرة وتخزين الأعمدة المتجانسة. تتم هذه العملية عبر إسناد مصفوفة نامباي الناتجة إلى المفتاح المحدد، مما يؤدي إلى توسيع أبعاد إطار البيانات وإضافة مدخل جديد في قائمة الأعمدة df.columns.

يجب التمييز هنا بين التعيين المباشر في نفس الإطار (In-place Mutation) وبين إنشاء نسخة جديدة كلياً من إطار البيانات. فالتعيين المباشر عبر df['Diff'] = df['A'] - df['B'] يعد فعالاً من حيث استخدام الذاكرة، ولكنه قد يؤدي في سياقات معينة إلى ظهور التحذير الشهير SettingWithCopyWarning إذا كان إطار البيانات الأصلي مجرد شريحة أو رؤية مأخوذة من إطار بيانات آخر.

للتحقق من سلامة العملية والتأكد من دقة النتائج الإسنادية، يُنصح دائماً بفحص الصفوف الأولى من الجدول المحدث باستخدام التابع df.head()، والتحقق من الأنواع البيانية للعمود الجديد عبر خاصية df.dtypes لضمان عدم حدوث تشوهات نوعية غير متوقعة أثناء الإسناد.

3. استخدام الدالة البرمجية subtract() ومقارنتها بالمعامل الحسابي

3.1 بنية الدالة DataFrame.subtract() والوسائط المتاحة

توفر مكتبة بانداس توابع برمجية صريحة للعمليات الحسابية، يأتي في مقدمتها التابع DataFrame.subtract() واختصاره الشائع DataFrame.sub(). يتيح هذا التابع تنفيذ عمليات الطرح الجبري بين السلاسل وأطر البيانات مع توفير مستوى متقدم من التحكم عبر وسائط المعالجة المخصصة التي لا تتوفر في المعامل الحسابي الرمزي المباشر.

تتضمن البنية النموذجية للدالة وسيط المحور axis، والذي يحدد اتجاه سريان العملية الرياضية؛ حيث تأخذ القيمة axis=0 أو axis='index' لإجراء الطرح عمودياً بالتوازي مع الفهرس، بينما تُستخدم القيمة axis=1 أو axis='columns' لإجراء الطرح أفقياً عبر الصفوف. هذا التمايز يمنح المبرمج قدرة فائقة على طرح سلسلة بيانات أحادية من جميع أعمدة إطار البيانات المتعددة دفعة واحدة وبسطر برمجي واحد.

أما الوسيط الأبرز فهو fill_value، والذي يسمح بتحديد قيمة افتراضية يتم استخدامها لاستبدال القيم المفقودة (NaN) في أحد الطرفين قبل إجراء عملية الطرح، مما يمنع تفشي القيم الفارغة في الناتج النهائي ويوفر معالجة استباقية متكاملة داخل نفس الأمر التنفيذي.

3.2 المقارنة بين المعامل المباشر (-) ودالة sub()

تتمحور المفاضلة الأساسية بين استخدام المعامل الرمزي - والتابع sub() حول معايير المقروئية، والقدرة على التحكم، وبناء خطوط معالجة البيانات المترابطة (Method Chaining). فرغم أن المعامل الرمزي يتميز بالبساطة والإيجاز والوضوح البصري في التعبيرات الرياضية المباشرة، إلا أنه يفتقر إلى المرونة عند الحاجة لمعالجة القيم المفقودة لحظياً.

تتفوق دالة sub() بشكل كاسح في بيئات البرمجة الوظيفية، حيث يمكن إدراجها بسلاسة ضمن سلاسل المعالجة التتابعية دون الحاجة لإنشاء متغيرات وسيطة، مثل: df.assign(Diff=df['A'].sub(df['B'], fill_value=0)).query('Diff > 10'). يسهم هذا النمط في رفع جودة الشيفرة البرمجية، وتحسين قابليتها للاختبار، وتسهيل تتبع تدفق البيانات في المشاريع المعقدة.

من زاوية الأداء الحسابي والسرعة، لا يوجد فرق جوهري بين الطريقتين عند غياب القيم المفقودة، إذ تنتهي كلاهما إلى استدعاء نفس الروتين الحسابي في طبقة C الخلفية. لكن عند التعامل مع مجموعات بيانات ضخمة تحتوي على فراغات، توفر دالة sub(fill_value=0) أداءً أسرع بكثير من استخدام دمج fillna() مع المعامل الرمزي لأنها تجري التعويض الحسابي في دورة معالجة واحدة دون إنشاء مصفوفات وسيطة في الذاكرة.

4. التعامل مع القيم المفقودة (NaN / None) أثناء طرح الأعمدة

4.1 سلوك المعامل الحسابي مع القيم الفارغة (NaN Propagation)

تتبع مكتبة بانداس المعيار الدولي للحسابات العشرية العائمة (IEEE 754)، والذي ينص على أن أي عملية حسابية يكون أحد أطرافها قيمة غير محددة (NaN – Not a Number) يجب أن تسفر حتماً عن قيمة NaN. تُعرف هذه الظاهرة بانتشار القيم المفقودة (NaN Propagation)، وهي السلوك الافتراضي لمعامل الطرح الحسابي المباشر -.

إذا كان العمود ‘A’ يحتوي على القيمة 50 والعمود ‘B’ يحتوي على np.nan في نفس موقع الفهرس، فإن ناتج عملية الطرح df['A'] - df['B'] سيكون NaN. قد يؤدي هذا السلوك التلقائي إلى تآكل البيانات وتشويه التحليلات الإحصائية اللاحقة، خاصة إذا كانت نسبة البيانات المفقودة مرتفعة، حيث ستتحول المجاميع التراكمية والمتوسطات الحسابية إلى قيم مفقودة أو غير دقيقة ما لم يتم التعامل معها بحذر.

لتشخيص وتقييم حجم المشكلة، يُفضل فحص عمود الناتج بشكل منهجي باستخدام df['Diff'].isnull().sum() لمعرفة إجمالي عدد السجلات التالفة، وحساب نسبتها المئوية من إجمالي البيانات لتحديد ما إذا كان الفقد عشوائياً تماماً (MCAR) أم يخضع لنمط منهجي يتطلب تدخلاً إحصائياً دقيقاً في مرحلة المعالجة القبلية.

4.2 معالجة القيم المفقودة مسبقاً باستخدام fillna()

تتمثل إحدى الاستراتيجيات الكلاسيكية لتفادي تفشي القيم الفارغة في تطهير الأعمدة مسبقاً باستخدام الدالة Series.fillna() قبل تطبيق معامل الطرح. تتيح هذه الطريقة تعويض الفراغات بقيم محددة وفق السياق المنطقي للبيانات، مثل استخدام الصفر لتمثيل غياب المبيعات أو النفقات: df['Diff'] = df['A'].fillna(0) - df['B'].fillna(0).

ومع ذلك، يجب التعامل مع دالة fillna() بحذر شديد؛ فالتعويض العشوائي بقيمة الصفر قد يؤدي إلى تحيزات إحصائية مضللة (Imputation Bias). فعلى سبيل المثال، إذا كان العمود يمثل درجات حرارة أو أسعار فائدة، فإن الصفر يحمل معنى فيزيائياً ومالياً حقيقياً وليس مجرد مؤشر على غياب التسجيل، وفي مثل هذه الحالات قد يكون التعويض باستخدام مقاييس النزعة المركزية كالوسيط (Median) أو المتوسط (Mean) أو استخدام الاستيفاء الخطي (Linear Interpolation) أكثر دقة وملاءمة.

في الحالات التي تتطلب تحليلاً صارماً لا يقبل التقدير، يكون الخيار الأفضل هو التخلص من الصفوف غير المكتملة كلياً عبر تطبيق الدالة dropna() على العمودين المستهدفين قبل الشروع في الطرح، لضمان أن كل فارق حسابي مبني على بيانات فعلية ومؤكدة.

4.3 استخدام وسيط fill_value داخل الدالة .sub()

يوفر التابع df['A'].sub(df['B'], fill_value=0) حلاً بيانياً مثالياً وأكثر كفاءة لمعالجة القيم المفقودة أثناء الطرح. تكمن القوة الهيكلية لهذا الوسيط في أنه لا يقوم باستبدال القيم المفقودة في السلاسل الأصلية، بل يطبق قيمة التعويض فقط عند وجود قيمة مفقودة في أحد العمودين يقابلها قيمة حقيقية في العمود الآخر.

النقطة الهامة التي يغفل عنها كثير من المبرمجين هي أن fill_value يعوض القيمة المفقودة إذا كانت موجودة في أحد الطرفين فقط؛ أما إذا كان كلا الموقعين المتناظرين في العمودين ‘A’ و ‘B’ يحتويان على NaN، فإن الناتج النهائي سيظل NaN. هذا السلوك الإحصائي المتزن يحافظ على نقاء البيانات ويمنع توليد نتائج صفرية زائفة من مدخلات غير موجودة أصلاً في كلا المصدرين.

تتفوق هذه المقاربة المضمنة على المعالجة المسبقة بواسطة fillna في المحافظة على سلامة الذاكرة وسرعة المعالجة، وتجنب تعقيد الأكواد البرمجية البرمجية، وتوفير مرونة فائقة عند بناء خطوط نقل البيانات التلقائية (ETL Pipelines).

5. محاذاة الفهارس (Index Alignment) والبث الحسابي (Broadcasting)

5.1 ميكانيكية المحاذاة التلقائية للفهارس في بانداس

تعتمد مكتبة بانداس مفهوماً محورياً يميزها عن مكتبة نامباي المجردة، وهو المحاذاة التلقائية للفهارس (Automatic Index Alignment). عند إجراء أي عملية جبرية بين كائنين من كائنات Series، تقوم بانداس بدمج الفهارس عبر عملية مطابقة اتحادية (Outer Join) بناءً على التسميات (Labels)، وتجري عملية الطرح فقط بين القيم التي تتشارك نفس تسمية الفهرس تماماً.

يترتب على هذا السلوك نتائج خطيرة يجب الانتباه إليها؛ فإذا قمت بطرح عمودين مستخرجين من أطر بيانات مختلفة، أو عمودين تعرض أحدهما للتصفية (Filtering) أو إعادة الترتيب، فإن الطرح لن يتم بناءً على الموقع النسبي للصف (Row Position)، بل بناءً على الفهرس. وإذا كانت هناك تسميات موجودة في عمود وغير موجودة في الآخر، فسيقوم بانداس بإدراج الفهرس في الناتج وتعيين القيمة NaN له تلقائياً.

لتجاوز المحاذاة التلقائية وإجراء الطرح بناءً على الترتيب الفيزيائي الموضعي للصفوف بصرف النظر عن تسميات الفهارس، يجب إعادة ضبط الفهرس صراحة باستخدام التابع reset_index(drop=True) قبل تنفيذ العملية، أو تحويل الأعمدة إلى مصفوفات نامباي غير المفهرسة عبر الخاصية .values أو الدالة .to_numpy().

5.2 مفهوم البث الحسابي (Broadcasting) في طرح الأعمدة

يشير مصطلح البث الحسابي (Broadcasting)، الموروث من مكتبة نامباي، إلى القواعد الصارمة التي تتبعها بانداس لتنفيذ العمليات الحسابية بين كائنات ذات أبعاد أو أشكال هندسية متباينة. يتيح البث إمكانية طرح قيمة قياسية وحيدة (Scalar) من عمود بأكمله، أو طرح عمود أحادي البعد من مصفوفة متعددة الأعمدة بشكل متزامن وبكفاءة فائقة.

عند تنفيذ عملية مثل df['A'] - 100، يقوم محرك الحساب بتوسيع القيمة القياسية (100) افتراضياً لتمتد على طول العمود ‘A’ بالكامل دون تكرار تخزينها في الذاكرة، ثم يجري الطرح المتجه بسرعة المعالج القصوى. وبالمثل، يمكن طرح عمود يمثل المتوسط الحسابي من جميع الأعمدة الرقمية في إطار البيانات دفعة واحدة عبر استخدام df.sub(df.mean(axis=0), axis=1).

تضمن قواعد البث تجنب أخطاء عدم تطابق الأبعاد (Shape Mismatch Errors)، وتتيح اختصار الأكواد الحسابية المعقدة إلى تعبيرات مقتضبة، بشرط مراعاة توافق المحاور وأبعاد المصفوفات المتقاطعة لتفادي إسقاط النتائج في اتجاهات خاطئة.

6. طرح الأعمدة الرقمية ذات الأنواع البيانية المتباينة (Data Types & Type Casting)

6.1 التفاعل بين الأعداد الصحيحة (Integers) والأعداد العشرية (Floats)

عند تطبيق عملية الطرح بين عمود من نوع الأعداد الصحيحة (مثل int64 أو int32) وعمود من نوع الأعداد العشرية ذات الفاصلة العائمة (مثل float64)، تقوم بانداس تلقائياً بإجراء ما يُعرف بالترقية النوعية القسرية (Type Coercion / Upcasting)، حيث يتم تحويل نوع العمود الناتج بالكامل إلى float64 لمنع فقدان الدقة الكسرية.

كما يظهر هذا التحول التلقائي نحو الأعداد العشرية عند وجود أي قيمة مفقودة NaN في عمود الناتج، حتى لو كانت جميع القيم المدخلة في كلا العمودين الأصليين أعداداً صحيحة خالصة؛ وذلك لأن القيمة القياسية np.nan في محرك نامباي تُعرف هندسياً كقيمة عائمة. ولمعالجة هذا القيد، قدمت الإصدارات الحديثة من بانداس أنواع بيانات صحيحة قابلة لامتلاك قيم فارغة (Nullable Integer Data Types) مثل Int64 (مع حرف I كبير)، والتي تسمح بالحفاظ على النوع الصحيح للعمود حتى مع وجود قيم مفقودة pd.NA.

لذا، يجب على المبرمج مراقبة مصفوفة الأنواع df.dtypes بانتظام لضمان توافق الأنواع الناتجة مع المتطلبات البرمجية اللاحقة وتفادي الارتفاع غير المبرر في استهلاك الذاكرة نتيجة التحويلات التلقائية غير المرغوبة.

6.2 التحويل الصريح للأنواع (Explicit Type Casting) قبل الطرح

في كثير من التطبيقات العملية، وخاصة عند استيراد البيانات من ملفات CSV أو قواعد بيانات غير منضبطة، قد يتم تخزين الأرقام كنصوص برمجية (Strings / Objects). وفي هذه الحالة، ستفشل محاولة الطرح المباشر وتطلق بايثون استثناء من نوع TypeError: unsupported operand type(s) for -: 'str' and 'str'.

لحل هذه المشكلة، يجب تطبيق التحويل النوعي الصريح باستخدام الدالة المتقدمة pandas.to_numeric(). تتيح هذه الدالة وسيطاً بالغ الأهمية هو errors='coerce'، والذي يقوم بتحويل القيم الرقمية بنجاح مع إجبار النصوص غير الصالحة أو الرموز التالفة على التحول إلى NaN تلقائياً دون إيقاف تنفيذ البرنامج: df['A'] = pd.to_numeric(df['A'], errors='coerce').

بعد تنظيف وتوحيد الأنواع، يمكن استخدام التابع astype() لتقليص دقة التخزين إذا لزم الأمر (مثل التحويل من float64 إلى float32) لتوفير الذاكرة، مع ضرورة الانتباه إلى مشكلة عدم دقة الفاصلة العائمة (Floating Point Inaccuracy) التي قد تنشأ عن تقريب الكسور الثنائية الدقيقة في الحسابات المالية الحساسة.

7. طرح أعمدة التواريخ والأوقات (Datetime and Timedelta Arithmetic)

7.1 حساب الفروق الزمنية بين عمودي تواريخ

يعد التعامل مع التواريخ والأوقات أحد أقوى مجالات مكتبة بانداس، حيث توفر دعماً مدمجاً للحسابات الزمنية الموجهة. لإجراء عملية طرح سليمة بين عمودي تواريخ، يجب التأكد أولاً من تحويلهما إلى النوع الزمني المتخصص datetime64[ns] عبر استخدام الدالة الموثوقة pandas.to_datetime().

عند طرح عمود تاريخ البداية من عمود تاريخ النهاية عبر التعبير df['Duration'] = df['End_Date'] - df['Start_Date']، لا ينتج عن العملية أرقام عادية، بل يتولد عمود جديد من نوع الفارق الزمني المتخصص (Timedelta). يمثل هذا الكائن مدة زمنية محددة بدقة النانو ثانية، ويحتفظ بالقدرة على التعبير عن الأيام والساعات والدقائق والثواني بشكل متناسق.

كما تتولى بانداس معالجة فروق التوقيت والمناطق الزمنية (Timezones) تلقائياً أثناء الطرح، شريطة أن تكون الأعمدة متوافقة؛ فإذا كانت التواريخ تحتوي على مناطق زمنية محددة (Timezone-Aware)، يجب أن تكون منتمية لنفس المنطقة أو يتم تحويلها مسبقاً إلى التوقيت العالمي المنسق (UTC) عبر dt.tz_convert('UTC') لتفادي أخطاء المقارنة الزمنية المتباينة.

7.2 تحويل الفروق الزمنية إلى وحدات عددية مخصصة

نادراً ما يكون كائن Timedelta بصيغته المركبة هو المخرج النهائي المطلوب للتحليلات الإحصائية ونماذج تعلم الآلة؛ إذ يحتاج المحلل عادة إلى تحويل هذه المدة إلى قيم رقمية قياسية تمثل الأيام أو الساعات أو السنوات. توفر بانداس الموصّل البرمجي .dt للوصول إلى الخصائص الزمنية للسلسلة بدقة وسلاسة.

للحصول على عدد الأيام كأرقام صحيحة، يمكن استدعاء الخاصية df['Duration'].dt.days. أما إذا كان المطلوب حساب الفارق بدقة كسرية عائمة (مثل حساب الساعات المنقضية بدقة أجزاء الساعة)، فإن أفضل ممارسة برمجية تتمثل في قسمة عمود Timedelta على وحدة نامباي الزمنية المستهدفة، مثل: df['Duration'] / np.timedelta64(1, 'h') لحساب الساعات، أو np.timedelta64(1, 'D') لحساب الأيام الكسرية.

بالإضافة إلى ذلك، توفر الدالة df['Duration'].dt.total_seconds() حساباً دقيقاً لإجمالي الثواني المنقضية كأرقام عائمة، وهو ما يمثل معياراً أساسياً في هندسة الميزات الزمنية، وتحليل سجلات الخوادم، وقياس معدلات الأداء الحاسوبية.

8. طرح الأعمدة الشرطي والمتقدم باستخدام أدوات متقدمة

8.1 استخدام الدالة np.where() لتطبيق طرح مشروط

تتطلب السيناريوهات التحليلية في كثير من الأحيان تطبيق عملية الطرح بناءً على تحقق شروط منطقية مسبقة، كأن يتم خصم مصاريف الشحن فقط للعملاء غير المشتركين في العضوية المميزة، أو طرح الضرائب فقط عند تجاوز الدخل حداً معيناً. تُعد دالة numpy.where() الأداة المتجهة المثلى لتنفيذ هذا الطرح المشروط بأعلى كفاءة ممكنة.

تأخذ الدالة الصياغة الثلاثية المتجهة: np.where(condition, x_if_true, y_if_false). فعلى سبيل المثال، يمكن صياغة الكود التالي: df['Net'] = np.where(df['Status'] == 'Active', df['Revenue'] - df['Cost'], df['Revenue']). يتميز هذا النهج بأنه يتجنب تماماً استخدام التكرارات البطيئة وعبارات if-else التقليدية في بايثون، ويقوم بتنفيذ الفحص والعملية الحسابية بالتوازي على مستوى مصفوفات C التحتية.

يمكن أيضاً دمج شروط بوليانية مركبة باستخدام المعاملات المنطقية المتجهة مثل المعامل & (AND) والمعامل | (OR) مع إحاطة كل شرط بأقواس دائرية، مما يمنح المبرمج القدرة على بناء منطق عمل معقد ومخصص بالكامل داخل عبارة حسابية واحدة فائقة السرعة.

8.2 تطبيق دالة assign() لإنشاء أعمدة مطروحة في تدفق برمجي موحد

تُعد الدالة DataFrame.assign() حجر الزاوية في تبني أسلوب البرمجة الوظيفية والأكواد المتسلسلة (Chained Pipelines) داخل بانداس. تتيح هذه الدالة إنشاء أعمدة جديدة ناتجة عن عمليات طرح مع إعادة كائن إطار بيانات جديد كلياً دون التعديل المباشر على الكائن الأصلي، مما يحقق مبدأ عدم قابلية التغيير (Immutability).

تُصاغ العملية عادة باستخدام دوال مجهولة الاسم (Lambda Functions) تتيح الإشارة إلى إطار البيانات المرحلي الناتج عن الخطوة البرمجية السابقة مباشرة، كالتالي: df_cleaned = (df.query('Sales > 0').assign(Profit=lambda x: x['Revenue'] - x['Expenses']).sort_values('Profit', ascending=False)).

يسهم هذا الأسلوب المعماري في كتابة برمجيات مقروءة للغاية، يسهل تتبع مدخلاتها ومخرجاتها في كل مرحلة من مراحل المعالجة، كما يلغي احتمالية الوقوع في أخطاء الآثار الجانبية (Side Effects) والتحذيرات الناتجة عن تعديل الشرائح المشتركة في الذاكرة.

8.3 استخدام دالة eval() للعمليات الحسابية فائقة السرعة

توفر مكتبة بانداس محرك تقييم متقدم مبني على مكتبة NumExpr من خلال الدالة DataFrame.eval(). تتيح هذه الأداة صياغة العمليات الحسابية المعقدة بين الأعمدة في صورة سلاسل نصية برمجية، مثل: df.eval('Diff = ColumnA - ColumnB', inplace=True).

يكمن السر وراء الكفاءة الاستثنائية لدالة eval() في قدرتها على تجنب تخصيص مصفوفات ذاكرة وسيطة للعمليات الحسابية المتعددة، حيث تقوم بتقسيم البيانات إلى أجزاء صغيرة تتسع داخل ذاكرة التخزين المؤقت للمعالج (CPU Cache L1/L2/L3)، مما يؤدي إلى تسريع العمليات بنسبة ملحوظة عند تطبيقها على أطر البيانات الضخمة التي تحتوي على مئات الآلاف أو الملايين من الصفوف.

ومع ذلك، يجب الانتباه إلى أن eval() تفرض بعض القيود الهيكلية؛ فهي تدعم مجموعة محددة من العمليات والأنواع البيانية القياسية، كما تتطلب الحذر الأمني عند التعامل مع مدخلات مستخدمين غير موثوقة لتفادي حقن الأكواد البرمجية الخبيثة عبر السلاسل النصية المنفذة.

9. طرح الأعمدة ضمن المجموعات والتجميعات (GroupBy Operations)

9.1 طرح قيم الأعمدة من متوسطات المجموعات (Demeaned Centering)

في التحليلات الإحصائية وتطبيقات القياس الاقتصادي، تبرز الحاجة المتكررة إلى طرح المتوسط الفئوي لكل مجموعة من القيم الفردية للعناصر التابعة لها، وهي العملية المعروفة إحصائياً بالتمركز حول المتوسط أو إزالة المتوسط (Demeaning / Centering). توفر بانداس تكاملاً قوياً بين عمليات التجميع GroupBy والتابع transform() لتحقيق ذلك.

تتم الصياغة البرمجية المباشرة لهذه العملية عبر التعبير: df['Centered_Val'] = df['A'] - df.groupby('Category')['A'].transform('mean'). يقوم التابع transform بحساب المتوسط الحسابي لكل فئة محددة، ثم يعيد بث وتوسيع هذه المتوسطات لتطابق أبعاد ومواقع الفهرس في إطار البيانات الأصلي بدقة، مما يتيح إجراء الطرح المتجه فوراً بين العمود الأصلي ومصفوفة المتوسطات المجمعة.

يعد هذا النمط الحسابي أساسياً في تنميط البيانات (Normalization)، وبناء نماذج التأثيرات الثابتة (Fixed Effects Models) في سلاسل البيانات الطولية (Panel Data)، وضمان التخلص من التباينات الفئوية العامة قبل تدريب خوارزميات تعلم الآلة.

9.2 طرح قيم الأعمدة التراكمية أو المتتابعة عبر الفئات

عند دراسة السلاسل الزمنية والبيانات المالية المقسمة حسب الفئات (مثل مبيعات المتاجر المختلفة عبر الأشهر)، يبرز متطلب حساب الفروق المتتالية والتغيرات الدورية داخل كل مجموعة بشكل مستقل. تجمع بانداس بين تجميع الفئات والتابع الزمني Series.diff() لإنجاز هذه المهمة الجبرية بكفاءة.

تُنفذ العملية عبر الشفرة: df['Period_Diff'] = df.groupby('Store_ID')['Sales'].diff(). يقوم هذا الأمر بطرح قيمة الصف السابق من الصف الحالي لكل متجر على حدة، مع إدراج القيمة NaN تلقائياً في السجل الأول لكل فئة لعدم وجود فترة زمنية سابقة تسبقه داخل نفس المجموعة الحسابية.

كما يمكن طرح قيمة فترة أساسية محددة (Baseline) من جميع الفترات اللاحقة لكل مجموعة من خلال دمج transform('first') مع عملية الطرح، مما يتيح متابعة معدلات النمو التراكمي وتطور الأداء النسبي عبر الزمن بمرونة إحصائية متناهية الدقة.

10. الأداء الحسابي وإدارة الذاكرة في مجموعات البيانات الضخمة (Big Data)

10.1 الاستهلاك الذاكري وعمليات النسخ مقابل العرض (Copy vs View)

عند التعامل مع مجموعات بيانات عملاقة تلامس حدود سعة الذاكرة العشوائية (RAM)، يصبح فهم الآليات الداخلية لإدارة الذاكرة في بانداس أمراً حاسماً لتفادي أخطاء نفاد الذاكرة (Out-Of-Memory Errors). تتأرجح العمليات الحسابية بين إنشاء مصفوفات بيانات جديدة بالكامل في الذاكرة (Copy) أو مجرد الإشارة إلى مقاطع محددة من المصفوفات الأصلية القائمة (View).

تؤدي عمليات الطرح الرياضي حتماً إلى إنشاء مصفوفة ذاكرة جديدة لتخزين نواتج الفروق المحسوبة. فإذا كان إطار البيانات يحتوي على 50 مليون صف من نوع float64، فإن إنشاء عمود ناتج جديد سيتطلب تخصيص حوالي 400 ميجابايت من الذاكرة الصافية دفعة واحدة. ومع تكرار العمليات الحسابية غير المحسوبة، قد يتضاعف حجم الإطار ويتدهور أداء النظام بسرعة كبيرة.

لتحسين استهلاك الذاكرة، يُنصح بتطبيق تقنية تقليص أحجام الأنواع (Downcasting) قبل إجراء الحساب؛ مثل تحويل الأعمدة من float64 إلى float32 أو من int64 إلى int16 طالما كانت القيم تقع ضمن المدى العددي المسموح، وهو ما يخفض الاستهلاك الذاكري للنصف أو الربع. كما يمكن استخدام أداة فحص الذاكرة memory_profiler لمراقبة وتقييم البصمة الذاكرية للعمليات خطوة بخطوة.

10.2 استخدام مكتبات NumPy و Numba لتسريع عمليات الطرح الحسابية

رغم كفاءة بانداس العالية، إلا أن طبقات التحقق الهيكلي من الفهارس والتسميات والأنواع البيانية تفرض عبئاً إدارياً إضافياً (Overhead) قد يحد من السرعة القصوى في البيئات الحسابية شديدة الحساسية للزمن. في مثل هذه الحالات، يمكن تجاوز طبقات بانداس والوصول المباشر إلى مصفوفات نامباي التحتية عبر التعبير: df['C'] = df['A'].to_numpy() - df['B'].to_numpy().

يحقق هذا الأسلوب سرعات معالجة فائقة من خلال إلغاء زمن مطابقة الفهارس، مما يجعله مثالياً عند التأكد التام من تطابق أبعاد وترتيب البيانات مسبقاً. وللعمليات الحسابية الأكثر تعقيداً والتي تتضمن حلقات تكرارية وشروطاً مركبة مع الطرح، يمكن اللجوء إلى مكتبة نومبا (Numba) والمترجم اللحظي (JIT – Just-In-Time Compiler).

تقوم نومبا بترجمة دوال بايثون المخصصة مباشرة إلى تعليمات لغة الآلة الثنائية (Machine Code) الموجهة للمعالج فائق السرعة، مما يتيح تنفيذ عمليات الطرح والتجميع الحسابي المخصص بسرعات تضاهي البرمجيات المكتوبة بلغة C الخالصة، مع الحفاظ على سهولة كتابة الكود بلغة بايثون.

11. الأخطاء الشائعة واستكشاف المشكلات وإصلاحها (Troubleshooting & Debugging)

11.1 تحليل أخطاء TypeError و ValueError الشائعة

أثناء تنفيذ عمليات الطرح بين الأعمدة، قد يواجه المبرمج مجموعة من الأخطاء البرمجية الصريحة. يأتي في مقدمتها الخطأ الشهير TypeError: unsupported operand type(s) for -، والذي يشير حتماً إلى محاولة إجراء الطرح بين أنواع بيانية غير متوافقة رياضياً، مثل طرح نص برمجي (String) من رقم عشري، أو محاولة طرح تواريخ غير محولة إلى كائنات زمنية موحدة.

أما الخطأ الشائع الآخر فهو ValueError: cannot reindex on an axis with duplicate labels، ويحدث عندما يحتوي أحد العمودين المطروحين على فهرس يضم تسميات مكررة وغير فريدة، مما يربك خوارزمية المحاذاة التلقائية في بانداس ويمنعها من تحديد كيفية مطابقة الصفوف المتناظرة بدقة رياضية لا لبس فيها. يتطلب حل هذا الخطأ إزالة التكرار من الفهرس عبر df[~df.index.duplicated()] أو إعادة ضبط الفهرس كلياً عبر reset_index().

لتجنب هذه الأخطاء بشكل استباقي، يُوصى دوماً بإجراء فحص تدقيقي للبيانات قبل العمليات الحسابية عبر استعراض ملخص الهيكل باستخدام df.info() والتحقق من تفرد الفهارس باستخدام df.index.is_unique لضمان استقرار البيئة التنفيذية للكود.

11.2 تجنب تحذير SettingWithCopyWarning أثناء تعيين عمود الناتج

يُعد تحذير SettingWithCopyWarning أحد أكثر التحذيرات إرباكاً للمطورين في مكتبة بانداس. يظهر هذا التحذير عندما يحاول المبرمج إنشاء عمود ناتج الطرح داخل إطار بيانات تم استخلاصه كشريحة أو تصفية جزئية من إطار بيانات رئيسي آخر، مثل: subset = df[df['A'] > 10]; subset['Diff'] = subset['A'] - subset['B'].

ينبع التحذير من عدم يقين محرك بانداس الداخلي حول ما إذا كان الكائن subset يمثل نسخة مستقلة في الذاكرة (Copy) أم مجرد نافذة عرض مرجعية للجدول الأصلي (View)، وبالتالي فإن تعديله قد يؤدي أو لا يؤدي إلى تعديل البيانات في الجدول الأساسي بشكل غير متوقع، وهو ما يهدد اتساق البيانات البرمجية.

لعلاج هذا التحذير جذرياً، يجب استخدام أحد حلين قياسيين: إما إنشاء نسخة صريحة ومعزولة تماماً في الذاكرة عند استقطاع الشريحة باستخدام التابع subset = df[df['A'] > 10].copy()، أو استخدام محدد الفهرسة الموضعي المباشر DataFrame.loc للكتابة في الكائن الأصلي بأمان: df.loc[df['A'] > 10, 'Diff'] = df['A'] - df['B'].

12. تطبيقات ودراسات حالة واقعية وأفضل الممارسات البرمجية

12.1 دراسة حالة مالية: حساب صافي الأرباح وهوامش التغير

تعتبر التطبيقات المحاسبية والمالية من أكثر المجالات التي تعتمد على عمليات طرح الأعمدة في بانداس. في هذه الدراسة التطبيقية، نفترض وجود جدول بيانات مالي ضخم يحتوي على سجلات الإيرادات الإجمالية (Gross Revenue)، وتكلفة المبيعات (COGS)، والنفقات التشغيلية (Operating Expenses)، مع وجود قيم مفقودة وسجلات نصوص مالية تتطلب التنظيف والمعالجة الجبرية الدقيقة.

تبدأ خطة المعالجة بتحويل الأعمدة المالية الملوثة برموز العملات (مثل ‘$’ أو ‘,’) إلى أرقام عائمة عبر دوال النصوص str.replace() ثم تطبيق pd.to_numeric(). بعد ذلك، يتم حساب إجمالي المصروفات، ثم طرحه من الإيرادات لاستخراج صافي الربح التشغيلي: df['Net_Profit'] = df['Gross_Revenue'] - (df['COGS'] + df['Operating_Expenses']).

كما تشمل الدراسة حساب الانحراف المالي عن الميزانية المقدرة عبر طرح التكلفة الفعلية من التكلفة التقديرية واستخراج نسبة التغير المئوي باستخدام الصيغة: (df['Actual_Cost'] - df['Budgeted_Cost']).div(df['Budgeted_Cost']).mul(100)، مع تنسيق المخرجات النهائية كبيانات نقدية قياسية مهيأة لإعداد التقارير التنفيذية الدقيقة للمؤسسات المالية.

12.2 دراسة حالة قياس الأداء: حساب زمن الاستجابة في أنظمة الخوادم

في هندسة البنية التحتية وتحليل سجلات الخوادم السحابية (Server Logs)، يُعد قياس زمن الاستجابة (Latency) لكل طلب طلباً أساسياً لتقييم جودة الخدمة واكتشاف الاختناقات البرمجية. يحتوي إطار البيانات النموذجي لهذه السجلات على عمود طابع زمن إرسال الطلب (Request_Timestamp) وعمود طابع زمن اكتمال الاستجابة (Response_Timestamp).

يتم استيراد وتوحيد التواريخ عبر pd.to_datetime()، ثم تطبيق عملية الطرح الزمني المتجه لإنشاء عمود الفارق الزمني: df['Latency'] = df['Response_Timestamp'] - df['Request_Timestamp']. ولإجراء التحليل الإحصائي، يتم تحويل الناتج إلى وحدة الميلي ثانية عبر القسمة المتجهة: df['Latency_MS'] = df['Latency'] / np.timedelta64(1, 'ms').

تستكمل الدراسة بتطبيق التصفية الإحصائية لإزالة القيم الشاذة الناتجة عن انقطاع الاتصال (Outliers)، وتجميع أوقات التأخير حسب نوع الخادم والمنطقة الجغرافية باستخدام groupby('Server_Region')['Latency_MS'].agg(['mean', 'median', 'std'])، مما يوفر رؤية بيانية دقيقة تدعم اتخاذ القرارات التقنية لتحسين البنية التحتية الموزعة.

12.3 ملخص القواعد الإرشادية لكتابة كود طرح فعال وموثوق

لضمان أعلى معايير الجودة والأداء والموثوقية عند تنفيذ عمليات طرح الأعمدة في مشاريع علوم البيانات وهندسة البرمجيات، يُنصح دائماً باتباع قائمة التحقق المعيارية التالية قبل كتابة واعتماد الأكواد الحسابية في بيئات الإنتاج الفعلية:

  • مطابقة وتدقيق الأنواع البيانية: التأكد من أن كلا العمودين ينتميان إلى نفس الفئة العددية أو الزمنية عبر df.dtypes قبل الطرح، وتجنب خلط النصوص مع الأرقام.
  • التحقق من سلامة وتفرد الفهارس: فحص الفهارس المشتركة للتأكد من خلوها من التكرار أو تباين المحاذاة غير المقصود، واللجوء إلى reset_index(drop=True) عند الرغبة في الطرح الموضعي.
  • إدارة القيم المفقودة بوعي إحصائي: استخدام الوسيط fill_value=0 داخل الدالة sub() أو تطبيق الاستيفاء الرياضي الملائم لطبيعة البيانات وتجنب الاعتماد على الانتشار الصامت للفراغات.
  • تحسين الذاكرة والسرعة: تقليص دقة التخزين البياني للأعمدة غير الحساسة، واستخدام الدالة eval() أو مصفوفات نامباي التحتية عند التعامل مع مجموعات البيانات المليونية الضخمة.
  • كتابة اختبارات الوحدة (Unit Tests): صياغة اختبارات آلية للتأكد من عدم توليد قيم سالبة غير منطقية (مثل أسعار أو أعمار سالبة) وضمان مطابقة النتائج الحسابية للافتراضات النظرية للمشروع.

خاتمة

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

إن إتقان الاختيار الواعي بين المعاملات الرمزية والتوابع المنهجية المتقدمة مثل sub() و eval() و assign()، واستيعاب التفاعلات الدقيقة للأنواع البيانية والقيم المفقودة، يمكّن مهندسي ومحللي البيانات من كتابة شفرات برمجية فائقة السرعة، عالية الموثوقية، وقابلة للصيانة والتوسع في بيئات العمل الحقيقية ومشاريع البيانات الضخمة المعاصرة.

References

  • McKinney, W. (2022). Python for Data Analysis: Data Wrangling with pandas, NumPy, and Jupyter (3rd ed.). O’Reilly Media. https://wesmckinney.com/book/
  • Pandas Development Team. (2023). pandas.DataFrame.subtract API Reference. PyData. https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.subtract.html
  • Pandas Development Team. (2023). Essential Basic Functionality: Matching / broadcasting behavior. PyData. https://pandas.pydata.org/docs/user_guide/basics.html#matching-broadcasting-behavior
  • Harris, C. R., Millman, K. J., van der Walt, S. J., Gommers, R., Virtanen, P., Cournapeau, D., … & Oliphant, T. E. (2020). Array programming with NumPy. Nature, 585(7825), 357-362. https://doi.org/10.1038/s41586-020-2649-2
  • VanderPlas, J. (2016). Python Data Science Handbook: Essential Tools for Working with Data. O’Reilly Media. https://jakevdp.github.io/PythonDataScienceHandbook/
  • IEEE Computer Society. (2019). IEEE Standard for Floating-Point Arithmetic (IEEE Std 754-2019). IEEE. https://standards.ieee.org/ieee/754/6210/

اقتباس هذا المقال

looti, M. (2026, أغسطس 29). كيفية طرح عمودين في إطار بيانات بانداس. عرب سايكلوجي. https://arabpsychology.com/statistics/how-to-subtract-two-columns-in-pandas-dataframe/
looti, Mohammed. “كيفية طرح عمودين في إطار بيانات بانداس.” عرب سايكلوجي, 29 أغسطس 2026, https://arabpsychology.com/statistics/how-to-subtract-two-columns-in-pandas-dataframe/.
looti, Mohammed. “كيفية طرح عمودين في إطار بيانات بانداس.” عرب سايكلوجي. أغسطس 29, 2026. https://arabpsychology.com/statistics/how-to-subtract-two-columns-in-pandas-dataframe/.