범주형 속성의 비유사도 문서의 검증 코드다. if __name__ == "__main__": 아래가 자체 테스트다.

"""명목·이진 속성의 비유사도 검증.

문서: 03.범주형 속성의 비유사도 (예시, 정의, 카드 C2·C3)
주장:
  1. 명목: d = (p - m) / p. 문서 예시의 객체 2, 3은 7개 중 5개 일치 -> d = 2/7, sim = 5/7.
  2. 대칭 이진: d = (r + s) / (q + r + s + t). 비대칭 이진: d = (r + s) / (q + r + s), sim = q / (q + r + s) (자카드).
  3. 0-0 일치(t)가 많을수록 대칭 비유사도는 0으로 가지만 비대칭은 영향을 받지 않는다.
  4. Han·Kamber·Pei 3판 2.4.3절 환자 예: d(Jack, Mary) = 0.33, d(Jack, Jim) = 0.67, d(Jim, Mary) = 0.75.
"""
from fractions import Fraction as F


def nominal(a, b):
    p = len(a); m = sum(x == y for x, y in zip(a, b))
    return F(p - m, p)


def counts(a, b):
    q = sum(x == 1 and y == 1 for x, y in zip(a, b))
    r = sum(x == 1 and y == 0 for x, y in zip(a, b))
    s = sum(x == 0 and y == 1 for x, y in zip(a, b))
    t = sum(x == 0 and y == 0 for x, y in zip(a, b))
    return q, r, s, t


def sym(a, b):
    q, r, s, t = counts(a, b); return F(r + s, q + r + s + t)


def asym(a, b):
    q, r, s, t = counts(a, b); return F(r + s, q + r + s)


def main():
    o2 = ["Black", "Student", "A", "B", "C", "D", "E"]
    o3 = ["Black", "Student", "A", "V", "C", "D", "F"]
    assert nominal(o3, o2) == F(2, 7) and 1 - nominal(o3, o2) == F(5, 7)
    print("[OK] 명목 예: d(3,2) = 2/7, sim = 5/7")

    # Y/P -> 1, N -> 0 (fever, cough, test-1..4)
    jack = [1, 0, 1, 0, 0, 0]; mary = [1, 0, 1, 0, 1, 0]; jim = [1, 1, 0, 0, 0, 0]
    assert round(float(asym(jack, mary)), 2) == 0.33
    assert round(float(asym(jack, jim)), 2) == 0.67
    assert round(float(asym(jim, mary)), 2) == 0.75
    print("[OK] 환자 예(비대칭): 0.33, 0.67, 0.75 -> Jack과 Mary가 가장 비슷하다")

    a = [1, 0, 1]; b = [1, 1, 0]
    for extra in (0, 10, 100):
        aa = a + [0] * extra; bb = b + [0] * extra
        assert asym(aa, bb) == F(2, 3)
    assert sym(a, b) == F(2, 3) and sym(a + [0] * 100, b + [0] * 100) == F(2, 103)
    print("[OK] 0-0 일치 100개를 더하면 대칭 d는 2/3 -> 2/103, 비대칭 d는 2/3 그대로")

    # 카드 C2: 상품 구매 기록 6개 중 둘 다 산 것 1, 한쪽만 산 것 2, 둘 다 안 산 것 3
    u = [1, 1, 0, 0, 0, 0]; v = [1, 0, 1, 0, 0, 0]
    assert counts(u, v) == (1, 1, 1, 3)
    assert sym(u, v) == F(1, 3) and asym(u, v) == F(2, 3) and 1 - asym(u, v) == F(1, 3)
    print("[OK] 카드 C2: 대칭 d = 1/3, 비대칭 d = 2/3, 자카드 = 1/3")
    print("ALL CHECKS PASSED")


if __name__ == "__main__":
    main()