Statistical Thinking in Python (Part 1)
Building the foundation you need to think statistically, speak the language of your data, and understand what your data is telling you.
 Graphical exploratory data analysis
 Libraries
 Quantitative exploratory data analysis
 Thinking probabilistically Discrete variables
 Probabilistic logic and statistical inference
 Random number generators and hacker statistics
 Probability distributions and stories: The Binomial distribution
 Probability mass function (PMF)
 Probability distribution
 Discrete Uniform distribution:the story The outcome of rolling a single fair die is Discrete Uniformly distributed.
 Binomial distribution:the story The number $r$ of successes in $n$ Bernoulli trials with
 Sampling out of the Binomial distribution
 Plotting the Binomial PMF
 Poisson processes and the Poisson distribution
 Thinking probabilistically Continuous variables
Graphical exploratory data analysis
Before diving into sophisticated statistical inference techniques, we should first explore our data by plotting them and computing simple summary statistics. This process, called exploratory data analysis, is a crucial first step in statistical analysis of data.
Introduction to Exploratory Data Analysis
Exploratory Data Analysis is the process of organizing, plo!ing, and summarizing a data set
“Exploratory data analysis can never be the whole story, but nothing else can serve as the foundation stone. ” > ~ John Tukey
Tukey's comments on EDA
 Exploratory data analysis is detective work.
 There is no excuse for failing to plot and look.
 The greatest value of a picture is that it forces us to notice what we never expected to see.
 It is important to understand what you can do before you learn how to measure how well you seem to have done it.
If you don't have time to do EDA, you really don't have time to do hypothesis tests. And you should always do EDA first.
Advantages of graphical EDA
 It often involves converting tabular data into graphical form.
 If done well, graphical representations can allow for more rapid interpretation of data.
 There is no excuse for neglecting to do graphical EDA.
While a good, informative plot can sometimes be the end point of an analysis, it is more like a beginning:it helps guide you in the quantitative statistical analyses that come next.
Plotting a histogram
Plotting a histogram of iris data
We will use a classic data set collected by botanist Edward Anderson and made famous by Ronald Fisher, one of the most prolific statisticians in history. Anderson carefully measured the anatomical properties of samples of three different species of iris, Iris setosa, Iris versicolor, and Iris virginica. The full data set is available as part of scikitlearn. Here, you will work with his measurements of petal length.
We will plot a histogram of the petal lengths of his 50 samples of Iris versicolor using matplotlib/seaborn's default settings.
The subset of the data set containing the Iris versicolor petal lengths in units of centimeters (cm) is stored in the NumPy array versicolor_petal_length
.
Libraries
# Import plotting modules
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
# Set default Seaborn style
sns.set()
%matplotlib inline
versicolor_petal_length = np.array([4.7, 4.5, 4.9, 4. , 4.6, 4.5, 4.7, 3.3, 4.6, 3.9, 3.5, 4.2, 4. ,
4.7, 3.6, 4.4, 4.5, 4.1, 4.5, 3.9, 4.8, 4. , 4.9, 4.7, 4.3, 4.4,
4.8, 5. , 4.5, 3.5, 3.8, 3.7, 3.9, 5.1, 4.5, 4.5, 4.7, 4.4, 4.1,
4. , 4.4, 4.6, 4. , 3.3, 4.2, 4.2, 4.2, 4.3, 3. , 4.1])
# Plot histogram of versicolor petal lengths
plt.hist(versicolor_petal_length)
plt.ylabel("count")
plt.xlabel("petal length (cm)")
plt.show()
Adjusting the number of bins in a histogram
The histogram we just made had ten bins. This is the default of matplotlib.
bins
keyword argument of plt.hist()
.
# Compute number of data points: n_data
n_data = len(versicolor_petal_length)
# Number of bins is the square root of number of data points: n_bins
n_bins = np.sqrt(n_data)
# Convert number of bins to integer: n_bins
n_bins = int(n_bins)
# Plot the histogram
_ = plt.hist(versicolor_petal_length, bins=n_bins)
# Label axes
_ = plt.xlabel('petal length (cm)')
_ = plt.ylabel('count')
# Show histogram
plt.show()
iris_petal_lengths = pd.read_csv("../datasets/iris_petal_lengths.csv")
iris_petal_lengths.head()
iris_petal_lengths.shape
iris_petal_lengths.tail()
# Create bee swarm plot with Seaborn's default settings
_ = sns.swarmplot(data=iris_petal_lengths, x="species", y="petal length (cm)")
# Label the axes
_ = plt.xlabel("species")
_ = plt.ylabel("petal length (cm)")
# Show the plot
plt.show()
Interpreting a bee swarm plot

