reporting/publication-tables/SKILL.md
Builds publication-ready tables - descriptive Table 1, regression and differential-expression result tables, and supplementary tables - with gtsummary, gt, flextable, and kableExtra (R) or great_tables, pandas, and tableone (Python), choosing the right statistics and the right export format. Use when making a Table 1, exporting a formatted results table for a paper, or writing a gene-symbol-safe supplementary table.
npx skillsauth add GPTomics/bioSkills bio-reporting-publication-tablesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Reference examples tested with: gtsummary 2.0+, gt 0.10+, flextable 0.9+, kableExtra 1.4+, great_tables 0.13+, pandas 2.2+, tableone 0.9+, openpyxl 3.1+
Before using code patterns, verify installed versions match. If versions differ:
packageVersion('gtsummary') then ?tbl_summary (gtsummary had a major API refresh at v2.0)pip show great_tables then help(great_tables.GT.save)If code throws an error, introspect the installed package and adapt the example to the actual API rather than retrying.
"Make my Table 1" / "export this results table for the paper" -> Generate the table programmatically with the right statistics and export it to the journal's target format.
gtsummary::tbl_summary(data, by=arm) then as_flex_table() -> Wordgreat_tables.GT(df) -> HTML/PNG; tableone.TableOne(...) for a descriptive tableThree orthogonal concerns, and conflating them is where tables go wrong:
3.14159265 mean is noise.The deepest framing, shared with figures: a table is a deterministic function of data + code. Same input + same code -> the same table, byte for byte. Manual edits in Word break this; every number must trace to a line of code. That principle dictates the tooling - generate programmatically and never hand-edit the output.
Target format dominates: Word for most biomedical journals, LaTeX for some genomics/physics venues, HTML for web/preprints/Quarto, CSV/Excel for machine-readable supplements.
| Need | Tool | Path |
|------|------|------|
| R, descriptive Table 1 or regression results | gtsummary | tbl_summary / tbl_regression -> as_flex_table() / as_gt() |
| R, going to Word/PowerPoint | flextable | save_as_docx() (most reliable Word fidelity; pairs with officer) |
| R, going to LaTeX/PDF | gt or kableExtra | gtsave('x.tex') / kbl(format='latex') |
| R, going to HTML/Quarto | gt or kableExtra | as_raw_html() / save_kable() |
| Python, display HTML/image | great_tables | GT(df) -> save('x.png') / as_raw_html() |
| Python, going to LaTeX | pandas Styler | df.style.format(...).to_latex() (pandas 1.3+) |
| Either, classic Table 1 with SMD | tableone | CreateTableOne (R) / TableOne (Python) |
| Machine-readable supplement | CSV (preferred) or Excel | gene-symbol-safe export (below) |
gt has the broadest R export (HTML/PNG/PDF/RTF/LaTeX/Word). great_tables.save() is image+PDF only (HTML via as_raw_html()/write_raw_html()) - NO native Word or LaTeX, a real limitation vs the R stack. DT is for interactive exploration and online-only/interactive HTML supplements - never for a static print/PDF table.
Table 1 reports baseline characteristics so the reader can judge who was studied and how comparable the groups are. It describes the sample; it is not a place to test hypotheses.
The p-value fallacy (randomized trials): adding a p-value column comparing arms in a randomized trial is discouraged by CONSORT and statisticians. In a properly randomized trial any baseline imbalance is by definition due to chance, so the test asks whether a difference could have arisen by chance when the assignment WAS by chance - it tests a null already known true. A "significant" baseline p-value is a Type I error by construction; a non-significant one tells nothing new. The right response to a worrying imbalance on a prognostic covariate is to adjust for it (pre-specified ANCOVA covariate), not test it (Senn 1994; CONSORT 2010 item 15). gtsummary's documentation cautions against add_p() on a randomized Table 1 for this reason.
add_difference() compares exactly two groups; for >2 arms, SMD is defined pairwise, so report reference-group or all-pairs SMDs rather than one omnibus value.median (p25, p75) - defensible because biomedical variables are usually skewed and normality cannot be assumed column by column. Override per-variable (statistic = list(age ~ "{mean} ({sd})")) only after checking normality. Categorical: n(%), and state row% vs column% (baseline tables want column%).missing = "ifany" (default) shows a missing row when any value is absent; missing_text = "Unknown" labels it. Never compute denominators that hide missingness; if rows are dropped, report the analyzed N in the table or a footnote.save_as_docx() is the most reliable path; gtsummary as_flex_table() then save_as_docx() gives the best Word fidelity; gt gtsave('x.docx') works but routes through rmarkdown and supports fewer Word styles. great_tables has NO native Word export.gtsave('x.tex'), kableExtra::kbl(format='latex', booktabs=TRUE), or pandas Styler.to_latex().as_raw_html(), great_tables as_raw_html()/write_raw_html(), kableExtra save_kable().Excel, with default settings, auto-converts gene symbols and IDs when it PARSES them - opening a CSV, double-clicking, or typing: SEPT2 -> 2-Sep, MARCH1 -> 1-Mar; RIKEN IDs like 2310009E13 -> 2.31E+13 (precision lost irreversibly); long numeric accessions lose trailing digits to float rounding. (The corruption is a parse behavior, not a write behavior - a string written by openpyxl stays intact until Excel re-interprets it, which is why forcing text format matters.) Ziemann et al. 2016 found ~19.6% of papers with supplementary Excel gene lists affected; Abeysooriya et al. 2021 showed it persisted at 30.9% and drove HGNC to rename the families (SEPT->SEPTIN, MARCH->MARCHF) in 2020 - biology changed its nomenclature to defend against a spreadsheet bug.
When a gene table must reach Excel:
'@' (openpyxl cell.number_format = '@'; XlsxWriter add_format({'num_format': '@'}) or write_string()). Note pandas Styler.format is IGNORED by to_excel - set the number format via the writer, not the styler.Report to significant figures, not the float default (fmt_number(decimals=), pvalue_fun, Styler .format('{:.2f}')). Watch the decimal-comma locale trap: a CSV written in a ,-decimal locale becomes unparseable elsewhere and Excel may re-misinterpret columns. Write numeric supplements with .-decimal and document the locale rather than relying on the system default.
| Symptom | Cause | Fix |
|---------|-------|-----|
| p-value column on a randomized Table 1 | testing a null known to be true | drop it; use SMD for balance |
| Percentages do not add up / hide dropped rows | missingness silently excluded | missing="ifany"; report analyzed N |
| SEPT2 became a date in the supplement | Excel auto-conversion | CSV + import-as-text, or '@' text format in .xlsx |
| mean(SD) misleads on a skewed variable | wrong summary statistic | median(IQR) for skewed data |
| Word table lost its formatting | exported HTML/LaTeX into Word | flextable save_as_docx() |
| great_tables won't save to Word/LaTeX | not supported (image/HTML/PDF only) | use the R stack, or export PNG/HTML |
| Excel export ignored my number format | Styler.format is dropped by to_excel | set number_format via the ExcelWriter |
development
Installs 425 bioinformatics skills covering sequence analysis, RNA-seq, single-cell, variant calling, metagenomics, structural biology, and 56 more categories. Use when setting up bioinformatics capabilities or when a bioinformatics task requires specialized skills not yet installed.
testing
Chains a somatic (tumor-normal) SNV/indel and structural-variant pipeline end to end with GATK Mutect2 (or Strelka2), wiring the somatic-specific machinery - panel-of-normals and gnomAD germline-resource priors, GetPileupSummaries/CalculateContamination, and LearnReadOrientationModel FFPE/oxoG orientation-bias filtering fed into FilterMutectCalls. Use when calling somatic mutations from a tumor-normal pair (or tumor-only with PoN caveats), deciding which artifact filter removes which class of false positive, reasoning about VAF/purity/ploidy and clonal-vs-subclonal detection, adding somatic SV/CNV or TMB/MSI/signatures, or routing variants to AMP/ASCO/CAP tier and oncogenicity interpretation (never germline ACMG).
development
End-to-end pooled and single-cell CRISPR screen analysis from FASTQ to hit genes. Orchestrates library design QC, guide counting, six-stage screen QC (plasmid Gini, replicate Pearson, CEGv2 PR-AUC, copy-number artifact), method-appropriate hit calling across MAGeCK RRA/MLE, BAGEL2, drugZ, JACKS, and Chronos, cancer-cell-line copy-number correction (CRISPRcleanR / Chronos), batch correction for multi-batch screens, and the specialized branches for combinatorial paralog screens, single-cell Perturb-seq, base-editor variant-function screens, prime-editor screens, and in vivo bottleneck-aware screens. Use when analyzing any pooled CRISPR screen end-to-end, matching the hit-calling method to the experimental design, integrating copy-number correction into the pipeline, or branching the workflow for single-cell, combinatorial, base-editor, prime-editor, or in vivo variants.
development
Transcribe DNA to RNA and translate to protein using Biopython, with NCBI codon-table selection, CDS validation, and six-frame ORF finding. Use when converting a CDS or ORF to its amino-acid sequence, selecting a non-standard (mitochondrial, bacterial, ciliate) genetic code, validating a coding sequence, or scanning all reading frames.