negspacy

repository·master·Indexed 42 days ago

https://github.com/jenojp/negspacy

A spaCy pipeline component that implements the NegEx algorithm to identify negated findings and diseases in text, specifically designed for clinical NLP. It allows for custom negation patterns via termsets, filtering by entity types, and support for Span Groups and noun chunks. Compatible with spaCy versions >=3.8 and <4.0.

Tokens
2.6K
Snippets
7
Records
11
Agent score
52%

What's inside negspacy

  1. Termset API and implementation details

    master

    The termset module provides functionality for managing negation patterns. Note the following technical details:

    • Class Naming: The class is named termset (lowercase). While this deviates from PEP 8, it is preserved for backward compatibility. Users should treat it as a standard class.
    • Pattern Management: The API includes methods for add_patterns and remove_patterns to manage the underlying termsets.
    • Error Handling: Attempting to use an invalid neg_termset will raise a KeyError.
  2. spaCy API compatibility and usage notes

    master

    When using negspacy with spaCy 3.8+, be aware of the following API details:

    • NegEx Factory: The @Language.factory("negex", default_config=...) pattern remains unchanged and is the standard way to add the component to a pipeline.
    • Span Extensions: Span.set_extension(name, default=False, force=True) is used to attach negation attributes to spans.
    • PhraseMatcher: The 2-argument form of PhraseMatcher.add("name", patterns) is deprecated. It is recommended to use the 3-argument form or pass patterns as a list to avoid UserWarning.
    • SpanGroups: Accessing spans via doc.spans[key] is compatible with spaCy 3.x and creates a SpanGroup.
  3. Handle negations in noun chunks using chunk_prefix

    master

    Some NER models (like en_core_sci_sm) might include negation words within the entity span itself (e.g., "no headache" as a single entity). This prevents the NegEx algorithm from seeing the negation as a separate preceding word.

    To fix this, use the chunk_prefix configuration key to tell negex which words to treat as part of the negation context even if they are part of a chunk.

    from negspacy.termsets import termset
    
    nlp = spacy.load("en_core_sci_sm")
    ts = termset("en_clinical")
    
    # Add negex with chunk_prefix to catch 'no' in 'no headache'
    nlp.add_pipe(
        "negex",
        config={
            "chunk_prefix": ["no"],
        },
        last=True,
    )
    
    doc = nlp("There is no headache.")
    for e in doc.ents:
        print(e.text, e._.negex)
    # Output: no headache True
  4. Install and set up negspacy

    master

    Install negspacy via pip. To use it, load a spaCy language model and add the negex pipeline component. You can optionally filter negation detection to specific entity types using the ent_types configuration key.

    pip install negspacy
    import spacy
    from negspacy.negation import Negex
    
    # Load model and add negex component
    nlp = spacy.load("en_core_web_sm")
    nlp.add_pipe("negex", config={"ent_types":["PERSON","ORG"]})
    
    # Process text
    doc = nlp("She does not like Steve Jobs but likes Apple products.")
    
    # Access negation status via the ._.negex attribute
    for e in doc.ents:
        print(e.text, e._.negex)
  5. Configure NegEx termsets

    master

    NegEx uses termsets to identify negation patterns. The default termset is en_clinical. You can choose different termsets using the termset function:

    • en: General English phrases.
    • en_clinical (Default): General English plus clinical domain phrases.
    • en_clinical_sensitive: Adds phrases to help rule out historical or irrelevant entities.

    To use a specific termset, pass its patterns to the neg_termset config key when adding the pipeline.

    from negspacy.negation import Negex
    from negspacy.termsets import termset
    
    # Create a termset
    ts = termset("en")
    
    nlp = spacy.load("en_core_web_sm")
    nlp.add_pipe(
        "negex",
        config={
            "neg_termset": ts.get_patterns()
        }
    )
  6. Add the NegEx component to a spaCy pipeline

    master

    The negex component can be added to a spaCy pipeline using the @Language.factory decorator. It identifies negated tokens and entities based on the NegEx algorithm.

    By default, it uses the en_clinical termset. You can customize its behavior via the following configuration parameters:

    • neg_termset: A dictionary containing negation patterns (pseudo_negations, preceding_negations, following_negations, and termination).
    • ent_types: A list of entity types (e.g., ['DISEASE']) to which negation should be applied. If None, all entities are considered.
    • extension_name: The name of the spaCy extension added to spans/entities (defaults to "negex"). This allows access via span._.negex.
    • chunk_prefix: A list of strings. If an entity's text starts with one of these, it is marked as negated.
    • span_keys: A list of keys to look for in doc.spans (e.g., ['sc']). If provided, the component will check spans within these groups instead of just doc.ents.
    import spacy
    
    # Load a spaCy model
    nlp = spacy.load("en_core_web_sm")
    
    # Add the negex component to the pipeline
    # You can pass custom configuration here
    nlp.add_pipe("negex", config={"ent_types": ["CONDITION"], "extension_name": "is_negated"})
    
    doc = nlp("Patient has no signs of cough.")
    
    for ent in doc.ents:
        # Access the negation status via the configured extension name
        print(f"{ent.text} - Negated: {ent._.is_negated}")
  7. Detect negations in Span Groups

    master

    If your spaCy pipeline uses a Span Categorizer that adds spans to the Doc object, you can instruct negex to detect negations for those spans by providing a list of keys to the span_keys argument in the configuration.

    # Assuming 'sc' is the SpanGroup key added by your model
    nlp = spacy.load("your_span_cat_model")
    nlp.add_pipe("negex", config={"span_keys":["sc"]})
    
    doc = nlp("Analysis showed no sign of Human TR Beta 1 mRNA")
    
    for span in doc.spans["sc"]:
        print(span.text, span.label, span._.negex)
  8. Customize NegEx negation patterns

    master

    You can fully replace the built-in patterns or modify them on the fly.

    Replace all patterns: Pass a dictionary containing pseudo_negations, preceding_negations, following_negations, and termination to the neg_termset config key.

    Modify patterns on the fly: Use the termset object's add_patterns or remove_patterns methods to update the pattern sets.

    # Replace all patterns
    nlp.add_pipe(
        "negex",
        config={
            "neg_termset":{
                "pseudo_negations": ["might not"],
                "preceding_negations": ["not"],
                "following_negations":["declined"],
                "termination": ["but","however"]
            }
        }
    )
    
    # Modify existing termsets
    from negspacy.termsets import termset
    ts = termset("en")
    ts.add_patterns({
                "pseudo_negations": ["my favorite pattern"],
                "termination": ["these are", "great patterns", "but"],
                "preceding_negations": ["wow a negation"],
                "following_negations": ["extra negation"],
            })
  9. Configure the NegEx component parameters

    master

    When adding the negex component via nlp.add_pipe("negex", config={...}), you can use the following keys to control the negation logic:

    ParameterTypeDescription
    neg_termsetdictMust contain pseudo_negations, preceding_negations, following_negations, and termination keys.
    ent_typeslist[str]List of entity labels to target. If empty, all entities are processed.
    extension_namestrThe attribute name for the boolean negation flag (e.g., "negex" results in span._.negex).
    chunk_prefixlist[str]Phrases that, if found at the start of a span, trigger negation.
    span_keyslist[str]Keys to search in doc.spans. If provided, the component iterates through these spans instead of doc.ents. Defaults to ["sc"] if not specified in the factory.
  10. Dependency requirements for negspacy

    master

    As part of the modernization plan, negspacy targets Python 3.9 or higher. The core dependency is spacy, specifically requiring versions >=3.8 and <4.0. For development purposes, the following optional dependencies are used:

    • pytest>=8
    • pytest-cov
    • ruff
    • pre-commit
    dependencies = ["spacy>=3.8,<4.0"]
    
    [project.optional-dependencies]
    dev = ["pytest>=8", "pytest-cov", "ruff", "pre-commit"]
  11. Access negation status on Spans and Entities

    master

    Once the negex component has processed a Doc, it attaches a boolean attribute to the spans (entities or chunks) it identifies as negated.

    By default, this attribute is accessed via ._.negex. If you configured a different extension_name during pipeline setup, use that name instead.

    Example:

    # If extension_name was 'negex'
    if span._.negex:
        print("This entity is negated")