I. virginica petals tend to be the longest, and I. setosa petals tend to be the shortest of the three species.
Note: Notice that we said "tend to be." Some individual I. virginica flowers may be shorter than individual I. versicolor flowers. It is also possible that an individual I. setosa flower may have longer petals than in individual I. versicolor flower, though this is highly unlikely, and was not observed by Anderson.
def ecdf(data):
"""Compute ECDF for a onedimensional array of measurements."""
# Number of data points: n
n = len(data)
# xdata for the ECDF: x
x = np.sort(data)
# ydata for the ECDF: y
y = np.arange(1, n+1) / n
return x, y
Plotting the ECDF
We will now use ecdf()
function to compute the ECDF for the petal lengths of Anderson's Iris versicolor flowers. We will then plot the ECDF.
ecdf()
function returns two arrays so we will need to unpack them. An example of such unpacking is x, y = foo(data)
, for some function foo()
.
# Compute ECDF for versicolor data: x_vers, y_vers
x_vers, y_vers = ecdf(versicolor_petal_length)
# Generate plot
_ = plt.plot(x_vers, y_vers, marker=".", linestyle="none")
# Label the axes
_ = plt.xlabel("versicolor petal length, (cm)")
_ = plt.ylabel("ECDF")
# Display the plot
plt.show()
setosa_petal_length = iris_petal_lengths["petal length (cm)"][iris_petal_lengths.species == "setosa"]
versicolor_petal_length = iris_petal_lengths["petal length (cm)"][iris_petal_lengths.species == "versicolor"]
virginica_petal_length = iris_petal_lengths["petal length (cm)"][iris_petal_lengths.species == "virginica"]
setosa_petal_length.head()
# Compute ECDFs
x_set, y_set = ecdf(setosa_petal_length)
x_vers, y_vers = ecdf(versicolor_petal_length)
x_virg, y_virg = ecdf(virginica_petal_length)
# Plot all ECDFs on the same plot
_ = plt.plot(x_set, y_set, marker=".", linestyle="none")
_ = plt.plot(x_vers, y_vers, marker=".", linestyle="none")
_ = plt.plot(x_virg, y_virg, marker=".", linestyle="none")
# Annotate the plot
plt.legend(('setosa', 'versicolor', 'virginica'), loc='lower right')
_ = plt.xlabel('petal length (cm)')
_ = plt.ylabel('ECDF')
# Display the plot
plt.show()
Onward toward the whole story!
Quantitative exploratory data analysis
We will compute useful summary statistics, which serve to concisely describe salient features of a dataset with a few numbers.
$$ mean = \bar{x} = \frac{1}{n} \sum_{i=1}^{n} x_i $$ Introduction to summary statistics: The sample mean and median
Outliers
● Data points whose value is far greater or less than most of the rest of the data
The median
● The middle value of a data set
Note:An outlier can significantly affect the value of the mean, but not the median
# Compute the mean: mean_length_vers
mean_length_vers = np.mean(versicolor_petal_length)
# Print the result with some nice formatting
print('I. versicolor:', mean_length_vers, 'cm')
# Specify array of percentiles: percentiles
percentiles = np.array([2.5, 25, 50, 75, 97.5])
# Compute percentiles: ptiles_vers
ptiles_vers = np.percentile(versicolor_petal_length, percentiles)
# Print the result
ptiles_vers
# Plot the ECDF
_ = plt.plot(x_vers, y_vers, '.')
_ = plt.xlabel('petal length (cm)')
_ = plt.ylabel('ECDF')
# Overlay percentiles as red diamonds.
_ = plt.plot(ptiles_vers, percentiles/100, marker='D', color='red',
linestyle="none")
# Show the plot
plt.show()
# Create box plot with Seaborn's default settings
_ = sns.boxplot(data=iris_petal_lengths, x="species", y="petal length (cm)")
# Label the axes
_ = plt.xlabel("species")
_ = plt.ylabel("petal length (cm)")
# Show the plot
plt.show()
Variance and standard deviation
Variance
● The mean squared distance of the data from their mean
Tip:Variance; nformally, a measure of the spread of data> $$ variance = \frac{1}{n} \sum_{i=1}^{n} (x_i  \bar{x})^2 $$
$$ std = \sqrt {\frac{1}{n} \sum_{i=1}^{n} (x_i  \bar{x})^2} $$ standard Deviation
Computing the variance
we will explicitly compute the variance of the petal length of Iris veriscolor, we will then use np.var()
to compute it.
# Array of differences to mean: differences
differences = versicolor_petal_lengthnp.mean(versicolor_petal_length)
# Square the differences: diff_sq
diff_sq = differences**2
# Compute the mean square difference: variance_explicit
variance_explicit = np.mean(diff_sq)
# Compute the variance using NumPy: variance_np
variance_np = np.var(versicolor_petal_length)
# Print the results
print(variance_explicit, variance_np)
# Compute the variance: variance
variance = np.var(versicolor_petal_length)
# Print the square root of the variance
print(np.sqrt(variance))
# Print the standard deviation
print(np.std(versicolor_petal_length))
Covariance and the Pearson correlation coefficient
Covariance
● A measure of how two quantities vary together $$ covariance = \frac{1}{n} \sum_{i=1}^{n} (x_i\ \bar{x})\ (y_i \  \bar{y}) $$
$$ \rho = Pearson\ correlation = \frac{covariance}{(std\ of\ x)\ (std\ of\ y)} = \frac{variability\ due\ to\ codependence}{independent variability} $$ Pearson correlation coefficient
Scatter plots
When we made bee swarm plots, box plots, and ECDF plots in previous exercises, we compared the petal lengths of different species of iris. But what if we want to compare two properties of a single species? This is exactly what we will do, we will make a scatter plot of the petal length and width measurements of Anderson's Iris versicolor flowers.
Important:If the flower scales (that is, it preserves its proportion as it grows), we would expect the length and width to be correlated.
versicolor_petal_width = np.array([1.4, 1.5, 1.5, 1.3, 1.5, 1.3, 1.6, 1. , 1.3, 1.4, 1. , 1.5, 1. ,
1.4, 1.3, 1.4, 1.5, 1. , 1.5, 1.1, 1.8, 1.3, 1.5, 1.2, 1.3, 1.4,
1.4, 1.7, 1.5, 1. , 1.1, 1. , 1.2, 1.6, 1.5, 1.6, 1.5, 1.3, 1.3,
1.3, 1.2, 1.4, 1.2, 1. , 1.3, 1.2, 1.3, 1.3, 1.1, 1.3])
# Make a scatter plot
_ = plt.plot(versicolor_petal_length, versicolor_petal_width, marker=".", linestyle="none")
# Label the axes
_ = plt.xlabel("petal length, (cm)")
_ = plt.ylabel("petal length, (cm)")
# Show the result
plt.show()
Computing the covariance
The covariance may be computed using the Numpy function np.cov()
. For example, we have two sets of data $x$ and $y$, np.cov(x, y)
returns a 2D array where entries [0,1
] and [1,0]
are the covariances. Entry [0,0]
is the variance of the data in x
, and entry [1,1]
is the variance of the data in y
. This 2D output array is called the covariance matrix, since it organizes the self and covariance.
# Compute the covariance matrix: covariance_matrix
covariance_matrix = np.cov(versicolor_petal_length, versicolor_petal_width)
# Print covariance matrix
print(covariance_matrix)
# Extract covariance of length and width of petals: petal_cov
petal_cov = covariance_matrix[0,1]
# Print the length/width covariance
print(petal_cov)
Computing the Pearson correlation coefficient
the Pearson correlation coefficient, also called the Pearson r, is often easier to interpret than the covariance. It is computed using the np.corrcoef()
function. Like np.cov(
), it takes two arrays as arguments and returns a 2D array. Entries [0,0]
and [1,1]
are necessarily equal to 1
, and the value we are after is entry [0,1]
.
We will write a function, pearson_r(x, y)
that takes in two arrays and returns the Pearson correlation coefficient. We will then use this function to compute it for the petal lengths and widths of $I.\ versicolor$.
def pearson_r(x, y):
"""Compute Pearson correlation coefficient between two arrays."""
# Compute correlation matrix: corr_mat
corr_mat = np.corrcoef(x,y)
# Return entry [0,1]
return corr_mat[0,1]
# Compute Pearson correlation coefficient for I. versicolor: r
r = pearson_r(versicolor_petal_length, versicolor_petal_width)
# Print the result
print(r)
Thinking probabilistically Discrete variables
Statistical inference rests upon probability. Because we can very rarely say anything meaningful with absolute certainty from data, we use probabilistic language to make quantitative statements about data. We will think probabilistically about discrete quantities: those that can only take certain values, like integers.
Probabilistic logic and statistical inference
the goal of statistical inference
 To draw probabilistic conclusions about what we might expect if we collected the same data again.
 To draw actionable conclusions from data.
 To draw more general conclusions from relatively few data or observations.
