دو مسیرِ جست‌وجو به همان سندها امتیاز می‌دهند؛ آیا ترتیبِ ترجیحِ آن‌ها شبیه است؟ بزرگیِ امتیازها ممکن است مقیاسِ متفاوتی داشته باشد و بعضی مقدارها مساوی باشند. این درس با دادهٔ کاملاً تألیفی، هم‌بستگیِ رتبه‌ایِ Spearman را محاسبه می‌کند و نشان می‌دهد چرا یک میان‌بُرِ آشنا با تساوی‌ها جوابِ دیگری می‌دهد. هیچ موتورِ جست‌وجو، مدلِ آموزش‌دیده یا قضاوتِ واقعیِ ارتباط در مثال اجرا نمی‌شود.

۱. دو امتیاز برای همان سندها

هویتِ سندها پیش از مقایسهٔ دو امتیاز ثابت است
تصویر مفهومی: هویتِ سندها پیش از مقایسهٔ دو امتیاز ثابت است؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

سه شناسهٔ فرضیِ DOC_A، DOC_B و DOC_C را در یک ترتیبِ مشترک قرار می‌دهیم. مسیرِ نخست امتیازهای یک، یک و دو و مسیرِ دوم یک، دو و سه دارد. امتیازِ بزرگ‌تر در هر مسیر ترجیحِ بیشتر را نشان می‌دهد. عددها و ضریبِ خروجی بی‌واحدند؛ امتیازِ دو، احتمالِ دویست درصد یا اندازه‌گیریِ فیزیکی نیست. این نام‌ها نیز به سندهای واقعیِ شرکت اشاره نمی‌کنند.

هر ردیف باید در هر دو ورودی به همان سند مربوط باشد. طولِ برابر، این هویت را ثابت نمی‌کند. اگر فقط ورودیِ دوم را جابه‌جا کنیم، تابع همچنان عددی معتبر حساب می‌کند، ولی پرسشِ دیگری را پاسخ داده‌ایم. در ادغامِ فهرست‌ها با RRF نیز شناسهٔ پایدار مهم بود؛ آن روش فهرستِ تازه می‌ساخت، اما اینجا دو مجموعهٔ امتیازِ داده‌شده را مقایسه می‌کنیم.

رابطِ آموزشی فقط دو tuple دقیق، هم‌اندازه و دارای دو تا هشت عضو می‌پذیرد. هر امتیاز باید int دقیق در بازهٔ بستهٔ منفیِ ۳۲ تا ۳۲ باشد؛ bool، اعشار، رشته و زیرکلاس رد می‌شوند. این محدودیتِ خودمان برای آزمایشِ کوچک و قابلِ بازبینی است؛ دامنهٔ عمومیِ آمار یا مشخصاتِ SciPy نیست. ورودی‌ها هم حذف، تبدیل یا بازنویسی نمی‌شوند.

۲. رتبهٔ متوسط برای امتیازهای مساوی

امتیازهای مساوی با قراردادِ رتبهٔ متوسط
تصویر مفهومی: امتیازهای مساوی با قراردادِ رتبهٔ متوسط؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

در این قرارداد، امتیازها صعودی مرتب و رتبه‌ها از یک شمرده می‌شوند. به مقدارهای مساوی، میانگینِ جایگاه‌هایی را می‌دهیم که اشغال کرده‌اند. مرجعِ rankdata در SciPy 1.18.0 این سیاست را average می‌نامد و آن را از رتبهٔ کمینه، بیشینه، متراکم و ترتیبی جدا می‌کند. تغییرِ این سیاست، صرفاً تغییرِ نمایشِ عدد نیست؛ ورودیِ محاسبهٔ بعدی را عوض می‌کند.

دو مقدارِ مساویِ مسیرِ اول، جایگاه‌های یک و دو را دارند؛ رتبهٔ هردو سه‌دوم است. رتبهٔ سندِ سوم سه می‌شود. مسیرِ دوم، رتبه‌های یک، دو و سه دارد. مجموعِ رتبه‌ها در هردو شش است؛ برای هرکدام، میانگین دو خواهد بود. مرتب‌سازیِ پایدار به‌تنهایی جایگزینِ این میانگین نیست؛ اگر تساوی را با ترتیبِ ورود بشکنیم، دو سندِ هم‌امتیاز رتبهٔ متفاوت می‌گیرند.

