Back to Compromise

README

README.md

14.15.060.1 KB
Original Source
<div align="center"> <div><b>compromise</b></div> <div>modest natural language processing</div> <div><code>npm install compromise</code></div> <div align="center"> <sub> by <a href="https://spencermounta.in/">Spencer Kelly</a> and <a href="https://github.com/spencermountain/compromise/graphs/contributors"> many contributors </a> </sub> </div> </div> <div align="center"> <div> <a href="https://npmjs.org/package/compromise"> </a> <a href="https://codecov.io/gh/spencermountain/compromise"> </a> <a href="https://bundlephobia.com/result?p=compromise">
<!--  -->
</a> </div> <div align="center"> <sub> <a href="https://github.com/nlp-compromise/fr-compromise">french</a> • <a href="https://github.com/nlp-compromise/de-compromise">german</a> • <a href="https://github.com/nlp-compromise/it-compromise">italian</a> • <a href="https://github.com/nlp-compromise/es-compromise">spanish</a> </sub> </div> </div> <!-- spacer --> <div align="left"> don't you find it strange, <ul>

<sub>how easy <b>text</b> is to <b>make</b>,</sub>

 <i><sub>ᔐᖜ</sub><b></b></i>   <sub></sub> and how hard it is to actually <b>parse</b> and <i>use</i>?

</ul> </div> <!-- spacer --> <div align="left"> compromise <i><a href="https://observablehq.com/@spencermountain/compromise-justification">tries its best</a></i> to turn text into data.

it makes limited and sensible decisions.

<sub > it's not as smart as you'd think. </sub> <!-- it is <a href="https://docs.compromise.cool/compromise-filesize">small, <a href="https://docs.compromise.cool/compromise-performance">quick</a>, and often <i><a href="https://docs.compromise.cool/compromise-accuracy">good-enough</a></i>. --> </div>
js
import nlp from 'compromise'

let doc = nlp('she sells seashells by the seashore.')
doc.verbs().toPastTense()
doc.text()
// 'she sold seashells by the seashore.'
<!-- spacer --> <div align="left"> <i>don't be fancy, at all:</i> </div>
js
if (doc.has('simon says #Verb')) {
  return true
}
<!-- spacer --> <div align="center"> </div> <div align="left"> <i>grab parts of the text:</i> </div>
js
let doc = nlp(entireNovel)
doc.match('the #Adjective of times').text()
// "the blurst of times?"
<div align="right"> <a href="https://docs.compromise.cool/compromise-match">match docs</a> </div> <div align="center"> </div> <!-- spacer -->

<i>and get data:</i>

js
import plg from 'compromise-speech'
nlp.extend(plg)

let doc = nlp('Milwaukee has certainly had its share of visitors..')
doc.compute('syllables')
doc.places().json()
/*
[{
  "text": "Milwaukee",
  "terms": [{
    "normal": "milwaukee",
    "syllables": ["mil", "wau", "kee"]
  }]
}]
*/
<div align="right"> <a href="https://docs.compromise.cool/compromise-json">json docs</a> </div> <div align="center"> </div> <!-- spacer -->

avoid the problems of brittle parsers:

js
let doc = nlp("we're not gonna take it..")

doc.has('gonna') // true
doc.has('going to') // true (implicit)

// transform
doc.contractions().expand()
doc.text()
// 'we are not going to take it..'
<div align="right"> <a href="https://docs.compromise.cool/compromise-contractions">contraction docs</a> </div> <div align="center"> </div> <!-- spacer -->

and whip stuff around like it's data:

js
let doc = nlp('ninety five thousand and fifty two')
doc.numbers().add(20)
doc.text()
// 'ninety five thousand and seventy two'
<div align="right"> <a href="https://docs.compromise.cool/compromise-values">number docs</a> </div> <div align="center"> </div> <!-- spacer -->

<sub>-because it actually is-</sub>

js
let doc = nlp('the purple dinosaur')
doc.nouns().toPlural()
doc.text()
// 'the purple dinosaurs'
<div align="right"> <a href="https://docs.compromise.cool/nouns">noun docs</a> </div> <div align="center"> </div> <!-- spacer -->

Use it on the client-side:

html
<script src="https://unpkg.com/compromise"></script>
<script>
  var doc = nlp('two bottles of beer')
  doc.numbers().minus(1)
  document.body.innerHTML = doc.text()
  // 'one bottle of beer'
</script>

or likewise:

typescript
import nlp from 'compromise'

var doc = nlp('London is calling')
doc.verbs().toNegative()
// 'London is not calling'
<!-- bragging graphs --> <!-- spacer -->

compromise is ~250kb (minified):

<div align="center"> <!-- filesize --> <a href="https://bundlephobia.com/result?p=compromise"> </a> </div>

it's pretty fast. It can run on keypress:

<div align="center"> <a href="https://observablehq.com/@spencermountain/compromise-performance"> </a> </div>

it works mainly by <a href="https://observablehq.com/@spencermountain/verbs">conjugating all forms</a> of a basic word list.

The final lexicon is <a href="https://observablehq.com/@spencermountain/compromise-lexicon">~14,000 words</a>:

<div align="center"> </div>

you can read more about how it works, here. it's weird.

<!-- spacer --> <!-- one/two/three parts --> <p align="left"> <sub>okay -</sub> <h1> <code>compromise/one</code> </h1> <p align="center">A <code>tokenizer</code> of words, sentences, and punctuation.</p> <p>
js
import nlp from 'compromise/one'

let doc = nlp("Wayne's World, party time")
let data = doc.json()
/* [{
  normal:"wayne's world party time",
    terms:[{ text: "Wayne's", normal: "wayne" },
      ...
      ]
  }]
*/
<div align="right"> <a href="https://docs.compromise.cool/compromise-tokenization">tokenizer docs</a> </div>

<b>compromise/one</b> splits your text up, wraps it in a handy API,

<ul> <sub>and does nothing else -</sub> </ul>

<b>/one</b> is quick - most sentences take a 10th of a millisecond.

It can do <b>~1mb</b> of text a second - or 10 wikipedia pages.

<i>Infinite jest</i> takes 3s.

<div align="right"> You can also parallelize, or stream text to it with <a href="https://github.com/spencermountain/compromise/tree/master/plugins/speed">compromise-speed</a>. </div> <!-- spacer --> <!-- two --> <p align="center"> <h1 align="left"> <code>compromise/two</code> </h1> <p align="center">A <code>part-of-speech</code> tagger, and grammar-interpreter.</p> <p>
js
import nlp from 'compromise/two'

let doc = nlp("Wayne's World, party time")
let str = doc.match('#Possessive #Noun').text()
// "Wayne's World"
<div align="right"> <a href="https://docs.compromise.cool/compromise-tagger">tagger docs</a> </div> <p> </p> <b>compromise/two</b> automatically calculates the very basic grammar of each word.

<sub>this is more useful than people sometimes realize.</sub>

Light grammar helps you write cleaner templates, and get closer to the information.

<!-- Part-of-speech tagging is profoundly-difficult task to get 100% on. It is also a profoundly easy task to get 85% on. -->

compromise has <b>83 tags</b>, arranged in <a href="https://observablehq.com/@spencermountain/compromise-tags">a handsome graph</a>.

<b>#FirstName</b><b>#Person</b><b>#ProperNoun</b><b>#Noun</b>

you can see the grammar of each word by running doc.debug()

you can see the reasoning for each tag with nlp.verbose('tagger').

if you prefer <a href="https://www.ling.upenn.edu/courses/Fall_2003/ling001/penn_treebank_pos.html"><i>Penn tags</i></a>, you can derive them with:

js
let doc = nlp('welcome thrillho')
doc.compute('penn')
doc.json()
<!-- three --> <p align="center"> <h1 align="left"> <code>compromise/three</code> </h1> <p align="center"><code>Phrase</code> and sentence tooling.</p> <p>
js
import nlp from 'compromise/three'

let doc = nlp("Wayne's World, party time")
let str = doc.people().normalize().text()
// "wayne"
<div align="right"> <a href="https://docs.compromise.cool/compromise-selections">selection docs</a> </div>

<b>compromise/three</b> is a set of tooling to <i>zoom into</i> and operate on parts of a text.

.numbers() grabs all the numbers in a document, for example - and extends it with new methods, like .subtract().

When you have a phrase, or group of words, you can see additional metadata about it with .json()

js
let doc = nlp('four out of five dentists')
console.log(doc.fractions().json())
/*[{
    text: 'four out of five',
    terms: [ [Object], [Object], [Object], [Object] ],
    fraction: { numerator: 4, denominator: 5, decimal: 0.8 }
  }
]*/
js
let doc = nlp('$4.09CAD')
doc.money().json()
/*[{
    text: '$4.09CAD',
    terms: [ [Object] ],
    number: { prefix: '$', num: 4.09, suffix: 'cad'}
  }
]*/

API

Compromise/one

Output
  • .text() - return the document as text
  • .json() - return the document as data
  • .debug() - pretty-print the interpreted document
  • .out() - a named or custom output
  • .html({}) - output custom html tags for matches
  • .wrap({}) - produce custom output for document matches
