مقال تقني

دوال Excel الإحصائية في Delphi: NORM وCHISQ وBETA

اكتب =NORM.DIST(115,100,15,TRUE) في خلية فيُعيد Excel القيمة 0.8413447 دون أي تكلّف. ويبدو الاستدعاء كأنه بحث في جدول. لكنه ليس كذلك. فخلف ذلك الرقم الواحد يقف التوزيع الطبيعي التراكمي، وهو تكامل لا صيغة مغلقة له، وخلف CHISQ.INV.RT وBETA.DIST تقبع دوال خاصة يتعيّن على المكتبة المتأنّية أن تقيّمها، لا أن تقرّبها يدويًا. ومكوّن جداول البيانات الذي يدّعي التوافق مع Excel عليه أن يعيد إنتاج هذه القيم حتى آخر رقم يعرضه Excel، ما يعني إعادة إنتاج الطرق العددية لا مجرد أسماء الدوال

ينفّذ HotXLS أكثر من خمسين من هذه الدوال الإحصائية، والعمل الذي يجعلها صحيحة يكاد يكون خفيًا بالكامل عن شريط الصيغ. وهذه جولة في كيفية حساب المحرك لها: نواة الدوال الخاصة المشتركة، وقرارات التفريع التي تُبقي الحساب مستقرًا، وخطأ واحد في المعكوس الطبيعي اختبأ في الذيل مدة طويلة لأن الحالة الشائعة لم تلمس السطر المعطوب قط

استدعاء واحد في ورقة العمل، وخمسون توزيعًا خلفه

تمتد الدوال عبر العائلات التي يلجأ إليها مصنّف إحصائي. فهناك العائلة الطبيعية، NORM.DIST وNORM.S.DIST مع معكوساتهما؛ وعائلة غاما ومربع كاي، GAMMA.DIST وCHISQ.DIST وCHISQ.DIST.RT وCHISQ.INV.RT؛ وعائلة بيتا، BETA.DIST وBETA.INV؛ وتوزيعات المعاينة T.DIST وT.DIST.2T وF.DIST وF.INV؛ والثنائي المتقطّع BINOM.DIST وPOISSON.DIST؛ ومساعدات الاستدلال مثل CONFIDENCE.T وCONFIDENCE.NORM. ومن مقعد المستدعي كل منها صيغة واحدة. تضع المدخلات في خلايا، وتطلب من المصنّف التقييم، وتقرأ النتيجة

var
  wb: IXLSWorkbook;
  sh: IXLSWorksheet;
begin
  wb := TXLSWorkbook.Create;
  sh := wb.Sheets.Add;
  sh.Range['A1', 'A1'].Value := 115;   // المشاهدة
  sh.Range['A2', 'A2'].Value := 100;   // المتوسط
  sh.Range['A3', 'A3'].Value := 15;    // الانحراف المعياري

  // يستخدم محلّل صيغ XLS الفاصلة المنقوطة ';' فاصلًا بين الوسائط.
  Writeln(wb.Calculate('=NORM.DIST(A1;A2;A3;TRUE())'));   // 0.8413447
  Writeln(wb.Calculate('=CHISQ.INV.RT(0.05;10)'));        // 18.3070381
  Writeln(wb.Calculate('=BETA.DIST(0.5;2;3;TRUE())'));    // 0.6875
end;

تُصرّف طريقة Calculate في المصنّف صيغة مخصّصة وتقيّمها على الورقة الحية وتُعيد Variant. وثمة تفصيل يُعثّر الناس في المحاولة الأولى: فمحلّل الصيغ خلف Calculate يتخذ الفاصلة المنقوطة فاصلًا بين الوسائط، فهي =SUM(A1;B1) لا =SUM(A1,B1). أما صيغ الخلايا المخزَّنة فتحتفظ بالفاصلة القياسية في Excel. ويوزّع المُقيِّم نفسه كل دالة إحصائية أدناه، ولذا متى عملت إحداها في Calculate سلكت البقية المسار نفسه

الدالتان اللتان يُبنى عليهما كل ما عداهما

