# Data Analysis

> When to activate: data analysis, statistical analysis, hypothesis testing, A/B test analysis, regression, data visualization, insight generation, descriptive statistics, inferential statistics

- Skill: `mattakushi432/data-analysis` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/data-analysis`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/data-analysis/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/data-analysis

---


# 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
```python
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
```python
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
```python
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
```python
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)
```python
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

```python
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]
```