Note: Statistical inference involves taking your data to probabilistic conclusions about what you would expect if you took even more data, and you can make decisions based on these conclusions.
Why we use the probabilistic language in statistical inference
 Probability provides a measure of uncertainty and this is crucial because we can quantify what we might expect if the data were acquired again.
 Data are almost never exactly the same when acquired again, and probability allows us to say how much we expect them to vary. We need probability to say how data might vary if acquired again.
Note: Probabilistic language is in fact very precise. It precisely describes uncertainty.
Random number generators and hacker statistics
Hacker statistics
 Uses simulated repeated measurements to compute probabilities.
The np.random module
 Suite of functions based on random number generation
np.random.random()
:draw a number between $0$ and $1$ ### Bernoulli trial ● An experiment that has two options, "success" (True) and "failure" (False).Random number seed
 Integer fed into random number generating algorithm
 Manually seed random number generator if you need reproducibility
 Specified using
np.random.seed()
Hacker stats probabilities
 Determine how to simulate data
 Simulate many many times
 Probability is approximately fraction of trials with the outcome of interest
Generating random numbers using the np.random module
we'll generate lots of random numbers between zero and one, and then plot a histogram of the results. If the numbers are truly random, all bars in the histogram should be of (close to) equal height.
# Seed the random number generator
np.random.seed(42)
# Initialize random numbers: random_numbers
random_numbers = np.empty(100000)
# Generate random numbers by looping over range(100000)
for i in range(100000):
random_numbers[i] = np.random.random()
# Plot a histogram
_ = plt.hist(random_numbers, bins=316, histtype="step", density=True)
_ = plt.xlabel("random numbers")
_ = plt.ylabel("counts")
# Show the plot
plt.show()
The np.random module and Bernoulli trials
n
Bernoulli trials, perform_bernoulli_trials(n, p)
, which returns the number of successes out of n
Bernoulli trials, each of which has probability $p$ of success. To perform each Bernoulli trial, we will use the np.random.random()
function, which returns a random number between zero and one.
def perform_bernoulli_trials(n, p):
"""Perform n Bernoulli trials with success probability p
and return number of successes."""
# Initialize number of successes: n_success
n_success = False
# Perform trials
for i in range(n):
# Choose random number between zero and one: random_number
random_number = np.random.random()
# If less than p, it's a success so add one to n_success
if random_number < p:
n_success += 1
return n_success
How many defaults might we expect?
Let's say a bank made 100 mortgage loans. It is possible that anywhere between $0$ and $100$ of the loans will be defaulted upon. We would like to know the probability of getting a given number of defaults, given that the probability of a default is $p = 0.05$. To investigate this, we will do a simulation. We will perform 100 Bernoulli trials using the perform_bernoulli_trials()
function and record how many defaults we get. Here, a success is a default.
# Seed random number generator
np.random.seed(42)
# Initialize the number of defaults: n_defaults
n_defaults = np.empty(1000)
# Compute the number of defaults
for i in range(1000):
n_defaults[i] = perform_bernoulli_trials(100, 0.05)
# Plot the histogram with default number of bins; label your axes
_ = plt.hist(n_defaults, density=True)
_ = plt.xlabel('number of defaults out of 100 loans')
_ = plt.ylabel('probability')
# Show the plot
plt.show()
Will the bank fail?
If interest rates are such that the bank will lose money if 10 or more of its loans are defaulted upon, what is the probability that the bank will lose money?
# Compute ECDF: x, y
x,y = ecdf(n_defaults)
# Plot the ECDF with labeled axes
_ = plt.plot(x,y, marker=".", linestyle="none")
_ = plt.xlabel("number of defaults")
_ = plt.ylabel("ECDF")
# Show the plot
plt.show()
# Compute the number of 100loan simulations with 10 or more defaults: n_lose_money
n_lose_money = np.sum(n_defaults >= 10)
# Compute and print probability of losing money
print('Probability of losing money =', n_lose_money / len(n_defaults))
Probability distributions and stories: The Binomial distribution
Probability mass function (PMF)
 The set of probabilities of discrete outcomes
