<jupyter_start><jupyter_text>Problem 1 answer:Even though this answer's code could produce a shorter output, I opted for this extended version because it serves as a check that this script is doing what it's supposed to be doing.I used the csv library to handle the input files ("LocusGene.tsv" and "Germplasm.tsv") because it is very useful and how easy it is to use. The first two variables I declared ("tmp" and "mismatch") are used to create the output. The former serves as a counter of the line that is being checked in each iteration of the loops and the latter serves as a counter of the times two lines are not the same in both documents. Next, I opened the files previously mentioned. As adding information to them isn't necessary, I opened them in read mode (the *"r"* part of the open call makes the files open in this mode). Note that I didn't close the connection to the files yet. This is because I still needed to make more operations on them in another problem's answer. Then, using the csv.DictReader function I loaded the data from both files to their respective functions ("locus_data" and "germplasm_data". Since these are ".tsv" files, the delimiter is set to a tab ("\t"). Even though there are no quotation marks in the documents, I set it to double quotation marks (") just in case in the future I wanted to use them. As the way the nested loops work is quite counter-intuitive, I will explain it in detail:Normally, when a for loop is nested into another, the resulting behaviour is that for each iteration of the first loop, the entirety of the nested loop is ran. In this particular situation, that means that the nested loops would cause the first line of the "LocusGene.tsv" file to be compared to **every** single line in the "Germplasm.tsv" document. But as the output shows, that's not what is happening. There are two reasons for this:* After all the code in the nested loop, I inserted a *"break"*. This means that instead of running this loop completely, only one iteration is actually run. That is because "break" causes the current loop to be terminated, and resumes execution at the next statement, which in this case is the next iteration of the first loop (the one that is not nested). * Ok, so for now the loops have compared the first line in the "LocusGene.tsv" and "Germplasm.tsv" files and the nested loop has been terminated, so now what should happen is that the second line of the LocusGene file should be compared with the **first** line on the Germplasm file again. But again, the output shows something different: the second line of both files is being compared. This is because I am reading from a file! When the for loop reads a line from either file, said file's pointer is set to the end of that line. This means that after reading one line, that line is no longer "visible" to the DictReader. The result of this interaction is that after comparing the fist line of both documents, and terminating the nested loop, the first loop reads the following line, which is the second one because of where the pointer is! Then, the second loop reads the "first" line in the Germplasm file, but since the pointer is at the end of the first line, the second line is what is really read. This is pretty hard to explain, so I will try to summarize:The for loop nested structure in my code works because when the files are read, the file pointer is set to the end of the last line read. With each iteration of the loops, the next lines are read and the pointer is subsecuentially placed on the end of those lines. Also, since I introduced a "break" in the nested loop, only one iteration is run, resulting in the designed behaviour consisting of comparing the first line in the LocusGene file with the first line in the Germplasm file, the second with the second and so on. Just to prove my explanation, I created a copy of this code in another code cell, but I commented out the "break" part. Check that explanation please.As for what the loops do themselves, they simply print a string stating the line number that is being compared, show the comparison *per se* and showing the result of the comparisons thanks to the if/else block. When I say that the lines from both documents are being compared, I mean that the "Locus" column from both documents is being compared. This is done in the *" if row1["Locus"] == row2["Locus"]"* line. That is because the DictReader function outputs a variant of a dictionary created from separating each column of the document. Its keys are the header line's text for each column (if present, otherwise they can be declared in the DictReader statement) and the values are, well, the values of each row corresponding to each column (in this case, for example, the first element of the dictionary would be {"Locus" : "AT1G01040"}, the second {"Gene" : "DCL1"} and so on until the first line is recorded. Then the loop would iterate over the second line and fill in the values in that row). If the lines are not the same, the "mismatch" counter is incremented. At the end of each iteration the "tmp" counter increments as well to show the next lines to be checked. The "tmp" function is set to 1 at the beginning because even though technically the first lines to be compared are number 0 (at least in Python), it is easier for a human to understand if the count begins in 1. Finally, the final results are created. If the "mismatch" variable's value is 0, that means that all comparisons showed that the lines were the same, so both documents are in the same order.<jupyter_code>#First of all, I have to import the csv library to be able to use its functions.
import csv

