# Project Set 5

Classes.

## Project 19

a) Use Pandas to read the file and capture the header information. Use the header to create of list of years. Convert to a numpy array of floats.

b) Write a class Bird that contains the species name as a string and a numpy array for the observations. Write a constructor that loads the values into an instance.

c) Create an empty list. Go through the dataframe and create a Bird instance from each row. Append to your list of instances.

d) Add one or two methods to your Bird class that compute the maximum, minimum, mean, and median numbers of birds observed. For the maximum and minimum also obtain the year. (Hint: look up argmax and argmin for numpy). You may wish to add an attribute years as well.

e) Read the name of a bird from the command line or from user input (your choice). Print a summary of the statistics to the console and produce a plot of observations versus years.

Example solution

import sys
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

class Birddata:

def __init__(self,species,years,data):
self.species=species
self.years=years
self.obs=np.array(data)

def stats(self):
mean=np.mean(self.obs)
std=np.std(self.obs)
median=np.median(self.obs)
return mean,std,median

def minmax(self):
min_val=np.min(self.obs)
max_val=np.max(self.obs)
min_year=self.years[np.argmin(self.obs)]
max_year=self.years[np.argmax(self.obs)]
return min_val,max_val,min_year,max_year

if (len(sys.argv)<2):
print("Please include the input file on the command line.")
sys.exit()
else:
infile=sys.argv[1]

years_strings=df.columns[1:].values
years=np.array([int(float(years_strings[i])) for i in range(len(years_strings))])

birds=[]
for i in range(len(df)):
species=df.iloc[i,0]
obs_list=df.iloc[i,1:].tolist()
birds.append(Birddata(species,years,np.array(obs_list)))

bird_name=input("Please enter the common name of a bird, without spaces:")

ind=-1
for bird in range(len(birds)):
common_name=birds[bird].species.lower()
if bird_name.lower()==common_name:
ind=bird
break

if ind==-1:
sys.exit()

mean,std,median=birds[ind].stats()
min_val,max_val,min_year,max_year=birds[ind].minmax()
obs=birds[ind].obs

print("Summary of Observational Data for {:}".format(bird_name))
print("Mean: {:.2f}, median: {:.2f}, standard deviation: {:.2f}".format(mean,median,std))
print("Min obs: {:.2f} in year {:}, max obs: {:.2f} in year {:}".format(min_val,min_year,max_val,max_year))

fig,ax=plt.subplots()

title_string="Observations for "+bird_name
ax.set_xlabel("Year")
ax.set_ylabel("Number of Observations")
ax.set_title(title_string)
plt.plot(years,obs)
plt.show()



## Project 20

Write a program that reads a file with the following format:each line consists of an actor’s name followed by a semicolon, followed by a (partial) list of movies in which the actor has appeared. Movie titles are separated by commas.

• You can handle this file by reading each line, then splitting on the semicolon so that you carve off the performer’s name. Append that to an actorslist. The rest of the line is a comma-separated string. Don’t forget to strip the end-of-line marker. Take the movies string and split on commas to create a list.

• Append (not extend) this list to a movies list. This gives you a two-dimensional list (each element is itself a list). Use your two lists to print the information in a nicer format.
Each line should be printed as has appeared in the following movies: You should use your two lists to construct the above string. Use join to rejoin the movies list into a string.
Use either a format string or concatenation to create the message string.

• Read the name of the input file from the command line. Use movies.txt as your example input file.

• Modify your code to define a class Actor whose attributes are
1. name
2. filmography
• Write a constructor that stores these attributes as members of the instance. The filmography will just be the movie list for this project.

• Write a printme method that uses code you wrote previously to print an instance in the format specified.
That is, it will use self.name and self.filmography in the formatting.

• Keep your Actor class definition in its own file. (The example solution is all one file for convenience in downloading. Split off the class.) Modify your code so that instead of storing actor and movielist separately, you will create instances of your Actor class. Specifically, your actors list will now be a list of instances of your Actor class.

• As you read the file you will create a new instance using a line like actors.append(Actor(something,something))

• After creating your list of instances, use your printme method to reproduce the output.

• In addition to a constructor and a printme method, your Actor class should contain a method to return the actor name (a “getter”) and another to return the filmography as a string. You can use these in your printme method.

• Write a separate program with a main() that reads the movies.txtfile and constructs the list of Actor instances.

• Use the list of Actor instances to create a dictionary in which the movie titles are the keys and the value corresponding to each key is a set of the cast member names.

You will need to process through your actors list, checking whether each movie title is already in the dictionary.
If it is not, first create an empty set, then immediately update the set with the actor name (use the “getter”). If the movie title is already a key, update the set of the castlist.

