<jupyter_start><jupyter_text>Tasks 2020 * **Author:** <NAME>* **Github:** JPLee01* **Email:** <EMAIL>* **Created:** 09-10-2020, **Last update:** 18-12-2020**** Jupyter Notebook created to store and highlight the four tasks for the Tasks 2020 Assessment for the Fundamentals of Data Analysis Subject.****  Task 1 - CountsRequirement is to write a Python function called *Counts* that takes a list as input and returns a dictionary of unique items in the list as keys and the number of times each item appears as values. Caveat is that the code should not depend on any module from the standard library or otherwise.  IntroductionFirstly in order to begin this task we need the understand what we mean by a list and dictionary are in Python. List and DictionariesAccording to Lisa Tagliaferri[1] a list is a data structure in Python that is a mutable or changeable, ordered sequence of elements. Each element or value that is inside of a list is called an item. Just as strings are defined as characters between quotes, lists are defined by having values between square brackets [ ].An example of a list is as follows:        list_example = [ "shark", "cuttlefish", "squid", "anemone" ]While w3schools.com[2] defines a dictionary as a collection which is unordered, changeable and indexed. Dictionaries are written with curly brackets, and they have keys and values.An example of a dictionary is as follows:        dictionary_example = { 11, "tom", "Sean",  2.4, "Seven", 2014 }    Dictionaries and lists share many of the same characteristics including:   * Both are mutable.   * Both are dynamic. They can grow and shrink as needed.   * Both can be nested. A list can contain another list. A dictionary can contain another dictionary. A dictionary can also contain a list and vice versa.However they differ in how elements are accessed:* List elements are accessed by their position in the list, via indexing.* Dictionary elements are accessed via keys.This difference can been seen in the example below where the function list[1] will return the second element in the list while the function dictionary[1] will search for the key value 1 and return the corresponding value.[3]<jupyter_code>list_example = [ "shark", "cuttlefish", "squid", "anemone" ]
dictionary_example = {2: 11, 3: "tom", 4: "Sean",  5: 2.4, 0: "Seven", 1: 2014 }
list_example[1]
dictionary_example[1]<jupyter_output><empty_output><jupyter_text>This means that in a list each element has a value while in a dictionary each element has a key and a value.[3]  Construction of the Counts FunctionFrom a review of the litarature available including the works of Jamel Dargan[4], Kite[5] and Geeks for Geeks[6], it appears that a number of sub-actions must take place in order for the Counts Function to be able to run in its entirety. These include:1. The creation of the designated list2. The creation of a blank dictionary3. The conversion of the designated list to a dictionary and it's addition to the blank dictionary4. Creation of a for loop through the dictionary to identify the unique itemsFor ease of explanation we will break down each sub-action into designated sub-paragraphs.  1. The creation of the designated listA predesignated list is selected for the function.<jupyter_code>#A predesignated list is selected
list = ["A", 2, "a", 1, "Tom", 1, "tom", 2]
print(list)<jupyter_output>['A', 2, 'a', 1, 'Tom', 1, 'tom', 2]<jupyter_text>2. The creation of a blank dictionaryA blank dictionary is then created.<jupyter_code>#Creation of a blank dictionary
converted_dict = {}<jupyter_output><empty_output><jupyter_text>3. The conversion of the designated list to a dictionary and it's addition to the blank dictionaryThe designated list is then converted to a dictionary and added to the blank dictionary.<jupyter_code>#Conversion of the designted list to a dictionary and added to the blank dictionary
for i in list:
    converted_dict.update({i:""})
print(converted_dict)<jupyter_output>{'A': '', 2: '', 'a': '', 1: '', 'Tom': '', 'tom': ''}<jupyter_text>4. Creation of a for loop through the dictionary to identify the unique itemsA for loop in created, which will loop through the dictionary. While looping through the dictionary the  count() function will be used to return the number of times a unique item appears in the dictionary.[7]<jupyter_code>for i in range(len(list)):
    converted_dict[list[i]] = list.count(list[i])
