Mathew K Analytics

Lesson 19 · Probability and Statistics in python

Understanding Monte Carlo Simulations in Python for Statistical Modeling and Analysis

Welcome! In this lesson, we will explore Monte Carlo simulations with practical Python examples. You will learn probability basics, run hands-on…

⬇ Download notebookOpen in Colab ↗

What you'll learn

Data

No separate download needed — the notebook creates or downloads everything it uses.

📓 Full notebook

Download .ipynb

Monte Carlo Simulations in Python: From Basics to Projects#

Welcome! In this lesson, we will explore Monte Carlo simulations with practical Python examples.

You will learn probability basics, run hands-on simulations, analyze results, and complete a mini-project.

Let us get started!

# Setup: Essential imports and settings
import warnings; warnings.filterwarnings("ignore")
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from statsmodels.tools.sm_exceptions import ConvergenceWarning, ValueWarning
warnings.filterwarnings("ignore", category=ValueWarning)
warnings.filterwarnings("ignore", category=ConvergenceWarning)
warnings.filterwarnings("ignore", category=RuntimeWarning)
 
 

What is a Monte Carlo Simulation?#

A Monte Carlo simulation uses random numbers to model experiments.

It helps you estimate answers by running many possible scenarios.

Real-world example: You can estimate how likely it is to win a game or predict sales.

# Coin flip basics: Run 10 flips
flips = np.random.choice(["H", "T"], size=10)
print("10 coin flips:", flips)
 
 
10 coin flips: ['T' 'H' 'T' 'H' 'H' 'H' 'T' 'T' 'H' 'H']
# Coin flips: Estimate the probability of heads
n_flips = 10000
results = np.random.choice(["H", "T"], size=n_flips)
prob_heads = np.sum(results == "H") / n_flips
print("Estimated probability of heads:", prob_heads)
 
 
Estimated probability of heads: 0.5

Descriptive Statistics: Average and Spread#

Descriptive statistics help summarize our results.

  • The mean measures the average.
  • The standard deviation tells us how spread out the data is.

Let us calculate these values for our simulations.

# Dice rolling: Simulate 1000 rolls and show basic stats
rolls = np.random.randint(1, 7, size=1000)
mean_roll = np.mean(rolls)
std_roll = np.std(rolls)
print("Mean roll:", mean_roll)
print("Standard deviation:", std_roll)
 
 
Mean roll: 3.476
Standard deviation: 1.6856523959583125
# Missing data: Introduce and handle missing values
rolls_with_nan = rolls.copy().astype(float)
rolls_with_nan[::100] = np.nan
mean_nan = np.nanmean(rolls_with_nan)
print("Mean with missing data:", mean_nan)
 
 
Mean with missing data: 3.473737373737374

Discrete Distributions: The Binomial#

A Binomial distribution models the number of successes in repeated yes/no experiments. Example: How many times do you get heads if you flip a coin 10 times?

Let us simulate and plot it.

# Simulate and plot a Binomial distribution
n_trials, p_successes = 10, 0.5
binom_sims = np.random.binomial(n=n_trials, p=p_successes, size=1000)
sns.histplot(binom_sims, bins=11, stat="probability")
plt.xlabel("Number of Heads in 10 Flips")
plt.title("Binomial Simulation (Coin Flips)")
plt.show()
No description has been provided for this image

Continuous Distributions: The Normal Curve#

Many natural outcomes follow a bell-shaped curve called the normal distribution.

Examples: Heights, test scores, errors in measurement.

Let us create and visualize a normal distribution.

# Simulate and plot a normal distribution
normal_data = np.random.normal(loc=0, scale=1, size=10000)
sns.histplot(normal_data, bins=40, kde=True, stat="density")
plt.title("Histogram of 10,000 Normal Samples")
plt.xlabel("Value")
plt.ylabel("Density")
plt.show()
No description has been provided for this image
# Central Limit Theorem: Sampling averages
pop = np.random.uniform(0, 10, size=100000)
sample_means = [np.mean(np.random.choice(pop, size=50)) for _ in range(1000)]
plt.hist(sample_means, bins=25, density=True)
plt.title("Sample Means (n=50) from Uniform Data")
plt.xlabel("Sample Mean")
plt.ylabel("Frequency")
plt.show()
No description has been provided for this image
# Confidence intervals: Estimate mean tip size
tips = sns.load_dataset("tips")
sample = tips["tip"].sample(n=30, random_state=42)
mean_tip = sample.mean()
std_tip = sample.std(ddof=1)
from scipy.stats import t
conf = 0.95
margin = t.ppf(1 - (1 - conf)/2, df=len(sample)-1) * std_tip / np.sqrt(len(sample))
lower, upper = mean_tip - margin, mean_tip + margin
print(f"95% confidence interval for mean tip: ({lower:.2f}, {upper:.2f})")
95% confidence interval for mean tip: (2.40, 3.27)
# Hypothesis testing: Are dinner tips bigger than lunch?
dinner_tips = tips[tips["time"] == "Dinner"]["tip"]
lunch_tips = tips[tips["time"] == "Lunch"]["tip"]
from scipy.stats import ttest_ind
stat, p = ttest_ind(dinner_tips, lunch_tips)
print("p-value:", p)
if p < 0.05:
    print("There is evidence that dinner tips are different from lunch tips.")
else:
    print("There is not enough evidence to say the tips are different.")
 
 
p-value: 0.05780153475171558
There is not enough evidence to say the tips are different.
# Correlation and covariance: Are total bill and tip related?
corr = tips["total_bill"].corr(tips["tip"])
cov = tips["total_bill"].cov(tips["tip"])
print("Correlation:", corr)
print("Covariance:", cov)
 
 
Correlation: 0.6757341092113645
Covariance: 8.323501629224854
# Simple regression: Predict tip from total bill
import statsmodels.api as sm
X = tips["total_bill"]
y = tips["tip"]
X = sm.add_constant(X)
model = sm.OLS(y, X).fit()
print(model.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:                    tip   R-squared:                       0.457
Model:                            OLS   Adj. R-squared:                  0.454
Method:                 Least Squares   F-statistic:                     203.4
Date:                Thu, 04 Sep 2025   Prob (F-statistic):           6.69e-34
Time:                        10:36:51   Log-Likelihood:                -350.54
No. Observations:                 244   AIC:                             705.1
Df Residuals:                     242   BIC:                             712.1
Df Model:                           1                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
const          0.9203      0.160      5.761      0.000       0.606       1.235
total_bill     0.1050      0.007     14.260      0.000       0.091       0.120
==============================================================================
Omnibus:                       20.185   Durbin-Watson:                   2.151
Prob(Omnibus):                  0.000   Jarque-Bera (JB):               37.750
Skew:                           0.443   Prob(JB):                     6.35e-09
Kurtosis:                       4.711   Cond. No.                         53.0
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
# Logistic regression: Predict Titanic survival
titanic = sns.load_dataset("titanic")
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
X = titanic[["pclass", "age", "sibsp", "parch", "fare"]].fillna(0)
y = titanic["survived"]
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
logreg = LogisticRegression(max_iter=200)
logreg.fit(X_train, y_train)
score = logreg.score(X_test, y_test)
print(f"Accuracy of survival prediction: {score:.2f}")
Accuracy of survival prediction: 0.73
# Time series basics: Airline passengers over time
url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/airline-passengers.csv"
airline = pd.read_csv(url, parse_dates=["Month"])
plt.plot(airline["Month"], airline["Passengers"])
plt.title("Monthly Airline Passengers (1949-1960)")
plt.xlabel("Date")
plt.ylabel("Number of Passengers")
plt.show()
No description has been provided for this image
# Bayesian intro: Update beliefs with Beta distribution
trials = 20
successes = 13
from scipy.stats import beta
x = np.linspace(0, 1, 100)
posterior = beta.pdf(x, successes + 1, trials - successes + 1)
plt.plot(x, posterior)
plt.title("Posterior after 13 successes in 20 trials")
plt.xlabel("Probability of Success")
plt.ylabel("Density")
plt.show()
No description has been provided for this image
# Mini-project Sim 1: Winning at dice
def play_game():
    rolls = np.random.randint(1, 7, 3)
    return np.sum(rolls) >= 15

results = [play_game() for _ in range(10000)]
prob_win = np.mean(results)
print(f"Probability of getting 15 or more in 3 rolls: {prob_win:.3f}")
Probability of getting 15 or more in 3 rolls: 0.092
# Mini-project Sim 2: Analyze real COVID-19 cases
import io
try:
    url = "https://raw.githubusercontent.com/owid/covid-19-data/master/public/data/jhu/new_cases.csv"
    covid = pd.read_csv(url)
    country = "United States"
    covid_long = covid.melt(id_vars="date", var_name="location", value_name="new_cases")
    cases = covid_long[covid_long["location"] == country]["new_cases"]
except Exception as e:
    # Fallback: Use random synthetic data if fetch fails
    np.random.seed(42)
    dates = pd.date_range("2021-01-01", periods=180)
    cases = pd.Series(np.random.poisson(20000, size=180), index=dates)
    cases.name = "new_cases"
    print("Warning: Could not load live COVID-19 data, using synthetic data for US instead.")
    cases = cases.reset_index(drop=True)
sim_samples = [np.mean(np.random.choice(cases.dropna(), size=14)) for _ in range(1000)]
plt.hist(sim_samples, bins=25, color="orange", alpha=0.7)
plt.title("Simulated Average COVID-19 Cases (2-week samples)")
plt.xlabel("Average New Cases per Day")
plt.ylabel("Frequency")
plt.show()
No description has been provided for this image

Best Practices & Troubleshooting#

  • Use enough simulations for stable results. Start with 1,000 repetitions or more.
  • Make sure to handle missing or weird data before analysis.
  • Keep random seeds for reproducibility.
  • Visualize your simulations to catch errors early.

If you get confusing results, check your code and try different settings.

Extra Tips#

  • Use vectorized operations in NumPy for speed.
  • The more random samples, the more accurate your estimatesif it runs quickly.
  • Save seeds or states if you want to repeat or share exact results.

Try to explain your findings in plain language to others.

Practice Challenge#

  1. Make your own simulation: What is the chance to draw at least one ace in five cards from a deck?
  2. Use Monte Carlo to estimate the probability of rolling doubles at least twice in five rolls of two dice.
  3. Try picking your own real dataset and find a creative scenario to simulate!

Share your solution in the comments.

Recap: What Did You Learn?#

You learned how to:

  • Build basic simulations with Python
  • Analyze randomness and uncertainty
  • Handle common data problems
  • Visualize distributions
  • Apply simulations to real-world datasets

Practice is the best way to master Monte Carlo. Experiment, ask questions, and have fun!

Thanks for learning Monte Carlo Simulations!#

If you enjoyed this lesson, please subscribe and like for more Python and stats tutorials.

Comment your results or share improvement ideas below.

Happy simulating!

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.