Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple New Functionalities #2

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions AFINN-111.json
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@
"courageous": 2,
"courteous": 2,
"courtesy": 2,
"cover-up": -3,
"cover up": -3,
"coward": -2,
"cowardly": -2,
"coziness": 2,
Expand Down Expand Up @@ -761,8 +761,9 @@
"dodging": -2,
"dodgy": -2,
"does not work": -3,
"doesn't work": -3,
"dolorous": -2,
"dont like": -2,
"don't like": -2,
"doom": -2,
"doomed": -2,
"doubt": -1,
Expand Down Expand Up @@ -1487,7 +1488,7 @@
"lackadaisical": -2,
"lagged": -2,
"lack": -2,
"made-up": -1,
"made up": -1,
"madly": -3,
"madness": -3,
"mandatory": -1,
Expand Down Expand Up @@ -1587,7 +1588,7 @@
"merry": 3,
"naive": -2,
"natural": 1,
"na�ve": -2,
"naïve": -2,
"needy": -2,
"negative": -2,
"negativity": -2,
Expand Down Expand Up @@ -1634,7 +1635,7 @@
"offline": -1,
"oks": 2,
"ominous": 3,
"once-in-a-lifetime": 3,
"once in a lifetime": 3,
"opportunities": 2,
"opportunity": 2,
"oppressed": -2,
Expand Down Expand Up @@ -1987,8 +1988,8 @@
"sedition": -2,
"seditious": -2,
"seduced": -1,
"self-confident": 2,
"self-deluded": -2,
"self confident": 2,
"self deluded": -2,
"selfish": -3,
"selfishness": -3,
"sentence": -2,
Expand All @@ -2014,8 +2015,8 @@
"shocking": -2,
"shocks": -2,
"shoot": -1,
"short-sighted": -2,
"short-sightedness": -2,
"short sighted": -2,
"short sightedness": -2,
"shortage": -2,
"shortages": -2,
"shrew": -4,
Expand Down Expand Up @@ -2074,7 +2075,7 @@
"solving": 1,
"somber": -2,
"some kind": 0,
"son-of-a-bitch": -5,
"son of a bitch": -5,
"soothe": 3,
"soothed": 3,
"soothing": 3,
Expand Down Expand Up @@ -2477,4 +2478,4 @@
"zealot": -2,
"zealots": -2,
"zealous": 2
}
}
47 changes: 36 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,43 @@

## Usage
```javascript
var sentimentAnalysis = require('sentiment-analysis');

sentimentAnalysis('Dinosaurs are awesome!'); // +0.4
sentimentAnalysis('Everything is stupid'); // -0.2
sentimentAnalysis('Windows is very unstable'); // -0.2
sentimentAnalysis('London is gloomy today because of all the smog'); // -0.4
sentimentAnalysis('I am so grateful for all the presents, thank you!'); // +0.5
sentimentAnalysis('Really enjoying the warm weather'); // +0.3
sentimentAnalysis('It was a catastrophic disaster'); // -0.6
var sentimentAnalysisLib = require('sentiment-analysis');
var sentimentAnalysis = new sentimentAnalysisLib();

sentimentAnalysis.analyseSentence('Dinosaurs are awesome!'); // +0.4
sentimentAnalysis.analyseSentence('Everything is stupid'); // -0.2
sentimentAnalysis.analyseSentence('Windows is very unstable'); // -0.2
sentimentAnalysis.analyseSentence('London is gloomy today because of all the smog'); // -0.4
sentimentAnalysis.analyseSentence('I am so grateful for all the presents, thank you!'); // +0.5
sentimentAnalysis.analyseSentence('Really enjoying the warm weather'); // +0.3
sentimentAnalysis.analyseSentence('It was a catastrophic disaster'); // -0.6
```
sentiment-analysis will return a score between -1 and +1, where negative numbers represent a negative overall sentiment.

### Options

*Custom file*
```javascript
var sentimentAnalysisLib = require('sentiment-analysis');
var sentimentAnalysis = new sentimentAnalysisLib({
customWordsFile : 'myCustomAfinn.json'
});

sentimentAnalysis.analyseSentence('Dinosaurs are awesome!');
```

*Custom words*
```javascript
var sentimentAnalysisLib = require('sentiment-analysis');
var sentimentAnalysis = new sentimentAnalysisLib({
customWords : {
dinosaurs : -4
}
});

sentimentAnalysis.analyseSentence('Dinosaurs are awesome!'); // 0
```


## Testing
`npm test`
Expand All @@ -32,7 +57,7 @@ See unit test, integration testing results on [Travis CI]
See the `gulpfile.js` for documentation of build process.

