Mixed methods research design with qualitative-quantitative integration, triangulation, content analysis, and systematic comparison using NVivo-style coding.
Use this skill when you need to:
Trigger keywords: mixed methods, convergent design, sequential explanatory, sequential exploratory, triangulation, content analysis, qualitative coding, constant comparative method, thematic analysis, NVivo, ATLAS.ti, qualitative comparative analysis, QCA, crisp-set QCA, fuzzy-set QCA, Boolean algebra, necessary conditions, sufficient conditions, MANOVA, ANOVA with coding, inter-rater reliability, saturation.
$$\text{Convergence} = \text{sign}(r_{\text{qual-quant}}) \times P(\text{same direction})$$
Strong triangulation: qual themes and quant results point in the same direction, increasing confidence in findings.
For nominal coding of $n$ items by $k$ coders:
where $D_o$ is observed disagreement, $D_e$ is expected disagreement.
Boolean truth table analysis. For crisp-set QCA (csQCA), each condition $C_i \in {0,1}$ and outcome $O \in {0,1}$.
Necessity: $C \Rightarrow O$: coverage $= \frac{\sum \min(C, O)}{\sum O}$, consistency $= \frac{\sum \min(C, O)}{\sum C}$
Sufficiency: $C \Rightarrow O$: coverage $= \frac{\sum \min(C, O)}{\sum O}$, consistency $= \frac{\sum \min(C, O)}{\sum C}$
A joint display maps qualitative themes to quantitative outcomes, identifying where the two strands converge, diverge, or complement.
pip install pandas>=2.0 numpy>=1.24 scipy>=1.11 scikit-learn>=1.3 \
statsmodels>=0.14 matplotlib>=3.7
import pandas as pd
import numpy as np
from scipy.stats import chi2_contingency
import matplotlib.pyplot as plt
print("Mixed methods environment ready")
import numpy as np
import pandas as pd
from scipy.stats import chi2_contingency
import matplotlib.pyplot as plt
import re
# -----------------------------------------------------------------
# Simulate interview data: 50 interview segments
# Two coders independently assign codes
# -----------------------------------------------------------------
np.random.seed(42)
CODE_SCHEME = {
"BARRIER": ["Barrier/Obstacle to implementation"],
"ENABLER": ["Enabler/Facilitator of implementation"],
"OUTCOME": ["Outcome/Result described"],
"RECOMMENDATION": ["Policy/Practice recommendation"],
"CONTEXT": ["Context/Background information"],
}
CODES = list(CODE_SCHEME.keys())
# Interview segments
themes_pool = [
"lack of funding prevented the initiative from scaling",
"management support was crucial for adoption",
"the program led to improved outcomes for participants",
"we recommend investing in training for frontline staff",
"this occurred in a resource-constrained setting",
"regulatory barriers slowed down implementation",
"strong leadership facilitated rapid adoption",
"participants reported significant benefits",
"future policy should prioritize equity concerns",
"the rural context posed unique challenges",
]
n_segments = 50
segments = np.random.choice(themes_pool, n_segments)
# True codes (ground truth based on segment content)
def assign_true_code(segment):
"""Assign code based on keyword matching."""
if any(w in segment for w in ["barrier", "lack", "prevented", "slowed"]):
return "BARRIER"
elif any(w in segment for w in ["support", "leadership", "facilitated"]):
return "ENABLER"
elif any(w in segment for w in ["outcomes", "benefits", "improved", "reported"]):
return "OUTCOME"
elif any(w in segment for w in ["recommend", "policy", "should"]):
return "RECOMMENDATION"
else:
return "CONTEXT"
true_codes = [assign_true_code(s) for s in segments]
# Coder 1: 90% agreement with truth
# Coder 2: 85% agreement with truth
def apply_coder_noise(true_codes, accuracy):
noisy = []
for code in true_codes:
if np.random.random() < accuracy:
noisy.append(code)
else:
noisy.append(np.random.choice([c for c in CODES if c != code]))
return noisy
coder1 = apply_coder_noise(true_codes, 0.90)
coder2 = apply_coder_noise(true_codes, 0.85)
df_coding = pd.DataFrame({
"segment_id": range(n_segments),
"text": segments,
"true_code": true_codes,
"coder1": coder1,
"coder2": coder2,
})
# -----------------------------------------------------------------
# Inter-rater reliability: Cohen's Kappa
# -----------------------------------------------------------------
def cohen_kappa(a, b):
"""Compute Cohen's kappa between two coders."""
categories = sorted(set(a) | set(b))
n = len(a)
# Observed agreement
P_o = sum(x == y for x, y in zip(a, b)) / n
# Expected agreement
freq_a = {c: a.count(c) / n for c in categories}
freq_b = {c: b.count(c) / n for c in categories}
P_e = sum(freq_a.get(c, 0) * freq_b.get(c, 0) for c in categories)
if P_e == 1.0:
return 1.0
return (P_o - P_e) / (1 - P_e)
kappa = cohen_kappa(coder1, coder2)
print(f"Cohen's Kappa (Coder1 vs Coder2): κ = {kappa:.3f}")
if kappa >= 0.80: print(" → Excellent agreement")
elif kappa >= 0.61: print(" → Substantial agreement")
elif kappa >= 0.41: print(" → Moderate agreement")