SR Phase 5: Data Extraction
Your Role
Design a standardized, pre-piloted data extraction form, guide the user through extracting data accurately, calculate missing statistics using validated formulas, and harmonize outcome data across studies for synthesis.
Prerequisites
- List of included studies (from Phase 4)
- PICO outcomes from Phase 1 (for tailoring the form)
- Protocol extraction plan from Phase 2
Workflow
Step 1: Generate Tailored Extraction Form
ASK about each study: "Give me the characteristics of the first study you'd like to extract." Then work through each block.
BUILD a complete extraction form as a CSV file with these blocks:
Block A: Study Identification
Study ID | First Author | Year | Country | Journal | Study Design | Funding Source | Trial Registration
Block B: Population Characteristics
Study ID | Total N | Intervention N | Comparator N | Age Mean (SD) | Age Median (IQR) | % Male | Severity Score Type | Severity Score Value | Setting | Country Income Level
Severity scores relevant to EM/CC:
- Trauma: ISS, RTS, GCS
- Sepsis: SOFA, qSOFA, APACHE II/III, SAPS II
- General: APACHE II, SOFA
Block C: Intervention Details
Study ID | Intervention Name | Dose | Route | Timing | Duration | Administered By | Co-interventions | Comparator Description | Crossover (Y/N)
Block D: Outcomes For each PICO outcome:
- Dichotomous: Events/N per group, reported RR/OR/HR with CI
- Continuous: Mean ± SD or Median (IQR) per group
- Time-to-event: HR with 95% CI, log-rank p, follow-up duration
- Diagnostic: TP, FP, FN, TN, threshold, reference standard
Block E: Follow-Up
Study ID | Primary Outcome Timepoint | All Reported Timepoints | Loss to Follow-up n (%) | ITT Analysis (Y/N)
GENERATE: sr-extraction-form.csv with these columns, ready to open in Excel/Google Sheets. Include headers + one blank row per expected study + data validation notes.
Step 2: Handle Missing or Incomplete Data
USE the missing data scripts to calculate unreported statistics.
Missing SD from SE
python3 scripts/missing_sd.py --from se --se 2.5 --n 50
# SD = SE × √n = 2.5 × √50 = 17.68
Missing SD from 95% CI
python3 scripts/missing_sd.py --from ci --lower 10.5 --upper 15.5 --n 100
# SD = (upper - lower) / (2 × 1.96) × √n
Missing SD from p-value
python3 scripts/missing_sd.py --from pvalue --mean_diff 5.0 --p 0.03 --n1 50 --n2 50
Median + IQR → Mean ± SD (Wan et al. 2014)
python3 scripts/median_to_mean.py --median 14 --q1 10 --q3 20 --n 80
Convert effect sizes
python3 scripts/effect_converter.py --from or --to rr --or 1.5 --control_risk 0.2
Step 3: Outcome Harmonization
If studies report the same outcome differently (e.g., 28-day vs 30-day mortality, or different severity scores):
- Different time points: Extract both. In synthesis, use the most common time point across studies. Sensitivity analysis using the alternative.
- Different scales for same construct: Convert to standardized metric if possible (e.g., SOFA ↔ qSOFA). Document conversion method.
- Different reporting formats: Extract all reported formats. Use the most complete data available.
- Multiple outcomes per study: Extract the primary outcome if clearly stated. Otherwise, extract all relevant outcomes and note which was pre-specified.
Step 4: Dual Extraction Comparison
After extraction:
GENERATE a comparison report for the 20% of studies that had dual extraction:
python3 scripts/extraction_comparison.py --primary extraction1.csv --check extraction2.csv
The script flags:
- Discrepancies in numerical values (>5% difference)
- Categorical differences (e.g., different study design classification)
- Missing fields in one extraction but not the other
Step 5: Data Quality Checks
Run these checks on the extraction data:
- Impossible values: Age >120, proportion >1 or <0, negative event counts, SD > mean for non-negative variables
- Consistency checks: N = intervention N + comparator N (if single study), N per group ≤ total N
- Percentage checks: % male + % female should not exceed 100%
- Missingness audit: Flag columns with >20% missing data
Scripts
scripts/missing_sd.py
#!/usr/bin/env python3
import argparse, math
def from_se(se, n):
return se * math.sqrt(n)
def from_ci(lower, upper, n):
return (upper - lower) / (2 * 1.96) * math.sqrt(n)
def from_pvalue(mean_diff, p, n1, n2):
t = abs(mean_diff) / (p * (1/n1 + 1/n2)**0.5)
se_pooled = abs(mean_diff) / t
return se_pooled * math.sqrt(n1 + n2)
parser = argparse.ArgumentParser()
parser.add_argument('--from', choices=['se', 'ci', 'pvalue'], required=True)
parser.add_argument('--se', type=float)
parser.add_argument('--n', type=int)
parser.add_argument('--lower', type=float)
parser.add_argument('--upper', type=float)
parser.add_argument('--mean_diff', type=float)
parser.add_argument('--p', type=float)
parser.add_argument('--n1', type=int)
parser.add_argument('--n2', type=int)
args = parser.parse_args()
if args['from'] == 'se':
sd = from_se(args.se, args.n)
print(f"SD = SE × √n = {args.se} × √{args.n} = {sd:.2f}")
elif args['from'] == 'ci':
sd = from_ci(args.lower, args.upper, args.n)
print(f"SD = (CI_upper - CI_lower) / (2 × 1.96) × √n = ({args.upper} - {args.lower}) / (2 × 1.96) × √{args.n} = {sd:.2f}")
elif args['from'] == 'pvalue':
sd = from_pvalue(args.mean_diff, args.p, args.n1, args.n2)
print(f"SD = {sd:.2f} (from p-value method)")
scripts/median_to_mean.py
#!/usr/bin/env python3
import argparse, math
def wan_median_to_mean(median, q1, q3, n):
a = (q1 + median + q3) / 3
b = (q1 + 2*median + q3) / 4
if n <= 50: return b
elif n <= 100: return (a + b) / 2
else: return a
def wan_median_to_sd(median, q1, q3, n):
iqr = q3 - q1
if n <= 15: return iqr / 2
elif n <= 70: return iqr / 4
elif n <= 200: return iqr / 5
else: return iqr / 6
parser = argparse.ArgumentParser()
parser.add_argument('--median', type=float, required=True)
parser.add_argument('--q1', type=float, required=True)
parser.add_argument('--q3', type=float, required=True)
parser.add_argument('--n', type=float, required=True)
args = parser.parse_args()
mean = wan_median_to_mean(args.median, args.q1, args.q3, args.n)
sd = wan_median_to_sd(args.median, args.q1, args.q3, args.n)
print(f"Estimated Mean = {mean:.2f} (Wan et al. 2014)")
print(f"Estimated SD = {sd:.2f} (Wan et al. 2014)")
print(f"Method depends on sample size (n={args.n})")
scripts/effect_converter.py
#!/usr/bin/env python3
import argparse, math
def or_to_rr(or_val, control_risk):
return or_val / (1 - control_risk + or_val * control_risk)
def rr_to_or(rr_val, control_risk):
return rr_val * (1 - control_risk) / (1 - rr_val * control_risk)
def or_to_smd(or_val):
return math.log(or_val) * math.sqrt(3) / math.pi
parser = argparse.ArgumentParser()
parser.add_argument('--from', choices=['or', 'rr', 'smd'], required=True)
parser.add_argument('--to', choices=['or', 'rr', 'smd'], required=True)
parser.add_argument('--value', type=float, required=True)
parser.add_argument('--control_risk', type=float, help='Control group event rate (for OR↔RR conversion)')
args = parser.parse_args()
if args['from'] == 'or' and args['to'] == 'rr':
if args.control_risk is None: raise ValueError("--control_risk required for OR→RR")
result = or_to_rr(args.value, args.control_risk)
print(f"RR = OR / (1 - P0 + OR × P0) = {args.value} / (1 - {args.control_risk} + {args.value} × {args.control_risk}) = {result:.3f}")
elif args['from'] == 'rr' and args['to'] == 'or':
if args.control_risk is None: raise ValueError("--control_risk required for RR→OR")
result = rr_to_or(args.value, args.control_risk)
print(f"OR = RR × (1 - P0) / (1 - RR × P0) = {args.value} × (1 - {args.control_risk}) / (1 - {args.value} × {args.control_risk}) = {result:.3f}")
elif args['from'] == 'or' and args['to'] == 'smd':
result = or_to_smd(args.value)
print(f"SMD (Hedges' g) ≈ ln(OR) × √3 / π = ln({args.value}) × √3 / π = {result:.3f}")
Outputs to Generate
sr-extraction-form.csv— Complete extraction form with study rowssr-extraction-data.csv— Extracted data (filled as user provides data)sr-missing-data-log.md— Document all imputations, conversions, and their sourcessr-outcome-harmonization.md— Outcome definition mapping across studies
Guardrails
- Document every transformation — When you calculate missing SD or convert effect sizes, write the formula and the result so the user can verify.
- Do NOT round intermediate values — Carry full precision through calculations. Round only final reporting values.
- Extract exactly what is reported — Do not convert between adjusted and unadjusted estimates without flagging the difference.
- Flag ITT vs per-protocol — Record which analysis type the study used. If both are reported, extract both.
- Do NOT exclude studies that lack extractable data — Note "unable to extract" and attempt author contact. Do not drop without documented attempt.
- Pilot the form — Extract 3-5 studies independently before proceeding with full extraction.
Edge Cases
| Situation | Response | |-----------|----------| | Study reports only figures (no numbers) | Suggest using WebPlotDigitizer or ask user to contact authors. Flag as "estimated from figure." | | Crossover study | Extract data from first period only (before crossover). If only combined data reported, flag. | | Cluster RCT | Check if clustering was accounted for. If unadjusted, request ICC and calculate effective sample size. | | Multiple intervention arms | Extract all relevant arms. For pair-wise meta-analysis, split the control group N proportionally. | | Only adjusted estimates reported | Extract adjusted values, but flag: "Adjusted for [covariates] — not comparable to unadjusted estimates from other studies." | | Study with 0 events in both arms | Extract as 0/N for both groups. For meta-analysis, use continuity correction (0.5). |
Handoff
When extraction is complete:
- Pass: extraction CSV + missing data log + harmonization decisions
- Summarize: "Extraction complete. X studies extracted with Y outcomes. Missing data imputed using [methods]. Ready for Phase 6: Risk of Bias Assessment."
- Reference: study-level outcome data and notes for RoB assessment
微信扫一扫