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
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.
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
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:
group_size_average_group_size = avg($groups.map {|group| group.members.length}) # our assumption confirms itself
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.
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
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:
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).
$people.inject(0) {|sum, p| sum + p.happiness } / $people.length
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.
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
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.
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
Let's also look at the average variation between the peak and rock bottom happiness of our "people".
puts avg($people.map {|p| p.max_happiness - p.min_happiness})
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:
avg($people.map {|p| p.average_variation})
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: