Problem statement:

Suppose that there are N people in a community, some pairs of which are acquaintances (meaning that they interact with each other) and some pairs of which do not interact with each other at all. Let us say that each person’s happiness is some function of the happiness values of the people that they interact with, as well as possibly some external factors that vary from person to person. In this problem, we would like you to propose one or more models (i.e. give the function) of how happiness values behave, and describe the properties that arise. Some examples of models or functions you may consider are: taking the average of all acquaintances, taking the average of the top 5 happiest acquaintances, or adding time dependency. Your final model should at least somewhat accurately reflect friendships and happiness in the “real world”. In addition, if you had models that at first seemed reasonable but turned out to not behave well, you should also mention these and describe how you ruled them out.

I'll be answering this question with an explanation of my model and an accompanying simulation with a time dependency in Ruby.

Our simulation will take place in a "world" with N people whose happiness and acquaintances will be tracked.

This happiness will be represented as a number hR

We could decide to simply randomly create acquaintances between members of our world, but I think it will be more interesting to instead try to emulate the idea of "groups" of society, like belonging to a family, friend group, etc... These groups can also have only 2 members, in which case they merely represent a relationship.

Each person will be assigned a random number representing their initial mental "inclination" to happiness, no matter their environment. We'll then assign and then randomly create groups of several members, and each group will have an updating "happiness" score based on its members happiness.

Let's build the scaffold of our simulation with Person and Group classes.

In [84]:
class Person
  attr_accessor :happiness, :belongs_to, :max_happiness, :min_happiness, :average_variation, :current_step_variation
  def initialize
    @happiness = (rand -100..100).to_f
    @belongs_to = []
    @max_happiness = @happiness
    @min_happiness = @happiness
    @current_step_variation = 0
    @average_variation = 0
  end

  def increment_happiness(added)
    @current_step_variation += added
    @happiness += added
    if @happiness > @max_happiness
      @max_happiness = @happiness
    end
    if @happiness < @min_happiness
      @min_happiness = @happiness
    end
  end
end

def avg(array)
  array.inject(:+).to_f / array.length # helper method to get an average of the values of an array
end

N_PEOPLE = 1000
$people = (1..N_PEOPLE).map { Person.new } # seed our "People"

class Group
  attr_accessor :members, :happiness
  def initialize(members)
    @members = members
  end
end

N_GROUPS = 700
MAX_GROUP_SIZE = 10
$groups = (0..N_GROUPS-1).map do |group_i| # let's create our random groups
  curr_group_size = rand 2..MAX_GROUP_SIZE
  members = (0..N_PEOPLE-1).to_a.shuffle.take(curr_group_size) # randomly assemble group members
  members.each do |i|
    $people[i].belongs_to.append(group_i)
  end
  Group.new(members)
end
avg($people.map {|p| p.happiness}) # average initial happiness in this run of the simulation
(eval):28: warning: already initialized constant N_PEOPLE
(eval):28: warning: previous definition of N_PEOPLE was here
(eval):38: warning: already initialized constant N_GROUPS
(eval):38: warning: previous definition of N_GROUPS was here
(eval):39: warning: already initialized constant MAX_GROUP_SIZE
(eval):39: warning: previous definition of MAX_GROUP_SIZE was here
Out[84]:
-2.807

Now let's keep building our world and already start looking at the parameters of our simulation. We picked the group size as a number from 2-10 so the average should be around 5-6. We can confirm this by computing the average:

In [85]:
group_size_average_group_size = avg($groups.map {|group| group.members.length}) # our assumption confirms itself
Out[85]:
5.97

Now let's start modeling the idea of "group" happiness that affects all its members. Thus, each round of the simulation will compute the 'happiness' of each group, and then factor it in to the happiness of each of its members, either decreasing or increasing it, depending on their difference.

This model follows this property: we're more or less likely to be affected by the negative or positive attitude of a given group depending on how many groups we can fall back to. So even if we are member of a negative group (as in its average happiness is less than ours), we can still have a "winning" (as in positive) happiness delta for group happiness if we are member of many other positive groups. This is also true the other way around.

PMH=previous member happinessGH=group happinessATTENUATION=value between 0.1 and 0.5new member happiness=PMH+(GHPMH)ATTENUATION
In [87]:
class Group
  def carry_happiness
    @members.each do |i|
      happiness_delta = (@happiness - $people[i].happiness).to_f * (rand 0.1..0.5)
      $people[i].increment_happiness(happiness_delta)
    end
  end
  def update_group_happiness
    @happiness = @members.inject(0.0) {|sum, i| sum + $people[i].happiness} / @members.length
    carry_happiness()
  end
end
Out[87]:
:update_group_happiness

Now let's build a "World" class for our simulation: with a time dependency. Each step modifies individual happiness by following the idea of group happiness developed earlier and a random fluctuation that mirrors the ebb and flow of happiness due to all the negative and positives events that affect us.

In this factor we will directly take into account a notion of resilience. If I have a strong and solid network of people on whom I can rely on (i.e. I am member of many groups), the turbulence and wild unpredictability of life will affect me less. Indeed, individuals can fall back on friends and family to either keep them happy in hard times but also sometimes, unfortunately, to weather their happiness when things are going too good.

This is something I'd like to think about more and refine in the future, because I think this model could go a bit further in trying to understand this idea of resilience, especially with regards to when our happiness grows.