قراردادِ رتبهٔ متوسط، مفهومِ تازه‌ای مختصِ این مثال نیست؛ در محاسبهٔ رتبه‌ایِ ROC-AUC نیز کاربرد دارد. تفاوتِ مسئلهٔ فعلی، داشتنِ دو بردارِ امتیاز و سنجیدنِ تغییراتِ مشترکِ رتبه‌های آن‌هاست. هیچ برچسبِ مثبت و منفی، شمارِ بردِ جفت‌های دو کلاس یا سطحِ منحنیِ ROC در این محاسبه وجود ندارد. یکسان بودنِ ابزارِ رتبه‌گذاری، دو معیار را معادل نمی‌کند.

۳. ضریبِ رتبه‌ای و ورودیِ ثابت

ورودیِ ثابت، ضریبِ رتبه‌ایِ تعریف‌شده نمی‌سازد
تصویر مفهومی: ورودیِ ثابت، ضریبِ رتبه‌ایِ تعریف‌شده نمی‌سازد؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

مستندِ correlation پایتون روشِ ranked را جایگزینیِ داده با رتبه و سپس محاسبهٔ هم‌بستگی معرفی می‌کند. ما رتبه‌ها را از میانگینشان کم می‌کنیم؛ مجموعِ ضربِ دو انحراف، صورت است و ریشهٔ حاصل‌ضربِ مجموعِ مربع‌های انحراف، مخرج. عاملِ مشترکِ مربوط به تعدادِ نمونه در این نسبت حذف می‌شود. صورت و مربعِ ضریب با Fraction دقیق نگه داشته می‌شوند؛ خودِ ریشه معمولاً تقریبی است.

برای شاهدِ سه‌سندی، صورت سه‌دوم و مجموعِ مربع‌ها سه‌دوم و دو است. بنابراین مربعِ ضریب سه‌چهارم و ضریبِ مثبت تقریباً ۰٫۸۶۶۰۲۵۴ است. اگر فرمولِ یک‌منهای شش‌برابرِ مجموعِ مربعِ اختلافِ رتبه‌ها، تقسیم بر حاصلِ ضربِ n در عبارتِ «مربعِ n منهای یک» را بی‌قید استفاده کنیم، هفت‌هشتم یا ۰٫۸۷۵ می‌گیریم. این میان‌بُر از حالتِ رتبه‌های بدونِ تساوی می‌آید؛ مخرجِ تغییراتِ رتبه‌های مساوی را نباید همان حالت فرض کرد.

اگر یکی از بردارهای امتیاز ثابت باشد، رتبه‌هایش همگی برابر و مجموعِ مربعِ انحرافش صفر است. ضریب در این حالت تعریف نمی‌شود. مرجعِ spearmanr نیز برای ورودیِ ثابت همین نامعین بودن را بیان می‌کند. رابطِ خودمان پرچمِ defined نادرست و مقدارِ None می‌دهد؛ رفتارِ هشدار و NaN کتابخانه یا خطای StatisticsError پایتون را تقلید نمی‌کند.

نامعین بودن را با ضریبِ تعریف‌شدهٔ صفر اشتباه نگیریم. برای امتیازهای یک، دو، سه و چهار در برابرِ دو، چهار، یک و سه، دو بردار تغییر دارند اما مجموعِ ضربِ انحرافِ رتبه‌ها صفر است. ضریبِ این شاهد واقعاً صفر می‌شود؛ این عدد استقلال، نبودِ هر رابطهٔ غیرخطی، بی‌فایده بودنِ سندها یا تصادفی بودنِ مسیرها را اثبات نمی‌کند.

۴. کدِ کامل و مرجعِ مستقلِ جایگشت‌ها

بازبینیِ مستقلِ ترتیب‌ها و مرزِ محاسبه
تصویر مفهومی: بازبینیِ مستقلِ ترتیب‌ها و مرزِ محاسبه؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

فایلِ زیر را با نامِ spearman_tie_contract.py ذخیره کنید. تابعِ اصلی ابتدا تمامِ نوع‌ها و کران‌ها را بررسی می‌کند؛ کمک‌تابع‌های رتبه و مرجع فقط برای ورودیِ معتبرِ داخلی‌اند. در محاسبهٔ اصلی، صورت و مجموعِ مربع‌ها کسری‌اند و مربعِ ضریب دقیق است. برای نمایشِ ضریب از ریشهٔ ممیزِ شناور استفاده می‌کنیم؛ ادعای جوابِ بیتیِ دقیق برای ریشهٔ گنگ نداریم.

