Data Analysis Patterns
Analysis Workflow
The Analysis Pipeline
1. Define Question — what decision does this answer?
2. Collect Data — identify sources, assess quality
3. Clean Data — handle nulls, outliers, types
4. Explore (EDA) — distributions, correlations, anomalies
5. Analyze — apply appropriate statistical method
6. Visualize — make findings legible
7. Interpret — connect back to business question
8. Communicate — executive summary + supporting detail
Descriptive Statistics
Key Metrics
| Metric |
When to Use |
Python |
| Mean |
Normal distribution, no outliers |
df.mean() |
| Median |
Skewed data, outliers present |
df.median() |
| Mode |
Categorical, most common value |
df.mode() |
| Std Dev |
Spread of normal distribution |
df.std() |
| IQR |
Spread when outliers present |
df.quantile(0.75) - df.quantile(0.25) |
| Percentiles |
Distribution shape |
df.quantile([.25,.5,.75,.95,.99]) |
EDA Checklist
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv('data.csv')
# Shape and types
print(df.shape)
print(df.dtypes)
print(df.head())
# Missing values
print(df.isnull().sum() / len(df) * 100) # % missing
# Distributions
df.describe() # count, mean, std, min, quartiles, max
# Correlations
df.corr()
# Categorical counts
df['category_col'].value_counts()
# Outliers (IQR method)
Q1 = df['value'].quantile(0.25)
Q3 = df['value'].quantile(0.75)
IQR = Q3 - Q1
outliers = df[(df['value'] < Q1 - 1.5*IQR) | (df['value'] > Q3 + 1.5*IQR)]
Hypothesis Testing
Choosing the Right Test
Question type → Test
─────────────────────────────────────────────────────
Compare means, 2 groups, normal → t-test (independent)
Compare means, 2 groups, paired → paired t-test
Compare means, 3+ groups → ANOVA
Compare proportions, 2 groups → z-test for proportions
Association between 2 categorical → Chi-square test
Correlation, continuous → Pearson (normal) / Spearman (non-normal)
Non-normal, 2 groups → Mann-Whitney U
Non-normal, 3+ groups → Kruskal-Wallis
Hypothesis Testing Framework
from scipy import stats
# 1. State hypotheses
# H0: no difference (null)
# H1: there is a difference (alternative)
# 2. Set significance level
alpha = 0.05 # 5% false positive rate
# 3. Run test
# Two-sample t-test
t_stat, p_value = stats.ttest_ind(group_a, group_b)
# 4. Interpret
if p_value < alpha:
print(f"Reject H0: p={p_value:.4f} < alpha={alpha}")
print("Statistically significant difference found")
else:
print(f"Fail to reject H0: p={p_value:.4f} >= alpha={alpha}")
print("No statistically significant difference")
# 5. Effect size (practical significance)
cohens_d = (group_a.mean() - group_b.mean()) /
((group_a.std()**2 + group_b.std()**2) / 2)**0.5
# d < 0.2: small, 0.2-0.8: medium, > 0.8: large
A/B Test Analysis
Pre-Test Planning
from statsmodels.stats.power import NormalIndPower
# Calculate required sample size
analysis = NormalIndPower()
n = analysis.solve_power(
effect_size=0.1, # minimum detectable effect (10% relative lift)
alpha=0.05, # significance level
power=0.8, # 80% power (chance of detecting real effect)
alternative='two-sided'
)
print(f"Required n per group: {n:.0f}")
A/B Test Results Template
Experiment: [Name]
Hypothesis: [Changing X will increase Y by Z%]
Start: [Date] End: [Date]
Traffic split: 50/50
Results:
Control Treatment Δ p-value
Conversion rate 3.2% 3.8% +18.75% 0.031
Revenue/visitor $1.24 $1.41 +13.7% 0.047
Sample size 12,400 12,380 — —
Statistical significance: YES (p < 0.05)
Practical significance: YES (18.75% lift exceeds 10% MDE)
Recommendation: SHIP treatment
A/B Test Pitfalls
- Peeking problem — don't stop early when you see significance; use sequential testing
- Multiple testing — each additional metric tested inflates false positives; use Bonferroni correction
- Novelty effect — new features get spike in engagement; run for 2+ weeks
- Sample ratio mismatch — if actual split differs from intended (e.g., 52/48 instead of 50/50), investigate before analyzing
- Segment imbalance — ensure groups are balanced on key covariates
Regression Analysis
Linear Regression
import statsmodels.api as sm
X = df[['feature1', 'feature2', 'feature3']]
y = df['target']
X = sm.add_constant(X)
model = sm.OLS(y, X).fit()
print(model.summary())
# Key outputs:
# R-squared: % variance explained
# Coef: effect of each feature on target
# p-value: significance of each feature
# Conf Int: uncertainty range
Logistic Regression (binary outcome)
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, roc_auc_score
model = LogisticRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
print(classification_report(y_test, y_pred))
print(f"AUC-ROC: {roc_auc_score(y_test, model.predict_proba(X_test)[:,1]):.3f}")
Data Visualization Principles
Chart Selection Guide
| Data type |
Chart type |
| Distribution |
Histogram, box plot, violin plot |
| Trend over time |
Line chart |
| Part-to-whole |
Pie (≤5 slices), stacked bar, treemap |
| Comparison |
Bar chart (horizontal for long labels) |
| Correlation |
Scatter plot, heatmap |
| Composition |
Stacked area, waterfall |
| Geographic |
Choropleth, bubble map |
Design Principles
- One message per chart — write the insight as the chart title
- Remove chartjunk — eliminate gridlines, borders, 3D effects
- Label directly — avoid legends that require eye movement
- Color sparingly — highlight only what matters; gray everything else
- Sort meaningfully — bar charts by value (not alphabetically) unless category order matters
import matplotlib.pyplot as plt
import seaborn as sns
# Clean chart template
fig, ax = plt.subplots(figsize=(10, 6))
sns.set_style("whitegrid")
# Bar chart with direct labels
bars = ax.barh(categories, values, color=['#2196F3' if v == max(values) else '#BDBDBD' for v in values])
ax.set_title("Conversion Rate by Channel", fontsize=14, fontweight='bold', loc='left')
ax.set_xlabel("Conversion Rate (%)")
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
# Direct labels
for bar, val in zip(bars, values):
ax.text(bar.get_width() + 0.1, bar.get_y() + bar.get_height()/2,
f'{val:.1f}%', va='center')
Communicating Findings
Insight Communication Framework (SCQA)
- Situation — what is the context?
- Complication — what changed or is the problem?
- Question — what question does this raise?
- Answer — your finding and recommendation
Executive Summary Formula
Finding: [X happened / X is true]
So what: [This means Y for the business]
Action: [We should do Z because of this]
Evidence: [p-value / % lift / confidence interval]