The mood light should be responsive enough to reflect what has just happened in the world, but it must not be so overly sensitive as to be susceptible to noise, and also not be too sluggish to be late in informing you of a big world event.
The important thing is to carefully normalize and smooth the data, and to adjust the thresholds to give the right level of responsiveness and alarm. (i.e. it should flash when a headline news story
happens and not when a TV show starts, GMT)
Emotion, mood, and temperament
Firstly, the "world's emotion" is calculated by searching twitter for tweets with each of the 7 mood types (love, joy, surprise, anger, fear, envy, sad) .
A measure of "tweets per minute" is used to calculate the current emotion. A higher number of tweets per minute suggests more people are currently feeling that emotion.
Emotions are volatile, so these short-lived emotional states are smoothed over time by using a "fast exponential moving average"
(see
en.wikipedia.org/wiki/Moving_average#Exponential_moving_average)
This gives us ratios for the different moods.
Each mood ratio is then compared to a base line, a "slow exponential moving average", that I call the "world temperament".
The mood that has deviated furthest from its baseline temperament value is considered to be the current world mood.
The deviation is measured as a percentage, so, for example, if
fear changes from accounting for 5% of tweets to 10% then this is more significant than
joy changing from 40% to 45% (They are both a +5% in additive terms, but
fear increased by 100% in multiplicative terms.)
Finally, the world temperament values are tweaked slightly in light of this new result. This gives the system a self adjusting property so that the world temperament can very slowly change over time.
These values in WorldMood.pde are used to adjust how sensitive the system is to information.
- Do you want it to pick up when people are happy about a sport result or scared about the weather?
- Or would you prefer to only track big events like natural disasters or terrorist attacks?
adjust accordingly...
#define emotionSmoothingFactor (0.1f)
#define moodSmoothingFactor (0.05f)
#define moderateMoodThreshold (2.0f)
#define extremeMoodThreshold (4.0f)
MOOD_TYPE WorldMood::ComputeCurrentMood()
{
// find the current ratios
float sum = 0;
for (int i = 0; i < NUM_MOOD_TYPES; i++)
{
sum += m_worldMoodCounts[i];
}
if (sum < 1e-4f)
{
#ifdef DEBUG
m_printer->print("unexpected total m_worldMoodCounts");
#endif // ifdef DEBUG
return m_worldMood;
}
for (int i = 0; i < NUM_MOOD_TYPES; i++)
{
m_worldMoodRatios[i] = m_worldMoodCounts[i] / sum;
}
// find the ratio that has increased by the most, as a proportion of its moving average.
// So that, for example, an increase from 5% to 10% is more significant than an increase from 50% to 55%.
float maxIncrease = -1.0f;
for (int i = 0; i < NUM_MOOD_TYPES; i++)
{
float difference = m_worldMoodRatios[i] - m_worldTemperamentRatios[i];
if (m_worldTemperamentRatios[i] < 1e-4f)
{
#ifdef DEBUG
m_printer->print("unexpected m_worldTemperamentRatios");
#endif // ifdef DEBUG
continue;
}
difference /= m_worldTemperamentRatios[i];
if (difference > maxIncrease)
{
maxIncrease = difference;
m_worldMood = (MOOD_TYPE)i; // this is now the most dominant mood of the world!
}
}
// update the world temperament, as an exponential moving average of the mood.
// this allows the baseline ratios, i.e. world temperament, to change slowly over time.
// this means, in affect, that the 2nd derivative of the world mood wrt time is part of the current mood calcuation.
// and so, after a major anger-inducing event, we can see when people start to become less angry.
sum = 0;
for (int i = 0; i < NUM_MOOD_TYPES; i++)
{
if (m_worldTemperamentRatios[i] <= 0)
{
#ifdef DEBUG
m_printer->print("m_worldTemperamentRatios should be initialised at construction");
#endif // #ifdef DEBUG
m_worldTemperamentRatios[i] = m_worldMoodRatios[i];
}
else
{
const float a = m_moodSmoothingFactor;
m_worldTemperamentRatios[i] = (m_worldTemperamentRatios[i] * (1.0f - a)) + (m_worldMoodRatios[i] * a);
}
sum += m_worldTemperamentRatios[i];
}
if (sum < 1e-4f)
{
#ifdef DEBUG
m_printer->print("unexpected total m_worldTemperamentRatios total");
#endif // #ifdef DEBUG
return m_worldMood;
}
// and finally, renormalise, to keep the sum of the moving average ratios as 1.0f
for (int i = 0; i < NUM_MOOD_TYPES; i++)
{
m_worldTemperamentRatios[i] *= 1.0f / sum;
#ifdef DEBUG
m_printer->print("temperament ratio: ");
m_printer->println(m_worldTemperamentRatios[i]);
#endif
}
#ifdef DEBUG
// debug code - check sum is 1.
sum = 0;
for (int i = 0; i < NUM_MOOD_TYPES; i++)
{
sum += m_worldTemperamentRatios[i];
}
if (sum > 1.0f + 1e-4f || sum < 1.0f - 1e-4f)
{
m_printer->println("unexpected renormalise result");
}
#endif // #ifdef DEBUG
return m_worldMood;
}