polars cNLP
polars-cnlp adds clinical NLP (cnlp) and research-data-wrangling (rdw) expressions to Polars.
Importing polars_cnlp registers two expression namespaces:
.cnlp— clinical concept matching and contextual interpretation.rdw— general research data-wrangling string helpers
import polars as pl
import polars_cnlp
Once imported, the namespaces are available directly on Polars expressions:
pl.col('note_text').cnlp.affirmed(r'\bpneumonia\b')
pl.col('code').rdw.starts_with_any(['A', 'B', 'C'])
Contents
- Installation
- Clinical NLP Namespace:
.cnlp - Choosing a context algorithm
- Custom rules and algorithms
- Research Data-Wrangling Namespace:
.rdw - Lazy queries
- Choosing the right method
- Regex patterns
- Null behavior
Installation
pip install polars-cnlp
Then:
import polars as pl
import polars_cnlp
Quick start
PNEUMONIA = r'\bpneumonia\b'
df = pl.DataFrame({'note_text': [
'Patient has pneumonia.',
'No pneumonia.',
'Possible pneumonia.',
'History of pneumonia.',
'Family history of pneumonia.',
'Patient has asthma.',
None,
]})
result = df.with_columns(
pl.col('note_text').cnlp.affirmed(PNEUMONIA).alias('pneumonia'),
)
print(result)
affirmed() returns:
Truewhen at least one matching finding is affirmed, current, and experienced by the patient.Falsewhen the concept is mentioned but all matching findings are non-affirmed, historical, hypothetical, or attributed to another experiencer.nullwhen the concept is not mentioned or the input text is null.
For the example above:
[True, False, False, False, False, None, None]
Clinical NLP namespace: .cnlp
API overview
| Method | Input | Output | Typical Usage |
|---|---|---|---|
contains() |
regex | Boolean |
Does the note mention a pattern? |
count() |
regex | UInt32 |
How many times does one concept occur? |
count_all() |
{label: regex} |
Struct[UInt32] |
Count several concepts at once |
affirmed() |
regex | Boolean/null |
Is one concept affirmed? |
affirmed_any() |
sequence or mapping | Boolean/null |
Is any requested concept affirmed? |
affirmed_all() |
sequence or mapping | Boolean/null |
Are all requested concepts affirmed? |
affirmed_each() |
{label: regex} |
Struct[Boolean] |
Get affirmation status for every concept |
find_best() |
regex or mapping | Struct/null |
Return the highest-ranked contextual finding |
find_all() |
regex or mapping | List[Struct]/null |
Return every contextualized finding |
The contextual methods use ConText by default. NegEx and custom algorithms can be selected with the algorithm=
argument.
cnlp.contains
contains(pattern: str) -> pl.Expr
Check whether text contains a regular expression.
df = pl.DataFrame({'note_text': [
'Pneumonia present.',
'No respiratory findings.',
None,
]})
result = df.select(
pl.col('note_text').cnlp.contains(r'\bpneumonia\b').alias('has_pneumonia'),
)
Use cases:
- build broad candidate cohorts before contextual analysis;
- identify notes containing a disease, medication, procedure, or symptom;
- combine regex presence checks with normal Polars filters.
candidates = df.filter(
pl.col('note_text').cnlp.contains(r'\banaphyl(?:axis|actic)\b'),
)
contains() only checks whether the regex occurs. It does not determine whether the concept is negated, historical,
hypothetical, or attributed to another experiencer.
cnlp.count
count(term: str) -> pl.Expr
Count occurrences of one concept.
PNEUMONIA = r'\bpneumonia\b'
df = pl.DataFrame({'note_text': [
'Pneumonia. Pneumonia improved.',
'No pneumonia.',
'Asthma only.',
None,
]})
result = df.select(
pl.col('note_text').cnlp.count(PNEUMONIA).alias('pneumonia_count'),
)
Expected counts:
[2, 1, 0, None]
Use count() for mention frequency, simple NLP features, or identifying notes with repeated discussion of a concept.
Because count() does not perform contextual analysis, it is cheaper than find_all() or affirmed().
cnlp.count_all
count_all(terms: Mapping[str, str]) -> pl.Expr
Count several named concepts in one expression.
terms = {
'pneumonia': r'\bpneumonia\b',
'anaphylaxis': r'\banaphylaxis\b',
'asthma': r'\basthma\b',
}
df = pl.DataFrame({'note_text': [
'Pneumonia and asthma. Pneumonia again.',
'Anaphylaxis.',
]})
result = df.select(
pl.col('note_text').cnlp.count_all(terms).alias('counts'),
)
The result is a Struct resembling:
{
pneumonia: 2,
anaphylaxis: 0,
asthma: 1
}
Struct fields can be selected with normal Polars expressions:
result = (
df.with_columns(
pl.col('note_text').cnlp.count_all(terms).alias('counts'),
)
.select(
pl.col('counts').struct.field('pneumonia'),
pl.col('counts').struct.field('anaphylaxis'),
pl.col('counts').struct.field('asthma'),
)
)
Use count_all() when you need multiple count features for a known vocabulary.
Contextual interpretation
Contextual findings have three independent dimensions.
Assertion
affirmed
possible
negated
Temporality
current
historical
hypothetical
Experiencer
patient
other
The dimensions are independent. For example:
Possible pneumonia.
can be:
assertion = possible
temporality = current
experiencer = patient
while:
If possible pneumonia develops...
can be:
assertion = possible
temporality = hypothetical
experiencer = patient
cnlp.affirmed
affirmed(
term: str,
*,
algorithm: AlgorithmLike = 'context',
prefilter: bool = False,
) -> pl.Expr
Determine whether a concept is affirmed.
PNEUMONIA = r'\bpneumonia\b'
df = pl.DataFrame({'note_text': [
'Patient has pneumonia.',
'No pneumonia.',
'Possible pneumonia.',
'History of pneumonia.',
'Family history of pneumonia.',
'If pneumonia develops, return to clinic.',
'Patient has asthma.',
None,
]})
result = df.select(
pl.col('note_text').cnlp.affirmed(PNEUMONIA).alias('pneumonia'),
)
Expected result:
[
True, # affirmed, current, patient
False, # negated
False, # possible
False, # historical
False, # other experiencer
False, # hypothetical
None, # concept absent
None, # null input
]
A finding is considered affirmed only when:
assertion = affirmed
temporality = current
experiencer = patient
Filtering to affirmed notes:
affirmed_notes = df.filter(
pl.col('note_text').cnlp.affirmed(PNEUMONIA),
)
cnlp.affirmed_any
affirmed_any(
terms: TermPatterns,
*,
algorithm: AlgorithmLike = 'context',
prefilter: bool = False,
) -> pl.Expr
Return whether any requested concept is affirmed.
The input can be a sequence:
patterns = [
r'\bpneumonia\b',
r'\banaphylaxis\b',
]
result = df.select(
pl.col('note_text').cnlp.affirmed_any(patterns).alias('any_affirmed'),
)
or a mapping:
terms = {
'pneumonia': r'\bpneumonia\b',
'anaphylaxis': r'\banaphylaxis\b',
}
result = df.select(
pl.col('note_text').cnlp.affirmed_any(terms).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
Example:
df = pl.DataFrame({'note_text': [
'No pneumonia. Anaphylaxis present.',
'No pneumonia.',
'Asthma only.',
]})
result = df.select(
pl.col('note_text').cnlp.affirmed_any(patterns).alias('result'),
)
Produces:
[True, False, None]
Typical use case:
cohort = df.filter(
pl.col('note_text').cnlp.affirmed_any([
r'\bpneumonia\b',
r'\bbronchitis\b',
r'\bbronchiolitis\b',
]),
)
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 = pl.DataFrame({'note_text': [
'Pneumonia and anaphylaxis are present.',
'No pneumonia but anaphylaxis is present.',
'Pneumonia present.',
'Asthma only.',
]})
result = df.select(
pl.col('note_text').cnlp.affirmed_all(patterns).alias('all_affirmed'),
)
The method uses three-valued logic:
True + True -> True
True + False -> False
False + null -> False
True + null -> null
null + null -> null
Typical use cases:
- phenotypes requiring several co-occurring concepts;
- validation rules where every required concept must be affirmed;
- direct filtering with multiple clinical criteria.
cohort = df.filter(
pl.col('note_text').cnlp.affirmed_all(patterns),
)
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',
}
df = pl.DataFrame({'note_text': [
'No pneumonia. Anaphylaxis present.',
]})
result = df.select(
pl.col('note_text').cnlp.affirmed_each(terms).alias('status'),
)
Produces a Struct resembling:
{
'pneumonia': False,
'anaphylaxis': True,
}
Each field is:
True -> concept has an affirmed occurrence
False -> concept occurs but has no affirmed occurrence
null -> concept is absent
Extract individual fields:
result = (
df.with_columns(
pl.col('note_text').cnlp.affirmed_each(terms).alias('status'),
)
.with_columns(
pl.col('status').struct.field('pneumonia').alias('pneumonia'),
pl.col('status').struct.field('anaphylaxis').alias('anaphylaxis'),
)
)
Use affirmed_each() when you need several contextual Boolean features while processing the note once.
Rich contextual findings
Finding structure
find_best() and find_all() return findings with:
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.
Example:
{
'label': 'pneumonia',
'start': 3,
'end': 12,
'assertion': 'negated',
'temporality': 'current',
'experiencer': 'patient',
}
cnlp.find_best
find_best(
terms: FindPatterns,
*,
algorithm: AlgorithmLike = 'context',
prefilter: bool = False,
) -> pl.Expr
Return the highest-ranked contextualized finding.
Single concept:
result = df.select(
pl.col('note_text')
.cnlp.find_best(r'\bpneumonia\b')
.alias('finding'),
)
When a single regex is passed, label is null.
Several named concepts:
terms = {
'pneumonia': r'\bpneumonia\b',
'anaphylaxis': r'\banaphylaxis\b',
'asthma': r'\basthma\b',
}
df = pl.DataFrame({'note_text': [
'No pneumonia. Possible anaphylaxis. Asthma present.',
]})
result = df.select(
pl.col('note_text').cnlp.find_best(terms).alias('finding'),
)
finding = result['finding'][0]
The selected finding is asthma because it is affirmed and current, while pneumonia is negated and anaphylaxis is
possible.
Ranking is deterministic:
- patient before other experiencer;
- current before historical before hypothetical;
- affirmed before possible before negated;
- earlier text occurrence;
- mapping order as the final tie-breaker.
Use cases:
- select one representative finding;
- reduce multiple candidate concepts to one feature;
- return the preferred span for review or annotation.
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 = pl.DataFrame({'note_text': [
'No pneumonia. Possible anaphylaxis. Pneumonia later developed.',
]})
result = 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:
- annotation review;
- NLP validation;
- exact source spans;
- mention-level datasets;
- comparing contextual status across repeated mentions.
Choosing a context algorithm
All contextual methods use ConText by default:
pl.col('note_text').cnlp.affirmed(PNEUMONIA)
is equivalent to:
pl.col('note_text').cnlp.affirmed(
PNEUMONIA,
algorithm='context',
)
The algorithm= argument is available on:
affirmed
affirmed_any
affirmed_all
affirmed_each
find_best
find_all
contains, count, and count_all do not perform contextual analysis.
Prefiltering
All contextual methods accept prefilter=True:
result = df.select(
pl.col('note_text').cnlp.affirmed(
PNEUMONIA,
prefilter=True,
),
)
For multiple concepts:
result = df.select(
pl.col('note_text').cnlp.affirmed_each(
terms,
prefilter=True,
).alias('status'),
)
When enabled, polars-cnlp first checks whether any requested concept regex matches the note. If none match, it skips
ConText, NegEx, or other contextual processing and returns the normal no-match result. This can improve performance when
the requested concepts are uncommon. When most notes contain a matching concept, the prefilter may add some overhead.
prefilter changes execution only; it does not change result semantics.
ConText
from polars_cnlp.algorithms import ConText
result = df.select(
pl.col('note_text').cnlp.affirmed(
PNEUMONIA,
algorithm=ConText(),
),
)
ConText supports independent assertion, temporality, and experiencer dimensions.
Examples:
No pneumonia.
assertion = negated
temporality = current
experiencer = patient
Possible pneumonia.
assertion = possible
temporality = current
experiencer = patient
History of pneumonia.
assertion = affirmed
temporality = historical
experiencer = patient
If pneumonia develops...
assertion = affirmed
temporality = hypothetical
experiencer = patient
Family history of pneumonia.
assertion = affirmed
temporality = historical
experiencer = other
NegEx
Use NegEx when you specifically want NegEx-style trigger and fixed-window behavior.
result = df.select(
pl.col('note_text').cnlp.affirmed(
PNEUMONIA,
algorithm='negex',
),
)
Or:
from polars_cnlp.algorithms import NegEx
result = df.select(
pl.col('note_text').cnlp.affirmed(
PNEUMONIA,
algorithm=NegEx(),
),
)
Customize the window:
algorithm = NegEx(window=8)
NegEx and ConText intentionally do not have identical rule vocabularies or scope behavior.
Custom rules and algorithms
from polars_cnlp.algorithms import (
ConText,
ContextEffect,
ContextRule,
PseudoRule,
RuleBased,
RuleSet,
TerminateRule,
)
Add rules to the defaults
Use additional_rules= when you want to keep the built-in ConText or NegEx rules and add only a few project-specific
expressions.
For example, extend ConText with an additional negation phrase:
from polars_cnlp.algorithms import ConText, ContextEffect, ContextRule
algorithm = ConText(
additional_rules=[
ContextRule(
pattern=r'\bfree\W+of\b',
direction='forward',
effect=ContextEffect(assertion='negated'),
),
],
)
result = df.select(
pl.col('note_text').cnlp.affirmed(
r'\bpneumonia\b',
algorithm=algorithm,
),
)
The built-in ConText rules remain active; the supplied rule is added to them.
The same approach works with NegEx:
from polars_cnlp.algorithms import NegEx, ContextEffect, ContextRule
algorithm = NegEx(
additional_rules=[
ContextRule(
pattern=r'\bcovid\b',
direction='forward',
effect=ContextEffect(assertion='negated'),
),
],
)
result = df.select(
pl.col('note_text').cnlp.affirmed_each(
{
'pneumonia': r'\bpneumonia\b',
'anaphylaxis': r'\banaphylaxis\b',
},
algorithm=algorithm,
),
)
Use additional_rules= to extend the defaults. Use rules= instead when you want to replace the built-in rule set
completely.
ConText() -> built-in ConText rules
ConText(additional_rules=[...]) -> built-in rules + additional rules
ConText(rules=RuleSet([...])) -> replacement rule set
NegEx() -> built-in NegEx rules
NegEx(additional_rules=[...]) -> built-in rules + additional rules
NegEx(rules=RuleSet([...])) -> replacement rule set
rules= and additional_rules= cannot be used together.
Concepts that also match modifier rules
A concept may also match a ConText or NegEx modifier rule. This can be useful when a term acts as a modifier for other concepts:
Kratom and morphine.
^^^^^^ ^^^^^^^^
modifier target
A modifier is not applied to a target whose text span overlaps the modifier itself. The overlapping modifier is also ignored as a modifier boundary when resolving that target.
For example, if kratom is both a searched concept and a forward negation rule:
Kratom.
kratom does not negate itself.
Kratom and morphine.
kratom can still negate morphine.
No kratom.
the overlapping kratom rule does not prevent the separate no rule from negating kratom.
This behavior is based on overlapping text spans, not on whether the concept and modifier use identical regular expressions. If a concept pattern intentionally includes text matched by a modifier, that modifier will not contextualize that concept occurrence.
Replace the ConText rule set
Sometimes, we want a completely new set of rules. To do this, we'll create a new RuleSet and pass it to the algorithm constructor.
rules = RuleSet([
ContextRule(
pattern=r'\babsent\b',
direction='forward',
effect=ContextEffect(assertion='negated'),
),
])
algorithm = ConText(rules=rules)
result = df.select(
pl.col('note_text').cnlp.affirmed(
PNEUMONIA,
algorithm=algorithm,
),
)
Supplying rules= replaces the built-in rule set rather than silently extending it.
Multi-dimensional effects
algorithm = ConText(
rules=RuleSet([
ContextRule(
pattern=r'\bfamily\W+record\W+of\b',
direction='forward',
effect=ContextEffect(
temporality='historical',
experiencer='other',
),
),
]),
)
Forward, backward, and bidirectional rules
ContextRule(
pattern=r'\bno\b',
direction='forward',
effect=ContextEffect(assertion='negated'),
)
ContextRule(
pattern=r'\bexcluded\b',
direction='backward',
effect=ContextEffect(assertion='negated'),
)
ContextRule(
pattern=r'\bsuspected\b',
direction='bidirectional',
effect=ContextEffect(assertion='possible'),
)
Limit scope
max_scope is the maximum number of intervening tokens:
ContextRule(
pattern=r'\bremote\b',
direction='forward',
effect=ContextEffect(temporality='historical'),
max_scope=3,
)
max_targets limits the number of target occurrences modified:
ContextRule(
pattern=r'\bstatus\W+post\b',
direction='forward',
effect=ContextEffect(temporality='historical'),
max_targets=1,
)
Termination rules
negated = ContextEffect(assertion='negated')
rules = RuleSet([
ContextRule(
pattern=r'\babsent\b',
direction='forward',
effect=negated,
),
TerminateRule(
pattern=r'\bbut\b',
effects=[negated],
),
])
For:
Absent fever but pneumonia present.
but prevents the earlier modifier from negating pneumonia.
Pseudo rules
negated = ContextEffect(assertion='negated')
rules = RuleSet([
ContextRule(
pattern=r'\bnegative\b',
direction='forward',
effect=negated,
),
PseudoRule(
pattern=r'\bnegative\W+attitude\b',
effects=[negated],
),
])
For:
Negative attitude regarding pneumonia.
the pseudo rule prevents negative from incorrectly negating pneumonia.
Load a NegEx trigger file
from polars_cnlp.algorithms import NegEx, RuleSet
rules = RuleSet.from_negex_file(
'negex_triggers.txt',
)
algorithm = NegEx(
rules=rules,
)
Supported trigger categories include:
PREN
POST
PREP
POSP
PSEU
CONJ
This is useful when a project needs to reproduce a specific NegEx lexicon.
Research data-wrangling namespace: .rdw
The .rdw namespace contains general-purpose helpers useful in research datasets but not specific to clinical NLP.
API overview
| Method | Input | Output | Typical use |
|---|---|---|---|
starts_with_any() |
collection of literal prefixes | Boolean |
Match code families, identifiers, or other prefix-based values |
ends_with_any() |
collection of literal suffixes | Boolean |
Match file types, code endings, or other suffix-based values |
Both methods treat the supplied values as literal strings rather than regular expressions.
rdw.starts_with_any
starts_with_any(prefixes: Sequence[str]) -> pl.Expr
Check if a column starts with any of a collection/list of strings.
Parameters
prefixes
: List of prefixes.
Returns
Expr
: Expression of data type Bool.
Example
df = pl.DataFrame({'code': [
'A123',
'B456',
'C789',
'X999',
None,
]})
result = df.select(
pl.col('code')
.rdw.starts_with_any(['A', 'B', 'C'])
.alias('selected'),
)
Expected result:
[True, True, True, False, None]
Values are treated as literal strings, not regular expressions.
df = pl.DataFrame({'value': [
'a.b',
'axb',
]})
result = df.select(
pl.col('value').rdw.starts_with_any(['a.']).alias('result'),
)
Produces:
[True, False]
Use case: ICD families
respiratory = df.filter(
pl.col('icd10').rdw.starts_with_any([
'J12', 'J13', 'J14', 'J15', 'J16', 'J17', 'J18',
]),
)
Use case: identifier families
selected = df.filter(
pl.col('study_id').rdw.starts_with_any([
'CASE-',
'CTRL-',
'PILOT-',
]),
)
Behavior:
empty prefix collection -> False for non-null input
empty-string prefix -> True for non-null input
null input -> null
rdw.ends_with_any
ends_with_any(suffixes: Sequence[str]) -> pl.Expr
Check if a column ends with any of a collection/list of strings.
Parameters
suffixes
: List of suffixes.
Returns
Expr
: Expression of data type Bool.
Example
df = pl.DataFrame({'filename': [
'notes.parquet',
'patients.csv',
'readme.md',
None,
]})
result = df.select(
pl.col('filename')
.rdw.ends_with_any(['.parquet', '.csv'])
.alias('data_file'),
)
Expected result:
[True, True, False, None]
Suffixes are literal strings, not regular expressions.
Use case: procedure-code suffixes
selected = df.filter(
pl.col('procedure_code').rdw.ends_with_any([
'01',
'02',
'03',
]),
)
Use case: file filtering
files = df.filter(
pl.col('path').rdw.ends_with_any([
'.parquet',
'.csv',
'.sas7bdat',
]),
)
Behavior:
empty suffix collection -> False for non-null input
empty-string suffix -> True for non-null input
null input -> null
Lazy queries
All namespace methods are designed for normal Polars lazy expressions.
result = (
pl.scan_parquet('notes/*.parquet')
.filter(
pl.col('note_text').cnlp.affirmed(
r'\bpneumonia\b',
),
)
.select('patient_id', 'note_id', 'note_text')
.collect()
)
Multi-concept processing:
terms = {
'pneumonia': r'\bpneumonia\b',
'anaphylaxis': r'\banaphylaxis\b',
}
result = (
pl.scan_parquet('notes/*.parquet')
.with_columns(
pl.col('note_text')
.cnlp.affirmed_each(terms)
.alias('concept_status'),
)
.collect()
)
Choosing the right method
Use contains() when:
- you only need regex presence;
- context is unnecessary;
- you are creating a broad candidate set.
Use count() or count_all() when:
- mention frequency matters;
- context does not;
- you want count features.
Use affirmed() when:
- you want a patient/current/affirmed Boolean phenotype for one concept.
Use affirmed_any() when:
- several alternative concepts can satisfy a phenotype.
Use affirmed_all() when:
- all required concepts must be affirmed.
Use affirmed_each() when:
- you need one contextual Boolean feature per concept.
Use find_best() when:
- one representative contextual finding is needed.
Use find_all() when:
- mention-level context and spans matter;
- you are validating NLP output;
- different mentions in the same note must remain distinguishable.
Use .rdw.starts_with_any() and .rdw.ends_with_any() when:
- matching literal prefixes or suffixes;
- a list is clearer than constructing a regular expression;
- the task is data wrangling rather than clinical NLP.
Example: clinical phenotype
terms = {
'pneumonia': r'\bpneumonia\b',
'anaphylaxis': r'\banaphylaxis\b',
'asthma': r'\basthma\b',
}
phenotype = (
df.lazy()
.with_columns(
pl.col('note_text')
.cnlp.affirmed_each(terms)
.alias('nlp'),
)
.with_columns(
pl.col('nlp').struct.field('pneumonia').alias('pneumonia'),
pl.col('nlp').struct.field('anaphylaxis').alias('anaphylaxis'),
pl.col('nlp').struct.field('asthma').alias('asthma'),
)
.filter(
pl.col('pneumonia').fill_null(False)
| pl.col('anaphylaxis').fill_null(False)
| pl.col('asthma').fill_null(False),
)
.collect()
)
Example: inspect findings during validation
terms = {
'pneumonia': r'\bpneumonia\b',
'anaphylaxis': r'\banaphylaxis\b',
}
review = (
df.lazy()
.with_columns(
pl.col('note_text')
.cnlp.find_all(terms)
.alias('findings'),
)
.filter(
pl.col('findings').list.len() > 0,
)
.collect()
)
This is useful for reviewing labels, spans, assertion, temporality, and experiencer.
Example: compare ConText and NegEx
comparison = df.select(
'note_text',
pl.col('note_text')
.cnlp.affirmed(
PNEUMONIA,
algorithm='context',
)
.alias('context'),
pl.col('note_text')
.cnlp.affirmed(
PNEUMONIA,
algorithm='negex',
)
.alias('negex'),
)
This is useful when validating whether an existing NegEx-based phenotype changes under richer ConText-style interpretation.
Regex patterns
Concepts are regular expressions.
Prefer explicit boundaries when appropriate:
r'\bpneumonia\b'
More flexible concepts can be written directly:
ANAPHYLAXIS = r'\banaphyl(?:axis|actic)\b'
Named mappings are recommended when results contain multiple concepts:
terms = {
'pneumonia': r'\bpneumonia\b',
'anaphylaxis': r'\banaphyl(?:axis|actic)\b',
}
The mapping key becomes the finding label or Struct field name.
Null behavior
polars-cnlp preserves the distinction between concept absence, non-affirmed concepts, and null input.
For contextual Boolean methods:
concept affirmed -> True
concept non-affirmed -> False
concept absent -> null
input text null -> null
For count():
concept absent -> 0
input text null -> null
For find_all():
concept absent -> []
input text null -> null
This distinction is intentional and works naturally with Polars' nullable expressions.
Metadata
Release files for polars-cnlp 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| polars_cnlp-0.2.0.tar.gz | 88.6 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| polars_cnlp-0.2.0-cp311-abi3-win_amd64.whl | CPython 3.11 | abi3 | Windows x86-64 | Details |
| polars_cnlp-0.2.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.11 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| polars_cnlp-0.2.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.11 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| polars_cnlp-0.2.0-cp311-abi3-macosx_11_0_arm64.whl | CPython 3.11 | abi3 | macOS 11.0+ ARM64 | Details |
| polars_cnlp-0.2.0-cp311-abi3-macosx_10_12_x86_64.whl | CPython 3.11 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 31.4 MB
Release files / polars_cnlp-0.2.0.tar.gz
| Download URL | polars_cnlp-0.2.0.tar.gz |
|---|---|
| Size | 88.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
28506493bfa06b707abe61124fef94c1f2d82ee32073c977c5fd664e89afa5fb
|
|
BLAKE2b-256 checksum How to use checksums |
e8945c988764af58441fd95cc71b5b7dcd1031a921a200f076668086cd957893
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency logRelease files / polars_cnlp-0.2.0-cp311-abi3-win_amd64.whl
| Download URL | polars_cnlp-0.2.0-cp311-abi3-win_amd64.whl |
|---|---|
| Size | 6.1 MB |
| Tags | CPython 3.11 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
6da54027d5d95ad75e7c5cdedeeb1701a98da553f83732555208e58e4ee5600e
|
|
BLAKE2b-256 checksum How to use checksums |
279935334201b679f2c18d7dfa6921057021c71ed476c619d7feb958d7a02f00
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency logRelease files / polars_cnlp-0.2.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | polars_cnlp-0.2.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 6.8 MB |
| Tags | CPython 3.11 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
6028b9b3015c943dc9f3085c37685e9f1ee19172b27e4d4154ddd677fcb808c6
|
|
BLAKE2b-256 checksum How to use checksums |
ebef90e9e4f14935ef56a56e58f8b9367ba75c514ba51c32c7573781ada1c3f6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency logRelease files / polars_cnlp-0.2.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | polars_cnlp-0.2.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 6.8 MB |
| Tags | CPython 3.11 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
2a42555a8867546426f12d3527963b875f66ca277150f104b5320e33a09081e8
|
|
BLAKE2b-256 checksum How to use checksums |
c64bee227b5be2f3bd9f052b795f982b1500757a4608e1d8a3422ef44ac55f47
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency logRelease files / polars_cnlp-0.2.0-cp311-abi3-macosx_11_0_arm64.whl
| Download URL | polars_cnlp-0.2.0-cp311-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 5.6 MB |
| Tags | CPython 3.11 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
395af88e555bd7e0a457ab1d81ff21c65639fcb3ac2b3f5a7f9a8e6c3711a2a5
|
|
BLAKE2b-256 checksum How to use checksums |
0bfc81bf5ffbdeee2ff2ae1d73294f9559fee912325edc0eec48940c04fef47a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency logRelease files / polars_cnlp-0.2.0-cp311-abi3-macosx_10_12_x86_64.whl
| Download URL | polars_cnlp-0.2.0-cp311-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 6.0 MB |
| Tags | CPython 3.11 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
244ed3da7507855a2daaae6486dfccfa2e204a347c8296ef324f36b2f6e55a26
|
|
BLAKE2b-256 checksum How to use checksums |
e8578b6045d8ed123ed3cf2a2ce232a35f14027174419e81e5970ca04e070e77
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency log