natural 6.8.0 → 6.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/natural/sentiment/SentimentAnalyzer.js +18 -6
- package/lib/natural/stemmers/index.d.ts +1 -0
- package/lib/natural/stemmers/index.js +1 -0
- package/lib/natural/stemmers/porter_stemmer_uk.js +129 -0
- package/lib/natural/stemmers/stemmer_uk.js +28 -0
- package/lib/natural/tokenizers/aggressive_tokenizer_uk.js +25 -0
- package/lib/natural/util/stopwords_uk.js +21 -0
- package/package.json +1 -1
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
|
|
23
23
|
'use strict'
|
|
24
24
|
|
|
25
|
+
const DEBUG = true
|
|
26
|
+
|
|
25
27
|
// Afinn
|
|
26
28
|
const englishAfinnVoca = require('afinn-165')
|
|
27
29
|
const spanishAfinnVoca = require('./Spanish/afinnShortSortedSpanish.json')
|
|
@@ -87,6 +89,17 @@ class SentimentAnalyzer {
|
|
|
87
89
|
// this.vocabulary must be a copy of the languageFiles object
|
|
88
90
|
// or in subsequent execution the polarity will be undefined
|
|
89
91
|
// shallow copy - requires ES6
|
|
92
|
+
if (languageFiles[type]) {
|
|
93
|
+
if (languageFiles[type][language]) {
|
|
94
|
+
if (languageFiles[type][language][0]) {
|
|
95
|
+
this.vocabulary = Object.create(languageFiles[type][language][0])
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
throw new Error('Type ' + type + ' for Language ' + language + ' not supported')
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
throw new Error('Type Language ' + type + ' not supported')
|
|
102
|
+
}
|
|
90
103
|
this.vocabulary = Object.assign({}, languageFiles[type][language][0])
|
|
91
104
|
Object.setPrototypeOf(this.vocabulary, null)
|
|
92
105
|
if (type === 'senticon') {
|
|
@@ -98,7 +111,6 @@ class SentimentAnalyzer {
|
|
|
98
111
|
Object.keys(this.vocabulary).forEach(word => {
|
|
99
112
|
this.vocabulary[word] = this.vocabulary[word].polarity
|
|
100
113
|
})
|
|
101
|
-
// console.log(JSON.stringify(this.vocabulary, null, 2));
|
|
102
114
|
}
|
|
103
115
|
}
|
|
104
116
|
|
|
@@ -120,24 +132,24 @@ class SentimentAnalyzer {
|
|
|
120
132
|
getSentiment (words) {
|
|
121
133
|
let score = 0
|
|
122
134
|
let negator = 1
|
|
123
|
-
|
|
135
|
+
let nrHits = 0
|
|
124
136
|
|
|
125
137
|
words.forEach((token) => {
|
|
126
138
|
const lowerCased = token.toLowerCase()
|
|
127
139
|
if (this.negations.indexOf(lowerCased) > -1) {
|
|
128
140
|
negator = -1
|
|
129
|
-
|
|
141
|
+
DEBUG && nrHits++
|
|
130
142
|
} else {
|
|
131
143
|
// First try without stemming
|
|
132
144
|
if (this.vocabulary[lowerCased] !== undefined) {
|
|
133
145
|
score += negator * this.vocabulary[lowerCased]
|
|
134
|
-
|
|
146
|
+
DEBUG && nrHits++
|
|
135
147
|
} else {
|
|
136
148
|
if (this.stemmer) {
|
|
137
149
|
const stemmedWord = this.stemmer.stem(lowerCased)
|
|
138
150
|
if (this.vocabulary[stemmedWord] !== undefined) {
|
|
139
151
|
score += negator * this.vocabulary[stemmedWord]
|
|
140
|
-
|
|
152
|
+
DEBUG && nrHits++
|
|
141
153
|
}
|
|
142
154
|
}
|
|
143
155
|
}
|
|
@@ -145,7 +157,7 @@ class SentimentAnalyzer {
|
|
|
145
157
|
})
|
|
146
158
|
|
|
147
159
|
score = score / words.length
|
|
148
|
-
|
|
160
|
+
DEBUG && console.log('Number of hits: ' + nrHits)
|
|
149
161
|
|
|
150
162
|
return score
|
|
151
163
|
}
|
|
@@ -40,6 +40,7 @@ export let PorterStemmerIt: Stemmer
|
|
|
40
40
|
export let PorterStemmerNl: Stemmer
|
|
41
41
|
export let PorterStemmerNo: Stemmer
|
|
42
42
|
export let PorterStemmerPt: Stemmer
|
|
43
|
+
export let PorterStemmerUk: Stemmer
|
|
43
44
|
export let PorterStemmerRu: Stemmer
|
|
44
45
|
export let PorterStemmerSv: Stemmer
|
|
45
46
|
export let StemmerId: Stemmer
|
|
@@ -27,6 +27,7 @@ exports.PorterStemmerFa = require('./porter_stemmer_fa')
|
|
|
27
27
|
exports.PorterStemmerFr = require('./porter_stemmer_fr')
|
|
28
28
|
exports.CarryStemmerFr = require('./Carry')
|
|
29
29
|
exports.PorterStemmerDe = require('./porter_stemmer_de')
|
|
30
|
+
exports.PorterStemmerUk = require('./porter_stemmer_uk')
|
|
30
31
|
exports.PorterStemmerRu = require('./porter_stemmer_ru')
|
|
31
32
|
exports.PorterStemmerEs = require('./porter_stemmer_es')
|
|
32
33
|
exports.PorterStemmerIt = require('./porter_stemmer_it')
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Stemmer = require('./stemmer_uk')
|
|
4
|
+
|
|
5
|
+
const PorterStemmer = new Stemmer()
|
|
6
|
+
module.exports = PorterStemmer
|
|
7
|
+
|
|
8
|
+
function attemptReplacePatterns (token, patterns) {
|
|
9
|
+
let replacement = null
|
|
10
|
+
let i = 0; let isReplaced = false
|
|
11
|
+
while ((i < patterns.length) && !isReplaced) {
|
|
12
|
+
if (patterns[i][0].test(token)) {
|
|
13
|
+
replacement = token.replace(patterns[i][0], patterns[i][1])
|
|
14
|
+
isReplaced = true
|
|
15
|
+
}
|
|
16
|
+
i++
|
|
17
|
+
}
|
|
18
|
+
return replacement
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function perfectiveGerund (token) {
|
|
22
|
+
const result = attemptReplacePatterns(token, [
|
|
23
|
+
[/[ая]в(ши|шись)$/g, ''],
|
|
24
|
+
[/(ив|ивши|ившись)$/g, '']
|
|
25
|
+
])
|
|
26
|
+
return result
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function adjectival (token) {
|
|
30
|
+
let result = adjective(token)
|
|
31
|
+
if (result != null) {
|
|
32
|
+
const pariticipleResult = participle(result)
|
|
33
|
+
result = pariticipleResult || result
|
|
34
|
+
}
|
|
35
|
+
return result
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function adjective (token) {
|
|
39
|
+
const result = attemptReplacePatterns(token, [
|
|
40
|
+
[/(ими|ій|ий|а|е|ова|ове|ів|є|їй|єє|еє|я|ім|ем|им|ім|их|іх|ою|йми|іми|у|ю|ого|ому|ої)$/g, '']
|
|
41
|
+
])
|
|
42
|
+
return result
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function participle (token) { // edit
|
|
46
|
+
const result = attemptReplacePatterns(token, [
|
|
47
|
+
[/(ий|ого|ому|им|ім|а|ій|у|ою|ій|і|их|йми|их)$/g, '']
|
|
48
|
+
])
|
|
49
|
+
return result
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function reflexive (token) {
|
|
53
|
+
const result = attemptReplacePatterns(token, [
|
|
54
|
+
[/(с[яьи])$/g, '']
|
|
55
|
+
])
|
|
56
|
+
return result
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function verb (token) {
|
|
60
|
+
const result = attemptReplacePatterns(token, [
|
|
61
|
+
[/(сь|ся|ив|ать|ять|у|ю|ав|али|учи|ячи|вши|ши|е|ме|ати|яти|є)$/g, '']
|
|
62
|
+
])
|
|
63
|
+
return result
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function noun (token) {
|
|
67
|
+
const result = attemptReplacePatterns(token, [
|
|
68
|
+
[/(а|ев|ов|е|ями|ами|еи|и|ей|ой|ий|й|иям|ям|ием|ем|ам|ом|о|у|ах|иях|ях|ы|ь|ию|ью|ю|ия|ья|я|і|ові|ї|ею|єю|ою|є|еві|ем|єм|ів|їв|ю)$/g, '']
|
|
69
|
+
])
|
|
70
|
+
return result
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function superlative (token) {
|
|
74
|
+
const result = attemptReplacePatterns(token, [
|
|
75
|
+
[/(ейш|ейше)$/g, '']
|
|
76
|
+
])
|
|
77
|
+
return result
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function derivational (token) {
|
|
81
|
+
const result = attemptReplacePatterns(token, [
|
|
82
|
+
[/[^аеиоуюяіїє][аеиоуюяіїє]+[^аеиоуюяіїє]+[аеиоуюяіїє].*(?<=о)сть?$/g, '']
|
|
83
|
+
])
|
|
84
|
+
return result
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// perform full stemming algorithm on a single word
|
|
88
|
+
PorterStemmer.stem = function (token) {
|
|
89
|
+
token = token.toLowerCase()
|
|
90
|
+
const volwesRegexp = /^(.*?[аеиоуюяіїє])(.*)$/g
|
|
91
|
+
let RV = volwesRegexp.exec(token)
|
|
92
|
+
if (!RV || RV.length < 3) {
|
|
93
|
+
return token
|
|
94
|
+
}
|
|
95
|
+
const head = RV[1]
|
|
96
|
+
RV = RV[2]
|
|
97
|
+
volwesRegexp.lastIndex = 0
|
|
98
|
+
const R2 = volwesRegexp.exec(RV)
|
|
99
|
+
let result = perfectiveGerund(RV)
|
|
100
|
+
if (result === null) {
|
|
101
|
+
const resultReflexive = reflexive(RV) || RV
|
|
102
|
+
result = adjectival(resultReflexive)
|
|
103
|
+
if (result === null) {
|
|
104
|
+
result = verb(resultReflexive)
|
|
105
|
+
if (result === null) {
|
|
106
|
+
result = noun(resultReflexive)
|
|
107
|
+
if (result === null) {
|
|
108
|
+
result = resultReflexive
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
result = result.replace(/и$/g, '')
|
|
114
|
+
let derivationalResult = result
|
|
115
|
+
if (R2 && R2[2]) {
|
|
116
|
+
derivationalResult = derivational(R2[2])
|
|
117
|
+
if (derivationalResult != null) {
|
|
118
|
+
derivationalResult = derivational(result)
|
|
119
|
+
} else {
|
|
120
|
+
derivationalResult = result
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let superlativeResult = superlative(derivationalResult) || derivationalResult
|
|
125
|
+
|
|
126
|
+
superlativeResult = superlativeResult.replace(/(н)н/g, '$1')
|
|
127
|
+
superlativeResult = superlativeResult.replace(/ь$/g, '')
|
|
128
|
+
return head + superlativeResult
|
|
129
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const stopwords = require('../util/stopwords_uk')
|
|
4
|
+
const Tokenizer = require('../tokenizers/aggressive_tokenizer_uk')
|
|
5
|
+
|
|
6
|
+
module.exports = function () {
|
|
7
|
+
const stemmer = this
|
|
8
|
+
|
|
9
|
+
stemmer.stem = function (token) {
|
|
10
|
+
return token
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
stemmer.tokenizeAndStem = function (text, keepStops) {
|
|
14
|
+
const stemmedTokens = []
|
|
15
|
+
|
|
16
|
+
new Tokenizer().tokenize(text).forEach(function (token) {
|
|
17
|
+
if (keepStops || stopwords.words.indexOf(token) === -1) {
|
|
18
|
+
let resultToken = token.toLowerCase()
|
|
19
|
+
if (resultToken.match(/[а-яґєії0-9]+/gi)) {
|
|
20
|
+
resultToken = stemmer.stem(resultToken)
|
|
21
|
+
}
|
|
22
|
+
stemmedTokens.push(resultToken)
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
return stemmedTokens
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Tokenizer = require('./tokenizer')
|
|
4
|
+
const util = require('util')
|
|
5
|
+
|
|
6
|
+
const AggressiveTokenizer = function () {
|
|
7
|
+
Tokenizer.call(this)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
util.inherits(AggressiveTokenizer, Tokenizer)
|
|
11
|
+
|
|
12
|
+
module.exports = AggressiveTokenizer
|
|
13
|
+
|
|
14
|
+
AggressiveTokenizer.prototype.withoutEmpty = function (array) {
|
|
15
|
+
return array.filter(function (a) { return a })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
AggressiveTokenizer.prototype.clearText = function (text) {
|
|
19
|
+
return text.replace(/[^a-zа-яґєії0-9]/gi, ' ').replace(/[\s\n]+/g, ' ').trim()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
AggressiveTokenizer.prototype.tokenize = function (text) {
|
|
23
|
+
// break a string up into an array of tokens by anything non-word
|
|
24
|
+
return this.withoutEmpty(this.clearText(text).split(' '))
|
|
25
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// a list of commonly used words that have little meaning and can be excluded
|
|
4
|
+
// from analysis.
|
|
5
|
+
const words = [
|
|
6
|
+
'о', 'після', 'всі', 'також', 'і', 'інші', 'все', 'як', 'в', 'бути',
|
|
7
|
+
'тому', 'був', 'до', 'є', 'між', 'вся', 'але', 'від', 'іди', 'можу',
|
|
8
|
+
'підійди', 'міг', 'робив', 'роблю', 'кожен', 'для', 'звідки', 'мав',
|
|
9
|
+
'він', 'має', 'її', 'тут', 'його', 'якщо', 'у', 'воно', 'за',
|
|
10
|
+
'робити', 'багато', 'може бути', 'самий', 'повинен',
|
|
11
|
+
'мій', 'ніколи', 'зараз', 'із', 'на', 'тільки', 'або', 'наше', 'поза',
|
|
12
|
+
'кінець', 'сказав', 'сказала', 'бачив', 'з',
|
|
13
|
+
'трохи', 'все ще', 'так', 'потім', 'той', 'їх', 'там', 'цей', 'вони', 'те',
|
|
14
|
+
'через', 'теж', 'під', 'над', 'дуже', 'був', 'добре',
|
|
15
|
+
'що', 'де', 'який', 'доки', 'хто', 'хотів би',
|
|
16
|
+
'а', 'б', 'в', 'г', 'ґ', 'д', 'е', 'є', 'ж', 'з', 'и', 'і', 'й', 'ї', 'к',
|
|
17
|
+
'л', 'м', 'н', 'o', 'п', 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ',
|
|
18
|
+
'ь', 'ю', 'я', '$', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '_']
|
|
19
|
+
|
|
20
|
+
// tell the world about the noise words.
|
|
21
|
+
exports.words = words
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "natural",
|
|
3
3
|
"description": "General natural language (tokenizing, stemming (English, Russian, Spanish), part-of-speech tagging, sentiment analysis, classification, inflection, phonetics, tfidf, WordNet, jaro-winkler, Levenshtein distance, Dice's Coefficient) facilities for node.",
|
|
4
|
-
"version": "6.
|
|
4
|
+
"version": "6.9.0",
|
|
5
5
|
"homepage": "https://github.com/NaturalNode/natural",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|