لا تُحسب معظم التوزيعات التراكمية في هذه المجموعة بجمع تعريفاتها أو تكاملها. بل تُحسب من دالتين خاصتين: دالة غاما الناقصة السفلى المنظَّمة، وتُكتب P(a, x)، ودالة بيتا الناقصة المنظَّمة، وتُكتب Ix(a, b). وداخليًا هاتان هما المساعدتان اللتان تتكئ عليهما الموزِّعات، والسلسلة قصيرة. فدالة التوزيع التراكمي لمربع كاي هي دالة غاما التراكمية بشكل df/2 ومقياس 2. ودالة غاما التراكمية هي P(a, x) مباشرة. والدوال التراكمية لـ t وF وذات الحدين كلها قيم لدالة بيتا الناقصة المنظَّمة عند الوسائط المناسبة. ودالة بواسون التراكمية هي دالة غاما الناقصة العليا Q. نفّذ دالتي غاما وبيتا جيدًا فترث دزينة من التوزيعات دقتهما مجانًا

خريطة توزيع من عائلات دوال Excel الإحصائية في HotXLS نزولًا إلى نواة الدوال الخاصة: دالة غاما الناقصة المنظَّمة P(a, x) ودالة بيتا الناقصة المنظَّمة Ix(a, b)
يحسب HotXLS معظم التوزيعات التراكمية في جانب Delphi من دالتين خاصتين منظَّمتين. تخدم نواة غاما عائلتي مربع كاي وبواسون بينما تخدم نواة بيتا t وF وذات الحدين وبيتا

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

متسلسلة تحت القطر، وكسر مستمر فوقه

يتخذ روتين غاما الناقصة قرارًا واحدًا قبل أن يحسب أي شيء: يقارن x بـ a + 1. وهذا الحد ليس اعتباطيًا. فمفكوك متسلسلة القوى لـ P(a, x) يتقارب بسرعة حين تكون x صغيرة نسبةً إلى a، وببطء، بل بلا جدوى في النهاية، حين تكون x كبيرة. وللكسر المستمر الطابع المعاكس. لذا يستخدم المحرك متسلسلة القوى حين تكون x أقل من a + 1، وكسر Lentz المستمر حين تكون x عند a + 1 أو أعلى، ويُطلب من كل فرع أن يؤدي العمل الذي يجيده فقط

ويحتاج الكسر المستمر إلى حارس واحد. فطريقة Lentz تعمل بحمل بسط ومقام جاريين وقلب المقام في كل خطوة، وإذا اقترب أي منهما من الصفر انفجر القلب. والحل أرضية ضئيلة: كلما هبط حد وسيط تحت نحو 1e-30 في المقدار يُقيَّد إلى 1e-30، ما يُبقي التكرار منتهيًا دون الإخلال بالقيمة المتقاربة. ويظهر التقييد نفسه في الكسر المستمر لدالة بيتا الناقصة للسبب نفسه. إنه ثابت صغير يؤدي عملًا حاملًا للأثقال، الفرق بين تقييم مستقر وقسمة على شيء لا يُميَّز عن الصفر

أما الذيل العلوي، Q(a, x)، فهو ببساطة 1 ناقص P(a, x)، وهكذا يُحسب الفرع التراكمي لبواسون: فاحتمال وقوع k حدثًا على الأكثر بمتوسط λ هو Q(k + 1, λ). وتوجيهه عبر دالة غاما الناقصة العليا بدلًا من جمع k + 1 من حدود بواسون هو، مرة أخرى، اختيار لتقييم تعبير متقارب واحد بدلًا من مراكمة كثير من الحدود الصغيرة

كتل متقطّعة دون طفحان المضروب

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

// كتلة الاحتمال ذات الحدين، مقيَّمة بالكامل في فضاء اللوغاريتم.
// LnGammaF(n+1) هي ln(n!)؛ تكوّن لوغاريتمات المضروب الثلاثة ln(C(n,k))،
// ويُبنى الأس كاملًا قبل استدعاء Exp واحد.
//   ln P(X=k) = ln(n!) - ln(k!) - ln((n-k)!) + k*ln(p) + (n-k)*ln(1-p)
result := Exp(LnGammaF(nt + 1) - LnGammaF(kk + 1) - LnGammaF(nt - kk + 1)
  + kk * Ln(pp) + (nt - kk) * Ln(1 - pp));

