<jupyter_start><jupyter_text>The numpy.random package   Programming for Data Analysis Assignment An investigation into the numpy.random package  <NAME> - <EMAIL> ![numpy.org](https://numpy.org/doc/stable/_static/numpylogo.svg)  1. Introduction This notebook contains an explanation of the ```numpy.random``` package in Python.  NumPy is an open source project aiming to enable numerical computing with Python[1](f1). It is used for working with arrays and also has functions for working in the domain of linear algebra, fourier transform, and matrices[2](f2). In has applications in a wide range of fields such as astronomy, physics, engineering, and economics.  The ```numpy.random``` package within NumPy is used to generate a sequence of numbers which approximate the properties of random numbers. The sequence of numbers that is generated is statistically random and can be used in a wide range of applications.     The package generates the sequence with the use of a BitGenerator and a Generator. The BitGenerator uses a seed to derive the initial state and create a sequence of statistically random numbers bits. The Generator converts the sequence of random bits from a BitGenerator into sequences of numbers that follow a specific probability distribution[3](f3).  This notebook will explain the following overarching features of the ```numpy.random``` package:- Simple random data- Permutations- The purpose of five Distribution functions    - Uniform     - Normal    - Weibull    - Binomial    - Multinomial- The use of seeds in generating pseudo-random numbers The notebook will explain each feature and the functions contained in each using code, plots, and diagrams where required.  Use of the notebook requires the following Python packages to be initalised:- ```numpy.random```- ```matplotlib.pyplot```In addition, a Generator needs to be constructed. The initialisation will be done in Section 2 of this Notebook. At the time of writing this notebook, the most recent release of ```numpy.random``` is 1.21.0. This contents of this notebook are based on this release. Further information on ```numpy``` can be found on its website: https://numpy.org/. All references are provided in Section 6 of this notebook.  2. Initalising the Required Python Packages Import NumPy:<jupyter_code>import numpy as np<jupyter_output><empty_output><jupyter_text>Construct a new Generator with the default BitGenerator (PCG64):<jupyter_code>rng = np.random.default_rng()<jupyter_output><empty_output><jupyter_text>A seed can be specified in ```default_rng```. The use of a known seed means that the the algorithm is repeatable. BitGenerators and seeds are discussed further in Section 5. Pyplot will also be used to allow plotting of outputs generated, where required:<jupyter_code>import matplotlib.pyplot as plt<jupyter_output><empty_output><jupyter_text>3. Simple Random Data The ```numpy.random``` package contains four functions to generate simple random data. These are discussed in this section.  3.1 Integers NumPy can be instructed to return random integers[4](f4). For example:<jupyter_code>rng.integers(0, 3, size=5)<jupyter_output><empty_output><jupyter_text>In this example, the first parameter (```0```) sets the lowest integer that is possible to be generated. The upper limit is defined by the second parameter (```3```); the highest integer that is possible to be generated is one lower than this number. The parameter ```size=5``` designates that 5 random numbers should be generated.   The shape of the output can be changed by specifying the number of rows and columns. For example, a 5 x 5 array containing random numbers between 0 and 9 can be generated using the following:<jupyter_code>rng.integers(0, 10, size=(5,5))<jupyter_output><empty_output><jupyter_text>In these examples, the output also includes the data type: ```dtype=int64```. By default, Python has the following data types:- strings- integer- float- boolean- complexNumPy also includes a number of additional data types (discussion of which is considered outside the scope of this notebook). ```dtype=int64``` refers to the output containing 64-bit integers. This can be changed, as shown in the following example to output 8-bit integers:<jupyter_code>rng.integers(0, 10, size=(5,5), dtype='int8')<jupyter_output><empty_output><jupyter_text>The integers are generated by default as a discrete probability distribution. This means that the probability of outcomes is equally likely and with finite values[5](f5).<jupyter_code>x = rng.integers(100, size=10000)
plt.hist(x)
plt.show()<jupyter_output><empty_output><jupyter_text>2.2 Random The NumPy random function returns random floats[6](f6). For example:<jupyter_code>rng.random()<jupyter_output><empty_output><jupyter_text>The size of output can be specified:<jupyter_code>rng.random(size=(5,5))<jupyter_output><empty_output><jupyter_text>To specify the low and high values the formula ```(b + a) * rng.random() + a``` can be used. The following example outputs five floats between 0 (inclusive) and 3 (exclusive):<jupyter_code>a = 0 #low value
b = 3 #high value
x = rng.random(size=(5,5)) 
y = (b + a) * x + a
y<jupyter_output><empty_output><jupyter_text>Or for negative numbers between 0 (inclusive) and -1 (exclusive):<jupyter_code>a = 0 
b = -1 
x = rng.random(size=(5,5)) 
y = (b + a) * x + a
y<jupyter_output><empty_output><jupyter_text>However, for negative numbers not including 0, the formula must be changed slightly. For example, for negative numbers between -1 (inclusive) and -2 (exclusive):<jupyter_code>a = -1 
b = -2 
x = rng.random(size=(5,5))  
y = (b - a) * x + a
y<jupyter_output><empty_output><jupyter_text>The floats are generated by default as a continuous probability distribution. This means that there are infinite number of equally likely measurable values[7](f7).<jupyter_code>x = rng.random(size=10000)
plt.hist(x)
plt.show<jupyter_output><empty_output><jupyter_text>2.3 Choice The NumPy choice function generates a random sample from a given array[8](f8). For example:<jupyter_code>rng.choice(10, 5)<jupyter_output><empty_output><jupyter_text>This returns an array of 5 integers between 0 and 9 (inclusive) and is the equivalent of ```rng.integers(0, 10, 5)```. By default, the distribution is uniform, as shown below:<jupyter_code>x = rng.choice(100, 10000)
plt.hist(x)
plt.show()<jupyter_output><empty_output><jupyter_text>A non-uniform distribution can be generated by specifying parameter ```p``` for the probabilty of a choice occuring. For example:<jupyter_code>rng.choice(5, 3, p=[0.1, 0, 0.3, 0.6, 0])<jupyter_output><empty_output><jupyter_text>In this example, there is a 10% chance of 0 occuring for any the three numbers generated, a 0% percentage chance for a 1, a 30% chance for a 2, a 60% chance for a 3, and a 0% chance for a 4.  Specifying parameter ```replace=False``` will result in an individual number not occurring more than once in the output:<jupyter_code>rng.choice(10, 10, replace=False)<jupyter_output><empty_output><jupyter_text>The axis along which the choice is performed can also be selected. The 0 axis refers to rows, and the 1 axis refers to columns[9](f9). The default is 0 if this parameter is not set. For example:<jupyter_code>x = rng.choice([[0,1,2], [3,4,5], [6,7,8], [9,'A','B'], ['C','D','E']], 5, axis=0)
x<jupyter_output><empty_output><jupyter_text>The above output contains five rows with three elements:<jupyter_code>(x.shape)<jupyter_output><empty_output><jupyter_text>Setting the axis to 1:<jupyter_code>x = rng.choice([[0,1,2], [3,4,5], [6,7,8], [9,'A','B'], ['C','D','E']], 5, axis=1)
x<jupyter_output><empty_output><jupyter_text>The above output contains five rows with five columns:<jupyter_code>(x.shape)<jupyter_output><empty_output><jupyter_text>The choice function is not restricted to integers. For example:<jupyter_code>season = ['spring', 'summer', 'autumn', 'winter']
rng.choice(season, 4, p=[0.5, 0, 0, 0.5])<jupyter_output><empty_output><jupyter_text>2.4 Bytes The NumPy bytes function returns random bytes, dependent on the specified length[10](f10). The following example returns 10 bytes:<jupyter_code>rng.bytes(10)<jupyter_output><empty_output><jupyter_text>The bytes are returned in hex and ASCII. Any two characters following an x character are hex characters making up 1 byte; any other characters are ASCII characters of 1 byte each. The initial ```b``` is a prefix for bytes, and the output is enclosed in matching single quotes (```'```)[11](f11).  For example, ```b'\xfd\xd7\x17\xac\xa3Ig\xf0`\xa1'``` would be made up of the following for a total of 10 bytes:- fd (hex)- d7 (hex)- 17 (hex)- ac (hex)- a3 (hex)- I (ASCII)- g (ASCII)- f0 (hex)- ` (ASCII)- a1 (hex) The ```numpy.random``` package contains three methods for randomly permutating a sequence. These are discussed in this section.   3. Permutations  3.1 Shuffle The NumPy shuffle function rearranges the order of a sequence's contents[12](f12). For example, the following will return a sequence of numbers from 0 to 9 (inclusive) in a random order, as detailed in Section 2.3:<jupyter_code>x = rng.choice(10, 10, replace=False)
x<jupyter_output><empty_output><jupyter_text>The squence order is then rearranged using the shuffle function:<jupyter_code>rng.shuffle(x)
x<jupyter_output><empty_output><jupyter_text>The shuffle function can also rearrange the sequence of arrays:<jupyter_code>x = np.arange(0, 25).reshape(5, 5)
x
rng.shuffle(x)
x<jupyter_output><empty_output><jupyter_text>By default, the axis parameter is set to 0. As can be seen in the above example, by default, the shuffle function will shuffle the order the rows, but not the order of the columns.  In the below example, the axis is set to 1:<jupyter_code>x = np.arange(0, 25).reshape(5, 5)
x
rng.shuffle(x, axis=1)
x<jupyter_output><empty_output><jupyter_text>This results in a shuffling of the contents of the columns, but the row order remaining the same.  3.2 Permutation The NumPy permutation function returns either a randomly permuted sequence or randomly rearranges the elements of an array[13](f13). A randomly permuted sequence is shown in the example below:<jupyter_code>rng.permutation(10)<jupyter_output><empty_output><jupyter_text>The elements of an array being rearrange is shown in the example below:<jupyter_code>x = np.arange(0, 25).reshape(5, 5)
x
y = rng.permutation(x)
y<jupyter_output><empty_output><jupyter_text>By default, the axis parameter is set to 0. As can be seen in the above example, by default, the permutation function will rearrange the order the rows, but not the order of the columns.  In the below example, axis is set to 1:<jupyter_code>x = np.arange(0, 25).reshape(5, 5)
x
y = rng.permutation(x, axis=1)
y<jupyter_output><empty_output><jupyter_text>As with the shuffle function, this results in a shuffling of the contents of the columns but the row order remaining the same.  3.2.2 Difference between Shuffle and Permutation Functions At first glance, the shuffle and permutation functions appear to perform similarly - both randomly rearrage a sequence or arrays. However, the key difference is that the shuffle function operates in-place, whereas the permutation function returns a copy[14](f14) The in-place operation of the shuffle function means that dataframe is overwritten with the output of the function, when it is performed, For example, a sequence of numbers 0 to 9 can be generated as the variable ```x```:<jupyter_code>x = np.arange(10)<jupyter_output><empty_output><jupyter_text>The output of ```x``` is shown as the sequence:<jupyter_code>x<jupyter_output><empty_output><jupyter_text>The shuffle function can then be performed:<jupyter_code>rng.shuffle(x)<jupyter_output><empty_output><jupyter_text>The variable ```x``` now contains the random sequence generated by the shuffle function:<jupyter_code>x<jupyter_output><empty_output><jupyter_text>The original sequence contained in ```x``` has now been overwritten and calling the variable ```x``` again will produce the same output as above and the original sequence is no longer stored:<jupyter_code>x<jupyter_output><empty_output><jupyter_text>Performing the shuffle function on ```x``` a further time would overwrite ```x``` again.  However, the permutation function operates differently and produces a copy of the original data. For example, taking the same sequence of 0 to 9:<jupyter_code>x = np.arange(10)
x<jupyter_output><empty_output><jupyter_text>The permutation function can be called on ```x``` to output a random sequence:<jupyter_code>rng.permutation(x)<jupyter_output><empty_output><jupyter_text>However, using the permutation function, the variable ```x``` contains the original data which has not been overwritten:<jupyter_code>x<jupyter_output><empty_output><jupyter_text>This could be useful if the original data is to be kept, but also a new sequence generated. For example:<jupyter_code>y = rng.permutation(x)
x, y<jupyter_output><empty_output><jupyter_text>3.3 Permuted The NumPy permutated function randomly permutes an array along a given axis. This function differs from the shuffle and permutation functions in that each slice of the array is shuffled independently of the others[15](f15). This can be demonstrated using a 3d-array, as in the following example:<jupyter_code>x = np.array([[[0, 1], [2, 3], [4, 5]], [[6, 7], [8, 9], [10, 11]], [[12, 13], [14, 15], [16, 17]], [[18, 19], [20, 21], [22,23]]])
x<jupyter_output><empty_output><jupyter_text>The above array has 4 lines in axis-0, 3 lines in axis-1, and 2 lines in axis-2[17](f17):<jupyter_code>x.shape<jupyter_output><empty_output><jupyter_text>The following image[16](f16) gives a representation of arrays in different dimensions; the 3d-array on the right is of the same 4, 3, 2 shape.  ![miro.medium.com](https://miro.medium.com/max/3000/0*fr4sfcnDc0KsXA8P.png) Calling the permuted function on the default axis-0 will randomly shuffle the whole array across all axis:<jupyter_code>rng.permuted(x)<jupyter_output><empty_output><jupyter_text>This differs from permutation or shuffle whereby the rows are shuffled along axis-0, but not along axis-1 or axis-2:<jupyter_code>rng.permutation(x, axis=0)<jupyter_output><empty_output><jupyter_text>Calling permutated or permunation on axis-1 results in a shuffling of the columns along axis-1; whilst the columns are shuffled within the rows, the row order itself is not changed.<jupyter_code>rng.permuted(x, axis=1)
rng.permutation(x, axis=1)<jupyter_output><empty_output><jupyter_text>Calling permutated on axis-2 results in a shuffling of axis-2 independently from the other axis:<jupyter_code>rng.permuted(x, axis=2)<jupyter_output><empty_output><jupyter_text>Whereas permutatation shuffles along axis-2 but is not idependent:<jupyter_code>rng.permutation(x, axis=2)<jupyter_output><empty_output><jupyter_text>By default, the permuted function returns a shuffled copy. The above examples have not overwritten ```x```:<jupyter_code>x<jupyter_output><empty_output><jupyter_text>The permuted function can be performed in-place through use of the out function. For example:<jupyter_code>y = rng.permuted(x, axis=0, out=x)
y
x<jupyter_output><empty_output><jupyter_text>4. Distributions  The ```numpy.random``` package contains a wide variety of distributions which can be applied to sequences generated by the Generator. Five distributions are discussed in this section: 1. Normal2. Uniform3. Weibull4. Binomial5. Multinomial These distributions are examples of probability distributions which are functions which give the probabilities of different outcomes[18](f18). The Probability Density Function (PDF) is a function that describes a continuous probability, i.e. the probability of all values in an array[19](f19). The more times experiments are run in NumPy, the more accurate the results.   4.1. Uniform Distribution A uniform distribution means that there is a constant probability of a value occurring within a given range[20](f20). The probability density of the uniform distribution can be written as[21](f21): *p(x) = 1/(b-a)*Where:*b* = the high point of range*a* = the low point of range For example, the following experiment has a range defined as between -1 and 1. The model is run 100 times and the results shown in a histogram:<jupyter_code>s = np.random.default_rng().uniform(-1,1,100)
s
plt.hist(s, bins = 15)<jupyter_output><empty_output><jupyter_text>Running the model 10,000 times provides more accurate results:<jupyter_code>s = np.random.default_rng().uniform(-1,1,10000)
plt.hist(s, bins = 15)<jupyter_output><empty_output><jupyter_text>The PDF for the uniform distribution shows that any number inside the specified range has a constant probability of being selected[22](f22):<jupyter_code>count, bins, ignored = plt.hist(s, 15, density=True)
plt.plot(bins, np.ones_like(bins), linewidth=2, color='r')
plt.show()<jupyter_output><empty_output><jupyter_text>Any values outside the range have a zero chance of being selected.  4.2. Normal Distribution Normal distribution, also known as Gaussian distribution or a Bell Curve, is a common type of probability distribution for independent, randomly generated variables[23](f23). A normal distribution is symmetrical around the central mean value with half of the values to the left of mean , and half to the right[24](f24), as reflected in the example below[25](f25): ![numpy.org](https://numpy.org/doc/stable/_images/numpy-random-Generator-normal-1_00_00.png) The y-axis is the PDF which defines the probability distribution for a random variable. In other words, it defines the likehood of an outcome occuring[26](f26). The x-axis is the standard deviation which is an indication of how much the data set deviates from the mean. The total area under the curve is the total probability of all the values that the variable can take; the total curve area is 100%[27](f27).  The formula for normal distribution can be written as: ![brittania.com](https://www.gstatic.com/education/formulas2/397133473/en/normal_distribution.svg) Where:*f(x)* is the probability density function*μ* (mu) is the mean*σ* (sigma) is the standard deviation In general:- approximately 68% of values are within 1 standard deviation of the mean- approximately 95% of values are within 2 standard deviations of the mean- approximately 99.7% of values are within 3 standard deviations of the mean[28](f28) Normal distributions appear naturally; NumPy can be used to help predict how the variables might occur in real-world situations and to help understanding the behaviour of the data set. For example, the height of population would closely follow a normal distribution. For example, NumPy can be used to provide data on expected deviations from the mean height of a population.  In this example the mean height and standard deviation in metres are defined in the variables mu (μ) and sigma (σ):<jupyter_code>mu, sigma = 1.79, 0.15<jupyter_output><empty_output><jupyter_text>The ```numpy.random.Generator.normal``` function is used to generate a number of samples, in this case 1000, in the normal distribution:<jupyter_code>s = np.random.default_rng().normal(mu, sigma, 1000)<jupyter_output><empty_output><jupyter_text>A histogram of the distribution is shown below, with height (metres) shown along the x-axis and PDF along the y-axis. In this example, the data is sorted into 30 'bins' with the range of data grouped into these bins.<jupyter_code>count, bins, ignored = plt.hist(s, 30)

plt.show()<jupyter_output><empty_output><jupyter_text>A line plot can be produced for the data using the formula for normal distribution:<jupyter_code>plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) *

               np.exp( - (bins - mu)**2 / (2 * sigma**2) ),

         linewidth=2, color='r')
plt.show()<jupyter_output><empty_output><jupyter_text>The two plots can be displayed together:<jupyter_code>count, bins, ignored = plt.hist(s, 30, density=True)

plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) *

               np.exp( - (bins - mu)**2 / (2 * sigma**2) ),

         linewidth=2, color='r')