Note: this effect is actually already somewhat taken into account when we compute the difference between individual happiness and modify it based on the happiness of the groups he belongs to (notion of group happiness). However, as you can see below I preferred to explicitly factor it into to chaotic variations (like for example a raise at your job which would make your happiness go up, or the death of a family member (down)) of happiness.

In the code, you'll see that this "chaos happiness change" is calculated like this:

CH=CURR HAPPINESSATTENUATION=NO OF GROUPS+1NEW HAPPINESS=CH+RANDOM(20 to 20)ATTENUATION
In [88]:
class World
  def initialize
    @epoch = 0
  end

  def sim_step
    $groups.each do |group|
      group.update_group_happiness
    end
    # random mood swings
    (0..$people.length - 1).each do |i|
      change = (rand -65..65).to_f / ($people[i].belongs_to.length + 1)
      $people[i].increment_happiness(change)
    end
    
    # compute average changes for later analysis
    (0..$people.length - 1).each do |i|
      $people[i].average_variation += $people[i].current_step_variation
      $people[i].current_step_variation = 0
    end
  end
  def start_sim(no_epoch)
    @no_epoch = no_epoch
    while @epoch != no_epoch
      sim_step
      @epoch += 1
    end
  end
end

z = World.new
z.start_sim(500) # starts simulations with 500 steps

Now let's start looking and analyzing our simulation, (and maybe what we can learn about it, on our model and more generally. Let's look at the average happiness population (which is usually not too far from 0, and is not skewed towards a happy (around 100) or sad (-100) population).

In [90]:
$people.inject(0) {|sum, p| sum + p.happiness } / $people.length
Out[90]:
-0.06864538745530707

Now we can look at the distribution of these happiness values depending on the number of groups each person is in. We can notice that due to the highly interlinked nature of this group model, generally, especially for the highly grouped, the average is quite similar. I would like to tweak more with the parameters of the happiness changes, but haven't had time to yet.

In [91]:
n_groups = {}
# seed dictionary object with happiness of all people belonging to 1..n groups
$people.each {|p| n_groups[p.belongs_to.length] = n_groups[p.belongs_to.length] ? n_groups[p.belongs_to.length] + [p.happiness] : [p.happiness]}
n_groups.sort.each do |k, v|
  puts "#{v.length} people belong to #{k} groups."
  puts "They have an average happiness of #{avg(v)}"
end
1
7 people belong to 0 groups.
They have an average happiness of 149.57142857142858
65 people belong to 1 groups.
They have an average happiness of 6.508037468308729
138 people belong to 2 groups.
They have an average happiness of -1.0441589872300674
187 people belong to 3 groups.
They have an average happiness of -1.6084946010402499
190 people belong to 4 groups.
They have an average happiness of -2.6015487796978123
183 people belong to 5 groups.
They have an average happiness of -1.2133300536366372
125 people belong to 6 groups.
They have an average happiness of -1.4897871676453698
43 people belong to 7 groups.
They have an average happiness of -2.3696848717197208
32 people belong to 8 groups.
They have an average happiness of -1.002986522974106
17 people belong to 9 groups.
They have an average happiness of -2.5864557343954115
6 people belong to 10 groups.
They have an average happiness of 0.34543812392599565
5 people belong to 11 groups.
They have an average happiness of -1.5107409919387
1 people belong to 13 groups.
They have an average happiness of -3.1790149025319625
1 people belong to 14 groups.
They have an average happiness of -4.606471526827767
Out[91]:
1

Now let's look at the average variation between a group's happiness and that of its members. This difference is not immense (in the simulations I've run), but still noticeable, and it reflects the fact that we can act independently and form our happiness, through our own actions and not just be defined by the groups we belong to, even if they have a significant role.

In [92]:
average_happiness_group_diff = 0
$people.each do |p|
  if p.belongs_to.length != 0
    average_happiness_group_diff += (p.happiness - avg(p.belongs_to.map {|cur_group_index| $groups[cur_group_index].happiness})).abs()
  end
end
puts average_happiness_group_diff / $people.length
8.25083850171079

Let's also look at the average variation between the peak and rock bottom happiness of our "people".

In [93]:
puts avg($people.map {|p| p.max_happiness - p.min_happiness})
94.20206655289579

It's interesting to see that our model does not lock our people to some form of permanent equilibrium of happiness, but that there are opportunities for variation and change in happiness. We can also look at the average variation, which is not huge, but still consequent over time, and shows us that this model is not static:

In [95]:
avg($people.map {|p| p.average_variation})
Out[95]:
2.7383546125446956

Elaborating this model was quite an interesting experience in simulating a very real and social concept like happiness and formalizing it in a way to try and simplify something so complex, as I also had to contemplate properties like the notions of social groups and resilience to events that affect one's happiness.

It also interestingly showed me the extent to which society and groups are so interlinked that changes on one end in the happiness of one person can reverberate and affect many others, thus drawing our "micro-society" of groups to a close average happiness.

I'd like to improve on some aspects of my simulation with which I haven't had time to yet:

  • investigate more with parameters and fine tune the way happiness varies over time
  • compound simulations into one large "average of averages" simulation where I explore more precisely the different attributes of the model and its changes.
  • brainstorm more datapoints and properties that might be interesting to analyze over time, like for example the properties of group happiness and how it shifts.