#These variables are used in creating the output of the file.
tmp = 1
mismatch = 0

#Next, I open both files in read mode and read their contents with the csv.DictReader function
locus_file = open("LocusGene.tsv", "r")
locus_data = csv.DictReader(locus_file, delimiter = "\t", quotechar = '"')
germplasm_file =  open ("Germplasm.tsv", "r")
germplasm_data = csv.DictReader(germplasm_file, delimiter = "\t", quotechar = '"')

#The nested for loops allow me to easily compare both files.
for row1 in locus_data:   
    for row2 in germplasm_data:
        print("Now checking line number: {}".format(tmp))
        print(row1["Locus"] + " vs. " + row2["Locus"])
        if row1["Locus"] == row2["Locus"]:
            print("Result: both are the same\n")
        else:
            print("Result: both are not the same\n")
            mismatch += 1
        tmp +=1    
        break
        
#The final results are created here.        
print ("FINAL RESULTS:")                
if mismatch == 0:
    print("All lines are in the same order!")
else:
    print("Warning! {} lines are not in the same order".format(mismatch))<jupyter_output><empty_output><jupyter_text>Explanation of the same code but with the break disabled.As we can see in the output, when the "break" is not present on the nested loop the behaviour is as explained previously: for each iteration of the first loop all the nested loop is run. This means that the first line of the LocusGene document is being compared to all the lines in the Germplasm document (as we can see in the fact that the first element of the comparison is always "AT1G0140").Furthermore, my point about the pointer being set to the end of each line when it is read is reflected in the fact that **only one iteration of the first loop is showing output**. This is because after the first iteration of the first loop the whole Germplasm document has been read, so the file pointer is at the end of said file! This means that all the other iterations of the first loop are comparing the LocusGene to a functionally *empty* document, so the nested loop can't run any more iterations, causing the lack of output.<jupyter_code>#First of all, I have to import the csv library to be able to use its functions.
import csv

#These variables are used in creating the output of the file.
tmp = 1
mismatch = 0

#Next, I open both files in read mode and read their contents with the csv.DictReader function
locus_file = open("LocusGene.tsv", "r")
locus_data = csv.DictReader(locus_file, delimiter = "\t", quotechar = '"')
germplasm_file =  open ("Germplasm.tsv", "r")
germplasm_data = csv.DictReader(germplasm_file, delimiter = "\t", quotechar = '"')

#The nested for loops allow me to easily compare both files.
for row1 in locus_data:   
    for row2 in germplasm_data:
        print("Now checking line number: {}".format(tmp))
        print(row1["Locus"] + " vs. " + row2["Locus"])
        if row1["Locus"] == row2["Locus"]:
            print("Result: both are the same\n")
        else:
            print("Result: both are not the same\n")
            mismatch += 1
        tmp +=1    
        #break
        
#The final results are created here.        
print ("FINAL RESULTS:")                
if mismatch == 0:
    print("All lines are in the same order!")