Utils
  • .found [getter] - is this document empty?
  • .docs [getter] get term objects as json
  • .length [getter] - count the # of characters in the document (string length)
  • .isView [getter] - identify a compromise object
  • .compute() - run a named analysis on the document
  • .clone() - deep-copy the document, so that no references remain
  • .termList() - return a flat list of all Term objects in match
  • .cache({}) - freeze the current state of the document, for speed-purposes
  • .uncache() - un-freezes the current state of the document, so it may be transformed
  • .freeze({}) - prevent any tags from being removed, in these terms
  • .unfreeze({}) - allow tags to change again, as default
Accessors
Match

(match methods use the match-syntax.)

  • .match('') - return a new Doc, with this one as a parent
  • .not('') - return all results except for this
  • .matchOne('') - return only the first match
  • .if('') - return each current phrase, only if it contains this match ('only')
  • .ifNo('') - Filter-out any current phrases that have this match ('notIf')
  • .has('') - Return a boolean if this match exists
  • .before('') - return all terms before a match, in each phrase
  • .after('') - return all terms after a match, in each phrase
  • .union() - return combined matches without duplicates
  • .intersection() - return only duplicate matches
  • .complement() - get everything not in another match
  • .settle() - remove overlaps from matches
  • .growRight('') - add any matching terms immediately after each match
  • .growLeft('') - add any matching terms immediately before each match
  • .grow('') - add any matching terms before or after each match
  • .sweep(net) - apply a series of match objects to the document
  • .splitOn('') - return a Document with three parts for every match ('splitOn')
  • .splitBefore('') - partition a phrase before each matching segment
  • .splitAfter('') - partition a phrase after each matching segment
  • .join() - merge any neighbouring terms in each match
  • .joinIf(leftMatch, rightMatch) - merge any neighbouring terms under given conditions
  • .lookup([]) - quick find for an array of string matches
  • .autoFill() - create type-ahead assumptions on the document
Tag
  • .tag('') - Give all terms the given tag
  • .tagSafe('') - Only apply tag to terms if it is consistent with current tags
  • .unTag('') - Remove this term from the given terms
  • .canBe('') - return only the terms that can be this tag
Case
Whitespace
  • .pre('') - add this punctuation or whitespace before each match
  • .post('') - add this punctuation or whitespace after each match
  • .trim() - remove start and end whitespace
  • .hyphenate() - connect words with hyphen, and remove whitespace
  • .dehyphenate() - remove hyphens between words, and set whitespace
  • .toQuotations() - add quotation marks around these matches
  • .toParentheses() - add brackets around these matches
Loops
  • .map(fn) - run each phrase through a function, and create a new document
  • .forEach(fn) - run a function on each phrase, as an individual document
  • .filter(fn) - return only the phrases that return true
  • .find(fn) - return a document with only the first phrase that matches
  • .some(fn) - return true or false if there is one matching phrase
  • .random(fn) - sample a subset of the results
Insert
Transform
  • .sort('method') - re-arrange the order of the matches (in place)
  • .reverse() - reverse the order of the matches, but not the words
  • .unique() - remove any duplicate matches
Lib

(these methods are on the main nlp object)

<!-- spacer -->

compromise/two:

Contractions
<!-- spacer -->

compromise/three:

Nouns
Verbs
Numbers
Sentences
Adjectives
Misc selections
<p> </p> <div align="center"> </div>

.extend():

This library comes with a considerate, common-sense baseline for english grammar.

You're free to change, or lay-waste to any settings - which is the fun part actually.

the easiest part is just to suggest tags for any given words:

js
let myWords = {
  kermit: 'FirstName',
  fozzie: 'FirstName',
}
let doc = nlp(muppetText, myWords)

or make heavier changes with a compromise-plugin.

js
import nlp from 'compromise'
nlp.extend({
  // add new tags
  tags: {
    Character: {
      isA: 'Person',
      notA: 'Adjective',
    },
  },
  // add or change words in the lexicon
  words: {
    kermit: 'Character',
    gonzo: 'Character',
  },
  // change inflections
  irregulars: {
    get: {
      pastTense: 'gotten',
      gerund: 'gettin',
    },
  },
  // add new methods to compromise
  api: View => {
    View.prototype.kermitVoice = function () {
      this.sentences().prepend('well,')
      this.match('i [(am|was)]').prepend('um,')
      return this
    }
  },
})
<div align="right"> <a href="https://docs.compromise.cool/compromise-plugins">.plugin() docs</a> </div> <div align="center"> </div> <!-- spacer --> <div > </div>

