# 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.

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.

## 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.

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

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.

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.