Missing Values
تقدم جوليا دعمًا لتمثيل القيم المفقودة بالمعنى الإحصائي. هذا لحالات حيث لا تتوفر قيمة لمتغير في ملاحظة، ولكن توجد قيمة صالحة نظريًا. يتم تمثيل القيم المفقودة عبر الكائن missing
، وهو مثيل فردي من النوع Missing
. missing
تعادل NULL
in SQL و NA
in R، وتتصرف مثلها في معظم الحالات.
Propagation of Missing Values
تتسبب القيم المفقودة
في انتشار تلقائي عند تمريرها إلى العمليات الرياضية القياسية والدوال. بالنسبة لهذه الدوال، فإن عدم اليقين بشأن قيمة أحد المعاملات يؤدي إلى عدم اليقين بشأن النتيجة. في الممارسة العملية، يعني ذلك أن عملية رياضية تتضمن قيمة مفقودة
تعيد عمومًا قيمة مفقودة
:
julia> missing + 1
missing
julia> "a" * missing
missing
julia> abs(missing)
missing
نظرًا لأن missing
هو كائن عادي في جوليا، فإن قاعدة الانتشار هذه تعمل فقط على الوظائف التي اختارت تنفيذ هذا السلوك. يمكن تحقيق ذلك من خلال:
- إضافة طريقة محددة معينة للوسائط من نوع
Missing
، - قبول الحجج من هذا النوع، وتمريرها إلى الدوال التي تنشرها (مثل العمليات الرياضية القياسية).
يجب على الحزم أن تأخذ في الاعتبار ما إذا كان من المنطقي تمرير القيم المفقودة عند تعريف وظائف جديدة، وأن تحدد الطرق بشكل مناسب إذا كان هذا هو الحال. تمرير قيمة missing
إلى دالة لا تحتوي على طريقة تقبل وسائط من نوع Missing
يؤدي إلى رمي MethodError
، تمامًا كما هو الحال مع أي نوع آخر.
يمكن جعل الدوال التي لا تنشر قيم missing
تنشرها عن طريق لفها في دالة passmissing
المقدمة من حزمة Missings.jl. على سبيل المثال، f(x)
تصبح passmissing(f)(x)
.
Equality and Comparison Operators
تتبع مشغلات المساواة والمقارنة القياسية قاعدة الانتشار المقدمة أعلاه: إذا كان أي من المعاملات مفقودًا
، فإن النتيجة تكون مفقودة
. إليك بعض الأمثلة:
julia> missing == 1
missing
julia> missing == missing
missing
julia> missing < 1
missing
julia> 2 >= missing
missing
بشكل خاص، لاحظ أن missing == missing
يُرجع missing
، لذا لا يمكن استخدام ==
لاختبار ما إذا كانت القيمة مفقودة. لاختبار ما إذا كان x
مفقودًا، استخدم ismissing(x)
.
مشغلات المقارنة الخاصة isequal
و ===
هي استثناءات لقواعد الانتشار. ستعيد دائمًا قيمة Bool
، حتى في وجود قيم missing
، مع اعتبار missing
متساويًا مع missing
ومختلفًا عن أي قيمة أخرى. يمكن استخدامها لذلك لاختبار ما إذا كانت القيمة missing
:
julia> missing === 1
false
julia> isequal(missing, 1)
false
julia> missing === missing
true
julia> isequal(missing, missing)
true
المشغل isless
هو استثناء آخر: تعتبر missing
أكبر من أي قيمة أخرى. يتم استخدام هذا المشغل بواسطة sort!
، الذي يضع بالتالي قيم missing
بعد جميع القيم الأخرى:
julia> isless(1, missing)
true
julia> isless(missing, Inf)
false
julia> isless(missing, missing)
false
Logical operators
العوامل المنطقية (أو البوليانية) |
، &
و xor
هي حالة خاصة أخرى لأنها تنقل فقط القيم المفقودة
عندما يكون ذلك مطلوبًا منطقيًا. بالنسبة لهذه العوامل، سواء كانت النتيجة غير مؤكدة أم لا، يعتمد على العملية المحددة. يتبع هذا القواعد المعروفة جيدًا لـ three-valued logic التي يتم تنفيذها بواسطة، على سبيل المثال، NULL
في SQL و NA
في R. تتوافق هذه التعريفات المجردة مع سلوك طبيعي نسبيًا يمكن شرحه بشكل أفضل من خلال أمثلة ملموسة.
دعونا نوضح هذه المبدأ باستخدام عامل "أو" المنطقي |
. وفقًا لقواعد المنطق البولياني، إذا كانت إحدى المعاملات true
، فإن قيمة المعاملة الأخرى ليس لها تأثير على النتيجة، والتي ستكون دائمًا true
:
julia> true | true
true
julia> true | false
true
julia> false | true
true
استنادًا إلى هذه الملاحظة، يمكننا أن نستنتج أنه إذا كان أحد المعاملين هو true
والآخر مفقود
، فإننا نعلم أن النتيجة هي true
على الرغم من عدم اليقين بشأن القيمة الفعلية لأحد المعاملين. إذا كنا قد تمكنا من ملاحظة القيمة الفعلية للمعامل الثاني، فلا يمكن أن تكون إلا true
أو false
، وفي كلا الحالتين ستكون النتيجة true
. لذلك، في هذه الحالة الخاصة، فإن عدم الوجود لا ينتشر:
julia> true | missing
true
julia> missing | true
true
على العكس، إذا كان أحد المعاملات هو false
، يمكن أن تكون النتيجة إما true
أو false
اعتمادًا على قيمة المعامل الآخر. لذلك، إذا كان ذلك المعامل مفقودًا، يجب أن تكون النتيجة مفقودة أيضًا:
julia> false | true
true
julia> true | false
true
julia> false | false
false
julia> false | missing
missing
julia> missing | false
missing
سلوك عامل "و" المنطقي &
مشابه لسلوك عامل |
، مع الفرق أن عدم الوجود لا ينتشر عندما يكون أحد المعاملات false
. على سبيل المثال، عندما يكون ذلك هو الحال بالنسبة للمعامل الأول:
julia> false & false
false
julia> false & true
false
julia> false & missing
false
من ناحية أخرى، تنتشر الفجوات عندما يكون أحد المعاملات true
، على سبيل المثال الأول:
julia> true & true
true
julia> true & false
false
julia> true & missing
missing
أخيرًا، فإن عامل التشغيل المنطقي "أو الحصري" xor
دائمًا ما ينشر القيم المفقودة
، حيث أن كلا العاملين لهما تأثير دائمًا على النتيجة. كما يجب ملاحظة أن عامل النفي !
يُرجع المفقود
عندما يكون العامل مفقودًا
، تمامًا مثل غيره من العوامل الأحادية.
Control Flow and Short-Circuiting Operators
عوامل التحكم في التدفق بما في ذلك if
، while
و ternary operator x ? y : z
لا تسمح بالقيم المفقودة. وذلك بسبب عدم اليقين بشأن ما إذا كانت القيمة الفعلية ستكون true
أو false
إذا كان بإمكاننا ملاحظتها. وهذا يعني أننا لا نعرف كيف يجب أن يتصرف البرنامج. في هذه الحالة، يتم رمي TypeError
بمجرد مواجهة قيمة missing
في هذا السياق:
julia> if missing
println("here")
end
ERROR: TypeError: non-boolean (Missing) used in boolean context
للنفس السبب، وعلى عكس عوامل التشغيل المنطقية المقدمة أعلاه، فإن عوامل التشغيل البوليانية ذات التوقف القصير &&
و ||
لا تسمح بالقيم المفقودة
في الحالات التي تحدد فيها قيمة المعامل ما إذا كان سيتم تقييم المعامل التالي أم لا. على سبيل المثال:
julia> missing || false
ERROR: TypeError: non-boolean (Missing) used in boolean context
julia> missing && false
ERROR: TypeError: non-boolean (Missing) used in boolean context
julia> true && missing && false
ERROR: TypeError: non-boolean (Missing) used in boolean context
على العكس، لا يتم إلقاء أي خطأ عندما يمكن تحديد النتيجة دون القيم المفقودة
. هذه هي الحالة عندما يتوقف الكود قبل تقييم المعامل المفقود
، وعندما يكون المعامل المفقود
هو الأخير:
julia> true && missing
missing
julia> false && missing
false
Arrays With Missing Values
يمكن إنشاء مصفوفات تحتوي على قيم مفقودة مثل المصفوفات الأخرى:
julia> [1, missing]
2-element Vector{Union{Missing, Int64}}:
1
missing
كما يظهر هذا المثال، نوع عنصر هذه المصفوفات هو Union{Missing, T}
، حيث T
هو نوع القيم غير المفقودة. وهذا يعكس حقيقة أن إدخالات المصفوفة يمكن أن تكون إما من نوع T
(هنا، Int64
) أو من نوع Missing
. تستخدم هذه النوعية من المصفوفات تخزينًا فعالًا في الذاكرة يعادل Array{T}
الذي يحمل القيم الفعلية مع Array{UInt8}
تشير إلى نوع الإدخال (أي ما إذا كان Missing
أو T
).
يمكن إنشاء مصفوفات تسمح بالقيم المفقودة باستخدام الصيغة القياسية. استخدم Array{Union{Missing, T}}(missing, dims)
لإنشاء مصفوفات مليئة بالقيم المفقودة:
julia> Array{Union{Missing, String}}(missing, 2, 3)
2×3 Matrix{Union{Missing, String}}:
missing missing missing
missing missing missing
استخدام undef
أو similar
قد يعطي حاليًا مصفوفة مليئة بـ missing
، لكن هذه ليست الطريقة الصحيحة للحصول على مثل هذه المصفوفة. استخدم مُنشئ missing
كما هو موضح أعلاه بدلاً من ذلك.
مصفوفة بنوع عنصر يسمح بإدخالات missing
(مثل Vector{Union{Missing, T}}
) والتي لا تحتوي على أي إدخالات missing
يمكن تحويلها إلى نوع مصفوفة لا يسمح بإدخالات missing
(مثل Vector{T}
) باستخدام convert
. إذا كانت المصفوفة تحتوي على قيم missing
، يتم إلقاء MethodError
أثناء التحويل:
julia> x = Union{Missing, String}["a", "b"]
2-element Vector{Union{Missing, String}}:
"a"
"b"
julia> convert(Array{String}, x)
2-element Vector{String}:
"a"
"b"
julia> y = Union{Missing, String}[missing, "b"]
2-element Vector{Union{Missing, String}}:
missing
"b"
julia> convert(Array{String}, y)
ERROR: MethodError: Cannot `convert` an object of type Missing to an object of type String
Skipping Missing Values
نظرًا لأن القيم المفقودة
تنتشر مع العمليات الرياضية القياسية، فإن دوال الاختزال تُرجع المفقودة
عند استدعائها على مصفوفات تحتوي على قيم مفقودة:
julia> sum([1, missing])
missing
في هذه الحالة، استخدم دالة skipmissing
لتخطي القيم المفقودة:
julia> sum(skipmissing([1, missing]))
1
تُعيد هذه الدالة المساعدة مُكرِّرًا يقوم بتصفية القيم المفقودة
بكفاءة. يمكن استخدامها بالتالي مع أي دالة تدعم المُكرِّرات:
julia> x = skipmissing([3, missing, 2, 1])
skipmissing(Union{Missing, Int64}[3, missing, 2, 1])
julia> maximum(x)
3
julia> sum(x)
6
julia> mapreduce(sqrt, +, x)
4.146264369941973
يمكن فهرسة الكائنات التي تم إنشاؤها عن طريق استدعاء skipmissing
على مصفوفة باستخدام الفهارس من المصفوفة الأصلية. الفهارس التي تتوافق مع القيم المفقودة ليست صالحة لهذه الكائنات، ويتم إلقاء خطأ عند محاولة استخدامها (كما يتم تخطيها أيضًا بواسطة keys
و eachindex
):
julia> x[1]
3
julia> x[2]
ERROR: MissingException: the value at index (2,) is missing
[...]
هذا يسمح للدوال التي تعمل على الفهارس بالعمل بالتزامن مع skipmissing
. هذه هي الحالة بشكل ملحوظ لدوال البحث والعثور. تعيد هذه الدوال الفهارس الصالحة للكائن الذي تم إرجاعه بواسطة skipmissing
، وهي أيضًا الفهارس للإدخالات المطابقة في المصفوفة الأصلية:
julia> findall(==(1), x)
1-element Vector{Int64}:
4
julia> findfirst(!iszero, x)
1
julia> argmax(x)
1
استخدم collect
لاستخراج القيم غير المفقودة
وتخزينها في مصفوفة:
julia> collect(x)
3-element Vector{Int64}:
3
2
1
Logical Operations on Arrays
تستخدم المنطق الثلاثي الموضح أعلاه لمشغلات المنطق أيضًا بواسطة الدوال المنطقية المطبقة على المصفوفات. وبالتالي، فإن اختبارات المساواة للمصفوفات باستخدام مشغل ==
تعيد missing
كلما لم يكن بالإمكان تحديد النتيجة دون معرفة القيمة الفعلية للمدخل missing
. في الممارسة العملية، يعني هذا أنه يتم إرجاع missing
إذا كانت جميع القيم غير المفقودة في المصفوفتين المقارنتين متساوية، ولكن تحتوي واحدة أو كلتا المصفوفتين على قيم مفقودة (ربما في مواضع مختلفة):
julia> [1, missing] == [2, missing]
false
julia> [1, missing] == [1, missing]
missing
julia> [1, 2, missing] == [1, missing, 2]
missing
بالنسبة للقيم الفردية، استخدم isequal
لمعالجة القيم المفقودة
على أنها متساوية مع قيم المفقودة
الأخرى، ولكنها مختلفة عن القيم غير المفقودة:
julia> isequal([1, missing], [1, missing])
true
julia> isequal([1, 2, missing], [1, missing, 2])
false
الدوال any
و all
تتبع أيضًا قواعد المنطق الثلاثي القيم. وبالتالي، تعيد missing
عندما لا يمكن تحديد النتيجة:
julia> all([true, missing])
missing
julia> all([false, missing])
false
julia> any([true, missing])
true
julia> any([false, missing])
missing