print("The following are the unique items and the number of times they appear in your list")
print(converted_dict)<jupyter_output>The following are the unique items and the number of times they appear in your list
{'A': 1, 2: 2, 'a': 1, 1: 2, 'Tom': 1, 'tom': 1}<jupyter_text>Finally the four parts will be combined to create the Counts function.<jupyter_code>def Counts():
    list = ["A", 2, "a", 1, "Tom", 1, "tom", 2]
    converted_dict = {}
    for i in list:
        converted_dict.update({i:""})
    for i in range(len(list)):
        converted_dict[list[i]] = list.count(list[i])  
    print("The following are the unique items and the number of times they appear in your list", converted_dict)
    
Counts()<jupyter_output>The following are the unique items and the number of times they appear in your list {'A': 1, 2: 2, 'a': 1, 1: 2, 'Tom': 1, 'tom': 1}<jupyter_text>The input for the Counts function will now be set to the input described in the Problem Statement.<jupyter_code>def Counts():
    list = ['A', 'A', 'B', 'C', 'A']
    converted_dict = {}
    for i in list:
        converted_dict.update({i:""})
    for i in range(len(list)):
        converted_dict[list[i]] = list.count(list[i])  
    print("The following are the unique items and the number of times they appear in your list", converted_dict)
    
Counts()<jupyter_output>The following are the unique items and the number of times they appear in your list {'A': 3, 'B': 1, 'C': 1}<jupyter_text>Tweaking of the codeIn its simplest form we can see that the above Counts function serves it purpose. However with some tweaking of the code the Counts function could be improved greatly.  Count the number of unique keys in the dictionaryThe number of unique keys can in the dictionary can be counted and displayed. This can be done by implementing the below code:[8]<jupyter_code>#find number of unique keys in the dictionary
keys_num = len(converted_dict.keys())<jupyter_output><empty_output><jupyter_text>This code will now be added to the Counts function:<jupyter_code>def Counts():
    list = [2, "False", "false", 2, 1, .1, 10, "ten"]
    converted_dict = {}
    for i in list:
        converted_dict.update({i:""})
    for i in range(len(list)):
        converted_dict[list[i]] = list.count(list[i])  
    keys_num = len(converted_dict.keys())
    print("The number of unique keys in the list are:", keys_num)
    print("The following are the unique items and the number of times they appear in your list", converted_dict)
    
Counts()<jupyter_output>The number of unique keys in the list are: 7
The following are the unique items and the number of times they appear in your list {2: 2, 'False': 1, 'false': 1, 1: 1, 0.1: 1, 10: 1, 'ten': 1}<jupyter_text>Users creation of the designated listThe user can also create the designated list by replacing the predesignated list with an input function. In this form the users input will be treated like a list.[9] It should be noted however that any words or letters the user enteres must be surrounded by quotation marks ("") and a comma must be placed after each element. While this may seem mundane, if the quotation marks were not included the users words would be treated as letters E.g. dog would be treated as the letters d o g. While the lack of commas after each element would result in the space inbetween being treated as an element. This will added to the Counts function below:<jupyter_code>a = input("Please enter your list here (please ensure quotation marks are around all words and letters and commas are placed after each element): ")

def Counts():
    list=a.split(",")
    converted_dict = {}
    for i in list:
        converted_dict.update({i:""})
    for i in range(len(list)):
        converted_dict[list[i]] = list.count(list[i])  
    print("The following are the unique items and the number of times they appear in your list", converted_dict)
    