Probability distribution
 A mathematical description of outcomes
Discrete Uniform distribution:the story The outcome of rolling a single fair die is Discrete Uniformly distributed.
Binomial distribution:the story The number $r$ of successes in $n$ Bernoulli trials with
probability $p$ of success, is Binomially distributed
 The number $r$ of heads in $4$ coin flips with probability $0.5$ of heads, is Binomially distributed
Sampling out of the Binomial distribution
We will compute the probability mass function for the number of defaults we would expect for $100$ loans as in the last section, but instead of simulating all of the Bernoulli trials, we will perform the sampling using np.random.binomial()
^{1}.
perform_bernoulli_trials()
function, but far more computationally efficient.
# Take 10,000 samples out of the binomial distribution: n_defaults
n_defaults = np.random.binomial(100, 0.05, size=10000)
# Compute CDF: x, y
x,y = ecdf(n_defaults)
# Plot the CDF with axis labels
_ = plt.plot(x,y, marker=".", linestyle="")
_ = plt.xlabel("number of defaults out of 100 loans")
_ = plt.ylabel("CDF")
# Show the plot
plt.show()
Plotting the Binomial PMF
bins
to pass to plt.hist()
via the bins
keyword argument. We want the bins centered on the integers. So, the edges of the bins should be $0.5, 0.5, 1.5, 2.5, ...$ up to max(n_defaults) + 1.5
. We can generate an array like this using np.arange()
and then subtracting 0.5
from the array.
# Compute bin edges: bins
bins = np.arange(0, max(n_defaults) + 1.5)  0.5
# Generate histogram
_ = plt.hist(n_defaults, density=True, bins=bins)
# Label axes
_ = plt.xlabel("number of defaults out of 100 loans")
_ = plt.ylabel("probability")
# Show the plot
plt.show()
Poisson processes and the Poisson distribution
Poisson process
 The timing of the next event is completely independent of when the previous event happened
