Friends and Opinions

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.

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.

The Program

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.

  • In __init__(self), you should add a line initializing self.friends as an empty list. Later, any references to friends should reference that attribute.
  • Other important values to define in the constructor are the Person’s 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.

  • Write 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.

What’s your opinion?

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.

    • For 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.
    • For 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!