Counts()<jupyter_output>Please enter your list here (please ensure quotation marks are around all words and letters and commas are placed after each element): "A", 2, "a", 1, "Tom", 1, "tom", 2
The following are the unique items and the number of times they appear in your list {'"A"': 1, ' 2': 2, ' "a"': 1, ' 1': 2, ' "Tom"': 1, ' "tom"': 1}<jupyter_text>Case SensitivityAs we can see from the above, the program takes into account case sensitivity. From research it can be seen through the works of Stack Overflow[10], Kite[11] and Geeks for Geeks[12] that case sensitivity can be disabled through converting case of elements in the list to either upper case or lower case. This can be seen in the code below:<jupyter_code>list = ['right', 'wrong', 'RIGHT', 'WRONG', 'RiGhT', 'WrOnG'] 
a_list = [x.lower() for x in list] 
print(a_list)<jupyter_output>['right', 'wrong', 'right', 'wrong', 'right', 'wrong']<jupyter_text>Unfortunately due to time constraints the author was not able to fully implement this into the Counts Function but it should be noted that this is another area in which the initial Counts Function could be improved.   Task 2 - DicerollsWrite a Python function called *dicerolls* that simulates rolling dice. Your function should take two parameters: the number of dice *k* and the number of times to roll the dice *n*. The function should simulate randomly rolling *k* dice *n* times, keeping track of each total face value. It should then return a dictionary with the number of times each possible total face value occurred.  ResearchFrom examination of the above statement the requirement of the task is to:1. Simulate the roll of a die and store the result (*k*)2. Simulate the roll of another die and store the result (*n*).3. Add the results of the two simulations and display them in a dictionary with the number of times each possible total face value occurred.Due to the random nature from the rolling of a die the *random* module from the Python Standard Libary will have to be imported. The *random* module implements pseudo-random number generators for various distributions.[13] This module is vital for the task as it will help simulate the random outcome that can be achieved from rolling a die. The randint() function from the *random* module will specifically be used to simulate the rolling of a dice. The randint() function returns a random number between the given range.[14] As the lowest score for a die is 1 and the highest is 6 this will be our range.  Constrction of *dicerolls* code In order to first construct the code the *random* module from the Python Standard Libary will have to be imported. This will simulate the random outcome that can be achieved from rolling a die.<jupyter_code>import random<jupyter_output><empty_output><jupyter_text>We then need to ask the user to select the number of die being rolled (*k*) and the number of times the die will be rolled (*n*).<jupyter_code>#Ask the user the input of n and k
k= int(input("The number of die being rolled:"))
n= int(input("The number of times the die will be rolled:"))<jupyter_output>The number of die being rolled:2
The number of times the die will be rolled:1000<jupyter_text>As we know the minimum and maximum score for a dice single roll we will set them.<jupyter_code>min_value = 1
max_value = 6<jupyter_output><empty_output><jupyter_text>There also needs to be a requirement to account for if more than one die (k) is rolled.<jupyter_code>if k > 1:
        min_value = k * 1
        max_value = k * 6<jupyter_output><empty_output><jupyter_text>A dictionary is then created to store the result of the die roll. It can be seen from the work of Pavlenko[15] that *+ 1* is added to the max_value function to allow the max_value (6) to be included.<jupyter_code>roll = {i:0 for i in range(min_value, max_value + 1)}<jupyter_output><empty_output><jupyter_text>A for loop is then created which allows the die roll to be looped through the range "n" times.[16][17]<jupyter_code>for i in range(n):
    roll = {random.randint(min_value, max_value) + 1}<jupyter_output><empty_output><jupyter_text>Finally all the above code is combined and contained in the program the program *dicerolls* which will display the number of times each possible total face value occurred.<jupyter_code>k= int(input("The number of die being rolled:"))
n= int(input("The number of times the die will be rolled:"))

def dicerolls(k,n):
    min_value = 1
    max_value = 6
    if k > 1:
        min_value = k * 1
        max_value = k * 6
    roll = {i:0 for i in range(min_value, max_value + 1)}  
    for i in range(n):
        roll[random.randint(min_value, max_value)] += 1
    print(roll)