"""Own bounded paired-score rank agreement, not search-quality inference."""
from decimal import Decimal, localcontext
from fractions import Fraction as F
from functools import cache
from itertools import permutations, product
from math import sqrt, isclose
from statistics import correlation, StatisticsError
import json
import platform


def checked_pair(x, y):
    if type(x) is not tuple or type(y) is not tuple:
        raise TypeError('Exact paired score tuples required')
    if not 2 <= len(x) <= 8 or len(y) != len(x):
        raise ValueError('Equal lengths2..8 required by own teaching profile')
    for values in (x, y):
        if any(type(value) is not int for value in values):
            raise TypeError('Exact int scores only; no bool, float or subclasses')
        if any(not -32 <= value <= 32 for value in values):
            raise ValueError('Scores outside own[-32,32] profile')


def average_ranks(values):
    """Internal already-validated fixture helper: ascending one-based ranks."""
    order = sorted(range(len(values)), key=values.__getitem__)
    ranks = [None] * len(values)
    start = 0
    while start < len(order):
        stop = start + 1
        while stop < len(order) and values[order[stop]] == values[order[start]]:
            stop += 1
        rank = F(start + 1 + stop, 2)
        for index in order[start:stop]:
            ranks[index] = rank
        start = stop
    return tuple(ranks)


def spearman_contract(x, y):
    """Paired rows refer to the same supplied identities; this is not checked."""
    checked_pair(x, y)
    rx, ry = average_ranks(x), average_ranks(y)
    mx, my = sum(rx, F(0)) / len(rx), sum(ry, F(0)) / len(ry)
    dx, dy = tuple(r - mx for r in rx), tuple(r - my for r in ry)
    cross = sum((a * b for a, b in zip(dx, dy, strict=True)), F(0))
    nx, ny = sum((a * a for a in dx), F(0)), sum((b * b for b in dy), F(0))
    if nx == 0 or ny == 0:
        coefficient, squared, reason = None, None, 'constant_score_vector'
    else:
        squared = cross * cross / (nx * ny)
        coefficient = (-1 if cross < 0 else 1) * sqrt(float(squared)) if cross else 0.0
        reason = None
    return {'defined': reason is None, 'ranks_x': rx, 'ranks_y': ry,
            'centered_cross_sum': cross, 'centered_square_sums': (nx, ny),
            'squared_coefficient_exact': squared, 'coefficient_approx': coefficient,
            'undefined_reason': reason}


@cache
def permutation_mean_ranks(values):
    """Independent small-fixture oracle, all weak-order-compatible orders.

    No sort, group-boundary formula or average_ranks call. Each tied member
    occupies every rank in its group equally often across accepted orders.
    """
    totals = [0] * len(values)
    accepted = 0
    for order in permutations(range(len(values))):
        if all(values[order[i]] <= values[order[i + 1]] for i in range(len(order) - 1)):
            for rank, index in enumerate(order, 1):
                totals[index] += rank
            accepted += 1
    assert accepted > 0
    return tuple(F(total, accepted) for total in totals)


def pair_difference_decimal_oracle(x, y):
    """Independent pair differences instead of centered sums; Decimal80 sqrt."""
    rx, ry = permutation_mean_ranks(x), permutation_mean_ranks(y)
    cross, nx, ny = F(0), F(0), F(0)
    for first in range(len(rx)):
        for second in range(first):
            a, b = rx[first] - rx[second], ry[first] - ry[second]
            cross += a * b
            nx += a * a
            ny += b * b
    if not nx or not ny:
        return rx, ry, None
    with localcontext() as context:
        context.prec = 80
        def decimal(q):
            return Decimal(q.numerator) / Decimal(q.denominator)
        value = decimal(cross) / (decimal(nx) * decimal(ny)).sqrt()
    return rx, ry, float(value)


def rejected(error, x, y):
    try:
        spearman_contract(x, y)
    except error:
        return True
    return False


def packed(value):
    if type(value) is F:
        return str(value)
    if type(value) is dict:
        return {key: packed(item) for key, item in value.items()}
    if type(value) in (tuple, list):
        return [packed(item) for item in value]
    return value


