The .cnlp namespace provides clinical concept matching and contextual interpretation as Polars expressions.

API overview

[!IMPORTANT] Import polars_cnlp before using .cnlp or .rdw. The import registers these Polars expression namespaces and may appear unused to editors or linters.

import polars as pl
import polars_cnlp  # noqa: F401  # registers .cnlp and .rdw namespaces
Method Input Output Typical use
contains() regex Boolean Presence check
count() regex UInt32 Count one concept
count_all() mapping Struct[UInt32] Count named concepts
affirmed() regex Boolean/null Contextual Boolean for one concept
affirmed_any() sequence or mapping Boolean/null Any requested concept affirmed
affirmed_all() sequence or mapping Boolean/null All requested concepts affirmed
affirmed_each() mapping Struct[Boolean] Status of each named concept
find_best() regex or mapping Struct/null Best finding
find_all() regex or mapping List[Struct]/null All findings

The contextual methods use ConText by default. See Algorithms to select NegEx or configure algorithm behavior.

cnlp.contains

contains(pattern: str) -> pl.Expr

Check whether the text contains a regular expression.

df.select(
    pl.col('note_text')
    .cnlp.contains(r'\bpneumonia\b')
    .alias('has_pneumonia'),
)

contains() performs regex presence matching only. It does not apply assertion, temporality, or experiencer rules.

cnlp.count

count(term: str) -> pl.Expr

Count occurrences of one concept.

df.select(
    pl.col('note_text')
    .cnlp.count(r'\bpneumonia\b')
    .alias('pneumonia_count'),
)

For non-null text, an absent concept returns 0. Null input remains null.

cnlp.count_all

count_all(terms: Mapping[str, str]) -> pl.Expr

Count occurrences of several named concepts in one expression.

terms = {
    'pneumonia': r'\bpneumonia\b',
    'anaphylaxis': r'\banaphylaxis\b',
    'asthma': r'\basthma\b',
}

result = df.select(
    pl.col('note_text')
    .cnlp.count_all(terms)
    .alias('counts'),
)

The result is a Struct with one UInt32 field per mapping key:

{
    pneumonia: 2,
    anaphylaxis: 0,
    asthma: 1
}

Extract fields using normal Polars Struct expressions:

result.select(
    pl.col('counts').struct.field('pneumonia'),
    pl.col('counts').struct.field('anaphylaxis'),
)

Contextual interpretation

Contextual findings have three independent dimensions:

Dimension Values
assertion affirmed, possible, negated
temporality current, historical, hypothetical
experiencer patient, other

For Boolean affirmation methods, True means the concept has an occurrence that is affirmed, current, and attributed to the patient.

cnlp.affirmed

affirmed(
    term: str,
*,
algorithm: AlgorithmLike = 'context',
prefilter: bool = False,
) -> pl.Expr

Determine whether a concept is affirmed.

result = df.select(
    pl.col('note_text')
    .cnlp.affirmed(r'\bpneumonia\b')
    .alias('pneumonia'),
)

Return semantics:

affirmed/current/patient occurrence -> True
concept mentioned but none affirmed -> False
concept absent                      -> null
input text null                     -> null

cnlp.affirmed_any

affirmed_any(
    terms: TermPatterns,
*,
algorithm: AlgorithmLike = 'context',
prefilter: bool = False,
) -> pl.Expr

TermPatterns may be a sequence of regexes or a mapping from labels to regexes.

patterns = [
    r'\bpneumonia\b',
    r'\banaphylaxis\b',
]

df.select(
    pl.col('note_text')
    .cnlp.affirmed_any(patterns)
    .alias('any_affirmed'),
)

Semantics:

at least one concept affirmed                 -> True
at least one concept mentioned, none affirmed -> False
none of the requested concepts mentioned      -> null
null input                                     -> null

cnlp.affirmed_all

affirmed_all(
    terms: TermPatterns,
*,
algorithm: AlgorithmLike = 'context',
prefilter: bool = False,
) -> pl.Expr

Return whether every requested concept is affirmed.

patterns = [
    r'\bpneumonia\b',
    r'\banaphylaxis\b',
]

df.select(
    pl.col('note_text')
    .cnlp.affirmed_all(patterns)
    .alias('all_affirmed'),
)

The expression uses three-valued logic:

True  + True  -> True
True  + False -> False
False + null  -> False
True  + null  -> null
null  + null  -> null

cnlp.affirmed_each

affirmed_each(
    terms: Mapping[str, str],
*,
algorithm: AlgorithmLike = 'context',
prefilter: bool = False,
) -> pl.Expr

Return independent affirmation status for every named concept.

terms = {
    'pneumonia': r'\bpneumonia\b',
    'anaphylaxis': r'\banaphylaxis\b',
}

result = df.select(
    pl.col('note_text')
    .cnlp.affirmed_each(terms)
    .alias('status'),
)

A result may resemble:

{
    'pneumonia': False,
    'anaphylaxis': True,
}

Each field is True, False, or null using the same concept-level semantics as affirmed().

Finding structure

find_best() and find_all() return contextual findings with these fields:

label        String/null
start        UInt64
end          UInt64
assertion    String
temporality  String
experiencer  String

start and end are UTF-8 byte offsets into the original text.

cnlp.find_best

find_best(
    terms: FindPatterns,
*,
algorithm: AlgorithmLike = 'context',
prefilter: bool = False,
) -> pl.Expr

FindPatterns may be one regex or a mapping from labels to regexes.

terms = {
    'pneumonia': r'\bpneumonia\b',
    'anaphylaxis': r'\banaphylaxis\b',
    'asthma': r'\basthma\b',
}

df.select(
    pl.col('note_text')
    .cnlp.find_best(terms)
    .alias('finding'),
)

When one regex is supplied, label is null. When several findings are available, selection is deterministic:

  1. patient before other experiencer;
  2. current before historical before hypothetical;
  3. affirmed before possible before negated;
  4. earlier text occurrence;
  5. mapping order as the final tie-breaker.

cnlp.find_all

find_all(
    terms: FindPatterns,
*,
algorithm: AlgorithmLike = 'context',
prefilter: bool = False,
) -> pl.Expr

Return every contextualized finding in text order.

terms = {
    'pneumonia': r'\bpneumonia\b',
    'anaphylaxis': r'\banaphylaxis\b',
}

df.select(
    pl.col('note_text')
    .cnlp.find_all(terms)
    .alias('findings'),
)

Semantics:

non-null note, matches found -> list of findings
non-null note, no matches     -> []
null note                     -> null

Use find_all() for mention-level datasets, annotation review, NLP validation, and inspecting repeated mentions with different context.

See also