dicerolls(k,n)<jupyter_output>The number of die being rolled:2
The number of times the die will be rolled:1000
{2: 87, 3: 76, 4: 102, 5: 92, 6: 100, 7: 87, 8: 94, 9: 91, 10: 91, 11: 88, 12: 92}<jupyter_text>Task 3 - numpy.random.binomialWrite some python code that simulates flipping a coin 100 times. Then run this code 1,000 times, keeping track of the number of heads in each of the 1,000 simulations. Select an appropriate plot to depict the resulting list of 1,000 numbers, showing that it roughly follows a bell-shaped curve.  ResearchFrom a reivew of the literature available on the simulation of flipping a coin it appears that the use of the binomial distribution function is not that frequent. The work of Data8[18] uses the random.choice function, while <NAME>[19] uses the random.randint function used in the previous task. <NAME>[20] in his calculation appreas to attach letters to the coin faces and use to random function.The programming forums Codecademy[21] and Stack Overflow[22] advocate the use of the random.randrange and random.randint functions respectively. While Datacamp[23] teaches it's students to use the bernoulli.rvs() function. The distinct lack of the use of the binomial distribution function is quiet strange, as by definition, the binomial distribution is a probability distribution that describes the outcome of *n* independent trials in an experiment. Each trial is assumed to have only two outcomes, either success or failure and have a probability associated with success and failure.[24]  The binomial distribution fuction by its nature, would be a very good function to use to simulate a coin flip as the user is able to input the number of trials (*n*) as well as set the success (Heads) or failure (tails) of the experiment. The porbability can also be set to allow for the coin flip to be fair (.5), biased (>.5) or unbiased (<.5).While the review of the literature does predominantly point away from the use of the binomial distribution fuction. It should be noted that the works of Cmdline[25], ReginaOfTech[26], <NAME>[27], and Tharaka Umayanga[28] do contain elements of the binomial distribution fuction and are used as inspiration of the below code.  Constrction of numpy.random.binomial codeIn order to first construct the code the NumPy library will need to be imported. This will allow for the use of the numpy.random.binomial function.<jupyter_code>import numpy as np<jupyter_output><empty_output><jupyter_text>Once this is achieved the user will then be asked to enter the number of times to flip the coin (*p*). This number will then be entered in the numpy.random.binomial function. The number of outcomes will be set to 1, with a 1 outcome signifying a heads and a outcome of 0 signifying tails. The probability will also be set to .5 to allow for a unbiased outcome. The numpy.random.binomial function will be constructed into a function called *coinflip* where total number of heads (1) and tails (0) will be totaled and displayed.We will now flip the coin 100 times and see the outcome.<jupyter_code>#Ask the user to input the number of times to flip the coin
p= int(input("Number of times to flip the coin: "))

def coinflip(p):
#Heads = 1
#Tails = 0
    result = np.random.binomial(1,.5, p)
    print("Head Count: ", np.count_nonzero(result == 1))
    print("Tail Count: ", np.count_nonzero(result == 0))
    
coinflip(p)<jupyter_output>Number of times to flip the coin: 100
Head Count:  49
Tail Count:  51<jupyter_text>As we can see from the above the results from flipping the coin 100 times is displayed.In order to run simulation on the flipping of the coin a for loop will be introduced within the coinflip function which will loop the numpy.random.binomial function for the number of simulations chosen by the user (*n*). The number of heads and tails produced in each simulation will be displayed.We will now flip the coin 100 times and simulate it 1000 times and see the outcome.<jupyter_code>import numpy as np

p= int(input("Number of times to flip the coin: "))
n= int(input("Number of simulations: "))

def coinflip(p, n):
    for i in range(n):
        result = np.random.binomial(1,.5, p)
        print("Head Count: ", np.count_nonzero(result == 1))
        print("Tail Count: ", np.count_nonzero(result == 0))
    