Docs:

gentle introduction:
<div > </div>
Documentation:
ConceptsAPIPlugins
AccuracyAccessorsAdjectives
CachingConstructor-methodsDates
CaseContractionsExport
FilesizeInsertHash
InternalsJsonHtml
JustificationCharacter OffsetsKeypress
LexiconLoopsNgrams
Match-syntaxMatchNumbers
PerformanceNounsParagraphs
PluginsOutputScan
ProjectsSelectionsSentences
TaggerSortingSyllables
TagsSplitPronounce
TokenizationTextStrict
Named-EntitiesUtilsPenn-tags
WhitespaceVerbsTypeahead
World dataNormalizationSweep
Fuzzy-matchingTypescriptMutation
Root-forms
<div > </div>
Talks:
Articles:
Some fun Applications:
Comparisons
<!-- spacer --> <div align="center"> <hr/> </div> <div align="center"> </div> <!-- <div align="center"> </div> -->

Plugins:

These are some helpful extensions:

Dates

npm install compromise-dates

Stats

npm install compromise-stats

Speech

npm install compromise-syllables

Wikipedia

npm install compromise-wikipedia

<!-- spacer --> <div > <hr/> </div>

Typescript

we're committed to typescript/deno support, both in main and in the official-plugins:

ts
import nlp from 'compromise'
import stats from 'compromise-stats'

const nlpEx = nlp.extend(stats)

nlpEx('This is type safe!').ngrams({ min: 1 })
<div align="right"> <a href="https://docs.compromise.cool/compromise-typescript">typescript docs</a> </div> <div > </div>

Limitations:

  • slash-support: We currently split slashes up as different words, like we do for hyphens. so things like this don't work: <code>nlp('the koala eats/shoots/leaves').has('koala leaves') //false</code>

  • inter-sentence match: By default, sentences are the top-level abstraction. Inter-sentence, or multi-sentence matches aren't supported without <a href="https://github.com/spencermountain/compromise/tree/master/plugins/paragraphs">a plugin</a>: <code>nlp("that's it. Back to Winnipeg!").has('it back')//false</code>

  • nested match syntax: the <s>danger</s> beauty of regex is that you can recurse indefinitely. Our match syntax is much weaker. Things like this are not <i>(yet)</i> possible: <code>doc.match('(modern (major|minor))? general')</code> complex matches must be achieved with successive .match() statements.

  • dependency parsing: Proper sentence transformation requires understanding the syntax tree of a sentence, which we don't currently do. We should! Help wanted with this.

FAQ
<ul align="left"> <p> <details> <summary>☂️ Isn't javascript too...</summary> <p></p> <ul> yeah it is!
    it wasn't built to compete with NLTK, and may not fit every project.
    

    string processing is synchronous too, and parallelizing node processes is weird.
    

    See <a href="https://observablehq.com/@spencermountain/compromise-performance">here</a> for information about speed & performance, and
    <a href="https://observablehq.com/@spencermountain/compromise-justification">here</a> for project motivations
  </ul>
  <p></p>
</details>
</p> <p> <details> <summary>💃 Can it run on my arduino-watch?</summary> <p></p> <ul> Only if it's water-proof!
    Read <a href="https://observablehq.com/@spencermountain/compromise-quickstart">quick start</a> for running compromise in workers, mobile apps, and all sorts of funny environments.
  </ul>
  <p></p>
</details>
</p> <p> <details> <summary>🌎 Compromise in other Languages?</summary> <p></p> <ul> we've got work-in-progress forks for <a href="https://github.com/nlp-compromise/de-compromise">German</a>, <a href="https://github.com/nlp-compromise/fr-compromise">French</a>, <a href="https://github.com/nlp-compromise/es-compromise">Spanish</a>, and <a href="https://github.com/nlp-compromise/it-compromise">Italian</a> in the same philosophy.
    and need some help.
  </ul>
  <p></p>
</details>
</p> <p> <details> <summary>✨ Partial builds?</summary> <p></p> <ul> we do offer a <a href="https://observablehq.com/@spencermountain/compromise-filesize">tokenize-only</a> build, which has the POS-tagger pulled-out.
    but otherwise, compromise isn't easily tree-shaken.
    

    the tagging methods are competitive, and greedy, so it's not recommended to pull things out.
    

    Note that without a full POS-tagging, the contraction-parser won't work perfectly. (<i>(spencer's cool)</i> vs. <i>(spencer's house)</i>)
    

    It's recommended to run the library fully.
  </ul>
  <p></p>
</details>
</p> </ul> <div align="center"> </div>

See Also:

<b>MIT</b>