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 gnu.trove.map.hash.TObjectIntHashMap;
033import gnu.trove.procedure.TObjectIntProcedure;
034
035import java.util.HashMap;
036import java.util.Map;
037
038import org.openimaj.text.nlp.sentiment.model.wordlist.util.TFF;
039import org.openimaj.text.nlp.sentiment.model.wordlist.util.TFF.Clue;
040import org.openimaj.text.nlp.sentiment.model.wordlist.util.TFF.Polarity;
041
042/**
043 * A Discrete count sentiment is one which a set of arbitrary sentiments are given a count in a given phrase .
044 * 
045 * The sentiments hold values for counts of words considered to be one of the N sentiments provided and the total number of words the sentiment was 
046 * decided against is also provided. 
047 * 
048 * An assumption is made that a single term can only have a single sentiment, therefore
049 * sum(sentiment_count) < total
050 * though perhaps not equal as some terms may be none of the N sentiments (stop words etc.)
051 * 
052 * @author Sina Samangooei (ss@ecs.soton.ac.uk)
053 *
054 */
055public class TFFCountSentiment implements Sentiment, BipolarSentimentProvider, WeightedBipolarSentimentProvider, DiscreteCountBipolarSentimentProvider{
056        
057        private static final class BipolarTFFPolarityIterator implements TObjectIntProcedure<TFF.Polarity> {
058                public int negative = 0;
059                public int neutral = 0;
060                public int positive = 0;
061
062                @Override
063                public boolean execute(Polarity a, int b) {
064                        if(a.equals(Polarity.positive) || a.equals(Polarity.strongpos) || a.equals(Polarity.weakpos)){
065                                positive+=b;
066                        }
067                        else if(a.equals(Polarity.both)){
068                                positive+=b;
069                                negative+=b;
070                        }
071                        else if(a.equals(Polarity.neutral)){
072                                neutral+=b;
073                        }
074                        else{
075                                negative+=b;
076                        }
077                        return true;
078                }
079        }
080        private TObjectIntHashMap<TFF.Polarity> sentiments;
081        private int total;
082        
083        /**
084         * all weights set to 0
085         */
086        public TFFCountSentiment() {
087                sentiments = new TObjectIntHashMap<TFF.Polarity>();
088                for (Polarity polarity : TFF.Polarity.values()) {
089                        sentiments.put(polarity, 0);
090                }
091        }
092        
093        /**
094         * @param total instnatiate with a total number of words
095         */
096        public TFFCountSentiment(int total) {
097                this();
098                this.total = total;
099        }
100
101        /**
102         * @param entry
103         * @param increment
104         */
105        public void incrementClue(TFF.Clue entry, int increment){
106                this.sentiments.adjustOrPutValue(entry.polarity, increment, increment);
107        }
108        
109        @Override
110        public BipolarSentiment bipolar() {
111                BipolarTFFPolarityIterator instance = new BipolarTFFPolarityIterator();
112                this.sentiments.forEachEntry(instance);
113                if(instance.positive > instance.negative){
114                        if(instance.positive > instance.neutral){
115                                return BipolarSentiment.POSITIVE;
116                        }
117                        else{
118                                return BipolarSentiment.NEUTRAL;
119                        }
120                }
121                else{
122                        if(instance.negative > instance.neutral){
123                                return BipolarSentiment.NEGATIVE;
124                        }
125                        else{
126                                return BipolarSentiment.NEUTRAL;
127                        }
128                }
129        }
130        
131        @Override
132        public BipolarSentiment bipolar(double deltaThresh) {
133                BipolarTFFPolarityIterator instance = new BipolarTFFPolarityIterator();
134                this.sentiments.forEachEntry(instance);
135                if(instance.positive > instance.negative * deltaThresh){
136                        if(instance.positive > instance.neutral * deltaThresh){
137                                return BipolarSentiment.POSITIVE;
138                        }
139                        else if(instance.neutral > instance.positive * deltaThresh){
140                                return BipolarSentiment.NEUTRAL;
141                        }
142                }
143                else{
144                        if(instance.negative > instance.neutral * deltaThresh){
145                                return BipolarSentiment.NEGATIVE;
146                        }
147                        else if(instance.neutral > instance.negative * deltaThresh){
148                                return BipolarSentiment.NEUTRAL;
149                        }
150                }
151                return null;
152        }
153
154        @Override
155        public WeightedBipolarSentiment weightedBipolar() {
156                BipolarTFFPolarityIterator instance = new BipolarTFFPolarityIterator();
157                this.sentiments.forEachEntry(instance);
158                return new WeightedBipolarSentiment(
159                        instance.positive / (double)this.total,
160                        instance.negative / (double)this.total,
161                        instance.neutral / (double)this.total
162                );
163        }
164        @Override
165        public DiscreteCountBipolarSentiment countBipolarSentiment() {
166                BipolarTFFPolarityIterator instance = new BipolarTFFPolarityIterator();
167                this.sentiments.forEachEntry(instance);
168                try {
169                        return new DiscreteCountBipolarSentiment(
170                                instance.positive ,
171                                instance.negative ,
172                                instance.neutral ,
173                                this.total
174                        );
175                } catch (InvalidSentimentException e) {
176                        return null; // should never happen
177                }
178        }
179        @Override
180        public Map<String, ?> asMap() {
181                HashMap<String, Integer> ret = new HashMap<String,Integer>();
182                for (Polarity polarity: TFF.Polarity.values()) {
183                        ret.put(polarity.name(), this.sentiments.get(polarity));
184                }
185                ret.put("total", total);
186                return ret;
187        }
188
189        @Override
190        public void fromMap(Map<String, ?> map) throws UnrecognisedMapException {
191                for (Polarity polarity : TFF.Polarity.values()) {
192                        Object value = map.get(polarity.name());
193                        if(value == null) throw new UnrecognisedMapException("Could not find polarity: " + polarity);
194                        this.sentiments.put(polarity, (Integer)value);
195                }
196                if(!map.containsKey("total")) throw new UnrecognisedMapException("Could not find total");
197                this.total = (Integer) map.get("total");
198        }
199        
200        @Override
201        public boolean equals(Object obj) {
202                if(!(obj instanceof TFFCountSentiment)) return false;
203                TFFCountSentiment that = (TFFCountSentiment) obj;
204                if(this.total != that.total) return false;
205                for (Object clue : this.sentiments.keys()) {
206                        if(this.sentiments.get(clue) != that.sentiments.get(clue))return false;
207                }
208                return true;
209        }
210
211        /**
212         * @param clue
213         * @return given a clue construct a {@link TFFCountSentiment} with 1 word and 1 clue and call {@link TFFCountSentiment#bipolar()}
214         */
215        public static BipolarSentiment bipolar(Clue clue) {
216                TFFCountSentiment count = new TFFCountSentiment(1);
217                count.incrementClue(clue, 1);
218                return count.bipolar();
219        }
220
221        
222
223}