## License
MIT � [Alicia Sykes](http://aliciasykes.com)
MIT � [Alicia Sykes](http://aliciasykes.com), [Maurizio Carboni](https://www.linkedin.com/in/mauriziocarboni)

[AFINN-111]: <http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6010>
[Travis CI]: <https://travis-ci.org/Lissy93/sentiment-analysis>
[Travis CI]: <https://travis-ci.org/Lissy93/sentiment-analysis>
7 changes: 4 additions & 3 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ var watch = require('gulp-watch');
require('coffee-script/register');

var footerTxt = "\/* (C) Alicia Sykes <[email protected]> 2015 " +
"*\\\r\n\\* MIT License. Read full license at: https:\/\/goo.gl\/IL4lQJ *\/";
"*\\\r\n|* (C) Maurizio Carboni <[email protected]> 2016 " +
"*|\r\n\\* MIT License. Read full license at: https:\/\/goo.gl\/IL4lQJ *\/";

/* Delete the files currently in finished directory */
gulp.task('clean', function () {
Expand All @@ -37,7 +38,7 @@ gulp.task('build', ['clean'], function(){
});

/* Run unit tests and generate coverage report */
gulp.task('test', function (cb) {
gulp.task('test', ['build'], function (cb) {
gulp.src(['./index.js'])
.pipe(istanbul())
.pipe(istanbul.hookRequire())
Expand All @@ -62,4 +63,4 @@ gulp.task('test-after-build',['build'],function(){
});

/* Defualt gulp task, deletes old files, compiles source files and runs tests */
gulp.task('default', ['test-after-build', 'watch']);
gulp.task('default', ['test-after-build', 'watch']);
142 changes: 85 additions & 57 deletions index.coffee
Original file line number Diff line number Diff line change
@@ -1,58 +1,86 @@
sentimentAnalysisPath = __dirname

afinnWordList = require __dirname + '/AFINN-111.json' # Get the AFINN-111 list

# Returns a boolean true if given word is found in word list
doesWordExist = (word)->
if word of afinnWordList then true else false

# Returns an integer value + or - sentiment score for given word
getScoreOfWord = (word)->
if afinnWordList[word] then afinnWordList[word] else 0

# Formats sentence and returns a lowercase a-z array of words
getWordsInSentence = (sentence)->
sentence = if sentence? then sentence else '' # Double check is defined
sentence = if typeof sentence == 'string' then sentence else ''
sentence = sentence.toLowerCase()
sentence = sentence.replace(/(?:https?|ftp):\/\/[\n\S]+/g, '') # Remove URLs
sentence = sentence.replace(/[^\w\s]/gi, '') # Remove special characters
sentence = sentence.split(' ') # Split into an array
sentence = sentence.filter((n) -> n != '') # Remove blanks
sentence = removeDuplicates(sentence)

# Remove Duplicates
removeDuplicates = (arr) ->
if arr.length == 0
return []
res = {}
res[arr[key]] = arr[key] for key in [0..arr.length-1]
value for key, value of res

# Ensure score is in a valid range between -1 to +1
scaleScore = (score)->
score = if score > 10 then 10 else score
score = if score < -10 then -10 else score
score/10

# Returns an overall sentiment score for sentence
analyseSentence = (sentence) ->
score = 0
wordsArr = getWordsInSentence(sentence)
for word in wordsArr
if doesWordExist(word)
score += getScoreOfWord(word)
scaleScore(score)

module.exports = analyseSentence # Export main method as module


# If we're developing/ testing then export the private methods too
if process.env.NODE_ENV == 'test'
module.exports =
main: analyseSentence
_private:
scaleScore: scaleScore
doesWordExist: doesWordExist
getScoreOfWord: getScoreOfWord
removeDuplicates: removeDuplicates
getWordsInSentence: getWordsInSentence
module.exports = class Analizer
constructor: ( opts ) ->
# Include inside the class the wordlist
@afinnWordList = require(sentimentAnalysisPath + '/AFINN-111.json')
if opts
# If using a custom file, load it
if opts.customWordsFile
opts.customWords = require(opts.customWordsFile)
# if using custom words
# ( in case of custom file, this words are loaded from the file )
if opts.customWords
# Overwrite existing words
for i,word of opts.customWords
@afinnWordList[i] = word
@afinnPhrases = []
@afinnPhrasesCamel = []
for word,value of @afinnWordList
wordA = @constructor.transformPlainApostrophe(word)
if ( wordA != word )
@afinnWordList[wordA] = value
delete @afinnWordList[word]
word = wordA
if @constructor.isPhrase(word)
compressedWord = @constructor.compressPhrase(word)
@afinnPhrases.push word
@afinnPhrasesCamel.push compressedWord
@afinnWordList[compressedWord] = value

# Returns an overall sentiment score for sentence
analyseSentence: ( sentence ) ->
score = 0
wordsArr = @getWordsInSentence(sentence)
for word in wordsArr
if @doesWordExist(word)
score += @getScoreOfWord(word)
@constructor.scaleScore(score)

# Returns a boolean true if given word is found in word list
doesWordExist: ( word ) ->
word of @afinnWordList

# Returns an integer value + or - sentiment score for given word
getScoreOfWord: ( word ) ->
@afinnWordList[word] || 0


# Formats sentence and returns a lowercase a-z array of words
getWordsInSentence: ( sentence ) ->
sentence = sentence || '' # Double check is defined
sentence = if typeof sentence == 'string'then sentence.toLowerCase() else ''
sentence = sentence.replace(/(?:https?|ftp):\/\/[\n\S]+/g, '') # Remove URLs
sentence = sentence.replace(/[\n\r\t]/gi, ' ') # Transform \n and \t
sentence = @constructor.transformPlainApostrophe(sentence) # Transform '
sentence = sentence.replace(/[^\w\s']/gi, '') # Remove special characters
# Replace phrases with the camelized version
for word,i in @afinnPhrases
sentence = sentence.replace( new RegExp(word,'g'), @afinnPhrasesCamel[i])
sentence = sentence.split(' ') # Split into an array
sentence = sentence.filter((n) -> n != '') # Remove blanks
@constructor.removeDuplicates(sentence)

# Remove Duplicates
@removeDuplicates: ( arr ) ->
if arr.length == 0
return []
res = {}
res[arr[key]] = arr[key] for key in [0..arr.length-1]
value for key, value of res

# Ensure score is in a valid range between -1 to +1
@scaleScore: ( score ) ->
score = if score > 10 then 10 else score
score = if score < -10 then -10 else score
score/10

@isPhrase: ( word ) ->
word.indexOf(' ') != -1

@compressPhrase: ( phrase ) ->
(w[0].toUpperCase() + w[1..-1].toLowerCase() for w in phrase.split /\s+/)
.join ''

@transformPlainApostrophe: ( phrase ) ->
phrase.replace(/[`"]/gi, '\'')
Loading