else:
    print("Warning! {} lines are not in the same order".format(mismatch))<jupyter_output><empty_output><jupyter_text>Problem 2 answer:Since sql magic is easier to use, its output is much easier to read and the following commands are basic, I chose to use it.First, I had to run the code that is used to connect to MySQL in Docker. Then I had to create a database for this exam, called "exam2", and inside of said database I created two tables. Since the "Locus" column is shared between both input files and locus' codes shouldn't appear more than once, I decided to use them as the primary keys for their respective tables. The locus column is the primary key, which means that its values **must** be unique in the entire table. It also means that it is the column that will be used to link both tables in this case. The values contained in this column must be VARCHAR, variable characters up to length 15 (even though all codes in the input file are 9 characters long, I just wanted to make sure that if there is any AGI code that is longer it can fit). The "protein_length" column is to be filled with INTEGERs. I made it so all columns must contain information (basically, they can't be null).The germplasm table has different columns but a similar configuration.<jupyter_code>#Commands to connect to MySQL in Docker. 
%load_ext sql
%sql mysql+pymysql://root:root@127.0.0.1:3306/mysql
#Now, I create a database for this exam (called "exam2")
%sql CREATE DATABASE exam2;
%sql USE exam2; #and I tell sql that I want to work with said database.
#%sql DROP TABLE locus_gene; #This is here just in case I need to delete the table and start over.

#This creates the "locus_gene" table.
%sql CREATE TABLE locus_gene(locus VARCHAR(15) PRIMARY KEY NOT NULL, \
                             gene VARCHAR(20) NOT NULL, protein_length INTEGER NOT NULL);
#This is used to check that the table was created correctly.
%sql DESCRIBE locus_gene; 
#%sql DROP TABLE germplasm;
%sql CREATE TABLE germplasm(locus VARCHAR(15) PRIMARY KEY NOT NULL, \
                            germplasm VARCHAR(25) NOT NULL, phenotype VARCHAR(500) NOT NULL, pubmed INTEGER NOT NULL);
%sql DESCRIBE germplasm;<jupyter_output><empty_output><jupyter_text>Problem 3 answer:As I have previously explained, right now the pointer in both files is at their end. Before doing anything else with them, I have to reset the pointers' position to the beginning of the first data line. I did this by setting them at the beginning of the file and then reading the first line, the header line, skipping it and setting the pointers in the desired position. Once again I have to iterate over both files. I did this with a for loop for each one. Both of them are essentially the same. As I said before, "row" is, in this case, a variation of dictionary that contains the name of each column as keys and the values corresponding to each entry for those columns as values. In the SQL commands I'm inserting into each of the database's columns their corresponding values by accessing the values in the row dictionary and adding them in the correct place. The .format method allows me to introduce "placeholders" in the SQL command ({}) and then passing the corresponding Python variables to each placeholder in order of appearance.<jupyter_code>#First of all, I have to import the pymysql.cursors library to be able to use its functions.
import pymysql.cursors

#Set the pointer to the beginning of the first line of data (skipping the header line)
locus_file.seek(0)
locus_file.readline()
germplasm_file.seek(0)
germplasm_file.readline()

#stablish a connection with the database.
connection = pymysql.connect(host='localhost',
                             user='root',
                             password='<PASSWORD>',
                             db='exam2',
                             charset='utf8mb4',  
                             cursorclass=pymysql.cursors.DictCursor)

#insert the data
try:
    with connection.cursor() as cursor:
        for row in locus_data:
            sql = """INSERT INTO locus_gene (locus, gene, protein_length)
            VALUES ('{}', '{}', {});""".format(row["Locus"], row["Gene"], row["ProteinLength"])
            cursor.execute(sql) 
        for row in germplasm_data:
            sql = """INSERT INTO germplasm (locus, germplasm, phenotype, pubmed)
            VALUES ('{}', '{}', '{}', {});""".format(row["Locus"], row["germplasm"], row["phenotype"], row["pubmed"])
            cursor.execute(sql) 
    connection.commit() #this commits the changes to the database.
finally:
    connection.close() #Here I'm closing the connection to the database.
    locus_file.close() #Here I'm closing the connection to the "LocusGene.tsv" file.
    germplasm_file.close() #Here I'm closing the connection to the "Germplasm.tsv" file.
#Here I'm checking if the data was inserted correctly.
%sql SELECT * FROM germplasm
%sql SELECT * FROM locus_gene;<jupyter_output><empty_output><jupyter_text>Problem 4 answer:As I was reading the things that were required for problem 4 and the note at the end reminding us about the DRY rule I thought to myself: why not try to make a multi-purpose report-making function? I could even use it in the future to make reports of different kinds and shapes. The problem is that this weekend I didn't have a lot of time to develop the function much more than the bare minimum to be able to do the things this problem asks for. I didn't have time to make the function user-proof (making it not crash if you don't pass the correct data types as arguments, making it completely abstract and other things (for example, I hard coded some of the table names in the parts of the function that count the amount of entries and the average protein length in each chromosome because there was no immediate need to make those parts completely customizable just yet). If I had had enough time, I would have made a lot of improvements, but right now is a barebones version. First of all, I defined the function "ReportMaker". I won't explain what each argument does since the docstring already has plenty of information about the functioning of the function and each of its arguments. The docstring is the really big multi line text that is just after the def ReportMaker line. Its purpose is to explain the user what the function and its arguments do. The easiest way to check this information is by executing "help(ReportMaker)".Then, the function tries to connect to the selected database. As for the purposes of this course the connection block is always the same and the only part that needs to be modified is the "db" variable, I created a try/except block. The way this block works is the following: * First, the try clause is executed.* If there is no exception during that execution, the except clause is skipped.* If an exception occurs during that execution, the rest of the try clause is skipped and if the type of the exception matches the one named after "except", the except clause is executed, prompting the user to check if they introduced the database name correctly.After that, there is a block of conditionals that handle the different modes of the function:* "individually" block: **This block is NOT used for this exam's answers**, but it is a simple example that works similarly to the rest of the more complex modes. First of all I used the str.lower() method to make this if statement be case insensitive to check if the mode is "individually". The way it works is that it makes the string assignated to the "mode" variable passed by the user lowercase before checking if it is "individually". Then, if there was no "gene" argument in the call to the function, it displays the selected table. Otherwise, it filters the selected table so that it only shows the entry in which the "gene" column matches the name inserted. The only newly introduced concept here is the filtering, which is made with the "LIKE" part of the sql query. The LIKE operator is used in a WHERE clause to search for a specified pattern in a column. **Note: notice that some of the sql placeholders ({}) have quotation marks (') and others don't.** It is better explained with an example. In this case, the query "SELECT * FROM {} WHERE gene LIKE '{}'".format(table1, gene) would look like this in SLQ itself: SELECT * FROM table1 WHERE gene LIKE "gene". Since the pattern that LIKE searches for must be quoted, its placeholder must be quoted as well.* "joined" block: The same check for the value of "gene" that happens in the previous block happens in this block. If there is no "gene" to filter by, the function outputs the two selected tables joined by their two respective selected columns. This is possible thanks to the "WHERE" clause. In this case it would be: "WHERE table1.join_col1 = table2.join_col2", effectively joining both tables. If there is a "gene" value, it is used to filter, just the same as before. The only new operator is "AND", that makes the SQL query check for various conditions. * "count" block: Since all of the results obtained from this mode are made so from a loop, I had to create the list "results" to be able to append the results to it in each iteration of the loops. In the "by_chromosome" count type, the for loop iterates in the range(1, chromosome_count + 1). That is because the chromosomes of all species (at least all I know of) start by the number 1, and since Python's first index is 0 I had to specify the range to begin by 1. Then, the chromosome count of the species is used as the last part of the range, but I had to add 1 to it because Python's ranges don't include the last number included in them. I could have hard coded the chromosome count to be 5, since this exam's data is about *Arabidopsis*, and it only has 5 chromosomes- But abstracting this part of the problem was easy enough so I decided to go for it. The only new parts are the "COUNT" and "AVG" operators, as well as a new filtering pattern;    - "COUNT": it literally counts entries that fulfill certain conditions. Also, if an "AS" operator is introduced in its statement, the number of entries that fulfill the condition(s) will be named as the string following AS.     - "AVG": it functions the same as the "COUNT" operator, but this one averages values.    - Filtering pattern: The new filtering pattern is "ATnG%". The "%" represents zero, one, or multiple characters and the n is the chromosome number. So for chromosome 2 this pattern would mean: look for entries that begin with AT2G.    In the main code, I called the function with the necessary parameters to create the required output for this problem. Note that, since the function returns the "results" variable, it is now available in the global scope and can be used outside of the function itself!Since I'm planning to create a ".tsv" file, **I strongly recommend opening the output document in LibreOffice calc or similar**, as it is MUCH easier to read the output in those programs.Since the "results" variable is a list of dictionaries, and it is stored in the "result" variable as dictionaries in the loop, I used the dict.keys() method to extract the key values from it. Those keys correspond to the column names in the SQL database, so I used them as the header of the first and second reports. Each column name is separated by a tab ("\t") because I want the ouput to be a ".tsv" file.Then, to fill the report with the data from each row I simply accessed the values associated with each key and inserted them in their corresponding output file column with a loop.The only differences appear in the reports 3 and 4, where I had to append the resulting dictionaries for each query into a list. Because of that, the "results" variable in these cases is a list of lists of dictionaries (a list containing lists containing dicionaries) and the "result" variable in the loops is therefore a list of dictionaries, and had to be accessed as one. This means that "tmp = result[0]["C_{}".format(n)]" basically translates to: in the result variable, look for the first element of the list of dictionaries, and inside look for the "C_n" key (where n is the chromosome number that is being checked), and assing that value to the tmp variable.I decided to add a *control* to the 3rd report, so I added a line with the total number of entries, which is the value of tmp2, that is the result of adding tmp to tmp2 in each of the loop's iterations. Tmp is the number of entries in a given chromosome.Finally, I open the ouput file, called "report_file.tsv". This is done in a with statement so that I don't forget to close the connection to the file after using it. Even though I could have created the file and accessed it in append mode, I thought it would be easier if the script created the file each time it is run instead of appending.<jupyter_code>def ReportMaker (database, table1, mode = "individually", table2 = None,
                 join_col1 = None, join_col2 = None, gene = None, count_type = None, chromosome_count = None):
    """ReportMaker creates highly customizable reports.
    
    Parameters:
    -----------
    database:
        string --The name of the database you want to create reports from. --OBLIGATORY
    
    table1: 
        string --The name of the table you want to appear in the report. --OBLIGATORY
    
    mode: 
        string --Currently, the available modes are:                           --OBLIGATORY
                 *"individually": the report will contain only one table. 
                 *"joined": the report will TRY to join two tables with a common column.
                 *"count": the report will show you a table and count as selected in the count_type parameter.
                 
    table2:
        string --If you selected the "joined" mode, the name of the second table. OBLIGATORY if you use 
                 mode "joined"--OPTIONAL
                 
    join_col1:
        string --The name of the column FROM THE FIRST TABLE you want to use to join the tables. OBLIGATORY if you use
                 mode "joined". --OPTIONAL
                 
    join_col2:
        string --The name of the column FROM THE SECOND TABLE you want to use to join the tables. OBLIGATORY if you use
                 mode "joined". --OPTIONAL
    
    gene:
        string --You can filter the report with the names of one gene. --OPTIONAL
        
    count_type:
        string --table1 will be used to count. Currently, the available count modes are:
                 *"by_chromosome": counts the number of entries for each chromosome.
                 *"avg_prot_len": the report contains the average protein length for the genes on each chromosome.
                 OBLIGATORY if you use mode "count"--OPTIONAL
    chromosome_count:
        integer --Number of chromosomes the species the table is about has. 
                  OBLIGATORY if you use mode "count". --OPTIONAL
    """ 
    import pymysql.cursors
    
    try:
        connection = pymysql.connect(host='localhost',
                                 user='root',
                                 password='<PASSWORD>',
                                 db= database,
                                 charset='utf8mb4',  
                                 cursorclass=pymysql.cursors.DictCursor)
    
    #this except block promts the user to ckeck if their MySQL server is running as this is the exception that occurs 
    #when it isn't
    except OperationalError:
        print("""Something went wrong! Check that your MySQL server is running""")
        
    #this except block prompts the user to check if they introduced the database name correctly, 
    #as every other parameter in the connection statement should work (in this course's environment)
    except InternalError:
        print("Something went wrong! Check the name you passed as the 'database' argument.")
        
    with connection.cursor() as cursor: 
        #This block of code handles the "individually" mode of the function.
        if mode.lower() == "individually":
            #if there is no "gene" argument passed, then the function simply shows the whole selected table.
            if gene == None:
                sql = "SELECT * FROM {}".format(table1)
                cursor.execute(sql) 
                results = cursor.fetchall()
            #otherwise, the value of "gene" will be used to filter the table.    
            else:
                sql = "SELECT * FROM {} WHERE gene LIKE '{}'".format(table1, gene) 
                cursor.execute(sql)
                results = cursor.fetchall()
                
        #This block of code handles the "joined" mode of the function
        elif mode.lower() == "joined":
            #if "gene" is None, then the function shows the two selected tables joined by the two selected columns.
            if gene == None:
                sql = "SELECT * FROM {}, {} WHERE {}.{}= {}.{}".format(table1, table2, table1,
                                                                       join_col1, table2, join_col2)
                cursor.execute(sql) 
                results = cursor.fetchall()
            #otherwise, the value of gene will be used to filter the table.    
            else:
                sql = "SELECT * FROM {}, {} WHERE {}.{}= {}.{} AND gene LIKE '{}'".format(table1, table2, table1, 
                                                                                          join_col1, table2,
                                                                                          join_col2, gene) #mirar por que en una hace falta las comillas y en otra no
                cursor.execute(sql)
                results = cursor.fetchall()
                
        #Finally, this block of code handles the "count" mode of the function.    
        elif mode.lower() == "count":
            #this list will be used to store all the results from each iteration of the loops.
            results = []
            #this part of the code counts the amount of entries for each chromosome.
            if count_type.lower() == "by_chromosome":
                for n in range(1, chromosome_count + 1):
                    sql = "SELECT COUNT(locus) AS 'C_{}' FROM {} WHERE locus LIKE 'AT{}G%'".format(n, table1, n)
                    cursor.execute(sql)
                    results.append(cursor.fetchall())
                    
            #this part of the code averages the protein length for each chromosome.        
            elif count_type.lower() == "avg_prot_len":
                for n in range(1, chromosome_count + 1):
                    sql = "SELECT AVG(protein_length) AS 'AVG_{}' FROM locus_gene WHERE locus LIKE 'AT{}G%'".format(n, n)
                    cursor.execute(sql)
                    results.append(cursor.fetchall())
                    
            #If the "count_type" value passed in the call to the function isn't one of the two currently available,
            #I raise an exception that prompts the user to check the help for the function.
            else:
                raise InternalError("""The count type you selected wasn't recognized by the function.
                                            Check 'help(ReportMaker)' for a detailed list of count types'""")
        #If the "mode" value selected isn't one of the currently available, I raise an exception that prompts
        #the user to check the help command for this function.
        else:
            raise InternalError("""The mode you selected wasn't recognized by the function. 
                                  Check 'help(ReportMaker)' for a detailed list of modes.""")

    #the "results" variable is returned from the function so it is available in the global scope.
    return results

#------------------
#   Main code
#------------------

#help(ReportMaker) #This is here just in case someone wants to see how it looks.

#This part of the code handles the creation of the text output for report 1.
result1 = ReportMaker(database ="exam2", table1 = "locus_gene", table2 = "germplasm", mode = "joined", 
                     join_col1 = "locus", join_col2 = "locus")

out_file1_header = ""
for key in result1[0].keys():
    out_file1_header += key + "\t"
out_file1_header += "\n"

out_file1_body = ""
for result in result1:
    out_file1_body  += result["locus"] + "\t" + result["gene"] + "\t" + str(result["protein_length"]) + "\t" \
    + result["germplasm.locus"] + "\t" + result["germplasm"] + "\t" + result["phenotype"] + "\t" \
    + str(result["pubmed"]) + "\n"

#This part of the code handles the creation of the text output for report 2.
result2 = ReportMaker(database ="exam2", table1 = "locus_gene", table2 = "germplasm", mode = "joined", 
                     join_col1 = "locus", join_col2 = "locus", gene = "SKOR")

result3 = ReportMaker(database ="exam2", table1 = "locus_gene", table2 = "germplasm", mode = "joined", 
                     join_col1 = "locus", join_col2 = "locus", gene = "MAA3")

out_file2 = ""
for result in result2:
    out_file2 += result["locus"] + "\t" + result["gene"] + "\t" \
    + str(result["protein_length"]) + "\t" + result["germplasm.locus"] + "\t" \
    + result["germplasm"] + "\t" + result["phenotype"] + "\t" + str(result["pubmed"]) + "\n"
    
for result in result3:
    out_file2 += result["locus"] + "\t" + result["gene"] + "\t" + str(result["protein_length"]) + "\t" \
    + result["germplasm.locus"] + "\t" + result["germplasm"] + "\t" + result["phenotype"] + "\t" \
    + str(result["pubmed"]) + "\n"    

result4 = ReportMaker(database = "exam2", table1 = "locus_gene", mode = "count", count_type = "by_chromosome",
                      chromosome_count = 5)

#This part of the code handles the creation of the text output for report 3.
out_file3 = ""
n = 1
tmp2 = 0
for result in result4:
    tmp = result[0]["C_{}".format(n)]
    out_file3 += "Number of entries for chromosome {}:".format(n) + "\t" + str(tmp) + "\n"
    tmp2 += tmp
    n += 1
out_file3 += "Total number of entries:" + "\t" + str(tmp2) + "\n"

result5 = ReportMaker(database = "exam2", table1 = "locus_gene", mode = "count", count_type = "avg_prot_len",
                      chromosome_count = 5)

#This part of the code handles the creation of the text output for report 4.
out_file4 = ""
m = 1
for result in result5:
    tmp = result[0]["AVG_{}".format(m)]
    out_file4 += "Average protein length for chromosome {}".format(m) + "\t" + str(tmp) + "\n"
    m += 1

#This part of the code writes every previously created output in the output file.    
with open ("reports_file.tsv", "w") as out_file:
    out_file.write("Report 1: Joined tables \n")
    out_file.write(out_file1_header)
    out_file.write(out_file1_body)
    out_file.write("""Comments on Report 1: As we can see, both files were in the same order. The first three columns
    correspond to the 'LocusGene.tsv' file and the last four correspond to the file 'Germplasm.tsv'\n""")
    out_file.write("\nReport 2: Joined tables filtered by Genes 'SKOR' and 'MAA3' \n")
    out_file.write(out_file1_header)
    out_file.write(out_file2)
    out_file.write("\nReport 3: Number of entries for each chromosome\n")
    out_file.write(out_file3)
    out_file.write("""Comments on Report 3: As we can see, chromosome 1 has 4 entries, 
    chromosome 2 has 4, chromosome 3 has 9, chromosome 4 has 8 and chromosome 5 has 7 entries.
    That makes a total of 32 entries, the total amount that was found in the input files\n""")
    out_file.write("\nReport 4: Average protein length for each chromosome \n")
    out_file.write(out_file4)
    out_file.write("""Comments on Report 4: As we can see, chromosome 1 has an average protein length of 258.75, 
    chromosome 2 has an average protein length of 215.5, chromosome 3 has an average protein length of 252, 
    chromosome 3 has an average protein length of 277.5 and finally chromosome 5 has an average protein length 
    of 271.2857 """)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07253823001854498, 'fraction_numerical': 0.007448393275164928, 'mean_word_length': 3.106616729088639, 'pattern_counts': {'":': 12, '<': 23, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 1, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 19, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '614131', 'license': 'MIT', 'n_tokens_mistral': 8784, 'n_tokens_neox': 8316, 'n_words': 4655, 'path': 'Exam_2_answers.ipynb', 'repo_name': 'Acb897/Accelerated_Intro_WilkinsonExams', 'size': '32881'}