def run():
    x, y = (1, 1, 2), (1, 2, 3)
    example = spearman_contract(x, y)
    ranks_x, ranks_y = example['ranks_x'], example['ranks_y']
    wrong_shortcut = 1 - F(6) * sum((a - b) ** 2 for a, b in zip(ranks_x, ranks_y, strict=True)) / (len(x) * (len(x) ** 2 - 1))
    checks = {}
    checks['literal_average_tied_ranks_and_preserved_total'] = (
        ranks_x == (F(3, 2), F(3, 2), F(3)) and ranks_y == (F(1), F(2), F(3))
        and sum(ranks_x) == sum(ranks_y) == F(6))
    checks['literal_correlation_squared_three_quarters_not_a_relevance_probability'] = (
        example['defined'] is True and example['squared_coefficient_exact'] == F(3, 4)
        and isclose(example['coefficient_approx'], sqrt(3) / 2, abs_tol=2e-12))
    checks['no_tie_shortcut_is_wrong_for_the_tied_witness'] = (
        wrong_shortcut == F(7, 8)
        and abs(float(wrong_shortcut) - example['coefficient_approx']) > 0.008)
    constants = [spearman_contract((4, 4, 4), y), spearman_contract(y, (4, 4, 4)),
                 spearman_contract((0, 0), (0, 0))]
    checks['constant_either_side_is_undefined_not_numeric_zero'] = all(
        result['defined'] is False and result['coefficient_approx'] is None
        and result['squared_coefficient_exact'] is None
        and result['undefined_reason'] == 'constant_score_vector' for result in constants)
    zero = spearman_contract((1, 2, 3, 4), (2, 4, 1, 3))
    checks['a_defined_zero_coefficient_is_distinct_from_constant_undefined'] = (
        zero['defined'] is True and zero['coefficient_approx'] == 0.0
        and zero['squared_coefficient_exact'] == 0)
    original_scores = (-2, -1, 0, 1, 2)
    monotone = spearman_contract(original_scores, tuple(v ** 3 for v in original_scores))
    checks['chosen_strictly_increasing_score_transforms_preserve_rank_agreement'] = (
        monotone['squared_coefficient_exact'] == 1 and monotone['coefficient_approx'] == 1.0
        and spearman_contract(tuple(2 * v + 1 for v in x), y) == example)
    reversed_x = spearman_contract(tuple(-v for v in x), y)
    reversed_both = spearman_contract(tuple(-v for v in x), tuple(-v for v in y))
    checks['one_reversed_score_direction_flips_coefficient_both_preserve_it'] = (
        reversed_x['squared_coefficient_exact'] == example['squared_coefficient_exact']
        and reversed_x['coefficient_approx'] == -example['coefficient_approx']
        and reversed_both['coefficient_approx'] == example['coefficient_approx'])
    order = (2, 0, 1)
    jointly = spearman_contract(tuple(x[i] for i in order), tuple(y[i] for i in order))
    misaligned = spearman_contract(x, tuple(y[i] for i in order))
    checks['joint_row_permutation_preserves_statistic_single_side_can_change_it'] = (
        jointly['coefficient_approx'] == example['coefficient_approx']
        and jointly['squared_coefficient_exact'] == example['squared_coefficient_exact']
        and misaligned['coefficient_approx'] != example['coefficient_approx'])

    oracle_cases, oracle_ok = 0, True
    for size in range(2, 5):
        values = tuple(product((0, 1, 2), repeat=size))
        for left, right in product(values, repeat=2):
            actual = spearman_contract(left, right)
            a, b, expected = pair_difference_decimal_oracle(left, right)
            oracle_ok = oracle_ok and actual['ranks_x'] == a and actual['ranks_y'] == b
            if expected is None:
                oracle_ok = oracle_ok and not actual['defined'] and actual['coefficient_approx'] is None
            else:
                oracle_ok = oracle_ok and actual['defined'] and isclose(actual['coefficient_approx'], expected, abs_tol=2e-12, rel_tol=2e-12)
            oracle_cases += 1
    checks['independent_tie_permutations_pair_differences_decimal80_ONE_family'] = bool(oracle_ok and oracle_cases == 7371)
    stdlib_cases = [(x, y), ((1, 2, 3, 4), (2, 4, 1, 3)),
                    ((-2, -1, 0, 1, 2), (-8, -1, 0, 1, 8)),
                    ((1, 2, 1, 3), (3, 2, 2, 1))]
    stdlib_ok = all(isclose(spearman_contract(a, b)['coefficient_approx'],
        correlation(a, b, method='ranked'), abs_tol=2e-12, rel_tol=2e-12) for a, b in stdlib_cases)
    try:
        correlation((1, 1, 1), (1, 2, 3), method='ranked')
    except StatisticsError:
        constant_library_error = True
    else:
        constant_library_error = False
    checks['actual_stdlib_ranked_crosscheck_and_explicit_constant_policy_difference'] = bool(stdlib_ok and constant_library_error)
    no_tie_cases, shortcut_ok = 0, True
    base = (1, 2, 3, 4)
    for order in permutations(base):
        result = spearman_contract(base, order)
        expected = 1 - F(6 * sum((a - b) ** 2 for a, b in zip(base, order, strict=True)), 4 * 15)
        shortcut_ok = shortcut_ok and isclose(result['coefficient_approx'], float(expected), abs_tol=2e-12)
        no_tie_cases += 1
    checks['no_tie_shortcut_matches_only_declared_distinct_rank_fixtures_ONE_family'] = bool(shortcut_ok and no_tie_cases == 24)

    class IntChild(int):
        pass
    class TupleChild(tuple):
        def __len__(self):
            raise AssertionError('Excluded subclass hooks must not execute')
    invalid_types = [(list(x), y), (x, list(y)), (TupleChild(x), y), (x, TupleChild(y)),
        ((True, 1), (1, 2)), ((1.0, 2), (1, 2)), ((F(1), 2), (1, 2)),
        ((IntChild(1), 2), (1, 2)), ((None, 2), (1, 2)), ((1j, 2), (1, 2)),
        ((float('nan'), 2), (1, 2)), ((1, 2), (1, float('inf'))), ((b'1', 2), (1, 2))]
    invalid_values = [((), ()), ((1,), (2,)), ((0,) * 9, (1,) * 9), (x, (1, 2)),
        ((-33, 0), (0, 1)), ((0, 1), (0, 33)), ((10 ** 100, 0), (1, 2))]
    checks['strict_shape_exact_numeric_types_and_finite_integer_bounds_ONE_family'] = (
        all(rejected(TypeError, a, b) for a, b in invalid_types)
        and all(rejected(ValueError, a, b) for a, b in invalid_values))
    boundary = tuple(range(-32, -24)), tuple(range(25, 33))
    unchanged = x, y
    checks['inclusive_domain_endpoints_eight_rows_and_input_values_preserved'] = (
        spearman_contract(*boundary)['coefficient_approx'] == 1.0
        and (x, y) == unchanged and spearman_contract(x, y) == example)
    assert all(type(value) is bool and value for value in checks.values()), checks
    return packed({'python': platform.python_version(), 'implementation': platform.python_implementation(),
        'dependencies': 'standard library only; SciPy not imported or executed',
        'profile': {'inputs': 'exact tuples2..8; exact int[-32,32]; paired same identities assumed',
            'rank_ties': 'exact integer equality; ascending one-based mean ranks',
            'unit': 'dimensionless supplied scores, ranks and coefficient',
            'constant_policy': 'definedFalse and None, not SciPy nan or StatisticsError parity'},
        'fixture_document_ids': ('DOC_A', 'DOC_B', 'DOC_C'), 'fixture_scores_x': x, 'fixture_scores_y': y,
        'fixture_result': example, 'incorrect_tied_shortcut': wrong_shortcut,
        'defined_zero_witness': zero, 'constant_witness': constants[0],
        'oracle_cases_inside_ONE_family': oracle_cases, 'oracle_decimal_precision': 80,
        'permutation_rank_vectors_cached': permutation_mean_ranks.cache_info().currsize,
        'stdlib_nonconstant_crosscheck_fixtures': len(stdlib_cases),
        'distinct_rank_shortcut_cases_inside_ONE_family': no_tie_cases,
        'invalid_cases_inside_ONE_family': len(invalid_types) + len(invalid_values),
        'unique_check_groups': len(checks), 'checks': checks,
        'pvalues_search_model_training_relevance_or_physical_measurement_executed': False})