coinflip(p, n)<jupyter_output>Number of times to flip the coin: 100
Number of simulations: 1000
Head Count:  55
Tail Count:  45
Head Count:  54
Tail Count:  46
Head Count:  46
Tail Count:  54
Head Count:  46
Tail Count:  54
Head Count:  52
Tail Count:  48
Head Count:  44
Tail Count:  56
Head Count:  38
Tail Count:  62
Head Count:  55
Tail Count:  45
Head Count:  58
Tail Count:  42
Head Count:  56
Tail Count:  44
Head Count:  50
Tail Count:  50
Head Count:  51
Tail Count:  49
Head Count:  54
Tail Count:  46
Head Count:  48
Tail Count:  52
Head Count:  41
Tail Count:  59
Head Count:  43
Tail Count:  57
Head Count:  47
Tail Count:  53
Head Count:  45
Tail Count:  55
Head Count:  52
Tail Count:  48
Head Count:  40
Tail Count:  60
Head Count:  60
Tail Count:  40
Head Count:  53
Tail Count:  47
Head Count:  41
Tail Count:  59
Head Count:  51
Tail Count:  49
Head Count:  45
Tail Count:  55
Head Count:  53
Tail Count:  47
Head Count:  49
Tail Count:  51
Head Count:  55
Tail Count:  45
Head Count:  47
Tail Count:  53
Head C[...]<jupyter_text>As we can see the results from flipping a coin 100 times and simulating it 1000 times are displayed. We will now display the results in graphical form depicting the number of heads and thier probability. A distribution plot was chosen to display the results as it simply displays the overall distribution as well as, displaying the Kernel Density Estimate to estimate the probability of distribution.[29]<jupyter_code>#Visualise the above results
import seaborn as sns
import matplotlib.pyplot as plt
sns.distplot(np.random.binomial(n,.5, p))
plt.xlabel("Number of Heads")
plt.ylabel("Probability")<jupyter_output><empty_output><jupyter_text>From the above results we can see that flipping a coin 100 times and simulating it 1000 times produces a bell curve with the highest probability of the number of heads at the 500 figure indicating a 50/50 chance of heads or tails on each flip.  It should also be noted that while the outcome of the above program calculates the number of heads and tails produced per simulation, it does not calculate the cumulative heads and tails produced. Should this action be required the author in thier research discovered analysis by w3resource which, while using the numpy.random.choice module, could serve as inspiration.[30]  Task 4 - Simpson’s ParadoxSimpson’s paradox is a well-known statistical paradox where a trend evident in a number of groups reverses when the groups are combined into one big data set. Use numpy to create four data sets, each with an ```x``` array and a corresponding ```y``` array, to demonstrate Simpson’s paradox. You might create your ```x``` arrays using ```numpy.linspace``` and create the ```y``` array for each ```x``` using notation like ```y = a * x + b``` where you choose the ```a``` and ```b``` for each ```x``` , ```y``` pair to demonstrate the paradox.  ResearchAccording to Koswara et al., Simpson's paradox is a statistical phenomenon which occurs when "some groups of data show a certain relationship in each group, but when the data is combined, that relationship is reversed".[31]Named after British statistician <NAME> and mentioned in his technical paper in 1951, Simpson's paradox has been found to be present across a broad range of spectrums including graduate admissions, baseball batting averafes and the US median wage.[32]The following is a very simple example of Simpson's paradox:As you can see in the above when you and a friend each take a set of exams your friend answers a higher proportion correctly than you on each of two days. However when the two days are combined you see that you have in fact answered a higher proportion correctly and won the challenge. This is Simpson's Paradox at its simpliest and it's identification and understanding can be vital.[33]While in the above simple example a misreading of the data would result in the wrong person being declared the champion, for other more serious cases it can have devastating effects. This can be seen in the study by Taylor and Mickel in which a claim of discrimination based on ethnicity was made through misinterpretation of the data.[34]The key to overcomming Simpson's paradox according to Ameringer et al. is to focus: "first identifying potential confounding variables during the planning phase of a study, and then controlling for these variables in both the design and analysis" while also rembering that the goal of the study is: "is to determine the true relationship between the independent and outcome variables".[35] Skrivanek also highlights to overcome Simpson's paradox it is "important to involve a cross‐functional team and especially subject matter experts and practitioners in the initial planning and selection of the variables to be measured."[36]It should be noted <NAME> makes an interesting argument in his article when he states that in the identifying and controling of the variables stage in the study, the author: "must then decide whether to break the data into separate distributions, or to keep the data combined. The correct decision is entirely situational and this is part of the reason why data science exists at the intersection of mathematics/statistics, computer science and business/domain knowledge: We need to know our data, and more importantly, what we want out of our data, in order to choose which approach to take". While further explaining in the article: "the key is to interpret the data in relation to the underlying domain, and to take the most appropriate data-viewpoint."[37]Simpson's Paradox will now be highlighted in the below example.  ExampleIn this example Simpon's Paradox will be highlighted using admissions based on gender. Special mention must be given to the 1973 UC Berkeley gender admissions case which was used for inspiration for the below example.[38]In this exampe male and female college admissions are examined across four departments (Maths, History, Music and English). This examination takes place in terms of number of admissions in each department and the percentage of male and females admitted. The results are then displayed in graphical form of analysis and interpretation.  As directed in the assessment instructions, the numpy linspace function will be used to calculate the number of male admissions (or *x* figure).[39]<jupyter_code>#Generate X (Number of male admissions) for your 4 Data Sets
import numpy as np

