كيف تعمل الطباعة في R
لدى R عادة لا تشاركها فيها أي لغة رئيسية أخرى: ففي الطرفية لا تحتاج إلى تعليمة طباعة أصلًا. اكتب اسم متغير واضغط Enter فتطبع R قيمته. تلك هي الطباعة التلقائية، ولهذا تمتلئ دروس R بتعبيرات مجردة.
لكن للطباعة التلقائية حدًا صارمًا: فهي لا تنطلق إلا للتعبيرات في المستوى الأعلى. أما داخل حلقة for أو جسم دالة، فتُقيَّم القيمة المجردة ثم تُرمى بصمت:
يُطبع x المنفرد (لأنه في المستوى الأعلى)، ولا تطبع الحلقة الأولى شيئًا على الإطلاق، وتطبع الثانية 1 و2 و3 لأنها تطلب ذلك صراحة. وسؤال "حلقتي تعمل لكنها لا تعرض شيئًا" من أكثر أسئلة R تكرارًا، وهذا هو الجواب كاملًا: داخل الحلقات والدوال، الطباعة مسؤوليتك دائمًا. (وينطبق الأمر نفسه حين تشغّل نصًا برمجيًا عبر Rscript - فما نجح سطرًا بسطر في الطرفية قد يصمت داخل حلقة.)
print(): الرسمية
تعرض print() القيمة كما تراها R - وهو ما يجعلها الأداة الصحيحة لـ فحص البيانات، والخاطئة للمخرجات المصقولة:
يبرز أمران. تحتفظ السلاسل النصية بـ علامات الاقتباس - فـ print() تعرض عليك تمثيلًا أمينًا، وعلامات الاقتباس تخبرك أن القيمة نصية. ويبدأ كل سطر برقم بين قوسين معقوفين مثل [1]: وهو فهرس أول عنصر في ذلك السطر، فحين يلتف متجه طويل على أسطر جديدة، يخبرك القوس في كل سطر بالعنصر الذي يستأنف عنده. إنه عون للقراءة في المتجهات، لا ضجيج.
وإن أردت مخرجات بأسلوب print() دون علامات اقتباس، فكل من print(x, quote = FALSE) وnoquote(x) يفي بالغرض - لكن ما تريده فعلًا عند تلك النقطة عادة هو cat().
cat(): مخرجات للبشر
تكتب cat() (اختصار "ادمج واطبع") نصًا خامًا: لا علامات اقتباس، ولا بادئات فهرسة، ووسائطها موصولة بمسافات. وثمن تلك السيطرة هو أن لا شيء يُضاف نيابة عنك - بما في ذلك السطر الجديد:
ونسيان "\n" هو طقس العبور العالمي مع cat() - فبدونه يلتصق الجزء التالي من المخرجات بالسطر نفسه. اجعل إنهاء استدعاءات cat() بالرمز "\n" انعكاسًا تلقائيًا.
والفرق في علامات الاقتباس بين الدالتين هو في حقيقته فرق في ما تعرضانه. فـ print() تعرض تسلسلات الهروب كما كتبتها؛ بينما cat() تصيّرها:
تعرض print() الرمزين \t و\n حرفيًا داخل علامات الاقتباس - وهو مفيد حين تصحح محتوى سلسلة نصية بدقة. وتحولهما cat() إلى مسافة جدولة حقيقية وفاصل أسطر حقيقي - وهو مفيد حين تنتج مخرجات. اختر بحسب النية: print() للفحص، وcat() للعرض.
التقارير المنسّقة: sprintf() مع cat()
وسائط cat() المفصولة بمسافات لا تكفي إلا إلى حد. فللمخرجات المنسّقة والمحاذاة - كسور ثابتة، وعدادات محشوة - ابنِ السلسلة بـ sprintf() وسلّمها إلى cat():
يحشو %-8s كل اسم إلى ثمانية محارف، ويمنح %6.2f كل درجة العرض نفسه ومنزلتين عشريتين، فتصطف الأعمدة. وهذا الثنائي هو حصان العمل في R الأساسية لرسائل التقدم والتقارير النصية الصغيرة (ورموز التنسيق مشروحة في السلاسل النصية).
message() وwarning(): القناة الأخرى
تكتب cat() وprint() إلى المخرج القياسي - وهو التيار الذي يحمل نتائج برنامجك. أما message() وwarning() فتكتبان إلى خطأ قياسي، وهو التيار المخصص للتعليق على سير التنفيذ:
على الشاشة يبدو الاثنان متشابهين، لكن الفصل بينهما يصبح مهمًا لحظة إعادة توجيه المخرجات: وجّه المخرج القياسي لنصك البرمجي إلى ملف، فتستقر القيمة 42 في الملف بينما تبقى ملاحظة الحالة في الطرفية. وهذه هي القاعدة العملية - النتائج إلى المخرج القياسي بـ cat()، والتعليقات إلى الخطأ القياسي بـ message(). ولهذا أيضًا تكون message() (لا cat()) هي الطريقة المهذبة لتخاطب الدوال والحزم مستخدميها: إذ يستطيع المستدعي إسكاتها بـ suppressMessages() دون فقدان المخرجات الحقيقية.
أما warning() فأقوى - بمعنى "ثمة خطب ما، لكني تابعت" - وحين يعمل نص برمجي عبر Rscript، تُجمع التحذيرات وتُعرض معًا في النهاية بدل عرضها في مواضعها (راجع تصحيح الأخطاء لقراءتها).
قراءة مدخلات المستخدم: readline() وreadLines()
لسؤال المستخدم في جلسة تفاعلية (طرفية R أو RStudio)، استخدم readline(). وهذه الأمثلة غير قابلة للتشغيل في المحرر المدمج - فمشغّل الصفحة لا يملك لوحة إدخال - لذا جربها في طرفية محلية:
# Interactive sessions only:
name <- readline("What is your name? ")
cat("Hello,", name, "\n")
age <- as.numeric(readline("Your age? "))
cat("Next year you'll be", age + 1, "\n")
هناك أمران يجدر تذكرهما. تعيد readline() دائمًا سلسلة نصية - فحوّلها بـ as.numeric() قبل إجراء أي حساب، وتحقق من النتيجة بحثًا عن NA تحسبًا لأن يكون المستخدم قد كتب شيئًا ليس رقمًا. وفي نص برمجي غير تفاعلي، لا تنتظر readline() أي إدخال أصلًا؛ بل تعيد "" فورًا.
وللنص البرمجي الذي يعمل عبر Rscript ويُفترض أن يقرأ مدخلات ممرّرة أو مكتوبة، اقرأ من الدخل القياسي بدلًا من ذلك:
# In a script run as: echo "Ada" | Rscript greet.R
line <- readLines(con = "stdin", n = 1)
cat("Hello,", line, "\n")
وتقرأ readLines(con = "stdin") دون الوسيط n كل سطر حتى نهاية المدخلات - وهي الصيغة القياسية للنصوص البرمجية بأسلوب المرشِّح. أما قراءة ملفات البيانات فمهمة مختلفة بأدوات أفضل؛ ابدأ بـ قراءة ملفات CSV حين تكون مدخلاتك مجموعة بيانات لا لوحة مفاتيح.
ما تخرج به
- الطباعة التلقائية تحدث في المستوى الأعلى فقط - وداخل الحلقات والدوال استدعِ
print()أوcat()بنفسك. - تفحص
print(): علامات اقتباس على السلاسل، وبادئات فهرسة[1]، وتسلسلات هروب معروضة حرفيًا. - تعرض
cat(): نص خام، ووسائط موصولة بمسافات، وأنت من يوفر"\n". - الثنائي
sprintf()معcat()هو نمط R الأساسية للمخرجات المنسّقة والمحاذاة. - النتائج تذهب إلى المخرج القياسي (
cat)، والتعليقات إلى الخطأ القياسي (messageوwarning). - الدالة
readline()للمدخلات التفاعلية (وهي سلسلة نصية دائمًا!)؛ وreadLines(con = "stdin")مع Rscript.
التالي: المعاملات - الحسابية، والمقارنة، والمنطقية التي تحرّك كل شرط ستكتبه.
الأسئلة الشائعة
ما الفرق بين print() وcat() في R؟
تعرض print() القيمة بالطريقة التي تمثلها بها R - فتحتفظ السلاسل النصية بعلامات الاقتباس ويحصل كل سطر على بادئة فهرس بأسلوب [1]. أما cat() فتكتب نصًا خامًا دون علامات اقتباس ودون فهارس ودون سطر جديد تلقائي (فتضيف "\n" بنفسك). استخدم print() لفحص الكائنات، وcat() لإنتاج مخرجات موجهة للبشر.
لماذا لا تطبع حلقة R لدي أي شيء؟
الطباعة التلقائية تحدث للتعبيرات في المستوى الأعلى فقط. فداخل حلقة for أو جسم دالة، يُقيَّم التعبير المجرد x ثم يُهمل بصمت. غلّفه صراحة: print(x) أو cat(x, "\n").
كيف تقرأ مدخلات المستخدم في R؟
في جلسة تفاعلية، تقرأ readline("prompt: ") سطرًا واحدًا من المدخلات المكتوبة على هيئة سلسلة نصية (حوّله بـ as.numeric() إن احتجت رقمًا). أما في نص برمجي يعمل عبر Rscript فإن readline() لا تنتظر - اقرأ من الدخل القياسي عبر readLines(con = "stdin", n = 1) بدلًا منها.
كيف تطبع دون علامات اقتباس في R؟
استخدم cat("hello\n") - فهي لا تضيف علامات اقتباس أبدًا. وإذا أردت تحديدًا سلوك print() دون علامات اقتباس، فإن print("hello", quote = FALSE) أو noquote("hello") تعملان أيضًا.