opinion.py: 11 points
In this exercise, you’ll do some computational social science—the code for this exercise models opinions spreading on social networks. Here’s the story you should have in mind: you’ve got a population of people, and each person starts with an initial opinion, which is a number between 0 and 1. Think of 0 as representing “I hate knitting,” and 1 being “I love knitting.” It’s reasonable to expect people to be influenced over time by their friends’ opinions. The code here will be a step towards modeling this phenomenon.
Note: we think of friendship in this lab as one way. Think about this in the same way Instagram friendship works. You do not need to be friends with your roommate on Instagram for them to be friends with you.
Note: we think of friendship in this lab as one way. Think about this in the same way Instagram friendship works. You do not need to be friends with your roommate on Instagram for them to be friends with you.
We’ll model our simple social network in the following way: each person will be a node, and they’ll have links pointing to the people they like. For example, you might have the following:
Each person’s node is colored in proportional to their initial opinion: 0 is black, 1 is white, and in-between values would be gray. Note that it’s possible to like someone who doesn’t like you. Ang likes Boone, but Boone does not like Ang.
Here’s how we’ll capture opinion dynamics in a step-by-step method. Each step, we’ll pick someone at random from the population. That person will then update their opinion by taking the average opinion of all the people they like, including themselves. In our example, if we chose Ang to update, their opinion would go from 0 to 0.33, because among the people they like, two people hate knitting (themselves and Charlie), and one person loves knitting (Boone). In the updated graph below, Ang’s shade got a bit lighter.
If you update lots of times, you might hope the network will settle on a steady-state opinion, and that this opinion will depend on the network structure. This is in fact what can happen - neat.
We have started implementing the Person
class and some test code in opinion.py
– it is set up to be used to do the simulation as described above. Like many files which define and then test/use a class, we have the class definition at the top of the file and the main()
function at the bottom. Your goal is to (a) write some of the class methods and (b) test the simulation by adding and running code in main()
.
Start by taking a look at the Person
class’ methods and think through what variables each instance should keep track of. For instance, in befriend(new_friend)
, we’re told a new person is our friend, and later in update()
we’re told to perform an update based on our friends’ opinions. So, an instance of the Person
class should probably refer to the same list of friends in both places.
The self keyword
Remember that a class is a general blueprint for creating specific instances of a class. Python uses the generic keyword self
to refer to an instance’s specific data and actions. Any data that should not disappear between method calls should be stored in self
, and initialized in the __init__
method.
You can always always tell the difference between a method and a generic function by looking at the arguments: a method will always have self
as the first argument.
Let’s start with our constructor method.
__init__(self)
, you should add a line initializing self.friends
as an empty list. Later, any references to friends
should reference that attribute.name
and opinion
, represented by a float between 0 and 1, inclusive. Instead of choosing a default name and opinion for every Person, the user will specify the initial values. To facilitate this, the __init__
method takes the arguments name
and opinion
. You need to now assign the values of those arguments to self.name
and self.opinion
, respectively.Heads Up!
The names name
, opinion
, and friends
variables in self
should be spelled exactly as they are here, or you won’t be able to use your class with the provided simulation code in opinion_helpers.py
in Part 2 of the lab.
Reminder
Be sure to commit and push your changes after you complete the constructor!
Next, we’ll write our other methods.
Go to befriend(self, new_friend)
. When this function gets called (for instance, by main
) it makes a connection between two people. You should add the new friend (a Person
object) to the current instance’s friends list. A reminder that the name
of a person is not the same as a Person
object! Make sure to befriend an object, not a string.
To make things easier later, if we need to befriend multiple people at once, we should support adding a whole list of people (as Person
objects) to our friends list in one method call. Do this in add_as_friends(self, friends_to_add)
. You can use the befriend
method you wrote to add one friend to help you here!
Reminder
Now is a good time to commit and push your changes.
All of the methods so far help us make new people and connections in the graph. Once we have all the connections for people in their friends lists, we’ll want to run the simulation. There are two additional methods we’ll need.
The update(self)
method will observe the opinions it cares about, which includes the current instance’s friends’ opinions and its own opinions, and update its own opinion to be the average of those. For each instance, its own opinion can be accessed by calling self.opinion
, but what about the others?
Luckily, each instance of the Person
class stores its own friends list. This means we can actually say self.friends[0].opinion
to access the opinion of the first friend in the list.
update(self)
to take the average of self.opinion
and all of the friends’ opinions. Make sure the current instance’s opinion gets updated!The last method in the class is the __eq__(self, other)
. This is already written for you, but you should know that it allows us to check if two Person
instances are equal.
Reminder
Commit and push your changes before moving on.
The Person
class is fully defined now! The next step is to test it.
Write the update_step(people_list)
function outside the Person
class. This means it’s below the class, and unindented all the way. You should import the random
library and use it to pick a person from people_list
and run their update function.
Write a main()
function that will help you test the update()
and update_step(people_list)
methods. Think about what the simplest example of updating might be.
update()
, we suggest making two Person
instances, one who likes knitting and one who does not, and having one befriend the other. This is like having a network with two people and one arrow connection. If you follow these instructions, once you run update()
, the person with a friend in their friends list should have an opinion of 0.5.update_step(people_list)
, we suggest making a population of three Person
instances and having them befriend a subset of each other. Run update_step
for a substantial number of iterations, then print each person’s opinion to see if/how they changed. Note that because your code uses the random
library, you should get different output each time! Make sure to run your test code multiple times to make sure you see a variation in input.Reminder
Don’t forget to commit and push your changes!