plt.show()<jupyter_output><empty_output><jupyter_text>As can be seem, the majority of the values fall around the mean, with fewer values one, two, or three standard deviations from the mean.<jupyter_code>#print (bins)
#np.average(bins)<jupyter_output><empty_output><jupyter_text>4.3. Weibull Distribution The Weibull distribution is a probability distribution named after Swedish mathmatician Waloddi Weibull who originally proposed the distribution as a model for material breaking strength. However, the Weibull distribution can also fit a wide range of data from many fields[29](f29).  The probability density for the Weibull distribution can be written as[30](f30):  ![www.homeenergy.com](https://www.homerenergy.com/products/pro/docs/latest/images/equations_weibull-pdf.png) Where:*k* = the shape parameter*c* = the scale parameterThe shape factor affects the general shape of a distribution; the scale parameter stretches or squeezes a plot[31](f31) . In NumPy, the Weibull function can be used to generate random data in a Weibull distribution. The following example uses a shape parameter of 1 and generates 1000 samples:<jupyter_code>k = 1
s = rng.weibull(k,1000)
plt.hist(s)<jupyter_output><empty_output><jupyter_text>In this second example, a shape factor of 2.5 is used:<jupyter_code>k = 2.5
s = rng.weibull(k,1000)
plt.hist(s)<jupyter_output><empty_output><jupyter_text>And in this third example, a shape factor of 5 is used:<jupyter_code>k = 5
s = rng.weibull(k,1000)
plt.hist(s)<jupyter_output><empty_output><jupyter_text>As can be seen, the shape factor affects the shape of the data. This can be applied to real-world situations. For example, the Weibull distribution is often used to characterise wind speed regimes, as it has been found to provide a good fit with measured wind data[32](f32). For example, the Weibull formula can be defined in a function within Python:<jupyter_code>def weib(v, c, k):

 return (k / c) * (v / c)**(k - 1) * np.exp(-(v / c)**k)<jupyter_output><empty_output><jupyter_text>A range of data, representing wind speeds at a certain location from 0 m/s to 25 m/s in 0.25 m/s increments can be generated:<jupyter_code>v = np.arange(0,101)/4
