Named entity recognition

spaCy · Essentials · 10 min

Entities are the most common thing teams pull out of text: names, companies, amounts, dates and locations.

The NER component predicts entity spans. doc.ents is a tuple of Span objects, each with ent.text, ent.label_, ent.start_char and ent.end_char.

The English core pipelines use the OntoNotes label scheme, including:

  • PERSON, ORG, GPE (countries, cities, states), LOC (non-GPE locations), NORP (nationalities, religious or political groups)
  • DATE, TIME, MONEY, PERCENT, QUANTITY, CARDINAL, ORDINAL
  • PRODUCT, EVENT, WORK_OF_ART, LAW, LANGUAGE, FAC

spacy.explain("NORP") describes a label, and nlp.get_pipe("ner").labels lists everything the model knows.

At the token level, token.ent_type_ gives the entity label and token.ent_iob_ the IOB position (B begin, I inside, O outside).

Entities are predictions: accuracy depends on the model size and how similar your text is to the training data (news and web text). Lowercased or domain-specific text such as product codes or drug names often needs rules or fine-tuning.

You can overwrite entities with doc.ents = [...] using spans you create, for example Span(doc, start, end, label="PRODUCT"), or use doc.set_ents for finer control. Entities can't overlap; for overlapping or nested spans use doc.spans groups instead. displacy.render(doc, style="ent") visualises entities as highlighted HTML.

Example

import spacy
from spacy.tokens import Span

nlp = spacy.load("en_core_web_sm")
doc = nlp("Apple paid $2 billion for a London startup on 5 May 2024, Tim Cook said.")

for ent in doc.ents:
    print(f"{ent.text:15} {ent.label_:6} {ent.start_char}-{ent.end_char}")
# Apple           ORG    0-5
# $2 billion      MONEY  11-21
# London          GPE    28-34
# 5 May 2024      DATE   46-56
# Tim Cook        PERSON 58-66

print(spacy.explain("GPE"))                  # Countries, cities, states
print(doc[0].ent_type_, doc[0].ent_iob_)     # ORG B
print(doc[1].ent_iob_)                       # O

doc.ents = list(doc.ents) + [Span(doc, 8, 9, label="PRODUCT")]   # "startup"
print([(e.text, e.label_) for e in doc.ents][2:4])
# [('London', 'GPE'), ('startup', 'PRODUCT')]

Remember

  • doc.ents holds entity Spans with label_ and character offsets
  • Core English labels follow OntoNotes: PERSON, ORG, GPE, DATE, MONEY...
  • token.ent_type_ and ent_iob_ give token-level views
  • Set doc.ents yourself; use doc.spans for overlapping spans

Watch out for

  • Assuming the pretrained NER recognises domain terms like drug names or SKUs without rules or training.

Check yourself

  1. Which label does the English model use for 'France' in 'He moved to France'?
  2. Why can't you put two overlapping spans into doc.ents?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: spacy.io/usage/linguistic-features#named-entities