@happyaccident/wink-sentiment
Version:
Accurate and fast sentiment scoring of phrases with emoticons :) & emojis 🎉
442 lines (384 loc) • 14.1 kB
HTML
<html lang="en">
<head>
<meta charset="utf-8">
<title>wink-sentiment - Wink JS - wink-sentiment.js</title>
<script src="scripts/prettify/prettify.js"></script>
<script src="scripts/prettify/lang-css.js"></script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-47082559-2"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-47082559-2');
</script>
<script src="https://use.typekit.net/cwc1qce.js"></script>
<script>
((window.gitter = {}).chat = {}).options = {
room: 'winkjs/Lobby'
};
window.onload = function () {
document.querySelector('.gitter-open-chat-button').innerText = 'Need help?'
document.querySelector('.gitter-open-chat-button').style.display = 'block';
}
</script>
<script src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer></script>
<script>try{Typekit.load({ async: true });}catch(e){}</script>
<style media="screen">
body {
font-family: 'ff-tisa-sans-web-pro', serif;
}
.navbar {
border-top: 4px solid #6C307D;
box-sizing: border-box;
height: 64px;
background-color: #222;
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: 1030;
display: flex;
justify-content: space-between;
}
.navbar-header {
font-weight: bold;
font-size: 30px;
}
.navbar-header a img {
height: 24px;
}
.navbar a {
color: #fff;
line-height: 60px;
margin-left: 0;
}
.navbar a.navbar-brand {
margin-left: 20px;
display: block;
min-width: 40px;
}
.navbar-right {
margin: 0;
line-height: 50px;
list-style: none;
}
.navbar-right li {
float: left;
margin-right: 20px;
text-transform: uppercase;
letter-spacing: 1px;
padding-left: 20px;
font-size: 14px;
}
/* Override docdash */
#main {
margin-top: 30px;
}
/* Gitter */
.gitter-open-chat-button {
background: #6C307D;
display: none;
}
.gitter-open-chat-button:hover {
opacity: 0.8;
background: #6C307D;
}
.gitter-chat-embed {
top: 64px;
}
</style>
<script type="text/javascript">
window.addEventListener('DOMContentLoaded', function () {
if ( window.location.hash ) {
openMember();
window.setTimeout( function () {
window.scrollTo( 0, (window.pageYOffset || document.documentElement.scrollTop) - 72);
}, 0)
}
document.getElementById('toc-button').addEventListener('click', function () {
if (document.getElementById('side-nav').className === '') {
document.getElementById('side-nav').className = 'is-open';
document.getElementById('toc-button-img').setAttribute('src','./close.svg');
} else {
document.getElementById('side-nav').className = '';
document.getElementById('toc-button-img').setAttribute('src','./menu.svg');
}
})
});
window.addEventListener( 'hashchange', function (e) {
e.stopPropagation();
openMember();
} );
function openMember() {
var id = window.location.hash.substr(1),
toggler = document.getElementById( id ),
offset = toggler.getBoundingClientRect().top,
y = window.scrollY + offset - 62 - 10;
window.scrollTo(0, y);
}
</script>
<header>
<div class="navbar">
<div class="navbar-header">
<a href="https://winkjs.org/" title="wink" class="navbar-brand">
<img src="https://winkjs.org/images/logo.svg">
<span>wink</span>
</a>
</div>
<div class="sausage-links">
<ul class="nav navbar-nav navbar-right collapse navbar-collapse" id="main-nav">
<li><a href="https://winkjs.org/packages.html" class="">Packages</a></li>
<li><a href="https://winkjs.org/showcase.html" class="">Showcase</a></li>
<li><a href="https://winkjs.org/blog.html" class="">Blog</a></li>
<li><a href="http://github.com/winkjs">Github</a></li>
<li><a href="https://winkjs.org/about.html" class="">About</a></li>
</div>
</nav>
</div>
</header>
<div id="toc-button">
<img src="./menu.svg" alt="" id="toc-button-img">
</div>
<div class="content-container">
<div class="main-container">
<div id="main">
<h1 class="page-title">wink-sentiment.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>// wink-sentiment
// Accurate and fast sentiment scoring of phrases with hashtags, emoticons & emojis.
//
// Copyright (C) 2017-18 GRAYPE Systems Private Limited
//
// This file is part of “wink-sentiment”.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
var emojis = require( './emojis.js' );
var afinn = require( './afinn-en-165.js' );
var emoticons = require( './emoticons.js' );
var negations = require( './negations.js' );
var affin2Grams = require( './afinn-en-165-2grams.js' );
var tokenize = require( 'wink-tokenizer' )().tokenize;
/* eslint max-depth: 0 */
// ### normalize
/**
*
* Computes the normalized sentiment score from the absolute scores.
*
* @param {number} hss absolute sentiment scrore of hashtags.
* @param {number} wss absolute sentiment scrore of words/emojis/emoticons.
* @param {number} sentiHashtags number of hashtags that have an associated sentiment score.
* @param {number} sentiWords wnumber of words that have an associated sentiment score.
* @param {number} totalWords total number of words in the text.
* @return {number} normalized score.
* @private
*/
var normalize = function ( hss, wss, sentiHashtags, sentiWords, totalWords ) {
// **N**ormalized **h**ashtags & **w**ords **s**entiment **s**cores.
let nhss = 0,
nwss = 0;
// 1. Normalize hashtags sentiment score by computing the average.
if ( sentiHashtags ) nhss = hss / sentiHashtags;
if ( sentiWords ) {
// 2. Normalize words sentiment score by computing the average.
nwss = wss / sentiWords;
// 3. Normalized words sentiment score is further adjusted on the basis of the
// total number of words in the text.
// Average sentence length in words (assumed).
const avgLength = 15;
// Make adjustments.
nwss /= Math.sqrt( ( totalWords > avgLength ) ? ( totalWords / avgLength ) : 1 );
}
return ( nhss && nwss ) ? ( ( nhss + nwss ) / 2 ) : ( nwss || nhss );
}; // normalize()
// ### sentiment
/**
*
* Computes the absolue and normalized sentiment scores of the input `phrase`,
* after tokenizing it.
*
* The normalized score is computed by taking into account of absolute scores of
* words, emojis, emoticons, and hashtags and adjusting it on the basis of total
* words in the text; this is always between -5 and +5. A score of less than 0 indicates
* negative sentiments and a score of more than 0 indicates positive sentiments;
* wheras a near zero score suggests a neutral sentiment. While counting tokens
* only the ones tagged as **`word`**, **`emoji`**, or **`emoticon`** are counted;
* and one letter words are ignored.
*
* It performs tokenization using [wink-tokenizer](http://winkjs.org/wink-tokenizer/).
* During sentiment analysis, each token may be assigned up to 3 new properties.
* These properties are:
*
* 1. **`score`** — contains the sentiment score of the word, emoji, emoticon or hashtag, which is always
* between -5 and +5. This is added only when the word in question has a positive or
* negative sentiment associated with it.
* 2. **`negation`** — is added & set to **true** whenever the `score` of the
* token has beeen impacted due to a negation word apprearing prior to it.
* 3. **`grouped`** — is added whenever, the token is the first
* word of a short idiom or a phrase. It's value provides the number of tokens
* that have been grouped together to form the phrase/idiom.
*
* @param {string} phrase whoes sentiment score needs to be computed.
* @return {object} absolute `score`, `normalizedScore` and `tokenizedPhrase` of `phrase`.
*
* @example
* sentiment( 'not a good product #fail' );
* // -> { score: -5,
* // normalizedScore: -2.5,
* // tokenizedPhrase: [
* // { value: 'not', tag: 'word' },
* // { value: 'a', tag: 'word' },
* // { value: 'good', tag: 'word', negation: true, score: -3 },
* // { value: 'product', tag: 'word' },
* // { value: '#fail', tag: 'hashtag', score: -2 }
* // ]
* // }
*/
var sentiment = function ( phrase ) {
if ( typeof phrase !== 'string' ) {
throw Error( 'wink-sentiment: input phrase must be a string, instead found: ' + typeof phrase );
}
// Early exit.
var tokenizedPhrase = tokenize( phrase );
if ( tokenizedPhrase.length === 0 ) return { score: 0, normalizedScore: 0 };
// Sentiment Score.
var ss = 0;
// Hash Tags SS.
var hss = 0;
// Number of sentiment containing hashtags and words encountered.
var sentiHashtags = 0,
sentiWords = 0;
// Number of words encountered.
var words = 0;
// Helpers: for loop indexes, token, temp ss, and word count.
var k, kmax, t, tkn, tss, wc;
for ( k = 0, kmax = tokenizedPhrase.length; k < kmax; k += 1 ) {
tkn = tokenizedPhrase[ k ];
t = tkn.value;
switch ( tkn.tag ) {
case 'emoji':
tss = emojis[ t ];
if ( tss ) {
ss += tss;
tkn.score = tss;
sentiWords += 1;
}
words += 1;
break;
case 'emoticon':
tss = emoticons[ t ];
if ( tss ) {
ss += tss;
tkn.score = tss;
sentiWords += 1;
}
words += 1;
break;
case 'hashtag':
tss = afinn[ t.slice( 1 ).toLowerCase() ];
if ( tss ) {
tkn.score = tss;
hss += tss;
sentiHashtags += 1;
}
break;
case 'word':
t = t.toLowerCase();
wc = 1;
// Check for bigram configurations i.e. token at `k` and `k+1`. Accordingly
// compute the sentiment score in `tss`. Convert to Lower Case for case insensitive comparison.
if ( ( k < ( kmax - 1 ) ) && affin2Grams[ t ] && ( affin2Grams[ t ][ tokenizedPhrase[ k + 1 ].value.toLowerCase() ] !== undefined ) ) {
tss = affin2Grams[ t ][ tokenizedPhrase[ k + 1 ].value.toLowerCase() ];
tkn.grouped = 1;
// Will have to count `2` words!
wc = 2;
// sentiWords += 1;
} else {
tss = afinn[ t ] || 0;
// sentiWords += 1;
}
// Check for negation — upto two words ahead; even a bigram AFINN config may be negated! Convert to Lower Case for case insensitive comparison.
if ( ( k > 0 && negations[ tokenizedPhrase[ k - 1 ].value.toLowerCase() ] ) || ( k > 1 && negations[ tokenizedPhrase[ k - 2 ].value.toLowerCase() ] ) ) {
tss = -tss;
tkn.negation = true;
}
ss += tss;
// Increment `k` by 1 if a bigram config was found earlier i.e. `wc` was set to **2**.
k += ( wc - 1 );
if ( tss ) {
tkn.score = tss;
sentiWords += 1;
}
// Update number of words accordingly.
words += wc;
break;
default:
// Do Nothing!
} // swtich ( t.tag )
}
// if ( words === 0 ) words = 1;
// Return score and its normalized value.
return {
score: ( ss + hss ),
normalizedScore: +( normalize( hss, ss, sentiHashtags, sentiWords, words ) ).toFixed( 4 ),
tokenizedPhrase: tokenizedPhrase
};
}; // sentiment()
module.exports = sentiment;
</code></pre>
</article>
</section>
</div>
</div>
<nav id="side-nav">
<h2><a href="index.html">Summary</a></h2><h2><a href="https://github.com/winkjs/wink-sentiment" target="_blank" >Github</a></h2><h3>Global</h3><ul><li><a href="global.html#sentiment">sentiment</a></li></ul>
</nav>
</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.3</a> on Thu Aug 08 2019 20:49:19 GMT+0530 (IST) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
</footer>
<script>prettyPrint();</script>
<script src="scripts/linenumber.js"></script>
<script src="https://embed.runkit.com"></script>
<script type="text/javascript">
var els = document.getElementsByClassName("runkit");
Array.prototype.forEach.call(els, function(el) {
// Do stuff here
var source = el.innerHTML;
el.innerHTML = '';
var n = RunKit.createNotebook( {
element: el,
source: source
} );
});
</script>
</body>
</html>