diff --git a/assignment1/assignment1.py b/assignment1/assignment1.py index fafa187..75dafcb 100644 --- a/assignment1/assignment1.py +++ b/assignment1/assignment1.py @@ -1 +1,227 @@ -# Write your code here. \ No newline at end of file +# Task 1: Hello +''' Write a hello function that takes no arguments and returns Hello!. Now, what matters here is what the function returns. +You can print() whatever you want for debugging purposes, but the tests ignore that, and only check the return value. +''' +import string + + +def hello(): + return "Hello!" + +#Task 2: Greet with a Formatted String +''' Write a greet function. It takes one argument, a name, and returns Hello, Name!. Use a formatted string. +Note that you have to return exactly the right string or the test fails -- but PyTest tells you what didn't match. ''' + +def greet(name): + return f"Hello, {name}!" + +# Task 3: Calculator +''' Write a calc function. It takes three arguments. The default value for the third argument is "multiply". The first two arguments are +values that are to be combined using the operation requested by the third argument, a string that is one of the following add, subtract, +multiply, divide, modulo, int_divide (for integer division) and power. The function returns the result. + +Error handling: When the function is called, it could ask you to divide by 0. That will throw an exception: Which one? You can find out by +triggering the exception in your program or in the Python Interactive Shell. Wrap the code within the calc function in a try block, and put +in an except statement for this exception. If the exception occurs, return the string "You can't divide by 0!". + +More error handling: When the function is called, the parameters that are passed might not work for the operation. For example, you can't +multiply two strings. Find out which exception occurs, catch it, and return the string "You can't multiply those values!". + +Here's a tip. You have to do different things for add, multiply, divide and so on. So you can do a conditional cascade, if/elif/elif/else. +That's perfectly valid. But you might want to use the match-case Python statement instead. Look it up! It just improves code appearance. ''' + +def calc(a, b, operation="multiply"): + try: + match operation: + case "add": + return a + b + case "subtract": + return a - b + case "multiply": + return a * b + case "divide": + return a / b + case "modulo": + return a % b + case "int_divide": + return a // b + case "power": + return a ** b + except ZeroDivisionError: + return "You can't divide by 0!" + except TypeError: + if operation == "multiply": + return "You can't multiply those values!" + +# Task 4: Data Type Conversion +''' +Create a function called data_type_conversion. It takes two parameters, the value and the name of the data type requested, one of float, str, or int. +Return the converted value. +Error handling: The function might be called with a bad parameter. For example, the caller might try to convert the string "nonsense" to a float. +Catch the error that occurs in this case. If this error occurs, return the string 'You can't convert {value} into a {type}.', except you use the value +and data type that are passed as parameters -- so again you use a formatted string. +''' + +def data_type_conversion(value, data_type): + try: + match data_type: + case "float": + return float(value) + case "str": + return str(value) + case "int": + return int(value) + except ValueError: + return f"You can't convert {value} into a {data_type}." + +# Task 5: Grading System, Using *args +''' +Create a grade function. It should collect an arbitrary number of parameters, compute the average, and return the grade. based on the following scale: +A: 90 and above +B: 80-89 +C: 70-79 +D: 60-69 +F: Below 60 + +When you use *args you get access to a variable named args in your function, which is a tuple, an ordered collection of values like a list. + +You'll learn more about tuples and lists in the next lesson. There are some helpful functions you can use at this point: sum(args), len(args), +and so on. One of the curiosities of Python is that these are not methods of any class. They are just standalone functions. + +Handle the error that occurs if the parameters are nonsense. Return the string "Invalid data was provided." in this case. (Typically, you don't +handle every possible exception in your error handling, except if the values in the parameters comes from the end user.) +''' + +def grade(*args): + try: + average = sum(args) / len(args) + if average >= 90: + return "A" + elif average >= 80: + return "B" + elif average >= 70: + return "C" + elif average >= 60: + return "D" + else: + return "F" + except TypeError: + return "Invalid data was provided." + +# Task 6: Use a For Loop with a Range +''' +Create a function called repeat. It takes two parameters, a string and a count, and returns a new string that is the old one repeated count times. + +You can get the test to pass by just returning string * count. That would produce the correct return value. +But, for this task, do it using a for loop and a range. +''' + +def repeat(string, count): + result = "" + for i in range(count): + result += string + return result + +#Task 7: Student Scores, Using **kwargs +''' +Create a function called student_scores. It takes one positional parameter and an arbitrary number of keyword parameters. +The positional parameter is either "best" or "mean". If it is "best", the name of the student with the higest score is returned. +If it is "mean", the average score is returned. + +As you are using **kwargs, your function can access a variable named kwargs, which is a dict. The next lesson explains about dicts. +What you need to know now is the following: + +A dict is a collection of key value pairs. + +You can iterate through the dict as follows: +for key, value in kwargs.items(): + +You can also get kwargs.keys() and kwargs.values(). + +The arbitrary list of keyword arguments uses the names of students as the keywords and their test score as the value for each. +''' + +def student_scores(statistic, **kwargs): + if statistic == "best": + best_student = max(kwargs, key=kwargs.get) + return best_student + elif statistic == "mean": + average_score = sum(kwargs.values()) / len(kwargs) + return average_score + +# Task 8: Titleize, with String and List Operations +''' +Create a function called titleize. It accepts one parameter, a string. The function returns a new string, where the parameter string is + capitalized as if it were a book title. +The rules for title capitalization are: (1) The first word is always capitalized. (2) The last word is always capitalized. (3) All + the other words are capitalized, except little words. For the purposes of this task, the little words are "a", "on", "an", "the", "of", "and", + "is", and "in". + +The following string methods may be helpful: split(), join(), and capitalize(). Look 'em up. +The split() method returns a list. You might store this in the words variable. words[-1] gives the last element in the list. +The in comparison operator: You have seen in used in loops. But it can also be used for comparisons, for example to check to see + if a substring occurs in a string, or a value occurs in a list. + +A new trick: As you loop through the words in the words list, it is helpful to have the index of the word for each iteration. + +You can access that index using the enumerate() function: + + for i, word in enumerate(words): +''' + +def titleize(string): + little_words = ["a", "on", "an", "the", "of", "and", "is", "in"] + words = string.split() + for i, word in enumerate(words): + if i == 0 or i == len(words) - 1 or word not in little_words: + words[i] = word.capitalize() + return " ".join(words) + +# Task 9: Hangman, with more String Operations +''' +Create a function hangman. It takes two parameters, both strings, the secret and the guess. +The secret is some word that the caller doesn't know. So the caller guesses various letters, which are the ones in the guess string. +A string is returned. Each letter in the returned string corresponds to a letter in the secret, except any letters that are not in the guess + string are replaced with an underscore. The others are returned in place. Not everyone has played this kid's game, but it's common in the US. + +Example: Suppose the secret is "alphabet" and the guess is "ab". The returned string would be "a___ab__". + +Note that Python strings are immutable. That means that the following code would give an error: + +secret = "alphabet" +secret[1] = "_" + +On the other hand, you can concatenate strings with the + operator. +''' +def hangman(secret, guess): + result = "" + for letter in secret: + if letter in guess: + result += letter + else: + result += "_" + return result + +# Task 10: Pig Latin, Another String Manipulation Exercise +''' +Pig Latin is a kid's trick language. Each word is modified according to the following rules. (1) If the string starts with a vowel (aeiou), + "ay" is tacked onto the end. (2) If the string starts with one or several consonants, they are moved to the end and "ay" is tacked on after them. + (3) "qu" is a special case, as both of them get moved to the end of the word, as if they were one consonant letter. +Create a function called pig_latin. It takes an English string or sentence and converts it to Pig Latin, returning the result. + We will assume that there is no punctuation and that everything is lower case. +''' +def pig_latin(string): + vowels = "aeiou" + words = string.split() + pig_latin_words = [] + for word in words: + if word[0] in vowels: + pig_latin_words.append(word + "ay") + else: + first_consonant_index = 0 + while first_consonant_index < len(word) and word[first_consonant_index] not in vowels: + first_consonant_index += 1 + if first_consonant_index > 0 and word[first_consonant_index - 1:first_consonant_index + 1] == "qu": + first_consonant_index += 1 + pig_latin_words.append(word[first_consonant_index:] + word[:first_consonant_index] + "ay") + return " ".join(pig_latin_words) \ No newline at end of file diff --git a/assignment2/assignment2.py b/assignment2/assignment2.py new file mode 100644 index 0000000..7e806fc --- /dev/null +++ b/assignment2/assignment2.py @@ -0,0 +1,421 @@ +# Task 2: Read a CSV File +''' +Remember to import the csv module for this task. +Create a function called read_employees that has no arguments, and do the following within it. + +Declare an empty dict. You'll add the key/value pairs to that. Declare also an empty list to store the rows. +You next read a csv file. Use a try block and a with statement, so that your code is robust and so that the file gets closed. +Read ../csv/employees.csv using csv.reader(). (This csv file is used in a later lesson to populate a database.) +As you loop through the rows, store the first row in the dict using the key "fields". These are the column headers. +Add all the other rows (not the first) to your rows list. +Add the list of rows (this is a list of lists) to the dict, using the key "rows". +The function should return the dict. +Add a line below the function that calls read_employees and stores the returned value in a global variable called employees. Then print out this value, + to verify that the function works. +In this case, it's not clear what to do if you get an exception. You might get an exception because the filename is bad, or because the file + couldn't be parsed as a CSV file. For now, just use the same approach as described above: catch the exception, print out the information, and + exit the program. One likely exception in this case is an error in the syntax of your code. +Run the test to see if you have this much right. + +A word about what's going on when the test runs: The test file imports your assignment2.py module. When the import statement occurs, all the + program statements in your module that are outside of functions do run. That means the statement which sets your employees global variable + is run. As a result, the assignment2-test.py can reference this global variable too -- and it does. If you forget to set this variable + in your program, the test reports an error. +''' + +def read_employees(): + import csv + import traceback + + try: + employees_dict = {} + rows_list = [] + + with open('../csv/employees.csv', 'r') as file: + reader = csv.reader(file) + for i, row in enumerate(reader): + if i == 0: + employees_dict["fields"] = row + else: + rows_list.append(row) + + employees_dict["rows"] = rows_list + return employees_dict + + except Exception as e: + trace_back = traceback.extract_tb(e.__traceback__) + stack_trace = list() + for trace in trace_back: + stack_trace.append(f'File : {trace[0]} , Line : {trace[1]}, Func.Name : {trace[2]}, Message : {trace[3]}') + print(f"Exception type: {type(e).__name__}") + message = str(e) + if message: + print(f"Exception message: {message}") + print(f"Stack trace: {stack_trace}") + exit(1) + +employees = read_employees() +print(employees) + +#Task 3: Find the Column Index +'''Create a function called column_index. The input is a string. The function looks in employees["fields"] (an array of column headers) to find + the index of the column header requested. There won't be much to this function, because you just use the index() method of the list class, like so: + +employees["fields"].index("first_name") + +The index() method returns the index of the matching value from the list. + +The column_index function should return this index. + +Run the test again to see if the test passes. + +Call the column_index function in your program, passing the parameter "employee_id". Store the column you get back in a variable called +employee_id_column. This global value is used for subsequent steps. +''' +def column_index(column_name): + return employees["fields"].index(column_name) + +column_index("employee_id") # Example usage +employee_id_column = column_index("employee_id") + +#Task 4: Find the Employee First Name +'''Create a function called first_name. It takes one argument, the row number. The function should retrieve the value of first_name + from a row as stored in the employees dict. + +You should first call your column_index function to find out what column index you want. + +Then you go to the requested row as stored in the employees dict, and get the value at that index in the row. + +Return the value. + +Try the test again. +''' +def first_name(row_number): + first_name_index = column_index("first_name") + return employees["rows"][row_number][first_name_index] + +#Task 5: Find the Employee: a Function in a Function +''' +Create a function called employee_find. This is passed one argument, an integer. Just call it employee_id in your function declaration. +We want it to return the rows with the matching employee_id. There should only be one, but sometimes a CSV file has bad data. + +We could do this with a loop. But we are going to use the filter() function. Inside the employee_find function (yes, you do declare +functions inside functions sometimes), create the following employee_match function: + +def employee_match(row): + return int(row[employee_id_column]) == employee_id + +This function is referencing the employee_id value that is passed to the employee_find function. It can access that value because the +employee_match function is inside the employee_find function. Note that we need to do type conversion here, because the CSV reader just +returns strings as the values in the roows. This inner function returns True if there is a match. We are using the employee_id_column +global value you set in Task 3. + +Now, still within the employee_find function, call the filter() function. This is another one of those Python free standing functions. +(It is not a method of the list class.) You call filter() as follows: + +matches=list(filter(employee_match, employees["rows"])) + +The filter() function needs to know how to filter, and the employee_match function provides that information. The filter() function calls +employee_match once per row, saying, Do we want this one? When the filter function completes, we need to do type conversion to convert the result +to a list. + +The employee_find function then returns the matches. + +Run the test and see if you got it right. +''' + +def employee_find(employee_id): + def employee_match(row): + return int(row[employee_id_column]) == employee_id + + matches = list(filter(employee_match, employees["rows"])) + return matches + + +#Task 6: Find the Employee with a Lambda +''' +The employee_match function is a silly one-liner. Lambdas allow us to give the logic inline. + +Create a function employee_find_2. This function does exactly what employee_find does -- but it uses a lambda. + +def employee_find_2(employee_id): + matches = list(filter(lambda row : int(row[employee_id_column]) == employee_id , employees["rows"])) + return matches + +Note that there is no return statement in the lambda. There is the parameter passed to the lambda (a row), followed by a colon, +followed by the expression that gives the result. + +Run the test to make sure things still work. +''' + +def employee_find_2(employee_id): + matches = list(filter(lambda row: int(row[employee_id_column]) == employee_id, employees["rows"])) + return matches + +#Task 7: Sort the Rows by last_name Using a Lambda +''' +We want to call the sort() method on the rows. However, we need to tell it which column to use for the sort. + +Create a function sort_by_last_name. It takes no parameters. You sort the rows you have stored in the dict. + +Within the function, you call employees["rows"].sort(). This sorts the list of rows in place. But, you need pass to the list.sort() method + a keyword argument called key (so you pass a parameter with key= when you call it). You set that keyword parameter equal to a lambda. + The lambda is passed the row, and the expression after the colon gives the value from the row to be used in the sort. You might want to + use your column_index function for last_name so you know which value from the row should be given in the lambda expression. Remember that + the sort() method sorts the list in place and does not return the sorted list. +The sort_by_last_name function returns the sorted list of rows. + +Run the test until this works. + +Call the function in your program, and then print out the employees dict, to see it in sorted form. +''' + +def sort_by_last_name(): + last_name_index = column_index("last_name") + employees["rows"].sort(key=lambda row: row[last_name_index]) + return employees["rows"] + +#Task 8: Create a dict for an Employee +''' +Create a function called employee_dict. It is passed a row from the employees dict (not a row number). It returns a dict. + +The keys in the dict are the column headers from employees["fields"]. +The values in the dict are the corresponding values from the row. +Do not include the employee_id in the dict. You skip that field for now. +Return the resulting dict for the employee. + +Add a line to your program that calls this function and prints the result. Use a row from the rows stored in the employees dict to pass to +the function for this test. + +Get the test working. + +If you want to try something extra, look up the zip() function, which can be used to simplify the code for this problem. +''' +def employee_dict(row): + employee_info = {} + for i, field in enumerate(employees["fields"]): + if field != "employee_id": # Skip the employee_id field + employee_info[field] = row[i] + return employee_info + + +# Task 9: A dict of dicts, for All Employees +''' +Create a function called all_employees_dict. + +The keys in the dict are the employee_id values from the rows in the employees dict. +For each key, the value is the employee dict created for that row. (Use the employee_dict function you created in task 8.) +The function should return the resulting dict of dicts. + +Add a line to your program that calls this function and prints the result. + +Get the test working. +''' +def all_employees_dict(): + employees_dict = {} + employee_id_index = column_index("employee_id") + + for row in employees["rows"]: + employee_id = row[employee_id_index] + employees_dict[employee_id] = employee_dict(row) + + return employees_dict + +#Task 10: Use the os Module +''' +Sometimes the behavior of a program is to be modified without changing the program itself. One way is to use environment variables. +Environment variables are also used to store secrets needed by the program, such as passwords. Environment variables are accessed via +the os.getenv() function. Of course, there are many other functions in the os package. + +Within the terminal, enter the command export THISVALUE=ABC. +Add a line to assignment2.py to import the os module. +Create a function get_this_value(). This function takes no parameters and returns the value of the environment variable THISVALUE. +Get the test working. (Note that each time you want this test to pass, you have to have the THISVALUE environment variable set in your terminal session.) +''' +import csv +import os + +def get_this_value(): + return os.getenv("THISVALUE") + +#Task 11: Creating Your Own Module +''' +In the same folder, create a file called custom_module.py, with the following contents: +secret = "shazam!" + +def set_secret(new_secret): + global secret + secret = new_secret + +Add the line import custom_module to assignment2.py. +Create a function called set_that_secret. It should accept one parameter, which is the new secret to be set. It should call +custom_module.set_secret(), passing the parameter, so as to set the secret in custom_module. + +Add a line to your program to call set_that_secret, passing the new string of your choice. + +In another line, print out custom_module.secret. Verify that it has the value you expect. + +Run the test until the next part passes. +''' +import custom_module +def set_that_secret(new_secret): + custom_module.set_secret(new_secret) + +set_that_secret("sweet potato") +print(custom_module.secret) + +#Task 12: Read minutes1.csv and minutes2.csv +''' +The "story" behind the following list of tasks is as follows. A club meets, and for each meeting, there is a chairperson. The club keeps + several notebooks that record who whas the chairperson on a given date. Some of the information is in one notebook, some in the other. + The club now wants to combine this information, to get the list of chairpersons sorted by date. But the information in the csv files + contains duplicates and is in no particular order. (Yeah, the story is lame, but it is similar to other data analysis tasks.) + +Create a function called read_minutes. It takes no parameters. It creates two dicts, minutes1 and minutes2, by reading ../csv/minutes1.csv +and ../csv/minutes2.csv. Each dict has fields and rows, just as the employees dict had. However! As you create the list of rows for both +minutes1 and minutes2, convert each row to a tuple. The function should return both minutes1 and minutes2. Note You can return several values +from a Python function, as follows: return v1, v2. Don't worry about duplicates yet. They will be dealt with in later tasks. +Think about the DRY (Don't repeat Yourself principal). You may want to create a helper function to avoid duplicating code. + +Call the function within your assignment2.py script. Store the values from the values it returns in the global variables minutes1 and minutes2. +Note When a function returns several values, you get them as follows: v1, v2 = function(). Print out those dicts, so that you can see what's stored. + +Run the test until this part passes. +''' +def read_minutes(): + import csv + import traceback + + def read_csv_to_dict(file_path): + minutes_dict = {} + rows_list = [] + + try: + with open(file_path, 'r') as file: + reader = csv.reader(file) + for i, row in enumerate(reader): + if i == 0: + minutes_dict["fields"] = row + else: + rows_list.append(tuple(row)) # Convert row to tuple + minutes_dict["rows"] = rows_list + return minutes_dict + + except Exception as e: + trace_back = traceback.extract_tb(e.__traceback__) + stack_trace = list() + for trace in trace_back: + stack_trace.append(f'File : {trace[0]} , Line : {trace[1]}, Func.Name : {trace[2]}, Message : {trace[3]}') + print(f"Exception type: {type(e).__name__}") + message = str(e) + if message: + print(f"Exception message: {message}") + print(f"Stack trace: {stack_trace}") + exit(1) + + minutes1 = read_csv_to_dict('../csv/minutes1.csv') + minutes2 = read_csv_to_dict('../csv/minutes2.csv') + + return minutes1, minutes2 + +minutes1, minutes2 = read_minutes() + +print(minutes1) +print(minutes2) + +#Task 13: Create minutes_set +''' +Create a function called create_minutes_set. It takes no parameters. It creates two sets from the rows of minutes1 and minutes2 dicts. +(This is just type conversion. However, to make it work, each row has to be hashable! Sets only support hashable elements. +Lists aren't hashable, so that is why you stored the rows as tuples in Task 10.) Combine the members of both sets into one single set. +(This operation is called a union.) The function returns the resulting set. + +Call the function within your assignment2.py script. Store the value returned in the global variable minutes_set. + +Run the test until the next part passes. + +''' +def create_minutes_set(): + minutes_set1 = set(minutes1["rows"]) + minutes_set2 = set(minutes2["rows"]) + + combined_set = minutes_set1.union(minutes_set2) + + return combined_set + +minutes_set = create_minutes_set() + +#Task 14: Convert to datetime +''' +Add a statement, + from datetime import datetime +, to your program. The datetime module has some nice capabilities for converting strings to dates. +You can look them up: strptime() and strftime(). + +Create a function called create_minutes_list. It takes no parameters, and does the following: + +Create a list from the minutes_set. This is just type conversion. +Use the map() function to convert each element of the list. At present, each element is a list of strings, where the first element of that list +is the name of the recorder and the second element is the date when they recorded. + +The map() should covert each of these into a tuple. The first element of the tuple is the name (unchanged). The second element of the tuple +is the date string converted to a datetime object. + +You convert the date strings into datetime objects using datetime.strptime(string, "%B %d, %Y"). +So, you could use the following lambda: lambda x: (x[0], datetime.strptime(x[1], "%B %d, %Y")) +The function should return the resulting list. +Call the function from within your program. Store the return value in the minutes_list global. Print it out, so you can see what it looks like. + +Run the test until the next part passes. +''' +from datetime import datetime + +def create_minutes_list(): + # Convert the global minutes_set to a list + raw_list = list(minutes_set) + + # Use map with a lambda to transform the data + # x[0] is the name, x[1] is the date string + transformed_map = map(lambda x: (x[0], datetime.strptime(x[1], "%B %d, %Y")), raw_list) + + # Convert the map object back into a list and return it + return list(transformed_map) + +#Call the function and store it in the global variable +minutes_list = create_minutes_list() + +#Use print statements to verify based on tha task instructions +print("\n--- Task 14: Minutes List with Datetime Objects ---") +print(minutes_list) + +# Task 15: Write Out Sorted List +''' +Create a function called write_sorted_list. It takes no parameters. It should do the following: + +Sort minutes_list in ascending order of datetime. +Call map again to convert the list. In this case, for each tuple, you create a new tuple. The first element of the tuple is the name (unchanged). +The second element of the tuple is the datetime converted back to a string, using datetime.strftime(date, "%B %d, %Y") + +Open a file called ./minutes.csv. Use a csv.writer to write out the resulting sorted data. The first row you write should be the value of fields +the from minutes1 dict. The subsequent rows should be the elements from minutes_list. + +The function should return the converted list. +Call this function from within your program. Then check that the file is created, and that it contains appropriate content. + +Run the test again until the next test has passed. +''' + +def write_sorted_list(): + # Sort minutes_list by the datetime element (index 1 of the tuple) + sorted_minutes = sorted(minutes_list, key=lambda x: x[1]) + + # Convert the datetime back to string format + converted_list = list(map(lambda x: (x[0], x[1].strftime("%B %d, %Y")), sorted_minutes)) + + # Write to CSV file + with open('./minutes.csv', 'w', newline='') as file: + writer = csv.writer(file) + writer.writerow(minutes1["fields"]) # Write the header + writer.writerows(converted_list) # Write the sorted data + + return converted_list \ No newline at end of file diff --git a/assignment2/custom_module.py b/assignment2/custom_module.py new file mode 100644 index 0000000..8ce1a6d --- /dev/null +++ b/assignment2/custom_module.py @@ -0,0 +1,5 @@ +secret = "shazam!" + +def set_secret(new_secret): + global secret + secret = new_secret \ No newline at end of file diff --git a/assignment2/diary.py b/assignment2/diary.py new file mode 100644 index 0000000..8dad16e --- /dev/null +++ b/assignment2/diary.py @@ -0,0 +1,88 @@ +# Task 1 Diary +''' +1) Create a program called diary.py. Add code to do the following: + + Open a file called diary.txt for appending. + In a loop, prompt the user for a line of input. The first prompt should say, "What happened today? ". All subsequent prompts should say "What else? " + As each line is received, write it to diary.txt, with a newline (\n) at the end. + When the special line "done for now" is received, write that to diary.txt. Then close the file and exit the program (you just exit the loop). + Wrap all of this in a try block. If an exception occurs, catch the exception and print out "An exception occurred." followed by the name of the + exception itself. Now, normally, you catch specific types of exceptions, and handle each according to program logic. In this case, you can catch + any non-fatal exceptions via an except for Exception, and then display the information from the exception and exit the program. The traceback module + provides a way to include function traceback information in your error message, which will make it easier to find the error. + + You can use the following code to handle exceptions using the traceback module. + + + +import traceback + +... + +except Exception as e: + trace_back = traceback.extract_tb(e.__traceback__) + stack_trace = list() + for trace in trace_back: + stack_trace.append(f'File : {trace[0]} , Line : {trace[1]}, Func.Name : {trace[2]}, Message : {trace[3]}') + print(f"Exception type: {type(e).__name__}") + message = str(e) + if message: + print(f"Exception message: {message}") + print(f"Stack trace: {stack_trace}") + + +Open the file using a with statement (inside the try block), and rely on that statement to handle the file close. +The input statement should be inside the loop inside the with block. + +2) Test the program. + +Run it a couple of times to create diary entries. (python diary.py) +Have a look at diary.txt to make sure it appears correct. Warning: diary.txt will end up in GitHub when you submit your homework, so don't put in +anything personal. +Trigger an exception while running the program: When it prompts you for input, press Ctrl-D. You may need to type Ctrl-C and newline to trigger +an exception if Ctrl-D doesn't work. Check to see that the exception is handled. +''' + +import traceback + +try: + # 1. Use 'diary.txt' and 'a' for appending as per instructions + with open('diary.txt', 'a') as file: + first_prompt = True + + while True: + # Use a simple boolean to manage the prompt text + if first_prompt: + line = input("What happened today? ") + # After the first prompt, set the flag to False so that subsequent prompts will be different + first_prompt = False + else: + line = input("What else? ") + + # Write line to file with newline + file.write(line + '\n') + + # Exit condition + if line.strip().lower() == "done for now": + break + +# 2. Employ detailed exception handling using the traceback module +except Exception as e: + print("An exception occurred.") + + # Extracting the traceback details + trace_back = traceback.extract_tb(e.__traceback__) + stack_trace = list() + for trace in trace_back: + stack_trace.append(f'File : {trace[0]} , Line : {trace[1]}, Func.Name : {trace[2]}, Message : {trace[3]}') + + # Displaying organized error information + print(f"Exception type: {type(e).__name__}") + message = str(e) + if message: + print(f"Exception message: {message}") + print(f"Stack trace: {stack_trace}") + +else: + print("The file was successfully appended.") + diff --git a/assignment2/minutes.csv b/assignment2/minutes.csv index 0acabb7..84a339b 100644 --- a/assignment2/minutes.csv +++ b/assignment2/minutes.csv @@ -1,46 +1,46 @@ Name,Date Jason Tucker,"September 20, 1980" -Austin Hester,"March 8, 1981" -Daniel Jackson,"October 2, 1981" +Austin Hester,"March 08, 1981" +Daniel Jackson,"October 02, 1981" Mrs. Samantha Johnson,"December 17, 1981" -Joseph Harris,"March 3, 1982" +Joseph Harris,"March 03, 1982" Mrs. Samantha Johnson,"March 12, 1982" Gina Maldonado,"December 16, 1982" Mrs. Samantha Johnson,"December 23, 1982" Yesenia Smith,"August 10, 1983" -Lori Martin,"February 5, 1984" +Lori Martin,"February 05, 1984" Jonathan Parrish,"June 12, 1984" Mrs. Samantha Johnson,"July 20, 1984" -Amanda Brown,"August 8, 1984" +Amanda Brown,"August 08, 1984" Sarah Murray,"October 30, 1984" Mrs. Samantha Johnson,"November 28, 1984" -Austin Hester,"June 4, 1985" +Austin Hester,"June 04, 1985" Jonathan Parrish,"March 18, 1986" -Yesenia Smith,"May 6, 1986" +Yesenia Smith,"May 06, 1986" Daniel Jackson,"December 13, 1986" Gina Maldonado,"February 13, 1987" Kimberly Stewart,"December 12, 1987" Mrs. Samantha Johnson,"July 10, 1988" Sarah Murray,"August 16, 1988" Aaron Kaufman,"October 24, 1988" -Tony Henderson,"November 7, 1988" +Tony Henderson,"November 07, 1988" Sarah Murray,"November 19, 1988" Austin Hester,"January 18, 1989" -Joseph Harris,"March 1, 1989" -Gina Maldonado,"April 7, 1989" +Joseph Harris,"March 01, 1989" +Gina Maldonado,"April 07, 1989" Aaron Kaufman,"November 14, 1989" -Daniel Jackson,"April 8, 1990" -Aaron Kaufman,"May 1, 1990" +Daniel Jackson,"April 08, 1990" +Aaron Kaufman,"May 01, 1990" Aaron Kaufman,"July 21, 1990" -Tony Henderson,"October 4, 1990" +Tony Henderson,"October 04, 1990" Yesenia Smith,"November 23, 1990" -Joseph Harris,"April 3, 1991" +Joseph Harris,"April 03, 1991" Jason Tucker,"April 30, 1991" Matthew Russell,"May 31, 1991" -Lori Martin,"July 8, 1991" +Lori Martin,"July 08, 1991" Mrs. Samantha Johnson,"July 23, 1991" Tony Henderson,"November 15, 1991" -Gina Maldonado,"February 9, 1992" +Gina Maldonado,"February 09, 1992" Sarah Murray,"June 27, 1992" Gina Maldonado,"October 31, 1992" Austin Hester,"December 10, 1992" diff --git a/assignment3/extend-point-to-vector.py b/assignment3/extend-point-to-vector.py new file mode 100644 index 0000000..492cd11 --- /dev/null +++ b/assignment3/extend-point-to-vector.py @@ -0,0 +1,47 @@ +# Task 5: Extending a Class +''' +1) Within the assignment3 folder, create a file called extend-point-to-vector.py. +2) Create a class called Point. It represents a point in 2d space, with x and y values passed to the __init__() method. It should include +methods for equality, string representation, and Euclidian distance to another point. +3) Create a class called Vector which is a subclass of Point and uses the same __init__() method. Add a method in the vector class which +overrides the string representation so Vectors print differently than Points. Override the + operator so that it implements vector addition, +summing the x and y values and returning a new Vector. +4) Print results which demonstrate all of the classes and methods which have been implemented. +''' +import math + +class Point: + def __init__(self, x, y): + self.x = x + self.y = y + + def __eq__(self, other): + return self.x == other.x and self.y == other.y + + def __str__(self): + return f"Point({self.x}, {self.y})" + + def distance_to(self, other): + return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2) + +class Vector(Point): + def __str__(self): + return f"Vector({self.x}, {self.y})" + + def __add__(self, other): + return Vector(self.x + other.x, self.y + other.y) + +if __name__ == "__main__": + p1 = Point(1, 2) + p2 = Point(3, 4) + print(p1) # Point(1, 2) + print(p2) # Point(3, 4) + print(p1 == p2) # False + print(p1.distance_to(p2)) # 2.8284271247461903 + + v1 = Vector(1, 2) + v2 = Vector(3, 4) + print(v1) # Vector(1, 2) + print(v2) # Vector(3, 4) + v3 = v1 + v2 + print(v3) # Vector(4, 6) \ No newline at end of file diff --git a/assignment3/hangman-closure.py b/assignment3/hangman-closure.py new file mode 100644 index 0000000..e683f47 --- /dev/null +++ b/assignment3/hangman-closure.py @@ -0,0 +1,34 @@ +# Task 4: Closures Practice +''' +1) Within the assignment3 folder, create a file called hangman-closure.py. + +2) Declare a function called make_hangman() that has one argument called secret_word. It should also declare an empty array called guesses. +Within the function declare a function called hangman_closure() that takes one argument, which should be a letter. Within the inner function, +each time it is called, the letter should be appended to the guesses array. Then the word should be printed out, with underscores substituted +for the letters that haven't been guessed. So, if secret_word is "alphabet", and guesses is ["a", "h"], then "a__ha__" should be printed out. +The function should return True if all the letters have been guessed, and False otherwise. make_hangman() should return hangman_closure. + +3) Within hangman-closure.py, implement a hangman game that uses make_hangman(). Use the input() function to prompt for the secret word. +Then use the input() function to prompt for each of the guesses, until the full word is guessed. + +4) Test your program by playing a few games. +''' + +def make_hangman(secret_word): + guesses = [] + def hangman_closure(letter): + guesses.append(letter) + display_word = "".join([letter if letter in guesses else "_" for letter in secret_word]) + print(display_word) + return display_word == secret_word + return hangman_closure + +if __name__ == "__main__": + secret_word = input("Enter the secret word: ") + hangman = make_hangman(secret_word) + while True: + guess = input("Enter a letter: ") + if hangman(guess): + print("Congratulations! You've guessed the word!") + break + diff --git a/assignment3/list-comprehensions.py b/assignment3/list-comprehensions.py new file mode 100644 index 0000000..9096933 --- /dev/null +++ b/assignment3/list-comprehensions.py @@ -0,0 +1,25 @@ +# Task 3: List Comprehensions Practice +''' +Within the assignment3 folder, create a file called list-comprehensions.py. Add code that reads the contents of ../csv/employees.csv into +a list of lists using the csv module. + +Using a list comprehension, create a list of the employee names, first_name + space + last_name. The list comprehension should iterate +through the items in the list read from the csv file. Print the resulting list. Skip the item created for the heading of the csv file. + +Using a list comprehension, create another list from the previous list of names. This list should include only those names that contain +the letter "e". Print this list. +''' +import csv + +with open("../csv/employees.csv", "r") as f: + reader = csv.reader(f) + employees = list(reader) + +employee_names = [f"{employee[1]} {employee[2]}" for employee in employees[1:]] + +print(employee_names) + +names_with_e = [name for name in employee_names if "e" in name] + +print(names_with_e) + diff --git a/assignment3/log-decorator.py b/assignment3/log-decorator.py new file mode 100644 index 0000000..5bcc865 --- /dev/null +++ b/assignment3/log-decorator.py @@ -0,0 +1,75 @@ +# Task 1: Writing and Testing a Decorator +''' +Within the assignment3 folder, create a file called log-decorator.py. It should contain the following. + +Declare a decorator called logger_decorator. This should log the name of the called function (func.__name__), the input parameters of that were passed, +and the value the function returns, to a file ./decorator.log. (Logging was described in lesson 1, so review this if you need to do so.) + +Functions may have positional arguments, keyword arguments, both, or neither. So for each invocation of a decorated function, the log would have: + +function: +positional parameters: +keyword parameters: +return: + +Here's a cookbook on logging: +# one time setup +import logging +logger = logging.getLogger(__name__ + "_parameter_log") +logger.setLevel(logging.INFO) +logger.addHandler(logging.FileHandler("./decorator.log","a")) +... + +# To write a log record: +logger.log(logging.INFO, "this string would be logged") +Declare a function that takes no parameters and returns nothing. Maybe it just prints "Hello, World!". Decorate this function with your decorator. +Declare a function that takes a variable number of positional arguments and returns True. Decorate this function with your decorator. +Declare a function that takes no positional arguments and a variable number of keyword arguments, and that returns logger_decorator. Decorate this +function with your decorator. + + +Within the mainline code, call each of these three functions, passing parameters for the functions that take positional or keyword arguments. +Run the program, and verify that the log file contains the information you want. +''' +import logging +logger = logging.getLogger(__name__ + "_parameter_log") +logger.setLevel(logging.INFO) +logger.addHandler(logging.FileHandler("./decorator.log","a")) + +def logger_decorator(func): + def wrapper(*args, **kwargs): + logger.log(logging.INFO, f"function: {func.__name__}") + if args: + logger.log(logging.INFO, f"positional parameters: {args}") + else: + logger.log(logging.INFO, "positional parameters: none") + if kwargs: + logger.log(logging.INFO, f"keyword parameters: {kwargs}") + else: + logger.log(logging.INFO, "keyword parameters: none") + return_value = func(*args, **kwargs) + logger.log(logging.INFO, f"return: {return_value}") + return return_value + return wrapper + +@logger_decorator +def hello_world(): + print("Hello, World!") + +@logger_decorator +def variable_positional_args(*args): + return True + +@logger_decorator +def variable_keyword_args(**kwargs): + return logger_decorator + +if __name__ == "__main__": + # Call 1: No parameters + hello_world() + + # Call 2: Variable positional arguments + variable_positional_args(1, 2, "three") + + # Call 3: Variable keyword arguments + variable_keyword_args(status="active", priority="high") \ No newline at end of file diff --git a/assignment3/tictactoe.py b/assignment3/tictactoe.py new file mode 100644 index 0000000..a75f656 --- /dev/null +++ b/assignment3/tictactoe.py @@ -0,0 +1,214 @@ +#Task 6: More on Classes +''' +Within the assignment3 folder, create a file called tictactoe.py. + +Within this file, declare a class called TictactoeException. This should inherit from the Exception class. Add an __init__ method +that stores an instance variable called message and then calls the __init__ method of the superclass. This is a common way of creating +a new type of exception. + +Declare also a class called Board. This should have an __init__ function that only has the self argument. It creates a list of lists, 3x3, all +git containing " " as a value. This is stored in the variable self.board_array. Create instance variables self.turn, which is initialized to "X". +The Board class should have a class variable called valid_moves, with the value: + + valid_moves=["upper left", "upper center", "upper right", "middle left", "center", "middle right", "lower left", "lower center", "lower right"] + +Add a __str__() method. This converts the board into a displayable string. You want it to show the current state of the game. The rows to be +displayed are separated by newlines ("\n") and you also want some "|" amd "-" characters. Once you have created this method, you can display +the board by doing a print(board). + +Add a move() method. This has two arguments, self and move_string. The following strings are valid in TicTacToe: "upper left", "upper center", "upper +right", "middle left", "center", "middle right", "lower left", "lower center", and "lower right". When a string is passed, the move() method will +check if it is one of these, and if not it will raise a TictactoeException with the message "That's not a valid move.". Then the move() method +will check to see if the space is taken. If so, it will raise an exception with the message "That spot is taken." If neither is the case, the +move is valid, the corresponding entry in board_array is updated with X or O, and the turn value is changed from X to O or from O to X. It +also updates last_move, which might make it easier to check for a win. + +Add a whats_next() method. This will see if the game is over. If there are 3 X's or 3 O's in a row, it returns a tuple, where the first value is +True and the second value is either "X has won" or "O has won". If the board is full but no one has won, it returns a tuple where the first value +is True and the second value is "Cat's Game". Otherwise, it returns a tuple where the first value is False and the second value is either "X's turn" +or "O's turn". + +Implement the game within the mainline code of tictactoe.py. At the start of the game, an instance of the board class is created, and then +the methods of the board class are used to progress through the game. Use the input() function to prompt for each move, indicating whose turn +it is. Note that you need to call board.move() within a try block, with an except block for TictactoeException. Give appropriate information to +the user. + +Test your program by playing a few games. + +On assembling this program, the assignment author found that it was too time consuming to write some of the methods. So, here are some pieces to reuse. +Please make sure you understand them. + + def __str__(self): + lines=[] + lines.append(f" {self.board_array[0][0]} | {self.board_array[0][1]} | {self.board_array[0][2]} \n") + lines.append("-----------\n") + lines.append(f" {self.board_array[1][0]} | {self.board_array[1][1]} | {self.board_array[1][2]} \n") + lines.append("-----------\n") + lines.append(f" {self.board_array[2][0]} | {self.board_array[2][1]} | {self.board_array[2][2]} \n") + return "".join(lines) + + def move(self, move_string): + if not move_string in Board.valid_moves: + raise TictactoeException("That's not a valid move.") + move_index = Board.valid_moves.index(move_string) + row = move_index // 3 # row + column = move_index % 3 #column + if self.board_array[row][column] != " ": + raise TictactoeException("That spot is taken.") + self.board_array[row][column] = self.turn + if self.turn == "X": + self.turn = "O" + else: + self.turn = "X" + + def whats_next(self): + cat = True + for i in range(3): + for j in range(3): + if self.board_array[i][j] == " ": + cat = False + else: + continue + break + else: + continue + break + if (cat): + return (True, "Cat's Game.") + win = False + for i in range(3): # check rows + if self.board_array[i][0] != " ": + if self.board_array[i][0] == self.board_array[i][1] and self.board_array[i][1] == self.board_array[i][2]: + win = True + break + if not win: + for i in range(3): # check columns + if self.board_array[0][i] != " ": + if self.board_array[0][i] == self.board_array[1][i] and self.board_array[1][i] == self.board_array[2][i]: + win = True + break + if not win: + if self.board_array[1][1] != " ": # check diagonals + if self.board_array[0][0] == self.board_array[1][1] and self.board_array[2][2] == self.board_array[1][1]: + win = True + if self.board_array[0][2] == self.board_array[1][1] and self.board_array[2][0] == self.board_array[1][1]: + win = True + if not win: + if self.turn == "X": + return (False, "X's turn.") + else: + return (False, "O's turn.") + else: + if self.turn == "O": + return (True, "X wins!") + else: + return (True, "O wins!") +''' + +class TictactoeException(Exception): + def __init__(self, message): + self.message = message + super().__init__(message) + +class Board: + valid_moves=["upper left", "upper center", "upper right", "middle left", "center", "middle right", "lower left", "lower center", "lower right"] + def __init__(self): + self.board_array = [[" ", " ", " "] for _ in range(3)] + self.turn = "X" + self.last_move = None + def __str__(self): + lines=[] + lines.append(f" {self.board_array[0][0]} | {self.board_array[0][1]} | {self.board_array[0][2]} \n") + lines.append("-----------\n") + lines.append(f" {self.board_array[1][0]} | {self.board_array[1][1]} | {self.board_array[1][2]} \n") + lines.append("-----------\n") + lines.append(f" {self.board_array[2][0]} | {self.board_array[2][1]} | {self.board_array[2][2]} \n") + return "".join(lines) + def move(self, move_string): + if not move_string in Board.valid_moves: + raise TictactoeException("That's not a valid move.") + move_index = Board.valid_moves.index(move_string) + row = move_index // 3 # row + column = move_index % 3 #column + if self.board_array[row][column] != " ": + raise TictactoeException("That spot is taken.") + self.board_array[row][column] = self.turn + self.last_move = (row, column) + if self.turn == "X": + self.turn = "O" + else: + self.turn = "X" + def whats_next(self): + cat = True + for i in range(3): + for j in range(3): + if self.board_array[i][j] == " ": + cat = False + else: + continue + break + else: + continue + break + if (cat): + return (True, "Cat's Game.") + win = False + for i in range(3): # check rows + if self.board_array[i][0] != " ": + if self.board_array[i][0] == self.board_array[i][1] and self.board_array[i][1] == self.board_array[i][2]: + win = True + break + if not win: + for i in range(3): # check columns + if self.board_array[0][i] != " ": + if self.board_array[0][i] == self.board_array[1][i] and self.board_array[1][i] == self.board_array[2][i]: + win = True + break + if not win: + if self.board_array[1][1] != " ": # check diagonals + if self.board_array[0][0] == self.board_array[1][1] and self.board_array[2][2] == self.board_array[1][1]: + win = True + if self.board_array[0][2] == self.board_array[1][1] and self.board_array[2][0] == self.board_array[1][1]: + win = True + if not win: + if self.turn == "X": + return (False, "X's turn.") + else: + return (False, "O's turn.") + else: + if self.turn == "O": + return (True, "X wins!") + else: + return (True, "O wins!") + +if __name__ == "__main__": + # 1. Create the instance of the board + game_board = Board() + game_over = False + + print("Welcome to Tic-Tac-Toe!") + + # 2. Start the game loop + while not game_over: + print(game_board) # This calls your __str__ method + + # 3. Check whose turn it is + status, message = game_board.whats_next() + print(message) + + print(f"Here are the valid moves: {', '.join(Board.valid_moves)}") + move = input("Please enter your move (e.g., 'center', 'upper left'): ").lower().strip() + + # 4. Try to make the move + try: + game_board.move(move) + + # 5. Check if the game ended after the move + game_over, final_message = game_board.whats_next() + if game_over: + print(game_board) + print("Game Over!") + print(final_message) + + except TictactoeException as e: + print(f"\n*** ERROR: {e.message} ***\n") \ No newline at end of file diff --git a/assignment3/type-decorator.py b/assignment3/type-decorator.py new file mode 100644 index 0000000..f84abb8 --- /dev/null +++ b/assignment3/type-decorator.py @@ -0,0 +1,48 @@ +# Task 2: A Decorator that Takes an Argument +''' +Within your assignment3 folder, write a script called type-decorator.py. + +Declare a decorator called type_converter. It has one argument called type_of_output, which would be a type, like str or int or float. It should +convert the return from func to the corresponding type, viz: + +x = func(*args, **kwargs) +return type_of_output(x) + +Write a function return_int() that takes no arguments and returns the integer value 5. Decorate that function with type-decorator. In the decoration, +pass str as the parameter to type_decorator. +Write a function return_string() that takes no arguments and returns the string value "not a number". Decorate that function with type-decorator. +In the decoration, pass int as the parameter to type_decorator. Think: What's going to happen? + +In the mainline of the program, add the following: +y = return_int() +print(type(y).__name__) # This should print "str" +try: + y = return_string() + print("shouldn't get here!") +except ValueError: + print("can't convert that string to an integer!") # This is what should happen +''' +def type_converter(type_of_output): + def decorator(func): + def wrapper(*args, **kwargs): + x = func(*args, **kwargs) + return type_of_output(x) + return wrapper + return decorator + +@type_converter(str) +def return_int(): + return 5 + +@type_converter(int) +def return_string(): + return "not a number" + +if __name__ == "__main__": + y = return_int() + print(type(y).__name__) # This should print "str" + try: + y = return_string() + print("shouldn't get here!") + except ValueError: + print("can't convert that string to an integer!") # This is what should happen \ No newline at end of file diff --git a/assignment4/additional_employees.json b/assignment4/additional_employees.json new file mode 100644 index 0000000..c2ce6a3 --- /dev/null +++ b/assignment4/additional_employees.json @@ -0,0 +1 @@ +[{"Name": "Eve", "Age": 28, "City": "Miami", "Salary": 60000}, {"Name": "Frank", "Age": 40, "City": "Seattle", "Salary": 95000}] \ No newline at end of file diff --git a/assignment4/assignment4.py b/assignment4/assignment4.py index e69de29..e26b336 100644 --- a/assignment4/assignment4.py +++ b/assignment4/assignment4.py @@ -0,0 +1,224 @@ +## Task 1: Introduction to Pandas - Creating and Manipulating DataFrames +''' +1) Create a DataFrame from a dictionary: +Use a dictionary containing the following data: +Name: ['Alice', 'Bob', 'Charlie'] +Age: [25, 30, 35] +City: ['New York', 'Los Angeles', 'Chicago'] + +Convert the dictionary into a DataFrame using Pandas. +Print the DataFrame to verify its creation. +save the DataFrame in a variable called task1_data_frame and run the tests. + +2) Add a new column: +Make a copy of the dataFrame you created named task1_with_salary (use the copy() method) +Add a column called Salary with values [70000, 80000, 90000]. +Print the new DataFrame and run the tests. + +3) Modify an existing column: +Make a copy of task1_with_salary in a variable named task1_older +Increment the Age column by 1 for each entry. +Print the modified DataFrame to verify the changes and run the tests. + +4) Save the DataFrame as a CSV file: +Save the task1_older DataFrame to a file named employees.csv using to_csv(), do not include an index in the csv file. +Look at the contents of the CSV file to see how it's formatted. +Run the tests. +''' +import json # We will use this to create the JSON file in Task 2.2 + +import pandas as pd + +#Addressing a downcasting warning when running the tests. This will ensure that + #when we fillna with a mean or median, the data type of the column will be + #preserved as numeric instead of being downcast to object. +pd.set_option('future.no_silent_downcasting', True) + +# T1.1) Create a DataFrame from a dictionary: +task1_data_dict = { + 'Name': ['Alice', 'Bob', 'Charlie'], + 'Age': [25, 30, 35], + 'City': ['New York', 'Los Angeles', 'Chicago'] +} +task1_data_frame = pd.DataFrame(task1_data_dict) + +# Print to verify +print("--- Task 1: Original DataFrame ---") +print(task1_data_frame) + +# T1.2) Add a new column: +task1_with_salary = task1_data_frame.copy() +task1_with_salary['Salary'] = [70000, 80000, 90000] + +print("\n--- Task 2: Added Salary Column ---") +print(task1_with_salary) + +# T1.3) Modify an existing column: +task1_older = task1_with_salary.copy() +task1_older.loc[:, 'Age'] = task1_older['Age'] + 1 + +print("\n--- Task 3: Incremented Age ---") +print(task1_older) + +# T1.4) Save the DataFrame as a CSV file: +task1_older.to_csv('employees.csv', index=False) + +# Verify the file content +with open('employees.csv', 'r') as f: + print(f.read()) + +## Task 2: Loading Data from CSV and JSON +''' +1) Read data from a CSV file: +Load the CSV file from Task 1 into a new DataFrame saved to a variable task2_employees. +Print it and run the tests to verify the contents. + +2) Read data from a JSON file: +Create a JSON file (additional_employees.json). The file adds two new employees. +Eve, who is 28, lives in Miami, and has a salary of 60000, and Frank, who is 40, lives in Seattle, and has a salary of 95000. +Load this JSON file into a new DataFrame and assign it to the variable json_employees. +Print the DataFrame to verify it loaded correctly and run the tests. + +3) Combine DataFrames: +Combine the data from the JSON file into the DataFrame Loaded from the CSV file and save it in the variable more_employees. +Print the combined Dataframe and run the tests. +''' + +# T2.1) Read data from a CSV file: +task2_employees = pd.read_csv('employees.csv') + +print("\n--- Task 1: Loaded CSV DataFrame ---") +print(task2_employees) + +# T2.2) Read data from a JSON file: +# Create the JSON file with additional employees +additional_employees = [ + {"Name": "Eve", "Age": 28, "City": "Miami", "Salary": 60000}, + {"Name": "Frank", "Age": 40, "City": "Seattle", "Salary": 95000} +] + +with open('additional_employees.json', 'w') as f: + json.dump(additional_employees, f) + +json_employees = pd.read_json('additional_employees.json') + +print("\n--- Task 2: Loaded JSON DataFrame ---") +print(json_employees) + +# T2.3) Combine DataFrames: +more_employees = pd.concat([task2_employees, json_employees], ignore_index=True) + +print("\n--- Task 3: Combined DataFrame ---") +print(more_employees) + + +## Task 3: Data Inspection - Using Head, Tail, and Info Methods +''' +1) Use the head() method: +Assign the first three rows of the more_employees DataFrame to the variable first_three +Print the variable and run the tests. +2) Use the tail() method: +Assign the last two rows of the more_employees DataFrame to the variable last_two +Print the variable and run the tests. +3) Get the shape of a DataFrame +Assign the shape of the more_employees DataFrame to the variable employee_shape +Print the variable and run the tests +4) Use the info() method: +Print a concise summary of the DataFrame using the info() method to understand the data types and non-null counts. +''' +# T3.1) Use the head() method: +first_three = more_employees.head(3) + +print("\n--- Task 1: First Three Rows ---") +print(first_three) + +# T3.2) Use the tail() method: +last_two = more_employees.tail(2) + +print("\n--- Task 2: Last Two Rows ---") +print(last_two) + +# T3.3) Get the shape of a DataFrame: +employee_shape = more_employees.shape + +print("\n--- Task 3: Employee Shape ---") +print(employee_shape) + +# T3.4) Use the info() method: +print("\n--- Task 4: Employee Info ---") +more_employees.info() + + +## Task 4: Data Cleaning +''' +1) Create a DataFrame from dirty_data.csv file and assign it to the variable dirty_data. +Print it and run the tests. +Create a copy of the dirty data in the varialble clean_data (use the copy() method). You will use data cleaning methods to update clean_data. + +2) Remove any duplicate rows from the DataFrame +Print it and run the tests. + +3) Convert Age to numeric and handle missing values +Print it and run the tests. + +4) Convert Salary to numeric and replace known placeholders (unknown, n/a) with NaN +print it and run the tests. + +5) Fill missing numeric values (use fillna). Fill Age with the mean and Salary with the median +Print it and run the tests + +6) Convert Hire Date to datetime +Print it and run the tests + +7) Strip extra whitespace and standardize Name and Department as uppercase +Print it and run the tests +''' +# T4.1) Create a DataFrame from dirty_data.csv file: +dirty_data = pd.read_csv('dirty_data.csv') + +print("\n--- Task 1: Dirty Data ---") +print(dirty_data) + +clean_data = dirty_data.copy() + +# T4.2) Remove any duplicate rows from the DataFrame +clean_data = clean_data.drop_duplicates() + +print("\n--- Task 2: Clean Data (Duplicates Removed) ---") +print(clean_data) + +# T4.3) Convert Age to numeric and handle missing values +clean_data['Age'] = pd.to_numeric(clean_data['Age'], errors='coerce') + +print("\n--- Task 3: Clean Data (Age Converted) ---") +print(clean_data) + +# T4.4) Convert Salary to numeric and replace known placeholders (unknown, n/a) with NaN +clean_data['Salary'] = pd.to_numeric(clean_data['Salary'], errors='coerce') + +print("\n--- Task 4: Clean Data (Salary Converted) ---") +print(clean_data) + +# T4.5) Fill missing numeric values (use fillna). Fill Age with the mean and Salary with the median +clean_data.loc[:, 'Age'] = clean_data['Age'].fillna(clean_data['Age'].mean()) +clean_data.loc[:, 'Salary'] = clean_data['Salary'].fillna(clean_data['Salary'].median()) + +#Force the DataFrame to realize these are now numeric columns after filling missing values, which will help ensure the tests recognize them as numeric. +clean_data = clean_data.infer_objects(copy=False) + +print("\n--- Task 5: Clean Data (Missing Values Filled) ---") +print(clean_data) + +# T4.6) Convert Hire Date to datetime +clean_data['Hire Date'] = pd.to_datetime(clean_data['Hire Date'], errors='coerce', format='mixed') + +print("\n--- Task 6: Clean Data (Hire Date Converted) ---") +print(clean_data) + +# T4.7) Strip extra whitespace and standardize Name and Department as uppercase +clean_data['Name'] = clean_data['Name'].str.strip().str.upper() +clean_data['Department'] = clean_data['Department'].str.strip().str.upper() + +print("\n--- Task 7: Clean Data (Name and Department Standardized) ---") +print(clean_data) + diff --git a/assignment4/employees.csv b/assignment4/employees.csv new file mode 100644 index 0000000..2bd2f60 --- /dev/null +++ b/assignment4/employees.csv @@ -0,0 +1,4 @@ +Name,Age,City,Salary +Alice,26,New York,70000 +Bob,31,Los Angeles,80000 +Charlie,36,Chicago,90000