a = np.linspace(32, 50, num=4)
print(a)<jupyter_output>[32. 38. 44. 50.]<jupyter_text>Once these figures are calculated they will be implemented to calculate the the number of female admissions (or *y* figure). This figure will be calculated using the ```y = a * x + b``` formula from the assessment instructions where x = number of male admissions, and a and b = integers chosen by the author.<jupyter_code>#Generate 4 Data Sets with an X (Number of male admissions) and Y (Number of female admissions) array 
#Use the figures generated above for your X (Number of male admissions) results

import pandas as pd

a = 2
b = 4
x = 32
y = a * x + b

df = pd.DataFrame({'Male Admissions': x, 'Female Admissions': y}, index = ['Maths Department'])

print(df) 


a1 = 2
b1 = 1
x1 = 38
y1 = a1 * x1 + b1

df1 = pd.DataFrame({'Male Admissions': x1, 'Female Admissions': y1}, index = ['History Department'])

print(df1) 


a2 = 2
b2 = 3
x2 = 44
y2 = a2 * x2 + b2

df2 = pd.DataFrame({'Male Admissions': x2, 'Female Admissions': y2}, index = ['Music Department'])

print(df2) 


a3 = 1
b3 = 7
x3 = 50
y3 = a3 * x3 + b3

df3 = pd.DataFrame({'Male Admissions': x3, 'Female Admissions': y3}, index = ['English Department'])

print(df3)<jupyter_output>Male Admissions  Female Admissions
Maths Department               32                 68
                    Male Admissions  Female Admissions
History Department               38                 77
                  Male Admissions  Female Admissions
Music Department               44                 91
                    Male Admissions  Female Admissions
English Department               50                 57<jupyter_text>From the above results it can be seen that across all departments there are quite a larger number of female admissions than male. While this may be a cause of concern (including accusations of gender bias) it should be noted that the above figures do not take into account the total number of males (50) and females (100) who attempted to gain admission. In order to develop a more accurate picture, a percentage of successful admissions to total admissions should be calculated.<jupyter_code>#Calculation of precentage of successful admissions to total admissions by gender 

x_percentage = ((x / 50) * 100)
y_percentage = ((y / 100) * 100)

x1_percentage = ((x1 / 50) * 100)
y1_percentage = ((y1 / 100) * 100)

x2_percentage = ((x2 / 50) * 100)
y2_percentage = ((y2 / 100) * 100)

x3_percentage = ((x3 / 50) * 100)
y3_percentage = ((y3 / 100) * 100)

Total_x_percentage = [((x+x1+x2+x3)/ 200) * 100]
Total_y_percentage = [((y+y1+y2+y3)/ 400) * 100]



df4 = pd.DataFrame({'Male Maths': x_percentage, 'Female Maths': y_percentage,
                    'Male History': x1_percentage, 'Female History': y1_percentage,
                    'Male Music': x2_percentage, 'Female Music': y2_percentage,
                    'Male English': x3_percentage, 'Female English': y3_percentage,
                    'Male Average': Total_x_percentage, 'Female Average': Total_y_percentage}, 
                   index = ['Admissions Precentage'])

print(df4)<jupyter_output>Male Maths  Female Maths  Male History  Female History  \
Admissions Precentage        64.0          68.0          76.0            77.0   

                       Male Music  Female Music  Male English  Female English  \
Admissions Precentage        88.0          91.0         100.0            57.0   

                       Male Average  Female Average  
Admissions Precentage          82.0           73.25<jupyter_text>As seen above, when the number of successful admissions is compared to the total admissions an entirely different picture is painted. In this new picture the disparity between male and female admission is greatly reduced and in fact accross the four departments there is an average of 8.75% more males admitted than females.  The above set of results will now be displayed in graphical form to highlight Simpson's Paradox. A grouped bar chart with labels will be implemented as it highlights Simpson's Paradox best to the user.[40]<jupyter_code>import matplotlib.pyplot as plt

