Finding noun chunks from grammar links

Compare spaCy phrases with small dependency chunks

phrases and entities
noun chunks
workforce research
Learn how noun chunks group nouns with attached words, using spaCy and a local UDPipe dependency parse.

After counting adjacent phrases, the Riverton Workforce Lab must choose its units for checks. It considers basic spreadsheet skills as one phrase or three separate words.

A grammar-aware method offers one convention for making that choice. It can group candidate phrases for checks, but it does not create a single correct inventory of English phrases.

Note

The author created the fictional Riverton Workforce Lab, its job board, and its training flyer for teaching.

TipWhat you will learn

Use this lesson to practice how to:

  • describe a noun chunk in plain language.
  • extract noun chunks with a trained spaCy pipeline.
  • build simple chunks from a UDPipe dependency parse.
  • explain why chunk definitions differ across tools.
  • separate agreement from accuracy.
  • explain why parser errors affect chunk rules.

Ask a trained pipeline for noun chunks

The setup reads the text file and runs spaCy. It also prepares inputs for a small UDPipe-based rule. A noun chunk is a short, nonnested phrase around one noun. It contains the noun and directly preceding determiners, numbers, compounds, and describing words.

Under this convention, chunks neither nest nor extend past the noun. Thus evening classes in October produces evening classes and October, not one longer phrase. Tools can apply different boundary conventions.

library(readr)
library(dplyr)
library(tibble)
library(purrr)
library(stringr)
library(tokenizers)
library(udpipe)
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 <- spacy_parse(
  setNames(sentences$text, sentences$sentence_id),
  pos = TRUE,
  lemma = TRUE,
  entity = TRUE,
  dependency = TRUE,
  nounphrase = TRUE
)
spacy_phrases <- nounphrase_extract(parsed_spacy) |>
  as_tibble() |>
  transmute(doc_id, chunk = str_replace_all(nounphrase, "_", " "))

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_phrases <- nounphrase_extract(example_parsed) |>
  as_tibble() |>
  transmute(chunk = str_replace_all(nounphrase, "_", " "))

spacy_finalize()

pipeline_table <- tibble(
  field = names(pipeline_info),
  value = unlist(pipeline_info, use.names = FALSE)
)
spaCy pipeline used for noun chunks
Pipeline field Value
name core_web_sm
version 3.8.0
lang en
license MIT
spacy 3.8.7
First spaCy noun chunks from the Riverton text units
Sentence ID spaCy noun chunk
s001 Paid 12-week training
s002 No prior data experience
s003 Evening schedules
s004 Applicants
s004 basic spreadsheet skills
s005 A high school diploma
s006 The employer
s006 certification training
s007 Rotating night shifts
s007 part
s007 the job
s008 Workers

The released spaCy pipeline returns 40 noun chunks for the 28 text units. For the check sentence, it returns three flat chunks: Riverton Skills Centre, evening classes, and October.

Compare the two lists

Exact agreement is strict: the sentence ID and the chunk text must both match. That makes agreement a string match, not accuracy. Neither list is ground truth, and spaCy chunks are non-overlapping while the UDPipe-built list can overlap.

agreed_chunks <- inner_join(
  distinct(spacy_phrases),
  distinct(udpipe_chunks),
  by = c("doc_id", "chunk")
)

comparison_summary <- tibble(
  measure = c("spaCy chunks", "UDPipe-built chunks", "exact agreements"),
  count = c(nrow(spacy_phrases), nrow(udpipe_chunks), nrow(agreed_chunks))
)

different_examples <- tibble(
  method = c("spaCy", "UDPipe-built"),
  sentence_id = c("s005", "s005"),
  chunk = c("A high school diploma", "A high school"),
  explanation = c(
    "spaCy keeps the full education phrase.",
    "The dependency rule attaches words to school and leaves diploma outside this chunk."
  )
)

spreadsheet_examples <- bind_rows(
  spacy_phrases |>
    filter(doc_id == "s004", str_detect(chunk, "spreadsheet")) |>
    mutate(method = "spaCy"),
  udpipe_chunks |>
    filter(doc_id == "s004", str_detect(chunk, "spreadsheet")) |>
    mutate(method = "UDPipe-built")
) |>
  select(method, doc_id, chunk)
String matches across two chunking conventions
Measure Count
spaCy chunks 40
UDPipe-built chunks 56
exact agreements 25
One sentence where the two chunk methods differ
Method Sentence ID Chunk Explanation
spaCy s005 A high school diploma spaCy keeps the full education phrase.
UDPipe-built s005 A high school The dependency rule attaches words to school and leaves diploma outside this chunk.
How each method handles the spreadsheet-skills phrase
Method Sentence ID Chunk
spaCy s004 basic spreadsheet skills
UDPipe-built s004 spreadsheet
UDPipe-built s004 need basic spreadsheet skills

The lists have 25 exact string matches, which measure agreement, not accuracy. The counts 40 and 56 describe different chunk definitions. spaCy groups basic spreadsheet skills. The UDPipe rule also emits spreadsheet and incorrectly includes need in need basic spreadsheet skills.

A bigram groups adjacent words even across phrase boundaries. A dependency chunk groups words according to parser links. Its usefulness depends on correct parsing.

What to remember

  • Noun chunks are flat, non-overlapping spans under a tool-specific convention.
  • spaCy returns 40 noun chunks for these 28 text units.
  • The UDPipe-built rule returns 56 overlapping chunks from a deliberately weak teaching model.
  • The 25 exact matches are agreement, not accuracy.
  • Paid week shows how a rule can manufacture text by pasting non-adjacent tokens.
  • Tokenization alone explains the 179 versus 178 row-count gap.

Use chunk lists to select text for human checks. When tools disagree, examine the source, tokenization, and parse before interpreting the phrase.

Sources