if __name__ == '__main__':
    print(json.dumps(run(), ensure_ascii=True, indent=2, allow_nan=False))

مرجعِ مستقل، رتبه‌ها را با فرمولِ مرزِ گروهِ اصلی نمی‌سازد. تمامِ جایگشت‌های اندیس‌های یک فیکسچرِ کوچک را بررسی می‌کند و فقط ترتیب‌های سازگار با امتیازهای نامتناقص را نگه می‌دارد. میانگینِ جایگاهِ هر عضو در این ترتیب‌ها، رتبهٔ مرجع است. سپس اختلافِ هر دو جایگاه را در دو بردار مقایسه می‌کند؛ این مسیر به‌جایِ انحراف از میانگین، از اختلاف‌های جفتی و ریشهٔ Decimal هشتادرقمی استفاده می‌کند.

برای طول‌های دو تا چهار و امتیازهای صفر، یک و دو، ۷۳۷۱ جفتِ ورودی در یک خانواده بررسی می‌شوند؛ تساوی و حالتِ ثابت نیز داخل‌اند. ریشه‌های تقریبی با تلورانسِ مطلق و نسبیِ دو ضرب‌در ده‌به‌توانِ منفیِ دوازده مقایسه می‌شوند. این مرجع، آزمونِ جایگشتیِ آماری یا محاسبهٔ p-value نیست؛ جایگشت‌ها فقط تعریفِ رتبهٔ مساوی را مستقل بازسازی می‌کنند.