#Plot the individual subject scores by Gender
labels = ['Maths', 'History', 'Music', 'English']
men_scores = [32, 38, 44, 50]
women_scores = [68, 77, 91, 57]

x = np.arange(len(labels))  # the label locations
width = 0.35  # the width of the bars

fig, ax = plt.subplots()
rects1 = ax.bar(x - width/2, men_scores, width, label='Men')
rects2 = ax.bar(x + width/2, women_scores, width, label='Women')

# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_ylabel('Admission Numbers')
ax.set_title('Admission Numbers in Subject Department by Gender')
ax.set_xticks(x)
ax.set_xticklabels(labels)
ax.legend()

def autolabel(rects):
    """Attach a text label above each bar in *rects*, displaying its height."""
    for rect in rects:
        height = rect.get_height()
        ax.annotate('{}'.format(height),
                    xy=(rect.get_x() + rect.get_width() / 2, height),
                    xytext=(0, 3),  # 3 points vertical offset
                    textcoords="offset points",
                    ha='center', va='bottom')

autolabel(rects1)
autolabel(rects2)

fig.tight_layout()

plt.show()


#Plot the individual percentage subject scores by Gender
labels = ['Maths', 'History', 'Music', 'English']
men_percentage = [64, 76, 88, 100]
women_percentage = [68, 77, 91, 57]

x = np.arange(len(labels))  # the label locations
width = 0.35  # the width of the bars

fig, ax = plt.subplots()
rects1 = ax.bar(x - width/2, men_percentage, width, label='Men')
rects2 = ax.bar(x + width/2, women_percentage, width, label='Women')

# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_ylabel('Admissions Precentage')
ax.set_title('Admissions Precentage in Subject Department by Gender')
ax.set_xticks(x)
ax.set_xticklabels(labels)
ax.legend()

def autolabel(rects):
    """Attach a text label above each bar in *rects*, displaying its height."""
    for rect in rects:
        height = rect.get_height()
        ax.annotate('{}'.format(height),
                    xy=(rect.get_x() + rect.get_width() / 2, height),
                    xytext=(0, 3),  # 3 points vertical offset
                    textcoords="offset points",
                    ha='center', va='bottom')


autolabel(rects1)
autolabel(rects2)

fig.tight_layout()

plt.show()




#Plot the total Scores by gender
labels = ['Average Admissions Precentage by Gender']
men_total = [82]
women_total = [73.25]

x = np.arange(len(labels))  # the label locations
width = 0.1  # the width of the bars

fig, ax = plt.subplots()
rects1 = ax.bar(x - width/2, men_total, width, label='Men')
rects2 = ax.bar(x + width/2, women_total, width, label='Women')

# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_ylabel('Admissions Precentage')
ax.set_title('Average Admissions Precentage by Gender')
ax.set_xticks(x)
ax.set_xticklabels(labels)
ax.legend()


def autolabel(rects):
    """Attach a text label above each bar in *rects*, displaying its height."""
    for rect in rects:
        height = rect.get_height()
        ax.annotate('{}'.format(height),
                    xy=(rect.get_x() + rect.get_width() / 2, height),
                    xytext=(0, 3),  # 3 points vertical offset
                    textcoords="offset points",
                    ha='center', va='bottom')


autolabel(rects1)
autolabel(rects2)

fig.tight_layout()

plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07834470388520547, 'fraction_numerical': 0.024406649370615933, 'mean_word_length': 4.082532964349666, 'pattern_counts': {'":': 0, '<': 98, '<?xml version=': 0, '>': 100, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '26', 'dir': 'jupyter-structured-clean-dedup', 'id': '253496', 'license': 'MIT', 'n_tokens_mistral': 9384, 'n_tokens_neox': 8557, 'n_words': 4358, 'path': 'Tasks-2020.ipynb', 'repo_name': 'JPLee01/Tasks-2020', 'size': '31264'}