Examples of Poisson processes
 Natural births in a given hospital
 Hit on a website during a given hour
 Meteor strikes
 Molecular collisions in a gas
 Aviation incidents
 Buses in Poissonville
Poisson distribution
 The number $r$ of arrivals of a Poisson process in a given time interval with average rate of $λ$ arrivals per interval is Poisson distributed.
 The number r of hits on a website in one hour with an average hit rate of 6 hits per hour is Poisson distributed.
Poisson Distribution
 Limit of the Binomial distribution for low probability of success and large number of trials.
 That is, for rare events.
Relationship between Binomial and Poisson distributions
Important:Poisson distribution is a limit of the Binomial distribution for rare events.
Tip: Poisson distribution with arrival rate equal to $np$ approximates a Binomial distribution for $n$ Bernoulli trials with probability $p$ of success (with $n$ large and $p$ small). Importantly, the Poisson distribution is often simpler to work with because it has only one parameter instead of two for the Binomial distribution.Let's explore these two distributions computationally. We will compute the mean and standard deviation of samples from a Poisson distribution with an arrival rate of $10$. Then, we will compute the mean and standard deviation of samples from a Binomial distribution with parameters $n$ and $p$ such that $np = 10$.
# Draw 10,000 samples out of Poisson distribution: samples_poisson
samples_poisson = np.random.poisson(10, size=10000)
# Print the mean and standard deviation
print('Poisson: ', np.mean(samples_poisson),
np.std(samples_poisson))
# Specify values of n and p to consider for Binomial: n, p
n = [20, 100, 1000]
p = [.5, .1, .01]
# Draw 10,000 samples for each n,p pair: samples_binomial
for i in range(3):
samples_binomial = np.random.binomial(n[i],p[i], size=10000)
# Print results
print('n =', n[i], 'Binom:', np.mean(samples_binomial),
np.std(samples_binomial))
Was 2015 anomalous?
In baseball, a nohitter is a game in which a pitcher does not allow the other team to get a hit. This is a rare event, and since the beginning of the socalled modern era of baseball (starting in 1901), there have only been 251 of them through the 2015 season in over 200,000 games. The ECDF of the number of nohitters in a season is shown to the right. The probability distribution that would be appropriate to describe the number of nohitters we would expect in a given season? is Both Binomial and Poisson, though Poisson is easier to model and compute.
# Draw 10,000 samples out of Poisson distribution: n_nohitters
n_nohitters = np.random.poisson(251/115, size=10000)
# Compute number of samples that are seven or greater: n_large
n_large = np.sum(n_nohitters >= 7)
# Compute probability of getting seven or more: p_large
p_large = n_large/10000
# Print the result
print('Probability of seven or more nohitters:', p_large)
Thinking probabilistically Continuous variables
It’s time to move onto continuous variables, such as those that can take on any fractional value. Many of the principles are the same, but there are some subtleties. We will be speaking the probabilistic language needed to launch into the inference techniques.
Probability density functions
Continuous variables
 Quantities that can take any value, not just discrete values
