Learn how small grammar checks can find planted errors and why they are not a full grammar checker.
Correctly spelled sentences can still make a public summary sound broken, which is why the coordinator hesitates. Real words are not enough when the words do not fit together.
Spelling asks whether a word exists. Grammar asks whether the words fit together in a sentence. This lesson builds three small checks and scores them on sentences where the planted errors are known.
This project does not install a grammar checker, so the lesson builds three small checks by hand and treats them as a demonstration. Offline means the check runs from local files, without sending text to a web service.
Note
The Riverton sentences and marked grammar examples are invented for this lesson.
TipWhat you will learn
By the end of this lesson, you will be able to:
explain the difference between spelling and grammar checks;
use UDPipe dependency output to compare a subject and a verb;
find doubled words with a stringr pattern;
test a simple a and an rule; and
explain why a demonstration check is not a working grammar checker.
Make a marked test set
The setup reads the Riverton CSV, prepares words with tokenizers and purrr, runs two pattern checks with stringr, and sends sentences to a local udpipe dependency parser. dplyr and tibble organise the results. A dependency parser labels how words relate to one another, such as which noun is the subject of a verb.
library(readr)library(dplyr)library(tibble)library(purrr)library(stringr)library(tokenizers)library(udpipe)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() ))marked_sentences <-tibble(example_id =sprintf("g%02d", 1:14),text =c("The worker checks the list.","The worker check the list.","The applicants submit forms.","The applicants submits forms.","Bring a badge to orientation.","Bring a envelope to orientation.","Apply for an evening class.","Apply for a evening class.","The session begins in an hour.","A university partner sends trainers.","The coordinator checks the the form.","The team needs needs support.","The employee uses a spreadsheet.","The employee use a spreadsheet." ),planted_check =c("none","subject-verb number","none","subject-verb number","none","article letters","none","article letters","none","none","doubled word","doubled word","none","subject-verb number" )) |>mutate(has_planted_error = planted_check !="none")knitr::kable( marked_sentences,col.names =c("Example ID", "Sentence", "Planted check", "Has planted error"),caption ="Author-marked sentences before running the checks",row.names =FALSE)
Author-marked sentences before running the checks
Example ID
Sentence
Planted check
Has planted error
g01
The worker checks the list.
none
FALSE
g02
The worker check the list.
subject-verb number
TRUE
g03
The applicants submit forms.
none
FALSE
g04
The applicants submits forms.
subject-verb number
TRUE
g05
Bring a badge to orientation.
none
FALSE
g06
Bring a envelope to orientation.
article letters
TRUE
g07
Apply for an evening class.
none
FALSE
g08
Apply for a evening class.
article letters
TRUE
g09
The session begins in an hour.
none
FALSE
g10
A university partner sends trainers.
none
FALSE
g11
The coordinator checks the the form.
doubled word
TRUE
g12
The team needs needs support.
doubled word
TRUE
g13
The employee uses a spreadsheet.
none
FALSE
g14
The employee use a spreadsheet.
subject-verb number
TRUE
Seven of the 14 test sentences are marked as correct, and seven contain exactly one planted error. These examples describe only themselves. A checker that works on them has not solved grammar checking.
Run the three checks
The subject-verb check uses the UDPipe parser’s feats column, which can contain values such as Number=Sing or Number=Plur. It looks for an nsubj relation, short for nominal subject, pointing to a verb. In the code, \(tokens) is R shorthand for a small function applied to one sentence’s tokens.
knitr::kable( check_scores,col.names =c("Check", "True positives", "False positives", "False negatives", "True negatives"),caption ="Scores for the three grammar checks on marked examples",row.names =FALSE)
Scores for the three grammar checks on marked examples
Check
True positives
False positives
False negatives
True negatives
subject-verb number
3
0
0
11
doubled word
2
0
0
12
article letters
2
2
0
10
The checks and expected answers share an author, so the score is a teaching check rather than an accuracy estimate. A true positive is a planted error that a check flags. A false positive is a correct sentence that a check flags. A false negative is a planted error that a check misses. A true negative is a correct sentence it leaves alone.
The subject-verb number check catches all three planted number errors in this small set. The doubled-word check catches both repeated-word examples. The article check catches the two planted a mistakes, but it also flags two correct sentences: an hour and A university. That rule uses the first letter, while English article choice depends on sound. Hour begins with a silent h, so it sounds like a vowel. University begins with a y sound, so it sounds like a consonant. The rule reads letters and cannot hear either word.
A real grammar checker uses thousands of rules, a trained model, or both. These three checks are a demonstration of the idea. The UDPipe model was trained here on 500 sentences and is deliberately weak, so every subject-verb result inherits its mistakes. The article check is wrong by design so the failure is visible.
Try the Riverton sentences
The same three checks can be run over the 28 Riverton sentences. The output is a screening list, not a proof that every unflagged sentence is correct.
knitr::kable( riverton_coverage,col.names =c("Coverage measure", "Sentences"),caption ="Subject-verb coverage in the Riverton sentences",row.names =FALSE)
Subject-verb coverage in the Riverton sentences
Coverage measure
Sentences
sentences with subject-verb pair
4
sentences without subject-verb pair
24
The three checks flag no Riverton sentence. The subject-verb check found only four subject-and-verb pairs in all 28 sentences, because it needs the tagger to mark a number feature on the verb and English verbs rarely carry one. On the other 24 sentences the check could not fire. A zero mostly measures coverage, not correctness.
Grammar checking in production
The three custom checks above demonstrate the concept, but real offline grammar checking requires a mature system. In the NLP ecosystem, open-source tools like LanguageTool are standard. Production grammar systems distinguish between:
Detection vs. correction: Highlighting a possible error (detection) is much safer than automatically rewriting the text (correction), especially when the engine’s coverage is low.
Normative context and auditability: What counts as “correct” grammar depends on the intended style (e.g., formal business English vs. informal messaging). Rule-based systems provide an auditable explanation for why text was flagged, whereas deep learning models can suggest fluent corrections but often struggle to explain their normative reasoning.
Rule/model/error-type comparison: Rule-based engines are highly precise for local stylistic and typographical errors (like doubled words or wrong articles). Neural models are generally used for catching semantic confusion where the grammar technically parses but the meaning is broken.
What to remember
Spelling checks words; grammar checks how words fit together.
UDPipe’s feats and nsubj labels can support a small subject-verb check.
A doubled-word pattern is simple and useful for one common typo.
The article-letter rule falsely flags an hour and A university.
These three checks are demonstrations, not an offline grammar checker for R.
No Riverton flags here means low coverage, not clean grammar.
The three checks can produce a review queue. They cannot approve public text on their own, especially when the most technical check fires on only four Riverton sentences.