{
  "python": "3.14.5",
  "implementation": "CPython",
  "dependencies": "standard library only; SciPy not imported or executed",
  "profile": {
    "inputs": "exact tuples2..8; exact int[-32,32]; paired same identities assumed",
    "rank_ties": "exact integer equality; ascending one-based mean ranks",
    "unit": "dimensionless supplied scores, ranks and coefficient",
    "constant_policy": "definedFalse and None, not SciPy nan or StatisticsError parity"
  },
  "fixture_document_ids": [
    "DOC_A",
    "DOC_B",
    "DOC_C"
  ],
  "fixture_scores_x": [
    1,
    1,
    2
  ],
  "fixture_scores_y": [
    1,
    2,
    3
  ],
  "fixture_result": {
    "defined": true,
    "ranks_x": [
      "3/2",
      "3/2",
      "3"
    ],
    "ranks_y": [
      "1",
      "2",
      "3"
    ],
    "centered_cross_sum": "3/2",
    "centered_square_sums": [
      "3/2",
      "2"
    ],
    "squared_coefficient_exact": "3/4",
    "coefficient_approx": 0.8660254037844386,
    "undefined_reason": null
  },
  "incorrect_tied_shortcut": "7/8",
  "defined_zero_witness": {
    "defined": true,
    "ranks_x": [
      "1",
      "2",
      "3",
      "4"
    ],
    "ranks_y": [
      "2",
      "4",
      "1",
      "3"
    ],
    "centered_cross_sum": "0",
    "centered_square_sums": [
      "5",
      "5"
    ],
    "squared_coefficient_exact": "0",
    "coefficient_approx": 0.0,
    "undefined_reason": null
  },
  "constant_witness": {
    "defined": false,
    "ranks_x": [
      "2",
      "2",
      "2"
    ],
    "ranks_y": [
      "1",
      "2",
      "3"
    ],
    "centered_cross_sum": "0",
    "centered_square_sums": [
      "0",
      "2"
    ],
    "squared_coefficient_exact": null,
    "coefficient_approx": null,
    "undefined_reason": "constant_score_vector"
  },
  "oracle_cases_inside_ONE_family": 7371,
  "oracle_decimal_precision": 80,
  "permutation_rank_vectors_cached": 117,
  "stdlib_nonconstant_crosscheck_fixtures": 4,
  "distinct_rank_shortcut_cases_inside_ONE_family": 24,
  "invalid_cases_inside_ONE_family": 20,
  "unique_check_groups": 13,
  "checks": {
    "literal_average_tied_ranks_and_preserved_total": true,
    "literal_correlation_squared_three_quarters_not_a_relevance_probability": true,
    "no_tie_shortcut_is_wrong_for_the_tied_witness": true,
    "constant_either_side_is_undefined_not_numeric_zero": true,
    "a_defined_zero_coefficient_is_distinct_from_constant_undefined": true,
    "chosen_strictly_increasing_score_transforms_preserve_rank_agreement": true,
    "one_reversed_score_direction_flips_coefficient_both_preserve_it": true,
    "joint_row_permutation_preserves_statistic_single_side_can_change_it": true,
    "independent_tie_permutations_pair_differences_decimal80_ONE_family": true,
    "actual_stdlib_ranked_crosscheck_and_explicit_constant_policy_difference": true,
    "no_tie_shortcut_matches_only_declared_distinct_rank_fixtures_ONE_family": true,
    "strict_shape_exact_numeric_types_and_finite_integer_bounds_ONE_family": true,
    "inclusive_domain_endpoints_eight_rows_and_input_values_preserved": true
  },
  "pvalues_search_model_training_relevance_or_physical_measurement_executed": false
}

