تُعد معالجة البيانات النصية وتحليل السلاسل المحرفية ركيزة أساسية لا غنى عنها في بيئات الحوسبة الإحصائية المعاصرة، حيث لم تعد البيانات المتاحة للباحثين ومحللي البيانات تقتصر على المتغيرات العددية والكمية فحسب. ومع تزايد تدفق البيانات غير المهيكلة وشبه المهيكلة الناتجة عن السجلات الطبية، وقواعد بيانات الأعمال، ومنصات التواصل الاجتماعي، واستطلاعات الرأي المفتوحة، برزت لغة البرمجة الإحصائية R كإحدى أقوى البيئات للتعامل مع هذا التنوع البياني المعقد. وعلى الرغم من أن لغة R قد بُنيت تاريخياً كأداة للتحليل الرياضي والإحصائي، فإنها تطورت لتشمل بنية تحتية برمجية متقدمة قادرة على التلاعب بالنصوص، وتفكيك السلاسل المحرفية، واستخراج المعلومات الفرعية منها بدقة فائقة وكفاءة حسابية متقدمة.
تعتمد المعالجة النصية داخل النواة البرمجية الأساسية للغة R (المعروفة بـ Base R) على مجموعة غنية من الدوال المتخصصة في التعامل مع السلاسل المحرفية. ومن بين هذه الأدوات البرمجية الحيوية، تبرز دالة substring كأداة مركزية لاقتطاع المقاطع النصية المحددة واستبدالها اعتماداً على فهارس المواضع المكانية للأحرف. يتيح الاستخدام الاحترافي لهذه الدالة لعلماء البيانات إعادة تشكيل المتجهات النصية، وفصل الأكواد التعريفية المدمجة، واستخراج الطوابع الزمنية، وتنظيف المتغيرات الفئوية دون الحاجة إلى اللجوء الدائم إلى تعبيرات الأنماط النمطية المعقدة (Regular Expressions) التي قد تؤثر سلباً على الأداء الحسابي وسهولة قراءة الشيفرة البرمجية.
يهدف هذا الدليل المرجعي الشامل إلى استعراض دالة substring في لغة R من منظور تقني وتطبيقي متعمق، مستنداً إلى أربعة أمثلة عملية رئيسية تغطي حالات الاستخدام الشائعة في معالجة أطر البيانات (Data Frames). كما يتناول المقال التحليل الداخلي لعمل الدالة، مع مقارنتها المنهجية بدوال النواة المقاربة مثل substr، واستعراض سلوكها عند التعامل مع المتجهات ذات الأطوال المتفاوتة والقيم المفقودة، وصولاً إلى إجراء مقارنات الأداء المعيارية وأفضل الممارسات لضمان كتابة شيفرات برمجية متينة وقابلة للتوسع.
- 1. مقدمة إلى معالجة البيانات النصية ودوال السلاسل في لغة R
- 2. البنية النحوية والمحددات التقنية لدالة substring()
- 3. المقارنة المعيارية والوظيفية بين دالتي substring() و substr()
- 4. تهيئة بيئة العمل وبناء إطار البيانات التجريبي
- 5. المثال الأول: استخراج المحارف الواقعة بين مواضع محددة بدقة
- 6. المثال الثاني: استخراج الأحرف الأولى من السلاسل النصية (البوادئ)
- 7. المثال الثالث: استخراج الأحرف الأخيرة وتحديد المواضع ديناميكياً
- 8. المثال الرابع: تعديل واستبدال الأجزاء الفرعية من السلاسل النصية
- 9. التعامل مع المتجهات وتطبيق خاصية التدوير المتقدمة (Vectorization)
- 10. إدارة القيم المفقودة (NA) والحالات الشاذة في المعالجة النصية
- 11. المقارنة الأدائية: دالة substring مقابل حزم المعالجة الحديثة
- 12. أفضل الممارسات البرمجية والتوصيات التطبيقية لمحللي البيانات
- خاتمة
- References
1. مقدمة إلى معالجة البيانات النصية ودوال السلاسل في لغة R
1.1 أهمية تحليل المتجهات النصية في بيئة R الإحصائية
تمثل البيانات النصية في الحوسبة الإحصائية مصدراً غنياً بالمعلومات التي تتكامل مع النماذج الكمية، إلا أنها تتطلب معالجة أولية صارمة لتحويلها إلى صيغ مهيكلة قابلة للتحليل. في لغة R، تُمثل السلاسل النصية كمتجهات محرفية (Character Vectors)، وهي تشكل العمود الفقري للعديد من المتغيرات الوصفية والتصنيفية. عندما يتعامل المحلل مع مجموعات بيانات تتضمن معرفات المرضى، أو الرموز الجغرافية، أو المتغيرات الفئوية، غالباً ما تكون هذه المتغيرات مدمجة ضمن سلاسل نصية طويلة تحتاج إلى تفكيك منهجي.
تبرز أهمية تنظيف السلاسل النصية وتنسيقها قبل تغذيتها في خوارزميات التعلم الآلي أو النماذج الإحصائية مثل الانحدار الخطي أو نماذج السلاسل الزمنية. فالأخطاء الطفيفة في إدخال النصوص، أو وجود مسافات بيضاء زائدة، أو دمج لواحق غير مرغوبة قد تؤدي إلى تكرار غير دقيق للمستويات الفئوية، مما يسبب تشوهاً في مصفوفات التصميم (Design Matrices) المستخدمة في التحليل. لذلك، فإن التحكم الدقيق في السلاسل النصية على المستوى الموضعي للأحرف يُعد خطوة تأسيسية لا غنى عنها في هندسة الخصائص (Feature Engineering).
تواجه أطر البيانات (Data Frames) تحديات بنيوية ملحوظة عند تخزين المتجهات النصية، لا سيما في المشروعات الضخمة التي تحتوي على مئات الآلاف من السجلات. يتطلب تخزين هذه السلاسل في الذاكرة الحية تخصيص موارد إضافية لإدارة مؤشرات السلاسل الموحدة داخل بنية R التحتية (مصفوفة السلاسل المحرفية العامة Global String Pool). وبالتالي، فإن تقليص أطوال النصوص وحذف الأجزاء الزائدة واستخراج المقاطع الحيوية فقط يساهم بشكل مباشر في تحسين كفاءة استخدام الذاكرة وزيادة سرعة العمليات اللاحقة عبر المعالجة الموجهة (Vectorized Operations).
1.2 مفهوم السلاسل النصية الفرعية (Substrings) والعمليات الأساسية
تُعرف السلسلة النصية الفرعية (Substring) رياضياً وبرمجياً بأنها تسلسل متصل من المحارف يمثل جزءاً مستقطعاً من سلسلة نصية رئيسية أطول. إذا كانت السلسلة الأصلية تمثل متتالية من الرموز تبدأ من الموضع الأول وتنتهي بالموضع الأخير، فإن السلسلة الفرعية هي المتتالية الجزئية الممتدة بين موضعين محددين داخل ذلك النطاق. تُعد هذه العملية من العمليات الحتمية التي تعتمد على تحديد مواضع البداية والنهاية باستخدام قيم عددية صحيحة تمثل الفهارس المكانية للأحرف.
يختلف الاستقطاع النصي القائم على الفهارس المكانية اختلافاً جوهرياً عن مطابقة الأنماط (Pattern Matching). فبينما يعتمد الاستقطاع على الموقع الفيزيائي للرمز داخل السلسلة بغض النظر عن محتواه، تعتمد مطابقة الأنماط عبر التعبيرات النمطية مثل دوال grep أو regexpr على البحث عن هياكل رمزية معينة (مثل البحث عن أرقام أو حروف خاصة). يوفر الاستقطاع الموضعي ميزة حاسمة تتعلق بالسرعة الفائقة وانخفاض التعقيد الحسابي، مما يجعله الخيار الأمثل عندما يكون الهيكل الداخلي للنص ثابتاً ومعروفاً مسبقاً.
تتجلى التطبيقات العملية لاستخراج السلاسل الفرعية في العديد من المجالات، مثل استخراج رموز البلدان من أرقام الهواتف الدولية، وتفكيك الأكواد البريدية، وتجزئة المعرفات الأكاديمية أو الأكواد الجينية (DNA Sequences) التي تتبع نسقاً طولياً ثابتاً. في مثل هذه السياقات، لا يحتاج المحلل إلى محركات مطابقة الأنماط الثقيلة، بل يحتاج إلى آلية مباشرة وسريعة تقتطع المحارف الواقعة ضمن نافذة فهرسية محددة، وهو ما تقدمه دوال الاقتطاع النصي الموضعي في R بأعلى درجات الموثوقية.
2. البنية النحوية والمحددات التقنية لدالة substring()
2.1 التشريح الدقيق لوسائط الدالة (Function Arguments)
تمتلك دالة substring بنية استدعاء محددة بدقة داخل بيئة The R Project for Statistical Computing، وتتميز بقدرتها على استقبال ثلاثة وسائط رئيسية تتحكم في عملية الاقتطاع، وتُكتب الصياغة العامة للدالة على النحو التالي:
substring(text, first, last = 1000000L)
يُمثل الوسيط الأول text المتجه النصي المستهدف الذي يحتوي على سلسلة محرفية واحدة أو مجموعة من السلاسل النصية المضمنة في متجه. تقبل الدالة المتجهات من نوع character، وإذا تم تمرير كائنات من أنواع أخرى مثل القيم المنطقية أو الأرقام، فإن R تقوم بتحويلها قسرياً (Coercion) إلى سلاسل نصية قبل تطبيق العملية، وهو سلوك يجب الانتباه إليه لتفادي التحويلات غير المقصودة في البيانات.
يحدد الوسيط الثاني first الموضع الفهرسي الذي تبدأ عنده عملية الاقتطاع. هذا الوسيط هو عدد صحيح يمثل ترتيب الحرف الأول المطلوب تضمينه في المخرج. أما الوسيط الثالث last، فيحدد الموضع الفهرسي النهائي للاقتطاع، وله قيمة افتراضية كبيرة جداً تبلغ 1000000L في كود المصدر الأساسي، مما يعني أنه في حال عدم تحديد هذا الوسيط، ستقوم الدالة باقتطاع السلسلة النصية بدءاً من موضع first وحتى نهاية السلسلة بالكامل، طالما أن طول السلسلة لا يتجاوز هذا الحد الافتراضي الضخم.
2.2 نظام الفهرسة (Indexing) المستند إلى الواحد في R وتأثيره
تعتمد لغة R نظام الفهرسة المستند إلى الواحد (1-based indexing)، وهو يختلف عن لغات البرمجة المعتمدة على الصفر (0-based indexing) مثل Python وC. في لغة R، يشير الفهرس 1 إلى الحرف الأول الفعلي في السلسلة النصية، بينما يشير الفهرس 2 إلى الحرف الثاني، وهكذا. يلعب هذا النظام دوراً حاسماً في تسهيل الفهم الرياضي والإحصائي، حيث يتطابق موضع الحرف مباشرة مع رتبته المكانية الطبيعية دون الحاجة إلى إزاحة الأرقام بمقدار خانة واحدة.
تتعامل دالة substring بمرونة استثنائية مع الحدود التي تتجاوز الطول الفعلي للنص. إذا تم تحديد وسيط last بقيمة تتجاوز طول السلسلة النصية الأصلية، فإن الدالة لا تُطلق خطأ برمجياً أو استثناء، بل تقوم باقتطاع النص وصولاً إلى آخر حرف متاح وتتوقف عنده بنجاح. تمنح هذه الخاصية كود R مرونة عالية عند التعامل مع سلاسل نصية متفاوتة الأطوال دون الحاجة إلى كتابة شروط تحقق مسبقة لكل عنصر.
في المقابل، إذا كانت قيمة first أكبر من طول السلسلة النصية، أو إذا تم تمرير قيم سالبة أو صفرية، فإن سلوك الدالة يتبع قواعد محددة: القيم الصفرية أو السالبة في first يتم تقريبها تلقائياً لتُعامل كالموضع 1 طالما أن last قيمة موجبة. أما إذا كانت قيمة first أكبر تماماً من قيمة last، فإن الدالة تُعيد سلسلة نصية فارغة تُمثل بالرمز "" دون توقف تنفيذ البرنامج، مما يضمن تدفقاً سلساً للعمليات داخل خطوط معالجة البيانات الضخمة.
3. المقارنة المعيارية والوظيفية بين دالتي substring() و substr()
3.1 الفروق الدلالية في تسمية الوسائط (Arguments Semantics)
توفر حزمة Base R دالتين أساسيتين متقاربتين للغاية للاقتطاع النصي الموضعي: دالة substring() ودالة substr(). على الرغم من أن كلتا الدالتين تستخدمان لتحقيق أهداف متشابهة، إلا أن الفروق بينهما تبدأ من التسمية الدلالية للوسائط. تستخدم substr الوسائط: x للإشارة إلى المتجه النصي، وstart لتحديد موضع البداية، وstop لتحديد موضع النهاية، بصيغة: substr(x, start, stop).
في المقابل، تعتمد دالة substring التسميات: text للمتجه النصي، وfirst لموضع البداية، وlast لموضع النهاية. هذا الاختلاف الدلالي في التسمية ليس مجرد تباين شكلي، بل يعكس اختلافاً في فلسفة التصميم والتوافق مع لغة S التاريخية (سلف لغة R). تتيح التسميتان التوافق التبادلي في العديد من المهام البسيطة، ولكن فهم الفروق في البنية الداخلية لكل منهما يُعد ضرورياً لمهندسي البيانات عند كتابة الحزم البرمجية والأنظمة الإنتاجية الحساسة للأداء.
توصي وثائق R الرسمية في حزمة النواة باستيعاب سلوك كلتا الدالتين بدقة، حيث يمكن استخدام substr عندما تكون نوافذ الاقتطاع ثابتة وموحدة لجميع عناصر المتجه، في حين تُفضل دالة substring عند الحاجة إلى استغلال مرونة قواعد إعادة التدوير المتقدمة للأطوال المتعددة، كما سنفصل في الجزء التالي.
3.2 خاصية إعادة التدوير (Recycling Rule) وتعدد أطوال المدخلات
يتمثل الفارق الجوهري والتقني الأبرز بين substring() وsubstr() في كيفية تطبيق قاعدة إعادة التدوير (Recycling Rule) الخاصة بلغة R. عندما نقوم بتمرير متجهات متعددة القيم لوسائط البداية والنهاية، فإن دالة substring تُعد دالة موجهة بالكامل لجميع وسائطها الثلاثة بالتساوي. يتم تحديد طول المتجه الناتج ليكون مساوياً لطول أطول متجه من بين المدخلات الثلاثة (النص، البداية، النهاية)، وتتم إعادة تدوير المتجهات الأقصر لتغطية الطول الكلي.
لتوضيح ذلك، إذا مررنا نصاً واحداً إلى substring مع متجه بداية يحتوي على القيم c(1, 3)، فإن الدالة ستنتج متجه مخرجات يحتوي على عنصرين مختلفين مستقطعين من نفس السلسلة الأصلية. هذا السلوك يسمح باستخراج مقاطع متعددة ومتباينة من نفس النص دفعة واحدة دون الحاجة إلى استخدام حلقات تكرار for أو دوال apply، وهو ما لا تدعمه دالة substr بنفس المرونة، إذ تُقيد substr طول المخرجات بطول المتجه النصي x فقط، متجاهلة التوسع الناتج عن أطوال وسائط البداية والنهاية.
تترتب على هذا الفارق آثار بالغة الأهمية على كفاءة الأداء؛ إذ توفر substring إمكانية تفكيك النصوص بأسلوب متجهي عالي السرعة (Vectorized) ينفذ بالكامل في طبقة لغة C الداخلية للغة R، مما يقلل من الاستهلاك الزمني واستهلاك الذاكرة مقارنة بتنفيذ عمليات الاستخراج المتعددة عبر تكرارات برمجية في بيئة R العليا.
4. تهيئة بيئة العمل وبناء إطار البيانات التجريبي
4.1 إنشاء إطار البيانات المرجعي (df) وتحميل البيانات
لتطبيق المفاهيم البرمجية عملياً واستعراض الحالات الأربع الرئيسية لاقتطاع وتعديل النصوص، سنقوم بإنشاء إطار بيانات (Data Frame) تجريبي يحتوي على أسماء أربعة فرق رياضية معروفة في دوري كرة السلة للمحترفين. يمثل هذا الهيكل نموذجاً واقعياً لأعمدة البيانات النصية التي يواجهها الباحثون في مشاريعهم اليومية. يمكن إنشاء هذا الإطار المرجعي عبر تنفيذ الشيفرة البرمجية التالية:
df <- data.frame(team = c('Mavericks', 'Hornets', 'Rockets', 'Grizzlies'), stringsAsFactors = FALSE)
يحتوي المتجه النصي في العمود team على السلاسل التالية: "Mavericks" (بطول 9 أحرف)، و"Hornets" (بطول 7 أحرف)، و"Rockets" (بطول 7 أحرف)، و"Grizzlies" (بطول 9 أحرف). يضمن ضبط المعامل stringsAsFactors = FALSE معاملة المتغير كمتجه محرفي خالص بدلاً من تحويله إلى متغير فئوي (Factor)، وهو الإعداد الافتراضي منذ إصدار R 4.0.0، ولكنه يُكتب صراحة هنا لضمان توافق الشيفرة مع جميع البيئات القديمة والحديثة على حد سواء.
يمثل هذا التباين في أطوال السلاسل النصية بيئة اختبار ممتازة لمحاكاة سلوك دالة substring مع حالات الاقتطاع المختلفة؛ سواء الثابتة منها في منتصف الكلمات، أو المرتبطة ببدايات النصوص ونهاياتها المتغيرة.
4.2 فحص بنية الكائنات النصية في R
قبل البدء في تنفيذ التحويلات والاستقطاعات النصية، يجب إجراء فحص هيكلي دقيق للكائن المنشأ للتحقق من اتساق أنماط التخزين وأنواع البيانات. نستخدم دالة str(df) للحصول على ملخص شامل للبنية الداخلية، حيث تُظهر المخرجات أن العمود team هو من النوع chr، ويحتوي على أربعة صفوف مفهرسة من 1 إلى 4.
يمكننا أيضاً استخدام دالتي typeof(df$team) وclass(df$team) للتأكد من أن الكائن ينتمي إلى الفئة الأساسية character. تتم إدارة السلاسل النصية في ذاكرة R عبر هياكل بيانات مخصصة تسمى STRSXP، وهي مؤشرات تشير إلى كائنات أحرف فردية (CHARSXP) مخزنة في الذاكرة الموحدة لتفادي التكرار وتحسين استهلاك الذاكرة. يضمن هذا الفحص الأولي سلامة المتجه النصي قبل إلحاق أي أعمدة جديدة ناتجة عن عمليات المعالجة النصية داخل إطار البيانات.
عند طباعة إطار البيانات الأولي باستخدام دالة print(df) أو تفحصه عبر head(df)، نحصل على جدول بسيط يتكون من عمود واحد وأربعة صفوف، وهو الأساس الذي سنبني عليه الأمثلة التطبيقية الأربعة عبر إضافة أعمدة جديدة مشتقة تمثل سيناريوهات المعالجة المختلفة.
5. المثال الأول: استخراج المحارف الواقعة بين مواضع محددة بدقة
5.1 التطبيق البرمجي لاستخراج النطاق الأوسط (المواضع 2 إلى 5)
يركز السيناريو الأول على استخراج مقطع وسطي محدد بمواضع فهرسية ثابتة من جميع السلاسل النصية داخل العمود. لنفترض أننا نريد استخراج المحارف الممتدة من الموضع الثاني إلى الموضع الخامس من كل اسم فريق. تُصاغ الشيفرة البرمجية لإتمام هذه العملية وإضافة الناتج كعمود جديد في إطار البيانات على النحو التالي:
df$between2_5 <- substring(df$team, first = 2, last = 5)
عند تنفيذ هذه العملية، تقوم دالة substring بالمرور على كل عنصر في متجه df$team واستخراج الأحرف المحصورة بين المؤشرين 2 و5 (بما يشمل موضعي البداية والنهاية). وبتفحص النتائج الناتجة في كل صف:
- السلسلة الأولى
"Mavericks": الحرف الأول هو ‘M’، والأحرف من 2 إلى 5 هي ‘a’ و’v’ و’e’ و’r’، فيكون الناتج المستخرج"aver". - السلسلة الثانية
"Hornets": الحرف الأول هو ‘H’، والأحرف من 2 إلى 5 هي ‘o’ و’r’ و’n’ و’e’، فيكون الناتج المستخرج"orne". - السلسلة الثالثة
"Rockets": الحرف الأول هو ‘R’، والأحرف من 2 إلى 5 هي ‘o’ و’c’ و’k’ و’e’، فيكون الناتج المستخرج"ocke". - السلسلة الرابعة
"Grizzlies": الحرف الأول هو ‘G’، والأحرف من 2 إلى 5 هي ‘r’ و’i’ و’z’ و’z’، فيكون الناتج المستخرج"rizz".
يُظهر هذا التطبيق كيف تتيح الدالة استخراج المقاطع الداخلية المتماثلة في موقعها النسبي بسهولة وبسطر واحد من الشيفرة البرمجية دون الحاجة إلى معالجات معقدة.
5.2 التحقق الرياضي من عدد الأحرف المستخرجة
يمكننا التحقق من صحة المخرجات رياضياً وحسابياً باستخدام معادلة حساب طول الفترة المغلقة. إذا كان موضع البداية هو $first$ وموضع النهاية هو $last$، فإن الطول الإجمالي المتوقع للسلسلة الفرعية المستخرجة يُحسب وفق العلاقة الرياضية التالية:
$$\text{Length} = \text{last} – \text{first} + 1$$
بتطبيق المعادلة على مثالنا حيث $first = 2$ و $last = 5$، نجد أن: $5 – 2 + 1 = 4$ محارف. وبمطابقة هذه النتيجة مع المخرجات الفعلية ("aver", "orne", "ocke", "rizz")، نجد أن كل سلسلة ناتجة تتكون بالفعل من 4 أحرف بالضبط، مما يؤكد دقة العملية الحسابية للاقتطاع الفهرسي المغلق.
في الحالات التي تكون فيها السلسلة الأصلية أقصر من موضع النهاية المحدد (مثلاً لو كانت السلسلة مكونة من 3 أحرف فقط وحاولنا الاقتطاع حتى 5)، فإن طول المخرج سيكون مساوياً لـ: $(\text{طول السلسلة الأصلي} – \text{first} + 1)$، حيث تتوقف الدالة تلقائياً عند آخر حرف متاح دون حشو السلسلة بمسافات فارغة أو التسبب في توقف البرنامج. يُدمج العمود الجديد between2_5 بسلاسة داخل إطار البيانات df، ليصبح الجدول محتوياً على عمودين جاهزين لمزيد من المعالجات الإحصائية.
6. المثال الثاني: استخراج الأحرف الأولى من السلاسل النصية (البوادئ)
6.1 استخراج أول N محارف بتثبيت موضع البداية عند 1
يُعد استخراج البوادئ النصية (Prefixes) من أكثر العمليات شيوعاً في إعداد البيانات، حيث تُستخدم الأحرف الأولى لتوليد الأكواد المختصرة، أو تصنيف المتغيرات، أو إنشاء المفاتيح التعريفية الموحدة (Primary Keys) للربط بين الجداول الإحصائية. لاستخراج أول 3 أحرف من أسماء الفرق، نحدد وسيط البداية بالقيمة الثابتة first = 1، ونحدد وسيط النهاية بعدد الأحرف المراد استخراجها last = 3، كما في الشيفرة التالية:
df$first3 <- substring(df$team, first = 1, last = 3)
تقوم هذه الشيفرة بقراءة السلاسل النصية ابتداءً من المحرف الأول وحتى المحرف الثالث، منتجة القيم التالية في العمود الجديد first3:
- من
"Mavericks"نحصل على بادئة الفريق:"Mav". - من
"Hornets"نحصل على بادئة الفريق:"Hor". - من
"Rockets"نحصل على بادئة الفريق:"Roc". - من
"Grizzlies"نحصل على بادئة الفريق:"Gri".
تتميز هذه التقنية بالسرعة الفائقة في اختزال السلاسل الطويلة إلى رموز قياسية موحدة الحجم، وهو أمر بالغ الأهمية في تجهيز لوحات العرض البياني (Dashboards) والتقارير الإحصائية التي تتطلب اختصارات معيارية لأسماء الكيانات.
6.2 تحسين كتابة الكود والتعامل مع البوادئ المتغيرة
لا تقتصر دالة substring على استخراج بوادئ ذات أطوال ثابتة لجميع الصفوف، بل يمكنها الاستفادة من خاصية المتجهات لتمرير أطوال متغيرة لكل صف على حدة. إذا أردنا مثلاً استخراج أول حرفين من الفريق الأول، وثلاثة أحرف من الفريق الثاني، وأربعة أحرف من الفريق الثالث، وحرفين من الفريق الرابع، يمكننا ببساطة تمرير متجه عددي إلى الوسيط last:
df$var_prefix <- substring(df$team, first = 1, last = c(2, 3, 4, 2))
ينتج عن هذه الشيفرة: "Ma" للفريق الأول، و"Hor" للفريق الثاني، و"Rock" للفريق الثالث، و"Gr" للفريق الرابع. توضح هذه المرونة كيفية تكييف دالة substring للتعامل مع متطلبات الترميز المعقدة في خطوة برمجية واحدة دون الحاجة إلى تفرعات شرطية (if-else statements).
عند توليد البوادئ لأغراض الفهرسة والترميز، يجب على المحلل دائماً فحص تفرد المعرفات الناتجة (Uniqueness) باستخدام دالة unique() أو duplicated() للتأكد من أن تقليص طول السلسلة النصية لم يؤدِ إلى تصادم الرموز (Collision) بين فئات مختلفة، مما قد يتسبب في فقدان التمييز الإحصائي بين المتغيرات المستقلة داخل النماذج اللاحقة.
7. المثال الثالث: استخراج الأحرف الأخيرة وتحديد المواضع ديناميكياً
7.1 دمج دالة nchar() لتحديد النهايات المتغيرة
يمثل استخراج اللواحق النصية (Suffixes) أو الأحرف الأخيرة تحدياً خاصاً في السلاسل النصية المتفاوتة الأطوال، حيث لا يمكن استخدام قيمة ثابتة للوسيط first لأن موقع البداية للأحرف الأخيرة يختلف من كلمة لأخرى تبعاً لطولها الإجمالي. لحل هذه المعضلة بأسلوب ديناميكي، نقوم بدمج دالة حساب أطوال النصوص الأساسية nchar() لحساب موضع البداية لكل صف على حدة.
إذا أردنا استخراج آخر $k$ أحرف من سلسلة نصية، فإن موضع البداية الرياضي $first$ يُحدد بالمعادلة:
$$\text{first} = \text{nchar}(\text{\text}) – k + 1$$
بينما يمثل موضع النهاية $last$ الطول الكلي للسلسلة $\text{nchar}(\text{\text})$، أو يمكن تركه للقيمة الافتراضية. لاستخراج آخر 3 أحرف من كل اسم فريق في إطار البيانات df، نكتب الكود التالي:
df$last3 <- substring(df$team, first = nchar(df$team) - 3 + 1, last = nchar(df$team))
أو بصيغة أكثر إيجازاً بالاعتماد على القيمة الافتراضية للوسيط last:
df$last3 <- substring(df$team, first = nchar(df$team) - 2)
دعنا نتتبع التطبيق الحسابي على عناصر إطار البيانات:
"Mavericks": طولها 9. موضع البداية = $9 – 3 + 1 = 7$. الأحرف من 7 إلى 9 هي:"cks"."Hornets": طولها 7. موضع البداية = $7 – 3 + 1 = 5$. الأحرف من 5 إلى 7 هي:"ets"."Rockets": طولها 7. موضع البداية = $7 – 3 + 1 = 5$. الأحرف من 5 إلى 7 هي:"ets"."Grizzlies": طولها 9. موضع البداية = $9 – 3 + 1 = 7$. الأحرف من 7 إلى 9 هي:"ies".
بهذا التكامل الذكي بين substring وnchar، نضمن استخراج اللواحق المطلوبة بدقة بغض النظر عن الاختلاف الهيكلي في طول كل كلمة داخل المتجه.
7.2 التعامل مع التفاوت في أطوال السلاسل النصية
عند تطبيق عمليات استخراج اللواحق على مجموعات بيانات كبيرة وغير متجانسة، قد تظهر حالات خاصة تكون فيها السلسلة النصية أقصر من عدد الأحرف المطلوب استخراجها ($k$). على سبيل المثال، إذا حاولنا استخراج آخر 5 أحرف من كلمة تتكون من 3 أحرف فقط، فإن ناتج المعادلة $(\text{nchar}(\text{\text}) – k + 1)$ سيكون قيمة صفرية أو سالبة ($3 – 5 + 1 = -1$).
في مثل هذا السيناريو، وبفضل التصميم المرن لدالة substring، تُعامل القيمة السالبة أو الصفرية للمحدد first تلقائياً كأنها الموضع 1. وبالتالي، ستقوم الدالة باقتطاع السلسلة من بدايتها وحتى نهايتها، معيدة الكلمة الأصلية كاملة (ذات الأحرف الثلاثة) دون التسبب في انهيار البرنامج أو إصدار تحذيرات معطلة للتدفق الحسابي.
ومع ذلك، في التحليلات الإحصائية الدقيقة التي تتطلب صرامة في الأطوال (مثل معالجة الامتدادات الثنائية للملفات أو صيغ التشفير)، يُفضل إحاطة معادلة حساب البداية بدالة pmax() لضمان عدم نزول الفهرس عن 1، أو استخدام دالة ifelse() لمعالجة النصوص القصيرة وفق منطق محدد (كوضع قيمة NA إذا كان النص أقصر من المطلوب)، مما يمنح المحلل تحكماً كاملاً في جودة المخرجات.
8. المثال الرابع: تعديل واستبدال الأجزاء الفرعية من السلاسل النصية
8.1 استخدام substring() كدالة إسناد وتعيين (Replacement Function)
تتمتع دالة substring في لغة R بخاصية فريدة ومتقدمة تميزها عن العديد من دوال النصوص في لغات البرمجة الأخرى، وهي إمكانية استخدامها كدالة إسناد وتعيين موضعي (Replacement Form). يسمح هذا النمط البرمجي بتعديل أحرف محددة واستبدالها داخل السلسلة النصية مباشرة بناءً على مواقعها الفهرسية دون الحاجة إلى إعادة بناء السلسلة من الصفر. تتبع صيغة الاستبدال النمط التالي:
substring(text, first, last) <- value
لتطبيق هذا المفهوم على إطار البيانات، لنفترض أننا نريد استبدال الحرفين الثاني والثالث في جميع أسماء الفرق بالمحرفين "ZZ". ننشئ أولاً نسخة من العمود الأصلي لتوضيح التغيير، ثم نطبق دالة الاستبدال:
df$team_modified <- df$team
substring(df$team_modified, first = 2, last = 3) <- "ZZ"
دعنا نتفحص ما حدث لكل نص بعد تنفيذ أمر الاستبدال الموضعي:
- السلسلة
"Mavericks": تم استبدال الحرفين 2 و3 (‘av’) بـ ‘ZZ’، فأصبحت السلسلة:"MZZericks". - السلسلة
"Hornets": تم استبدال الحرفين 2 و3 (‘or’) بـ ‘ZZ’، فأصبحت السلسلة:"HZZnets". - السلسلة
"Rockets": تم استبدال الحرفين 2 و3 (‘oc’) بـ ‘ZZ’، فأصبحت السلسلة:"RZZkets". - السلسلة
"Grizzlies": تم استبدال الحرفين 2 و3 (‘ri’) بـ ‘ZZ’، فأصبحت السلسلة:"GZZzzlies".
توضح هذه الآلية كيف يتم التعديل في الموضع المحدد بدقة متناهية مع الحفاظ التام على باقي بنية النص كما هي قبل الاستبدال وبعده.
8.2 التعديل الموضعي مقابل الدوال البديلة مثل gsub()
يختلف الاستبدال الموضعي عبر substring<- اختلافاً جوهرياً عن دوال الاستبدال القائمة على الأنماط مثل sub() وgsub(). في دالة gsub، يتم البحث عن تطابق لمحتوى معين (مثل البحث عن حرف ‘a’ واستبداله بـ ‘Z’) أينما وجد، بينما في substring<-، يتم الاستبدال في الموضع المحدد بغض النظر عن الحرف الموجود فيه. إذا كان الحرف في الموضع الثاني ‘a’ أو ‘o’ أو أي رمز آخر، فسيتم استبداله حتماً.
من القواعد الهيكلية الهامة لعملية الاستبدال عبر substring<- أن طول النص الإجمالي لا يتغير إذا كانت السلسلة البديلة أطول من النطاق المستهدف. إذا حددنا نطاق استبدال بطول حرفين (من 2 إلى 3) ومررنا قيمة بديلة مكونة من 5 أحرف مثل "ABCDE"، فإن R ستأخذ فقط أول حرفين من السلسلة البديلة ("AB") ليطابقا طول النطاق المستهدف المحدد بـ $(3 – 2 + 1 = 2)$، ولن تتوسع السلسلة الأصلية.
تُعد هذه الخاصية مفيدة جداً في عمليات تنقيح البيانات الحساسة وإخفاء الهوية (Anonymization)، مثل تشفير جزء من أرقام الهويات الوطنية أو بطاقات الائتمان (كاستبدال الأرقام الوسطى برمز "XXXX" مع الحفاظ على طول السلسلة الإجمالي وشكل الحقل دون أي تشويه في التنسيق البنيوي لقاعدة البيانات).
9. التعامل مع المتجهات وتطبيق خاصية التدوير المتقدمة (Vectorization)
9.1 تمرير متجهات غير متساوية الطول لوسائط دالة substring
تعد خاصية المعالجة المتجهية (Vectorization) حجر الزاوية في كفاءة لغة R البرمجية. تدعم دالة substring تمرير متجهات متعددة لجميع وسائطها (النص، البداية، النهاية)، وعندما تختلف أطوال هذه المتجهات، تطبق R “قاعدة إعادة التدوير” (Recycling Rule). بموجب هذه القاعدة، يتم تكرار عناصر المتجه الأقصر دورياً حتى يتطابق طوله مع طول أطول متجه ممرر للدالة.
لنأخذ مثالاً متقدماً يوضح هذه الخاصية بدقة؛ إذا قمنا بتمرير سلسلة نصية واحدة فقط إلى الوسيط text مع متجهات تحتوي على مواضع بداية ونهاية متعددة، كما يلي:
sample_text <- "STATISTICS"
sub_parts <- substring(sample_text, first = c(1, 3, 5), last = c(2, 4, 8))
في هذا المثال، تم تكرار النص الفردي ثلاث مرات تلقائياً ليطابق طول متجهي البداية والنهاية (المكونين من 3 عناصر)، مما ينتج متجراً نصياً ثلاثي العناصر يحتوي على:
- المقطع الأول (من 1 إلى 2):
"ST". - المقطع الثاني (من 3 إلى 4):
"AT". - المقطع الثالث (من 5 إلى 8):
"ISTI".
ومع ذلك، يجب التعامل مع إعادة التدوير بحذر؛ فإذا كانت أطوال المتجهات غير متطابقة ولا تمثل مضاعفات عددية صحيحة لبعضها البعض، فإن R ستكمل العملية ولكنها قد تصدر تحذيراً برمجياً (Warning: longer object length is not a multiple of shorter object length)، مما يستدعي التأكد دائماً من اتساق أطوال المتجهات المدخلة لتجنب الاقتطاعات غير المقصودة.
9.2 تطبيق الدالة عبر هياكل بيانات متعددة الأبعاد
لا يقتصر عمل دالة substring على المتجهات البسيطة وأعمدة أطر البيانات، بل يمتد ليشمل المصفوفات (Matrices) والجداول متعددة الأبعاد والقوائم (Lists). عند تمرير مصفوفة نصية إلى الدالة، يتم تطبيق الاقتطاع على جميع العناصر مع الاحتفاظ بأبعاد المصفوفة وشكلها الهيكلي وأسماء الصفوف والأعمدة (Dimnames)، مما يجعلها ممتازة لمعالجة البيانات الجدولية الضخمة دفعة واحدة.
في حالة التعامل مع القوائم التي تحتوي على متجهات نصية متباينة الأطوال، يمكن دمج دالة substring بكفاءة عالية مع عائلة دوال apply، لا سيما lapply وvapply. يتيح ذلك تطبيق عمليات الاقتطاع الموضعي عبر آلاف العناصر النصية غير المتجانسة بشكل متوازٍ وسريع:
text_list <- list(group1 = c("Alpha", "Beta"), group2 = c("Gamma", "Delta"))
extracted_list <- lapply(text_list, function(x) substring(x, 1, 2))
يضمن هذا الأسلوب الحفاظ على البنية الهرمية للبيانات المعقدة مع استغلال السرعة الفائقة لدوال لغة C المضمنة في النواة الحسابية لدالة substring، وهو الأسلوب المفضل في معالجة مصفوفات التعبير الجيني والنصوص اللغوية الكبيرة.
10. إدارة القيم المفقودة (NA) والحالات الشاذة في المعالجة النصية
10.1 سلوك دالة substring مع القيم الفارغة والناقصة
تُعد معالجة القيم المفقودة (Missing Values) التي يرمز لها في R بالرمز NA من أهم متطلبات المعالجة الإحصائية الرصينة. تتبع دالة substring مبدأ “انتشار القيم المفقودة” (Propagation of NA) المعتمد في R؛ فإذا كان أي عنصر في المتجه النصي المدخل يحمل القيمة NA، فإن ناتج اقتطاع ذلك العنصر سيكون حتماً NA_character_ بغض النظر عن قيم وسائط البداية والنهاية المحددة.
أما بالنسبة للسلاسل النصية الفارغة ذات الطول صفر (Empty Strings الممثلة بـ "")، فإن دالة substring تتعامل معها بمنطقية تامة، حيث تُعيد سلسلة نصية فارغة "" دون التسبب في خطأ برمجية. وبالمثل، إذا كانت قيمة الوسيط first أكبر تماماً من الوسيط last (مثل first = 5, last = 3)، فإن الدالة تُعيد سلسلة فارغة أيضاً.
لتجنب المشكلات الحسابية في المراحل المتقدمة من التحليل، يوصى دائماً بتصفية المتجهات النصية أو فحص احتوائها على قيم مفقودة باستخدام دوال التحقق مثل is.na() قبل تطبيق الاقتطاع، أو استخدام دالة complete.cases() لعزل السجلات المكتملة داخل إطار البيانات، لضمان اتساق القياسات الإحصائية وحسابات الترددات الفئوية.
10.2 معالجة المحارف الخاصة والترميز الدولي (Encoding)
في بيئات العمل العالمية التي تتعامل مع نصوص متعددة اللغات، يبرز تأثير ترميز المحارف (Character Encoding) كعامل حاسم في دقة المعالجة النصية. تدعم R ترميزات متعددة أشهرها UTF-8 وترميز ASCII القياسي وتنسيقات الرموز متعددة البايت (Multi-byte characters) مثل النصوص العربية والشرق آسيوية.
تتعامل دالة substring في الإصدارات الحديثة من لغة R مع السلاسل النصية على مستوى “المحارف المرئية” (Characters) وليس على مستوى “البايتات الفردية” (Bytes)، وذلك اعتماداً على إعدادات بيئة اللغة (Locale). هذا يعني أنه عند اقتطاع نص عربي مثل "الإحصاء" وتحديد first = 1, last = 3، ستقوم الدالة باقتطاع أول 3 أحرف فعلية ("الإح") بدقة، على الرغم من أن كل حرف عربي قد يشغل بايتين أو أكثر في الذاكرة الحية.
لضمان عدم حدوث تشوهات في النصوص عبر أنظمة التشغيل المختلفة (مثل الانتقال بين Windows وLinux وmacOS)، يُنصح بالتحقق من ترميز المتجهات باستخدام دالة Encoding(text) وتحويلها صراحة إلى ترميز موحد باستخدام enc2utf8() قبل تطبيق عمليات الاقتطاع الفهرسي، مما يضمن اتساقاً مطلقاً في نتائج التحليل عبر كافة منصات الحوسبة الإحصائية.
11. المقارنة الأدائية: دالة substring مقابل حزم المعالجة الحديثة
11.1 المقارنة مع دالة str_sub من حزمة stringr
مع تطور بيئة R وظهور منظومة مكتبات Tidyverse، ظهرت حزم متخصصة في تبسيط المعالجة النصية، أبرزها حزمة stringr التي تقدم دالة str_sub() المبنية داخلياً على محرك حزمة stringi فائق القوة المكتوب بلغة C++. تبرز مقارنة منهجية بين substring وstr_sub في عدة نقاط جوهرية:
- دعم الفهارس السالبة: تتيح دالة
str_subاستخدام الأرقام السالبة للعد المباشر من نهاية السلسلة (مثلاًstr_sub(text, -3, -1)لاستخراج آخر 3 أحرف)، بينما تتطلب دالةsubstringدمج دالةnchar()للوصول إلى نفس النتيجة برمجياً. - الاتساق النحوي والتكامل: تتكامل دوال
stringrبسلاسة فائقة مع مشغلات الأنابيب البرمجية (Pipes) سواء المشغل التقليدي%>%أو المشغل الأصلي في R الحديثة|>، مما يجعل الشيفرة أسهل في القراءة الصريحة من اليسار إلى اليمين. - الاعتمادية الخارجية: تتميز دالة
substringبأنها دالة أصلية مدمجة في Base R، مما يعني أنها لا تتطلب تثبيت أي حزم خارجية أو تحميل تبعيات برمجية إضافية، وهو عامل حاسم في البيئات الإنتاجية المغلقة والمدمجة ذات المتطلبات الصارمة في الاستقرار.
يوضح هذا التباين أن الاختيار بين الدالتين يعتمد على طبيعة المشروع؛ فبينما توفر حزمة stringr واجهة استخدام مريحة وسريعة التطوير، تظل substring الخيار الأول للشيفرات الأساسية المستقلة والخفيفة.
11.2 تحليل الأداء الحاسوبي واستهلاك الذاكرة (Benchmarking)
عند معالجة مجموعات البيانات الضخمة (Big Data) التي تحتوي على ملايين السجلات النصية، تصبح السرعة الزمنية واستهلاك الذاكرة الحية المعيار الحاسم لتقييم الدوال البرمجية. لقياس الأداء الحسابي بدقة، نلجأ إلى حزم الاختبارات المعيارية مثل microbenchmark لمقارنة سرعة تنفيذ substring مقابل بدائلها الحديثة.
تُظهر نتائج الاختبارات المعيارية على متجهات نصية تضم مليون عنصر أن دالة substring المدمجة في Base R تتفوق في كثير من الأحيان من حيث انخفاض استهلاك الذاكرة وسرعة البدء (Overhead Time) مقارنة بالدوال المغلفة في الحزم الخارجية، وذلك لعدم وجود طبقات تحقق وسيطة إضافية. ومع ذلك، تتفوق الدوال المعتمدة على محرك stringi في العمليات شديدة التعقيد التي تتطلب معالجة متعددة الخيوط (Multithreading) أو نصوصاً ضخمة تحتوي على ترميزات معقدة للغاية.
يوصى بالاعتماد على دالة substring في الحالات التي تتطلب استقطاعات موضعية مباشرة داخل الحلقات التكرارية السريعة وفي بناء الحزم البرمجية المستقلة (Standalone R Packages)، لضمان تقليل زمن التنفيذ الإجمالي وتقليل الاعتماد على المكتبات الخارجية التي قد تتغير بنيتها عبر التحديثات الزمنية.
12. أفضل الممارسات البرمجية والتوصيات التطبيقية لمحللي البيانات
12.1 كتابة شيفرات نظيفة وقابلة لإعادة الاستخدام في R
تتطلب كتابة الشيفرات البرمجية الاحترافية في بيئة R مراعاة معايير الجودة والوضوح لضمان سهولة الصيانة والتعاون البحثي. عند استخدام دالة substring في مشاريع تنظيف البيانات، ينبغي الالتزام بمجموعة من التوصيات الهيكلية:
- التسمية الوصفية للأعمدة: احرص دائماً على تسمية الأعمدة الجديدة المشتقة بأسماء تعكس بدقة نطاق الاقتطاع الموضعي (مثل استخدام
prefix_3أوyear_codeبدلاً من مسميات مبهمة مثلsub1أوtemp). - التحقق البرمجي من صحة المدخلات (Data Validation): عند كتابة دوال مخصصة تعتمد على الاقتطاع، قم بتضمين شروط تحقق باستخدام
stopifnot()للتأكد من أن المدخلات هي بالفعل متجهات نصية وليست عوامل فئوية، وللتأكد من أن وسائط البداية والنهاية هي قيم عددية موجبة وصحيحة. - التوثيق البرمجي لمنطق الاقتطاع: قم دائماً بإضافة تعليقات توضيحية تشرح السبب المنطقي لاختيار مواضع الفهرسة المحددة، لا سيما إذا كانت البيانات تعتمد على أكواد مركبة أو مواصفات قياسية دولية (مثل معايير ISO أو أكواد السجلات الطبية ICD).
يساهم تطبيق هذه المعايير في بناء خطوط معالجة بيانات قوية وموثوقة يسهل تدقيقها وتطويرها مستقبلاً من قبل فرق العمل الإحصائية المتكاملة.
12.2 قائمة التحقق لتفادي الأخطاء الشائعة أثناء الاقتطاع النصي
لتجنب الأخطاء البرمجية والحسابية الشائعة أثناء تطبيق عمليات الاقتطاع النصي باستخدام دالة substring، يُنصح بمراجعة قائمة التحقق التالية قبل اعتماد الشيفرة في خطوط الإنتاج:
- مراجعة نوع البيانات: التأكد من أن العمود المستهدف ليس من النوع
factor؛ فإذا كان كذلك، يجب تحويله صراحة عبرas.character()لتفادي تشويه مستويات العامل الفئوي أثناء التعديل. - فحص حدود الفهرسة: التحقق من عدم عكس قيم البداية والنهاية (حيث يؤدي جعل $first > last$ إلى إرجاع سلاسل فارغة دون أخطاء صريحة، مما قد يخفي مشاكل منطقية في الحساب).
- معالجة التباين الطولي: عند استخراج اللواحق من نهايات النصوص، تأكد دائماً من دمج دالة
nchar()بشكل ديناميكي بدلاً من استخدام فهارس ثابتة قد لا تناسب جميع السجلات. - التعامل مع القيم المفقودة: التحقق من كيفية تأثير قيم
NAعلى التحليلات اللاحقة، والتأكد من تصفيتها أو معالجتها مسبقاً بما يتوافق مع أهداف الدراسة الإحصائية.
تضمن هذه القائمة الاسترشادية لمحللي البيانات تنفيذ عمليات التحويل النصي بكفاءة مطلقة وأعلى درجات الدقة الإحصائية والبرمجية.
خاتمة
استعرض هذا المقال الشامل دالة substring في لغة البرمجة الإحصائية R كإحدى أهم الأدوات الأساسية في معالجة واستقطاع وتعديل السلاسل المحرفية. ومن خلال تفكيك بنيتها النحوية وتحليل نظام الفهرسة الموضعي، تعرفنا على كيفية توظيف هذه الدالة عبر أربعة أمثلة تطبيقية رئيسية غطت استخراج المقاطع الوسطية، والبوادئ الثابتة والمتغيرة، واللواحق الديناميكية باستخدام دالة nchar()، وصولاً إلى استغلال قدرتها المتقدمة كدالة إسناد لتعديل النصوص موضعياً.
كما بينت المقارنات المعيارية والأدائية المكانة الفريدة التي تتمتع بها دوال النواة الأساسية في Base R، حيث تجمع بين خفة الوزن والاستقلالية التامة عن الحزم الخارجية والسرعة الحسابية الفائقة الناتجة عن التنفيذ المباشر في طبقة لغة C. يظل الإتقان العميق لهذه الدوال الأساسية مهارة جوهرية لكل عالم بيانات ومحلل إحصائي يسعى إلى بناء خطوط معالجة بيانات متينة، قابلة للتكرار، وقادرة على إدارة البيانات النصية المعقدة بكفاءة وموثوقية عالية.
References
- Becker, R. A., Chambers, J. M., & Wilks, A. R. (1988). The New S Language: A Programming Environment for Data Analysis and Graphics. Wadsworth & Brooks/Cole. https://doi.org/10.1201/9781351074988
- Gagolewski, M. (2022). stringi: Fast and portable character string processing in R. Journal of Statistical Software, 103(2), 1–59. https://doi.org/10.18637/jss.v103.i02
- R Core Team. (2023). R: A language and environment for statistical computing. R Foundation for Statistical Computing, Vienna, Austria. https://www.R-project.org/
- Wickham, H. (2019). Advanced R (2nd ed.). Chapman and Hall/CRC. https://doi.org/10.1201/9781351201315
- Wickham, H., & Grolemund, G. (2017). R for Data Science: Import, Tidy, Transform, Visualize, and Model Data. O’Reilly Media. https://r4ds.had.co.nz/