v<jupyter_output><empty_output><jupyter_text>The probability of a certain wind speed occurring at that particular location can then be represented in a plot. The following plot represents 4 different ```k``` parameters:<jupyter_code>scale = count.max()/weib(v, 1, 1).max()

plt.xlim(0, 15)
plt.ylim(0, 1)

plt.plot(v, weib(v, 4, 1.5)*scale)
plt.plot(v, weib(v, 4, 2)*scale)
plt.plot(v, weib(v, 4, 2.5)*scale)
plt.plot(v, weib(v, 4, 3)*scale)
plt.legend(["k = 1.5", "k = 2", "k = 2.5", "k = 3"])
plt.xlabel("Wind Speed (m/s)")
plt.ylabel("PDF")


plt.show()<jupyter_output><empty_output><jupyter_text>A higher value of ```k```, such as 3, indicates a site where the variation of hourly mean wind speedabout the annual mean is small; a lower value of ```k```, such as 1.5  indicates greater variability about the mean.  4.4. Binomial Distibution A Binomial distribution can be thought of as the probability of the success or failure of a certain outcome[33](f33). The probability density for the binomial distribution can be written as: ![gstatic.com](https://www.gstatic.com/education/formulas2/397133473/en/binomial_distribution_formula.svg) Where:*n* = the number of trials*p* = the probability of success*x* = the number of successes An example of the use of binomial distribution is when tossing a coin. The result of a coin toss can either be heads or tails; there is a 50% chance of heads and a 50% chance of tails.  Using the random number generator, the toss of a single coin can be simulated 100 times, for example:<jupyter_code>n = 1
p = 0.5  
s = rng.binomial(n, p, 100)
s<jupyter_output><empty_output><jupyter_text>A histogram can be plotted showing the results. In this example, 0 represents heads and 1 represents tails.<jupyter_code>bins = np.arange(3) - 0.5
plt.hist(s, bins)
plt.xticks(range(2))<jupyter_output><empty_output><jupyter_text>The number of heads and tails tossed can be calculated, with the total equalling 100 tosses:<jupyter_code>sum(s == 0)
sum (s == 1)<jupyter_output><empty_output><jupyter_text>The binomial distribution function in NumPy can also be used to predict the probability of an event occuring. For example, if four individuals have their own standard deck of shuffled playing cards (excluding jokers) each person has a 25% chance of choosing a diamond from the deck, if they pick a single card. Running the function 10,000 times (for example) can provide a model of how many of the four people choose a diamond:<jupyter_code>s = rng.binomial(4,0.25,10000)<jupyter_output><empty_output><jupyter_text>A histogram will provide a graphical representation of how many of the individuals all chose a diamond:<jupyter_code>bins = np.arange(6) - 0.5
plt.hist(s, bins)
plt.xticks(range(5))<jupyter_output><empty_output><jupyter_text>The following code results in a description of the chances of the individuals drawing a diamond:<jupyter_code>a = sum(s == 0)/100
b = sum(s == 1)/100
c = sum(s == 2)/100
d = sum(s == 3)/100
e = sum(s == 4)/100
print ("There is approximately a {}% chance of no individuals drawing a diamond.".format(a))
print ("There is approximately a {}% chance of one of the four individuals drawing a diamond.".format(b))
print ("There is approximately a {}% chance of two of them drawing a diamond.".format(c))
print ("There is approximately a {}% chance of three of them drawing a diamond.".format(d))
print ("There is approximately a {}% chance of all four individuals drawing a diamond.".format(e))<jupyter_output>There is approximately a 31.99% chance of no individuals drawing a diamond.
There is approximately a 41.22% chance of one of the four individuals drawing a diamond.
There is approximately a 21.59% chance of two of them drawing a diamond.
There is approximately a 4.85% chance of three of them drawing a diamond.
There is approximately a 0.35% chance of all four individuals drawing a diamond.<jupyter_text>In a similar way, the binomial distribution could be used to predict how many of the four individuals would draw an ace of diamonds, if there was a 1 in 52 chance for each of them:<jupyter_code>s = rng.binomial(4,1/52,10000)
bins = np.arange(6) - 0.5
plt.hist(s, bins)
plt.xticks(range(5))

a = sum(s == 0)/100
b = sum(s == 1)/100
c = sum(s == 2)/100
d = sum(s == 3)/100
e = sum(s == 4)/100
print ("There is approximately a {}% chance of no individuals drawing an ace of diamonds.".format(a))
print ("There is approximately a {}% chance of one of the four individuals drawing an ace of diamonds.".format(b))
print ("There is approximately a {}% chance of two of them drawing an ace of diamonds.".format(c))
print ("There is approximately a {}% chance of three of them drawing an ace of diamonds. ".format(d))
print ("There is approximately a {}% chance of all four individuals drawing an ace of diamonds.".format(e))<jupyter_output>There is approximately a 92.58% chance of no individuals drawing an ace of diamonds.
There is approximately a 7.21% chance of one of the four individuals drawing an ace of diamonds.
There is approximately a 0.21% chance of two of them drawing an ace of diamonds.
There is approximately a 0.0% chance of three of them drawing an ace of diamonds. 
There is approximately a 0.0% chance of all four individuals drawing an ace of diamonds.<jupyter_text>It should be noted that the data produced by NumPy in these instances are representative and can be used to model potential outcomes. As can be seen in the above example, the model has predicted a 0.0% chance for three of the four, or all four individuals, drawing an ace of diamonds. In reality, of course, there is a chance of this happening. A larger size of model might produce results which reflect this.   4.5. Multinomial Distribution The multinomial distribution arises from an extension of the binomial distribution and is used to find probabilities in experiments where there are more than two outcomes[34](f34) . Multinomial experiements consist of repeated trials (e.g. rolling a die multiple times instead of just once) and each trial must be independent of the others (i.e. the outcome of a the roll of of die does not affect the others)[35](f35). The probability density can be written as[36](f36): ![statisticshow.com](https://www.statisticshowto.com/wp-content/uploads/2015/05/multinomial-formula-2.png) Where:     *n* = number of events *n1* = number of outcomes, event 1*n2* = number of outcomes, event 2*nx* = number of outcomes, event 2*p1* = probability event 1 happens*p2* = probability event 2 happens*px* = probability event x happens An example of multinomial distribution is in modelling how many times a card of each of the four suits in a standard deck of 52 playing cards is drawn:<jupyter_code>s = rng.multinomial(20000, [1/4]*4, size=1)
s<jupyter_output><empty_output><jupyter_text>In the above example, which is run 20,000 times, it can be seen that:<jupyter_code>print ("A card from the first suit (e.g. diamonds) was drawn {} times.".format(s[0, 0]))
print ("A card from the first suit (e.g. hearts) was drawn {} times.".format(s[0, 1]))
print ("A card from the first suit (e.g. clubs) was drawn {} times.".format(s[0, 2]))
print ("A card from the first suit (e.g. spades) was drawn {} times.".format(s[0, 3]))
print ("A total of {} cards were drawn.".format(np.sum(s)))
print ("The mean is {}.".format(np.average(s)))<jupyter_output>A card from the first suit (e.g. diamonds) was drawn 4894 times.
A card from the first suit (e.g. hearts) was drawn 5014 times.
A card from the first suit (e.g. clubs) was drawn 5077 times.
A card from the first suit (e.g. spades) was drawn 5015 times.
A total of 20000 cards were drawn.
The mean is 5000.0.<jupyter_text>The number of times the experiment is performed can be set using the size parameter. For example, to run the above experiment five times:<jupyter_code>rng.multinomial(20000, [1/4]*4, size=5)<jupyter_output><empty_output><jupyter_text>Bias can also be taken into account. For example, the results of an experiment to model how many times each of the 52 cards in a standard shuffled deck of playing cards is drawn at random are shown below:<jupyter_code>x = rng.multinomial(20000, [1/52]*52, size=1)
x
a = np.ndarray.min(x)
b = np.ndarray.max(x)
c = x.size
print ("As can be seen, there are {} results with each card being drawn between {} and {} times".format(c,a,b))<jupyter_output>As can be seen, there are 52 results with each card being drawn between 332 and 435 times<jupyter_text>In a standard shuffled deck of playing cards including two jokers, there is a 1 in 54 chance of choosing any individual card. However, as there are two jokers, the chances of choosing a joker is greater than any other individual card:<jupyter_code>x = rng.multinomial(20000, [1/54]*53, size=1)
x
y = x[0,:52]
z = x[0,52]
a = np.ndarray.min(y)
b = np.ndarray.max(y)
c = x.size
d = y.size
print ("As can be seen, there are {} results with each of the {} suited cards being drawn between {} and {} times. ".format(c,d,a,b))
print ("However, a joker was drawn {} times. ".format(z))<jupyter_output>As can be seen, there are 53 results with each of the 52 suited cards being drawn between 328 and 410 times. 
However, a joker was drawn 760 times.<jupyter_text>5. Seeds Almost all random-number generation on computers is done using algorithms to produce numbers that approximate the properties of random numbers[37](f37); the numbers generated are not completely random and are known as "pseudo-random numbers". Most commonly - and as in the ```numpy.random``` package - random-number generation algorithms use a seed to derive the initial state with a sequence of statistcally random numbers generated from this known starting point; the use of a known seed means that the the algorithm is repeatable and if the seed is known to an individual, the sequence of numbers is predictable.   In computers, hardware is used to harvest entropy; for example movement from the mouse pointer or keys typed on the keyboard[38](f38). Such sources of data are likely to be difficult to predict or replicate. By default, if no seed is specified, the random number generator will use a seed which is generated using entropy from the computer's operating system[39](f39).  The below code generates random numbers and displays them in a bar plot without specifying a seed:<jupyter_code>x = rng.integers(10, size=100)
plt.hist(x)
plt.show()
x<jupyter_output><empty_output><jupyter_text>Reproducing the code (without specifying a seed) generates different numbers:<jupyter_code>x = rng.integers(10, size=100)
plt.hist(x)
plt.show()
x<jupyter_output><empty_output><jupyter_text>The below two plots demonstrate the use of a seed which is given in the ```rng = np.random.default_rng(12345)``` line of code as (12345)<jupyter_code>rng = np.random.default_rng(12345)
x = rng.integers(10, size=100)
plt.hist(x)
plt.show()
x
rng = np.random.default_rng(12345)
x = rng.integers(10, size=100)
plt.hist(x)
plt.show()
x<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09536439809512327, 'fraction_numerical': 0.028030622701790343, 'mean_word_length': 5.472688255950058, 'pattern_counts': {'":': 0, '<': 310, '<?xml version=': 0, '>': 310, 'https://': 8, 'lorem ipsum': 0, 'www.': 5, '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': '78', 'dir': 'jupyter-structured-clean-dedup', 'id': '360009', 'license': 'MIT', 'n_tokens_mistral': 10756, 'n_tokens_neox': 9910, 'n_words': 4165, 'path': 'numpy-random.ipynb', 'repo_name': 'AndyWalker81/ProgrammingForDataAnalysis_Assessment', 'size': '33195'}