Probability density function (PDF)
 Continuous analog to the PMF
 Mathematical description of the relative likelihood of observing a value of a continuous variable
Introduction to the Normal distribution
Normal distribution
 Describes a continuous variable whose PDF has a single symmetric peak.
Parameter Calculated from data mean of a Normal distribution ≠ mean computed from data st. dev. of a Normal distribution ≠ standard deviation computed from data
The Normal PDF
# Draw 100000 samples from Normal distribution with stds of interest: samples_std1, samples_std3, samples_std10
samples_std1 = np.random.normal(20,1,size=100000)
samples_std3 = np.random.normal(20, 3, size=100000)
samples_std10 = np.random.normal(20, 10, size=100000)
# Make histograms
_ = plt.hist(samples_std1, density=True, histtype="step", bins=100)
_ = plt.hist(samples_std3, density=True, histtype="step", bins=100)
_ = plt.hist(samples_std10, density=True, histtype="step", bins=100)
# Make a legend, set limits and show plot
_ = plt.legend(('std = 1', 'std = 3', 'std = 10'))
plt.ylim(0.01, 0.42)
plt.show()
# Generate CDFs
x_std1, y_std1 = ecdf(samples_std1)
x_std3, y_std3 = ecdf(samples_std3)
x_std10, y_std10 = ecdf(samples_std10)
# Plot CDFs
_ = plt.plot(x_std1, y_std1, marker=".", linestyle="none")
_ = plt.plot(x_std3, y_std3, marker=".", linestyle="none")
_ = plt.plot(x_std10, y_std10, marker=".", linestyle="none")
# Make a legend and show the plot
_ = plt.legend(('std = 1', 'std = 3', 'std = 10'), loc='lower right')
plt.show()
The Normal distribution: Properties and warnings
Are the Belmont Stakes results Normally distributed?
Since 1926, the Belmont Stakes is a $1.5$ milelong race of 3year old thoroughbred horses. Secretariat ran the fastest Belmont Stakes in history in $1973$. While that was the fastest year, 1970 was the slowest because of unusually wet and sloppy conditions. With these two outliers removed from the data set, we will compute the mean and standard deviation of the Belmont winners' times. We will sample out of a Normal distribution with this mean and standard deviation using the np.random.normal()
function and plot a CDF. Overlay the ECDF from the winning Belmont times ^{2}.
belmont_no_outliers = np.array([148.51, 146.65, 148.52, 150.7 , 150.42, 150.88, 151.57, 147.54,
149.65, 148.74, 147.86, 148.75, 147.5 , 148.26, 149.71, 146.56,
151.19, 147.88, 149.16, 148.82, 148.96, 152.02, 146.82, 149.97,
146.13, 148.1 , 147.2 , 146. , 146.4 , 148.2 , 149.8 , 147. ,
147.2 , 147.8 , 148.2 , 149. , 149.8 , 148.6 , 146.8 , 149.6 ,
149. , 148.2 , 149.2 , 148. , 150.4 , 148.8 , 147.2 , 148.8 ,
149.6 , 148.4 , 148.4 , 150.2 , 148.8 , 149.2 , 149.2 , 148.4 ,
150.2 , 146.6 , 149.8 , 149. , 150.8 , 148.6 , 150.2 , 149. ,
148.6 , 150.2 , 148.2 , 149.4 , 150.8 , 150.2 , 152.2 , 148.2 ,
149.2 , 151. , 149.6 , 149.6 , 149.4 , 148.6 , 150. , 150.6 ,
149.2 , 152.6 , 152.8 , 149.6 , 151.6 , 152.8 , 153.2 , 152.4 ,
152.2 ])
# Compute mean and standard deviation: mu, sigma
mu = np.mean(belmont_no_outliers)
sigma = np.std(belmont_no_outliers)
# Sample out of a normal distribution with this mu and sigma: samples
samples = np.random.normal(mu, sigma, size=10000)
# Get the CDF of the samples and of the data
x_theor, y_theor = ecdf(samples)
x,y = ecdf(belmont_no_outliers)
# Plot the CDFs and show the plot
_ = plt.plot(x_theor, y_theor)
_ = plt.plot(x, y, marker='.', linestyle='none')
_ = plt.xlabel('Belmont winning time (sec.)')
_ = plt.ylabel('CDF')
plt.show()
What are the chances of a horse matching or beating Secretariat's record?
The probability that the winner of a given Belmont Stakes will run it as fast or faster than Secretariat assuming that the Belmont winners' times are Normally distributed (with the 1970 and 1973 years removed)
# Take a million samples out of the Normal distribution: samples
samples = np.random.normal(mu, sigma, size=1000000)
# Compute the fraction that are faster than 144 seconds: prob
prob = np.sum(samples<=144)/len(samples)
# Print the result
print('Probability of besting Secretariat:', prob)
The Exponential distribution
The waiting time between arrivals of a Poisson process is Exponentially distributed
Possible Poisson process
 Nuclear incidents: Timing of one is independent of all others
