Skip to content

Candidate Matching

matchminer_ai.matching.match

Candidate match generation helpers.

generate_candidate_matches

generate_candidate_matches(query_df: DataFrame, corpus_df: DataFrame, *, k: int | None = 20) -> pd.DataFrame

Generate top-k candidate matches by ranking corpus items for each query item.

Parameters:

Name Type Description Default
query_df DataFrame

Query-side DataFrame with embeddings. Must contain: - patient_id or space_trial_id: str - embedding : array-like

required
corpus_df DataFrame

Corpus-side DataFrame with embeddings. Must contain: - patient_id or space_trial_id: str - embedding : array-like

required
k int | None

Number of top matches to return per query entity. If None, return all corpus items per query (sorted by similarity).

20

Returns:

Type Description
DataFrame

Ranked top-k match pairs including: - patient_id or space_trial_id (from query_df) - patient_id or space_trial_id (from corpus_df) - similarity_score : float - rank : int (1 = highest similarity per query)

Source code in src/matchminer_ai/matching/match.py
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
def generate_candidate_matches(
    query_df: pd.DataFrame,
    corpus_df: pd.DataFrame,
    *,
    k: int | None = 20,
) -> pd.DataFrame:
    """
    Generate top-k candidate matches by ranking corpus items for each query item.

    Parameters
    ----------
    query_df : pd.DataFrame
        Query-side DataFrame with embeddings.
        Must contain:
            - patient_id or space_trial_id: str
            - embedding : array-like
    corpus_df : pd.DataFrame
        Corpus-side DataFrame with embeddings.
        Must contain:
            - patient_id or space_trial_id: str
            - embedding : array-like
    k : int | None, default 20
        Number of top matches to return per query entity. If None, return all
        corpus items per query (sorted by similarity).

    Returns
    -------
    pd.DataFrame
        Ranked top-k match pairs including:
            - patient_id or space_trial_id (from query_df)
            - patient_id or space_trial_id (from corpus_df)
            - similarity_score : float
            - rank : int (1 = highest similarity per query)
    """
    # Validate required schema for IDs and embeddings.
    query_id_col = _resolve_id_column(query_df, "query_df")
    corpus_id_col = _resolve_id_column(corpus_df, "corpus_df")
    if "embedding" not in query_df.columns:
        raise ValueError("query_df must include embedding")
    if "embedding" not in corpus_df.columns:
        raise ValueError("corpus_df must include embedding")
    if k is not None and k <= 0:
        raise ValueError("k must be a positive integer or None")

    # Parse embeddings and ensure consistent vector dimensionality.
    query_embeddings = _stack_embeddings(query_df["embedding"])
    corpus_embeddings = _stack_embeddings(corpus_df["embedding"])
    if query_embeddings.numel() == 0 or corpus_embeddings.numel() == 0:
        return pd.DataFrame(
            columns=[query_id_col, corpus_id_col, "similarity_score", "rank"]
        )

    # Normalize embeddings and compute cosine similarity matrix.
    query_norm = _normalize_rows(query_embeddings)
    corpus_norm = _normalize_rows(corpus_embeddings)
    similarity = query_norm @ corpus_norm.T

    # Extract top-k corpus matches per query.
    query_ids = query_df[query_id_col].astype(str).tolist()
    corpus_ids = corpus_df[corpus_id_col].astype(str).tolist()

    records: list[dict[str, object]] = []
    num_corpus = similarity.shape[1]
    top_k = num_corpus if k is None else min(k, num_corpus)
    for row_idx, query_id in enumerate(query_ids):
        scores = similarity[row_idx]
        if top_k == 0:
            continue
        if top_k == num_corpus:
            ranked_idx = torch.argsort(scores, descending=True)
        else:
            ranked_idx = torch.topk(scores, k=top_k, largest=True).indices
        for rank, corpus_idx in enumerate(ranked_idx, start=1):
            corpus_idx_int = int(corpus_idx)
            records.append(
                {
                    query_id_col: query_id,
                    corpus_id_col: corpus_ids[corpus_idx_int],
                    "similarity_score": float(scores[corpus_idx_int]),
                    "rank": int(rank),
                }
            )

    result = pd.DataFrame.from_records(records)
    if result.empty:
        return result

    return result