ودالة لوغاريتم غاما نفسها تقريب Lanczos، دقيق عبر المحور الموجب كله ورخيص التقييم. ولأن كل كمية كبيرة تُحفظ بلوغاريتمها حتى Exp الأخيرة، فإن أكبر عدد يجسّده الروتين على الإطلاق هو الاحتمال نفسه، وهو واحد على الأكثر. وتتبع دالة كتلة بواسون الوصفة نفسها، مع حد لوغاريتم غاما الوحيد قائمًا مقام المضروب في المقام. وتُعالج الصيغ المغلقة كحالات خاصة عند الأطراف، حيث p صفر أو واحد تمامًا، كي لا يستدعي الكود Ln(0) أبدًا. ويُعيد HotXLS القيمة 0.2460938 لـ BINOM.DIST(5,10,0.5,FALSE) و0.6766764 للتراكمية POISSON.DIST(2,2,TRUE)، مطابقًا Excel عبر الأرقام التي يطبعها

المعكوسات بحصر المنحنى الأمامي

تطرح دالة التوزيع المعكوسة السؤال المعاكس: بمعلومية احتمال، أوجد القيمة التي تساويه دالتها التراكمية. ومعكوس واحد فقط في هذه المجموعة له صيغة مباشرة سريعة. فـ NORM.S.INV، معكوس التوزيع الطبيعي المعياري، يستخدم تقريب Acklam الكسري، وهو زوج من نسب كثيرات الحدود دقيق إلى ما يقارب دقة العدد المضاعف عبر المدى كله، مقسّم إلى منطقة مركزية وذيلين. إنه تقييم بصيغة مغلقة دون أي تكرار

أما المعكوسات الأخرى فلا صيغة كهذه لها، فيعكسها المحرك عدديًا. يحصر الجواب بحد أدنى وحد أعلى مختارين من مدى التوزيع، ثم ينصّف: يقيّم الدالة التراكمية الأمامية عند نقطة المنتصف، ويحرّك أيًّا من الحدين يُبقي الاحتمال المستهدف محصورًا، ويكرّر حتى تضيق الفترة. ولمعكوسي غاما ومربع كاي يبدأ الحصر من الصفر وتقدير علوي سخي مبني من الشكل والمقياس، مع مضاعفة الحد الأعلى إن لم يكن الاحتمال محصورًا بعد. ويحصر معكوس t حدودًا متناظرة تتسع نحو الخارج؛ وينصّف معكوس F على فترة غير سالبة. والكلفة بضع عشرات من تقييمات الدالة التراكمية لكل استدعاء، وهو ما لا يُلحظ بسرعة جداول البيانات، والفائدة أن كل معكوس دقيق بالضبط بقدر الدالة الأمامية التي يعكسها. ولهذا تُعيد رحلة ذهاب وإياب مثل CHISQ.DIST(CHISQ.INV(0.7,5),5,TRUE) القيمة 0.7 بفارق شعرة

طرق التوزيع المعكوس في HotXLS: تقريب Acklam بصيغة مغلقة لـ NORM.S.INV بمنطقتي المركز والذيل، والعكس بالحصر والتنصيف للمعكوسات الإحصائية الأخرى في Delphi
يعكس NORM.S.INV التوزيع الطبيعي عبر تقريب Acklam الكسري دون تكرار. وكل معكوس آخر يحصر الدالة التراكمية الأمامية وينصّف حتى تنغلق رحلة الذهاب والإياب

لوغاريتم الأساس 10 الذي اختبأ في الذيل

وهذا هو الخطأ الجدير بالحكاية، لأنه من النوع الذي ينجو مدة طويلة. فلروتين Acklam للمعكوس الطبيعي ثلاثة فروع. الفرع المركزي الواسع، المستخدم كلما وقع الاحتمال بين نحو 0.025 و0.975، يمرّر المدخل عبر نسبة كثيرات حدود لا لوغاريتم فيها في أي موضع. أما فرعا الذيل، للاحتمالات الصغيرة جدًا أو الكبيرة جدًا، فيأخذ كل منهما لوغاريتم المدخل أولًا، لأن الذيل يسلك سلوك الجذر التربيعي لسالب اللوغاريتم الطبيعي لـ p

