001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.text.nlp.sentiment.type;
031
032import java.util.HashMap;
033import java.util.Map;
034
035import org.openimaj.util.math.ObjectArithmetic;
036import org.openimaj.util.math.ScalarArithmetic;
037
038/**
039 * @author Sina Samangooei (ss@ecs.soton.ac.uk)
040 *
041 */
042/**
043 * A weighted bipolar sentiment is one which is positive, negative or netural to
044 * some degree.
045 * 
046 * The sentiment holds values for the three and the total number of words the
047 * sentiment was decided against. In a simple case the numbers might be wieghted
048 * counts of words judged as being positive, negative or neutral.
049 * 
050 * In more complex implementations the numbers may represent probability
051 * estimates for the three states
052 * 
053 * No guarantee is made on the range of weights
054 * 
055 * @author Sina Samangooei (ss@ecs.soton.ac.uk)
056 * 
057 */
058public class WeightedBipolarSentiment
059                implements
060                        Sentiment,
061                        BipolarSentimentProvider,
062                        ScalarArithmetic<WeightedBipolarSentiment, Double>,
063                        ObjectArithmetic<WeightedBipolarSentiment>
064
065{
066
067        private double positive, negative, neutral;
068
069        /**
070         * all weights set to 0
071         */
072        public WeightedBipolarSentiment() {
073        }
074
075        /**
076         * @param positive
077         *            the positive
078         * @param negative
079         *            the negative
080         * @param neutral
081         *            the neutral
082         */
083        public WeightedBipolarSentiment(double positive, double negative, double neutral) {
084                this.positive = positive;
085                this.negative = negative;
086                this.neutral = neutral;
087        }
088
089        /**
090         * @return positive
091         */
092        public double positive() {
093                return positive;
094        }
095
096        /**
097         * @return negative
098         */
099        public double negative() {
100                return negative;
101        }
102
103        /**
104         * @return neutral
105         */
106        public double neutral() {
107                return neutral;
108        }
109
110        @Override
111        public BipolarSentiment bipolar() {
112                if (this.positive > this.negative) {
113                        if (this.positive > this.neutral) {
114                                return BipolarSentiment.POSITIVE;
115                        } else {
116                                return BipolarSentiment.NEUTRAL;
117                        }
118                } else {
119                        if (this.negative > this.neutral) {
120                                return BipolarSentiment.NEGATIVE;
121                        } else {
122                                return BipolarSentiment.NEUTRAL;
123                        }
124                }
125        }
126
127        @Override
128        public BipolarSentiment bipolar(double deltaThresh) {
129                if (this.positive > this.negative * deltaThresh) {
130                        if (this.positive > this.neutral * deltaThresh) {
131                                return BipolarSentiment.POSITIVE;
132                        } else if (this.neutral > this.positive * deltaThresh) {
133                                return BipolarSentiment.NEUTRAL;
134                        }
135                } else {
136                        if (this.negative > this.neutral * deltaThresh) {
137                                return BipolarSentiment.NEGATIVE;
138                        } else if (this.neutral > this.negative * deltaThresh) {
139                                return BipolarSentiment.NEUTRAL;
140                        }
141                }
142                return null;
143        }
144
145        @Override
146        public Map<String, ?> asMap() {
147                final HashMap<String, Double> ret = new HashMap<String, Double>();
148                ret.put("positive", positive);
149                ret.put("negative", negative);
150                ret.put("neutral", neutral);
151                return ret;
152        }
153
154        @Override
155        public void fromMap(Map<String, ?> map) throws UnrecognisedMapException {
156                if (!map.containsKey("positive") || !map.containsKey("negative") || !map.containsKey("neutral")) {
157                        throw new UnrecognisedMapException("positive", "negative", "neutral");
158                }
159                this.positive = (Double) map.get("positive");
160                this.negative = (Double) map.get("negative");
161                this.neutral = (Double) map.get("neutral");
162        }
163
164        @Override
165        public String toString() {
166                final String out = "+(%.4f),-(%.4f),~(%.4f)";
167
168                return String.format(out, this.positive, this.negative, this.neutral);
169        }
170
171        // ARITHMATIC CODE FROM HERE
172
173        @Override
174        public WeightedBipolarSentiment addInplace(WeightedBipolarSentiment that) {
175                this.negative += that.negative;
176                this.positive += that.positive;
177                this.neutral += that.neutral;
178                return this;
179        }
180
181        @Override
182        public WeightedBipolarSentiment add(WeightedBipolarSentiment that) {
183                return this.clone().add(that);
184        }
185
186        @Override
187        public WeightedBipolarSentiment divide(WeightedBipolarSentiment that) {
188                return this.clone().divideInplace(that);
189        }
190
191        @Override
192        public WeightedBipolarSentiment divideInplace(WeightedBipolarSentiment that) {
193                this.negative /= that.negative;
194                this.neutral /= that.neutral;
195                this.positive /= that.positive;
196                return this;
197        }
198
199        @Override
200        public WeightedBipolarSentiment clone() {
201                return new WeightedBipolarSentiment(this.positive, this.negative, this.neutral);
202        }
203
204        /**
205         * @return the total of the bipolar weightings
206         */
207        public double total() {
208                return this.negative + this.neutral + this.positive;
209        }
210
211        @Override
212        public WeightedBipolarSentiment add(Double f) {
213                return this.clone().addInplace(f);
214        }
215
216        @Override
217        public WeightedBipolarSentiment addInplace(Double f) {
218                this.negative += f;
219                this.positive += f;
220                this.neutral += f;
221                return this;
222        }
223
224        @Override
225        public WeightedBipolarSentiment divide(Double d) {
226                return this.clone().divideInplace(d);
227        }
228
229        @Override
230        public WeightedBipolarSentiment divideInplace(Double d) {
231                this.negative /= d;
232                this.neutral /= d;
233                this.positive /= d;
234                return this;
235        }
236
237        @Override
238        public WeightedBipolarSentiment subtract(WeightedBipolarSentiment s) {
239                return this.clone().subtractInplace(s);
240        }
241
242        @Override
243        public WeightedBipolarSentiment subtractInplace(WeightedBipolarSentiment f) {
244                this.negative -= f.negative;
245                this.positive -= f.positive;
246                this.neutral -= f.neutral;
247                return this;
248        }
249
250        @Override
251        public WeightedBipolarSentiment multiply(WeightedBipolarSentiment that) {
252                return this.clone().multiplyInplace(that);
253        }
254
255        @Override
256        public WeightedBipolarSentiment multiplyInplace(WeightedBipolarSentiment that) {
257                this.negative *= that.negative;
258                this.neutral *= that.neutral;
259                this.positive *= that.positive;
260                return this;
261        }
262
263        @Override
264        public WeightedBipolarSentiment multiply(Double value) {
265                return this.clone().multiplyInplace(value);
266        }
267
268        @Override
269        public WeightedBipolarSentiment multiplyInplace(Double value) {
270                this.negative *= value;
271                this.neutral *= value;
272                this.positive *= value;
273                return this;
274        }
275
276        @Override
277        public WeightedBipolarSentiment subtract(Double s) {
278                return this.add(-s);
279        }
280
281        @Override
282        public WeightedBipolarSentiment subtractInplace(Double s) {
283                return this.addInplace(-s);
284        }
285
286        /**
287         * @return whether any weight is NaN
288         */
289        public boolean containsNaN() {
290                return new Double(this.negative).isNaN() || new Double(this.neutral).isNaN() || new Double(this.positive).isNaN();
291        }
292
293        /**
294         * @return log the weights in place
295         */
296        public WeightedBipolarSentiment logInplace() {
297                if (this.negative > 0)
298                        this.negative = Math.log(negative);
299                if (this.positive > 0)
300                        this.positive = Math.log(positive);
301                if (this.neutral > 0)
302                        this.neutral = Math.log(neutral);
303                return this;
304        }
305
306        /**
307         * @param neutral
308         */
309        public void neutral(double neutral) {
310                this.neutral = neutral;
311        }
312
313        /**
314         * @param negative
315         */
316        public void negative(double negative) {
317                this.negative = negative;
318        }
319
320        /**
321         * @param positive
322         */
323        public void positive(double positive) {
324                this.positive = positive;
325        }
326
327        /**
328         * @param d
329         *            set NaN instances to this value
330         */
331        public void correctNaN(double d) {
332                if (new Double(this.negative).isNaN())
333                        this.negative = d;
334                if (new Double(this.positive).isNaN())
335                        this.positive = d;
336                if (new Double(this.neutral).isNaN())
337                        this.neutral = d;
338        }
339
340        @Override
341        public boolean equals(Object obj) {
342                if (!(obj instanceof WeightedBipolarSentiment))
343                        return false;
344                final WeightedBipolarSentiment that = (WeightedBipolarSentiment) obj;
345                return this.negative == that.negative && this.positive == that.positive && this.neutral == that.neutral;
346        }
347
348        /**
349         * @return {@link Math#exp(double)} each value inplace
350         */
351        public WeightedBipolarSentiment expInplace() {
352                this.negative = Math.exp(negative);
353                this.positive = Math.exp(positive);
354                this.neutral = Math.exp(neutral);
355                return this;
356        }
357
358        /**
359         * @param d
360         * @return clip each value to d inplace
361         */
362        public WeightedBipolarSentiment clipMaxInplace(double d) {
363                if (this.negative > d)
364                        this.negative = d;
365                if (this.positive > d)
366                        this.positive = d;
367                if (this.neutral > d)
368                        this.neutral = d;
369                return this;
370        }
371}