$f(x; \frac{1}{\beta}) = \frac{1}{\beta} \exp(\frac{x}{\beta})$
If you have a story, you can simulate it!
Sometimes, the story describing our probability distribution does not have a named distribution to go along with it. In these cases, fear not! You can always simulate it.
we looked at the rare event of nohitters in Major League Baseball. Hitting the cycle is another rare baseball event. When a batter hits the cycle, he gets all four kinds of hits, a single, double, triple, and home run, in a single game. Like nohitters, this can be modeled as a Poisson process, so the time between hits of the cycle are also Exponentially distributed.
How long must we wait to see both a nohitter and then a batter hit the cycle? The idea is that we have to wait some time for the nohitter, and then after the nohitter, we have to wait for hitting the cycle. Stated another way, what is the total waiting time for the arrival of two different Poisson processes? The total waiting time is the time waited for the nohitter, plus the time waited for the hitting the cycle.
def successive_poisson(tau1, tau2, size=1):
"""Compute time for arrival of 2 successive Poisson processes."""
# Draw samples out of first exponential distribution: t1
t1 = np.random.exponential(tau1, size=size)
# Draw samples out of second exponential distribution: t2
t2 = np.random.exponential(tau2, size=size)
return t1 + t2
# Draw samples of waiting times: waiting_times
waiting_times = successive_poisson(764, 715, size=100000)
# Make the histogram
_ = plt.hist(waiting_times, bins=100, density=True, histtype="step")
# Label axes
_ = plt.xlabel("Waiting times")
_ = plt.ylabel("probability")
# Show the plot
plt.show()
Notice that the PDF is peaked, unlike the waiting time for a single Poisson process. For fun (and enlightenment), Let's also plot the CDF.
x,y = ecdf(waiting_times)
_ = plt.plot(x,y)
_ = plt.plot(x,y, marker=".", linestyle="none")
_ = plt.xlabel("Waiting times")
_ = plt.ylabel("CDF")
plt.show()
1. For this exercise and all going forward, the random number generator is preseeded for you (with np.random.seed(42))
to save you typing that each time.↩
2. we scraped the data concerning the Belmont Stakes from the Belmont Wikipedia page.↩