library(readr)
library(dplyr)
library(tibble)
library(stringr)
library(spacyr)
source("R/use-spacy.R")
sentences <- read_csv(
"data/workforce/workforce_sentences.csv",
na = character(),
col_types = cols(
sentence_id = col_character(),
document_id = col_character(),
source_line = col_character(),
text = col_character(),
reference_label = col_character(),
uncertainty = col_character(),
annotator_id = col_character(),
rationale = col_character(),
codebook_version = col_character(),
codebook_hash = col_character(),
derived = col_character(),
transformation = col_character()
)
)
pipeline <- use_project_spacy()
pipeline_info <- spacy_pipeline_version()
parsed <- spacy_parse(
setNames(sentences$text, sentences$sentence_id),
pos = TRUE,
lemma = TRUE,
entity = TRUE,
dependency = TRUE,
nounphrase = TRUE
)
entities <- entity_extract(parsed, type = "all") |>
as_tibble() |>
transmute(
sentence_id = doc_id,
entity = str_replace_all(entity, "_", " "),
entity_type
)
example_text <- "Riverton Skills Centre will run evening classes in October."
example_parsed <- spacy_parse(
c(example = example_text),
pos = TRUE,
lemma = TRUE,
entity = TRUE,
dependency = TRUE,
nounphrase = TRUE
)
example_entities <- entity_extract(example_parsed, type = "all") |>
as_tibble() |>
transmute(
entity = str_replace_all(entity, "_", " "),
predicted_type = entity_type,
actual_type = c("ORG", "TIME", "DATE"),
correct = predicted_type == actual_type
)
title_case_heading <- "Riverton Skills Open House"
title_case_parsed <- spacy_parse(
c(title_case = title_case_heading),
pos = TRUE,
lemma = TRUE,
entity = TRUE,
dependency = TRUE,
nounphrase = TRUE
)
title_case_entities <- entity_extract(title_case_parsed, type = "all") |>
as_tibble() |>
transmute(
entity = str_replace_all(entity, "_", " "),
entity_type
)
spacy_finalize()
pipeline_table <- tibble(
field = names(pipeline_info),
value = unlist(pipeline_info, use.names = FALSE)
)Examining named entities against domain text
Examine correct and incorrect NER outputs
Names in the Riverton job-board and flyer text need labels, not just highlights. The coordinator wants to know whether a span names an organisation, a date, a time, a quantity, or something else.
A trained named-entity model can supply those labels quickly. The team examines its labels for local names absent from the training examples.
The author created the fictional Riverton Workforce Lab, its job board, and its training flyer for teaching.
By the end of this lesson, you will be able to:
- describe named entity recognition in plain language.
- run spaCyβs small English pipeline through spacyr.
- read spaCy entity labels such as PERSON, ORG, DATE, TIME, QUANTITY, and GPE.
- examine entity spans from the 28 Riverton text units.
- explain why fictional local names need checks in context.
Run the released pipeline
The setup chunk reads the sentences, runs the released spaCy pipeline, and formats entity spans for inspection. Named entity recognition, or NER, finds names and assigns each one a type.
| Pipeline field | Value |
|---|---|
| name | core_web_sm |
| version | 3.8.0 |
| lang | en |
| license | MIT |
| spacy | 3.8.7 |
| Entity | Predicted type | Actual type | Correct |
|---|---|---|---|
| Riverton Skills Centre | PERSON | ORG | FALSE |
| evening | TIME | TIME | TRUE |
| October | DATE | DATE | TRUE |
| Entity | Entity type |
|---|---|
| Riverton Skills Open House | PERSON |
spaCy incorrectly labels Riverton Skills Centre as PERSON in the check sentence. The name refers to the provider, so the hand-assigned label is ORG. A facility interpretation would instead suggest FAC. The fictional name explains the domain mismatch but does not make the output correct. The title-case heading produces one PERSON span. This shows that surface form can change model output.
Read the labels
These type names come from the OntoNotes annotation project. The categories follow that projectβs conventions.
PERSONmeans a person.ORGcovers organisations such as companies, agencies, and institutions.DATEcovers dates and periods such as months or years.TIMEmarks a time of day or similar expression.QUANTITYmarks an amount with a unit.GPEcovers geopolitical places such as cities, states, and countries.
Context affects the label. In Riverton raised the training budget, Riverton can be a GPE. In Riverton Skills Centre, it can be part of an ORG.
entity_type_counts <- entities |>
count(entity_type, name = "entities", sort = TRUE)| Entity type | Entities |
|---|---|
| DATE | 6 |
| TIME | 2 |
| ORG | 1 |
| QUANTITY | 1 |
The model finds 10 entity spans in the 28 text units. Six are DATE, two are TIME, one is ORG, and one is QUANTITY. It returns no separate entity for RIVERTON in the all-capital heading. That is defensible because RIVERTON belongs to the event name rather than referring to the town.
The error concerns HOUSE. The model selects one word from a four-word event heading and labels it as an organisation. Both the span and type are wrong.
Examine the Riverton entities
The table gives hand-assigned labels for the returned spans. These teaching judgments cover 28 text units, not a public benchmark.
entity_answers <- tibble(
sentence_id = c("s001", "s003", "s008", "s010", "s011", "s014", "s019", "s020", "s023", "s028"),
entity = c("12 - week", "Evening", "50 pounds", "Six months", "two days", "daytime", "Two years", "Weekend", "HOUSE", "October 15"),
expected_span = c("12 - week", "Evening", "50 pounds", "Six months", "two days", "daytime", "Two years", "Weekend", "RIVERTON SKILLS OPEN HOUSE", "October 15"),
expected_type = c("DATE", "TIME", "QUANTITY", "DATE", "DATE", "TIME", "DATE", "DATE", "EVENT", "DATE"),
note = c(
rep("The span and type fit this sentence.", 8),
"The event heading is four words; `HOUSE` alone is the wrong span and `ORG` is the wrong type.",
"The span and type fit this sentence."
)
)
entity_judgments <- entities |>
left_join(entity_answers, by = c("sentence_id", "entity")) |>
mutate(
correct = entity == expected_span & entity_type == expected_type
)
entity_score <- entity_judgments |>
summarise(
returned_spans = n(),
correct_spans = sum(correct),
wrong_spans = sum(!correct),
.groups = "drop"
)| Sentence ID | Entity | Predicted type | Expected span | Expected type | Note | Correct |
|---|---|---|---|---|---|---|
| s001 | 12 - week | DATE | 12 - week | DATE | The span and type fit this sentence. | TRUE |
| s003 | Evening | TIME | Evening | TIME | The span and type fit this sentence. | TRUE |
| s008 | 50 pounds | QUANTITY | 50 pounds | QUANTITY | The span and type fit this sentence. | TRUE |
| s010 | Six months | DATE | Six months | DATE | The span and type fit this sentence. | TRUE |
| s011 | two days | DATE | two days | DATE | The span and type fit this sentence. | TRUE |
| s014 | daytime | TIME | daytime | TIME | The span and type fit this sentence. | TRUE |
| s019 | Two years | DATE | Two years | DATE | The span and type fit this sentence. | TRUE |
| s020 | Weekend | DATE | Weekend | DATE | The span and type fit this sentence. | TRUE |
| s023 | HOUSE | ORG | RIVERTON SKILLS OPEN HOUSE | EVENT | The event heading is four words; HOUSE alone is the wrong span and ORG is the wrong type. |
FALSE |
| s028 | October 15 | DATE | October 15 | DATE | The span and type fit this sentence. | TRUE |
| Returned spans | Correct spans | Wrong spans |
|---|---|---|
| 10 | 9 | 1 |
Nine of the ten returned spans match the hand judgments. This is not a general model evaluation. Eight correct spans are dates, durations, or times of day. The ninth is a weight. The only organisation-like output is wrong.
A single score conceals those differences between types. The check also says nothing about names that the model misses.
Keep domain mismatch visible
spaCy en_core_web_sm 3.8.0 is a released production pipeline. The author invented the Riverton names for this lesson. The model cannot assume those names occur in its training data. It must generalise from surface cues, word shape, and context. This illustrates domain mismatch, not proof of generally weak performance.
What to remember
- NER labels spans under an annotation scheme such as OntoNotes.
- The spaCy run returned 10 entity spans from the 28 Riverton text units.
- Ten returned spans are too few, and too uneven by type, to evaluate a model.
- Examine fictional local names directly because domain mismatch can change outputs.
For Riverton work, use NER to collect candidates. Examine the local names. Report the model version beside the examples.