• Write code to request from the user a movie title. Use that movie title to print the castlist of the movie. You can convert a set to a list with list(movie_dict[key]) and then use join to print the castlist neatly.

Be sure to use appropriate functions rather than monolithic code throughout this project.

Example solution

import sys

class Actor (object):
"""Encapsulates data about an actor and his resume"""

def __init__(self,name,filmography=""):
self._name=name
self._filmography=filmography

def get_name(self):
return self._name

def get_filmlist(self):
return self._filmography

def get_filmography(self):
return "".join(self._filmography)

def printme(self):
print(self.get_name()," has appeared in ",self.get_filmography())

def movie_dict(filmographies):
"""Create the dictionary"""
castlist={}
for actor in filmographies:
for movie in actor.get_filmlist():
if movie in castlist:
else:
castlist[movie]=set()
return castlist

def compress_ws(string):
"""Removes excess whitespace."""
return ' '.join(string.split())

def fix_string(name):
"""Manipulates a string into a title format with no leading the or apostrophes."""
name1=name.strip()
name2=name1.replace("'","")
name3=name2.lower().strip()
if name3.startswith("the "):
fixed_name=compress_ws(name3[4:])
else:
fixed_name=compress_ws(name3)
return fixed_name.title()

def fix_data(movies):
"""Adjusts titles to standard format.  Works through side effects."""
for i,movie in enumerate(movies):
movies[i]=fix_string(movie)

def prntCastList(castlist,movie):
if movie in castlist:
print(",".join(list(set(castlist[movie]))))
else:
print()
print("Cannot find this movie.")
print()

def main():

if len(sys.argv)<2:
print("No data file specified.  Using default.")
in_file="movies.txt"
else:
in_file=sys.argv[1]

try:
moviefile=open(in_file,'r')
except IOError:
sys.exit("Unable to open movie data file.")

actors=[]
for line in moviefile:
player,movies=line.rstrip("\r\n").split(";")
values=movies.rstrip(',').split(',')
fix_data(values)
actors.append(Actor(player,values))

castlist=movie_dict(actors)

while True:
movie=input("Type a movie name, or q/Q to quit:")
if movie.lower()=='q':
break
else:
prntCastList(castlist,movie)

if __name__=='__main__':
main()



## Project 21

1. Write a Fraction class that implements a representation of a fraction, where each instance consists of a numerator and a denominator. Overload addition, subtraction, and multiplication for this class. Write a dunder to format each fraction in the form
5/7


For your first attempt it is not necessary to reduce the fraction, i.e. it is acceptable to have fractions like 6/8. Be sure to check for division by zero in your __truediv__ method.

1. Add a reduce method that finds the least common multiple to obtain the lowest common denominatorand reduce the fraction.

2. Use NaN to represent division by zero and isnan to check for it.

Example solution

import math

nan=float('NaN')

def _gcd(a,b):
while b!=0:
t=b
b=a%b
a=t
return a

class Fraction (object):
"""
This class implements an object to define, manipulate, and print fractions.
Version with a reduce method.
Author:    K. Holcomb
Changelog: 20150323 Initial version
"""

def __init__(self,num,denom):
if denom !=0:
self.num=num
self.denom=denom
else:
self.num=nan
self.denom=nan

if math.isnan(self.denom) or math.isnan(f.denom):
f3=Fraction(0,0)
else:
denom=self.denom*f.denom
num  =self.num*f.denom+f.num*self.denom
f3=Fraction(num,denom)
f3.reduce()
return f3

def __sub__(self,f):
if math.isnan(self.denom) or math.isnan(f.denom):
f3=Fraction(0,0)
else:
denom=self.denom*f.denom
num  =self.num*f.denom-f.num*self.denom
f3=Fraction(num,denom)
f3.reduce()
return f3

def __mul__(self,f):
if math.isnan(self.denom) or math.isnan(f.denom):
f3=Fraction(0,0)
else:
denom=self.denom*f.denom
num  =self.num*f.num
f3=Fraction(num,denom)
f3.reduce()
return f3

def __truediv__(self,f):
if math.isnan(self.denom) or math.isnan(f.denom) or f.num==0:
f3=Fraction(0,0)
else:
denom=self.denom*f.num
num  =self.num*f.denom
f3=Fraction(num,denom)
f3.reduce()
return f3

def reduce(self):
if not math.isnan(self.denom):
gcd=_gcd(self.num,self.denom)
self.num/=gcd
self.denom/=gcd

def __copy__(self):
return Fraction(self.num,self.denom)

def __str__(self):
return "%s/%s"%(str(self.num),str(self.denom))