بررسیِ جدا با correlation استانداردِ پایتون روی چهار شاهدِ غیرثابت انجام می‌شود. برای ورودیِ ثابت، تفاوتِ عمدیِ سیاستِ دو رابط نیز سنجیده می‌شود. SciPy اجرا نمی‌شود؛ صفحه‌های کاملِ API آن منبعِ تعریف‌اند. نسخهٔ مستنداتِ خوانده‌شده 1.18.0 و پایتونِ مستندات 3.14.8 است؛ نسخهٔ اجرای واقعی جدا در خروجی ثبت می‌شود. دادهٔ ارزیابی، زمان‌سنجی، برازش یا آزمونِ محصول نداریم.

۵. چهار پرسش دربارهٔ توافقِ رتبه‌ها

ضریبِ توافق جایگزینِ قضاوتِ ارتباط نیست
تصویر مفهومی: ضریبِ توافق جایگزینِ قضاوتِ ارتباط نیست؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

پرسش اول: آیا جابه‌جاییِ ترتیبِ سندها ضریب را عوض می‌کند؟ پاسخ: جابه‌جاییِ مشترکِ ردیف‌های هردو ورودی، نسبت را حفظ می‌کند؛ فقط برچسبِ جایگاه‌ها عوض شده است. جابه‌جاییِ یک ورودی می‌تواند جفت‌شدنِ سندها و ضریب را تغییر دهد. تابع فقط طول را می‌سنجد و هویتِ اشتباه را کشف نمی‌کند؛ نگاشتِ شناسه‌ها مسئولیتِ مرحلهٔ قبلی است.

پرسش دوم: آیا تغییرِ مقیاسِ امتیازها همیشه مهم است؟ پاسخ: تبدیلی که ترتیب و تساویِ مقدارها را دقیقاً حفظ کند، رتبه‌ها را ثابت می‌گذارد. شاهدِ مکعبِ صحیحِ مقدارهای منفیِ دو تا دو، ضریبِ یک دارد. تبدیلِ دلخواهِ شناور ممکن است مقدارها را یکی کند؛ آزمونِ صحیحِ محدود، تضمینِ حفظِ تساوی برای تمامِ تبدیل‌ها نیست. برای دو بردارِ غیرثابت، منفی‌کردنِ همهٔ امتیازهای یکی از دو بردار نیز جهتِ ضریب را برمی‌گرداند؛ منظور قرینه‌شدنِ مقدار است و ضریبِ صفر همچنان صفر می‌ماند.

پرسش سوم: چرا برای همهٔ امتیازهای برابر، صفر گزارش نکنیم؟ پاسخ: مخرج صفر است و ضریب تعریف نشده؛ صفرِ عددی، شاهدِ غیرثابتِ بخشِ سوم را از این وضعیت جدا می‌کند. برگرداندنِ None سیاستِ صریحِ رابطِ ماست. این نتیجه را نیز نباید با نیمِ ROC-AUC برای امتیازهای برابر و حضورِ دو کلاس یکی گرفت؛ تعریف و مخرجِ آن مسئله متفاوت‌اند.

پرسش چهارم: ضریبِ بزرگ‌تر یعنی نتیجهٔ جست‌وجوی بهتر؟ پاسخ: خیر؛ دو مسیر می‌توانند روی همان ترتیبِ نامرتبط توافق کنند. برای کیفیت باید قضاوتِ مستقلِ ارتباط و قراردادِ ارزیابی داشته باشیم؛ درسِ Precision، Recall و nDCG به آن پرسشِ جدا می‌پردازد. این ضریب نه احتمالِ درست‌بودنِ پاسخ است و نه گواهِ اعتبارِ سند. منابع در ۲۸ مهر ۱۴۰۵ خوانده شده‌اند؛ تاریخِ انتشارِ اولیهٔ صفحه‌های زنده معلوم نیست و این متن خبرِ تازه نیست.

منابع و مطالعهٔ بیشتر

بررسی منابع: ۲۸ مهر ۱۴۰۵. تحلیل‌ها و پیشنهادهای اجرایی، جمع‌بندی نویسنده‌اند.