وكانت نسخة مبكرة من فرع الذيل تأخذ لوغاريتمًا بالأساس 10 حيث كان ينبغي اللوغاريتم الطبيعي. ويختلف الاثنان بعامل ثابت يبلغ نحو 2.30، فكانت نتائج الذيل خاطئة بهامش متّسق وكبير. ومع ذلك بدت الدالة سليمة في كل فحص عابر، لأن الفحوص العابرة تقيم في المنتصف. فـ NORM.S.INV(0.5) صفر، وNORM.S.INV(0.975) هي 1.959964 المعروفة في الكتب، وكلتاهما تمرّان عبر كثيرة الحدود المركزية التي لا تستدعي لوغاريتمًا قط. ولم يظهر الخطأ إلا حين عبر احتمال إلى ذيل، مثل NORM.S.INV(0.001)، التي يجب أن تُعيد -3.0902323 لكنها عادت منحرفة بما يقارب نسبة اللوغاريتم الطبيعي إلى الأساس 10. وكل دالة تعتمد على المعكوس الطبيعي في ذيله، بما فيها مساعدات فترات الثقة، ورثت الانحراف نفسه. والدرس عادي ومكلف: الدالة ذات البنية المتفرّعة تحتاج إلى نقاط اختبار داخل كل فرع، لأن المسار الشائع الصحيح سيحجب بكل سرور مسارًا نادرًا معطوبًا. وكان الإصلاح تغيير رمز واحد من لوغاريتم الأساس 10 إلى اللوغاريتم الطبيعي، فانطبقت قيم الذيل على قيم Excel

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

إشارة x تقرّر ذيل توزيع t

تحمل دالة توزيع Student t التراكمية دقة يسهل قلبها. فقيمتها تأتي من دالة بيتا الناقصة المنظَّمة مقيَّمة عند df / (df + x²)، لكن قيمة بيتا تلك هي الاحتمال في الذيل الواقع خلف مقدار x، لا الاحتمال التراكمي حتى x. والشكل المتناظر لتوزيع t يعني أن التحويل يعتمد على أي جانب من الصفر تقع فيه x

// دالة التوزيع التراكمي لـ Student t. ib هي دالة بيتا الناقصة المنظَّمة عند df/(df+x*x)،
// وهي تقيس الذيل المتناظر. تعتمد القيمة التراكمية على
// إشارة x؛ وإعادة ib دون تحويل تعطي الذيل الخاطئ.
ib := BetaIF(df / 2, 0.5, df / (df + x * x));
if x > 0 then
  result := 1 - 0.5 * ib        // فوق المتوسط: واحد ناقص نصف الذيل
else if x < 0 then
  result := 0.5 * ib            // تحت المتوسط: نصف الذيل
else
  result := 0.5;                // عند المتوسط تمامًا

فحين تكون x فوق الصفر يكون الاحتمال التراكمي واحدًا ناقص نصف الذيل المتناظر؛ وحين تكون تحت الصفر يكون نصف ذلك الذيل؛ وعند الصفر يكون نصفًا بالضبط. أعد قيمة بيتا مباشرة فتُبلغ عن الجانب الخاطئ من التوزيع، منحرفًا بجسم المنحنى كله لأي x غير صفرية. وتُبنى صيغتا الذيل الأيمن والذيلين على الفرع نفسه، ولهذا تعود T.DIST.2T(1,1) بالقيمة 0.5 وT.DIST(1,1,TRUE) بالقيمة 0.75، وينصّف المعكوس T.INV على هذه الدالة التراكمية المصحَّحة كي تنغلق رحلة الذهاب والإياب

لا شيء من هذا مرئي من الخلية، وهذه هي النتيجة المقصودة. تكتب صيغة وتقرأ رقمًا يتفق مع Excel. وإذا كنت توسّع المحرك بمنطقك الخاص، فآليات تسجيل دالة مشروحة في جولتنا في محرك الصيغ والدوال المخصصة، وطريقة وصول الصيغ عبر الأوراق والنطاقات المسمّاة مشروحة في المقال عن الأسماء المعرَّفة والصيغ بين الأوراق. وكل ذلك يُشحن داخل مكوّن HotXLS لجداول البيانات في Delphi لـ Delphi وC++Builder، إلى جانب واجهات القراءة والكتابة والرسوم البيانية والتنسيق البرمجية المشروحة في مواضع أخرى من هذه المدونة