Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Load gene expression count matrices from various formats including CSV, TSV, featureCounts, Salmon, kallisto, and 10X. Use when importing quantification results for downstream analysis.
.claude/skills/bio-expression-matrix-counts-ingest/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 76% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 65% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 32% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 92% | 0% |
| case-06 | ✓→✓ | = Same ✓ | 276% | 0% |
<!--
#
#
-->
pythonimport pandas as pd # TSV with gene IDs as first column counts = pd.read_csv('counts.tsv', sep='\t', index_col=0) # CSV with header counts = pd.read_csv('counts.csv', index_col=0) # Skip comment lines counts = pd.read_csv('counts.txt', sep='\t', index_col=0, comment='#')
pythonimport pandas as pd # featureCounts format has 6 metadata columns before counts fc = pd.read_csv('featurecounts.txt', sep='\t', comment='#') counts = fc.set_index('Geneid').iloc[:, 5:] # Skip Chr, Start, End, Strand, Length counts.columns = [c.replace('.bam', '').split('/')[-1] for c in counts.columns]
pythonimport pandas as pd from pathlib import Path def load_salmon_quants(quant_dirs, column='NumReads'): '''Load multiple Salmon quant.sf files into a count matrix.''' dfs = {} for qdir in quant_dirs: sample = Path(qdir).name sf = pd.read_csv(f'{qdir}/quant.sf', sep='\t', index_col=0) dfs[sample] = sf[column] return pd.DataFrame(dfs) # Usage quant_dirs = ['salmon_out/sample1', 'salmon_out/sample2', 'salmon_out/sample3'] counts = load_salmon_quants(quant_dirs, column='NumReads') tpm = load_salmon_quants(quant_dirs, column='TPM')
pythonimport pandas as pd from pathlib import Path def load_kallisto_quants(abundance_files, column='est_counts'): '''Load multiple kallisto abundance.tsv files.''' dfs = {} for f in abundance_files: sample = Path(f).parent.name ab = pd.read_csv(f, sep='\t', index_col=0) dfs[sample] = ab[column] return pd.DataFrame(dfs) # Usage files = ['kallisto_out/sample1/abundance.tsv', 'kallisto_out/sample2/abundance.tsv'] counts = load_kallisto_quants(files, column='est_counts') tpm = load_kallisto_quants(files, column='tpm')
pythonimport scanpy as sc # Load 10X directory (contains matrix.mtx, genes.tsv/features.tsv, barcodes.tsv) adata = sc.read_10x_mtx('filtered_feature_bc_matrix/') # Load 10X H5 file adata = sc.read_10x_h5('filtered_feature_bc_matrix.h5') # Convert to dense DataFrame if needed counts = adata.to_df()
pythonimport anndata as ad import scanpy as sc # Load h5ad adata = sc.read_h5ad('data.h5ad') # Access count matrix counts = adata.to_df() # Dense DataFrame sparse_counts = adata.X # Sparse matrix (if stored sparse) # Access raw counts if normalized data is in .X raw_counts = adata.raw.to_adata().to_df()
pythonimport pyreadr # Read RDS file result = pyreadr.read_r('counts.rds') counts = result[None] # Access the data # For Seurat objects, use anndata2ri or convert in R first
pythonimport pandas as pd from pathlib import Path def combine_count_files(file_pattern, index_col=0, sep='\t'): '''Combine multiple count files into one matrix.''' files = sorted(Path('.').glob(file_pattern)) dfs = {} for f in files: sample = f.stem.replace('_counts', '') dfs[sample] = pd.read_csv(f, sep=sep, index_col=index_col).iloc[:, 0] return pd.DataFrame(dfs) # Usage counts = combine_count_files('counts/*_counts.tsv')
python# Keep genes with at least 10 counts in at least 3 samples min_counts, min_samples = 10, 3 expressed = (counts >= min_counts).sum(axis=1) >= min_samples counts_filtered = counts.loc[expressed] # Alternative: total counts threshold counts_filtered = counts[counts.sum(axis=1) >= 50]
python# Remove Ensembl version numbers (ENSG00000123456.12 -> ENSG00000123456) counts.index = counts.index.str.split('.').str[0] # Or keep as-is for compatibility
python# Save as TSV counts.to_csv('count_matrix.tsv', sep='\t') # Save as compressed counts.to_csv('count_matrix.tsv.gz', sep='\t', compression='gzip') # Save as AnnData import anndata as ad adata = ad.AnnData(counts) adata.write_h5ad('counts.h5ad')
r# Basic CSV/TSV counts <- read.csv('counts.csv', row.names=1) counts <- read.delim('counts.tsv', row.names=1) # featureCounts fc <- read.delim('featurecounts.txt', comment.char='#', row.names=1) counts <- fc[, 6:ncol(fc)] # tximport for Salmon/kallisto library(tximport) files <- file.path('salmon_out', samples, 'quant.sf') txi <- tximport(files, type='salmon', txOut=TRUE) counts <- txi$counts
<!-- AUTHOR_SIGNATURE: 9a7f3c2e-MD-BABU-MIA-2026-MSSM-SECURE -->
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-06 | pass→pass | 3,147 | 2,901 | -8% | 1 | 1 | 0% | 578 | 2,173 | +276% | 0 | 0 | — |
case-01 | pass→pass | 8,401 | 5,720 | -32% | 1 | 1 | 0% | 1,684 | 2,860 | +70% | 0 | 0 | — |
case-02 | pass→pass | 10,160 | 6,439 | -37% | 1 | 1 | 0% | 1,953 | 3,034 | +55% | 0 | 0 | — |
case-03 | pass→pass | 11,858 | 7,798 | -34% | 1 | 1 | 0% | 2,322 | 3,188 | +37% | 0 | 0 | — |
case-04 | pass→pass | 12,224 | 8,869 | -27% | 1 | 1 | 0% | 2,510 | 3,459 | +38% | 0 | 0 | — |
case-05 | pass→pass | 3,067 | 3,277 | +7% | 1 | 1 | 0% | 575 | 2,319 | +303% | 0 | 0 | — |
case-07 | fail→pass | 7,632 | 5,013 | -34% | 1 | 1 | 0% | 1,509 | 2,662 | +76% | 0 | 0 | — |
case-08 | fail→pass | 8,486 | 6,111 | -28% | 1 | 1 | 0% | 1,739 | 2,869 | +65% | 0 | 0 | — |
case-09 | pass→pass | 12,400 | 5,405 | -56% | 1 | 1 | 0% | 2,364 | 2,600 | +10% | 0 | 0 | — |
case-10 | fail→pass | 13,764 | 7,622 | -45% | 1 | 1 | 0% | 2,396 | 3,152 | +32% | 0 | 0 | — |
case-11 | pass→pass | 6,496 | 3,417 | -47% | 1 | 1 | 0% | 1,313 | 2,360 | +80% | 0 | 0 | — |
case-12 | pass→pass | 8,401 | 5,447 | -35% | 1 | 1 | 0% | 1,711 | 2,799 | +64% | 0 | 0 | — |
case-13 | pass→pass | 4,652 | 3,259 | -30% | 1 | 1 | 0% | 902 | 2,288 | +154% | 0 | 0 | — |
case-14 | pass→pass | 7,451 | 4,400 | -41% | 1 | 1 | 0% | 1,317 | 2,507 | +90% | 0 | 0 | — |
case-15 | fail→pass | 7,119 | 4,686 | -34% | 1 | 1 | 0% | 1,369 | 2,633 | +92% | 0 | 0 | — |
case-16 | pass→pass | 8,518 | 5,732 | -33% | 1 | 1 | 0% | 1,636 | 2,766 | +69% | 0 | 0 | — |
case-17 | pass→pass | 6,810 | 4,466 | -34% | 1 | 1 | 0% | 1,254 | 2,518 | +101% | 0 | 0 | — |
case-18 | pass→pass | 3,258 | 2,381 | -27% | 1 | 1 | 0% | 563 | 2,106 | +274% | 0 | 0 | — |
case-19 | pass→pass | 10,269 | 4,857 | -53% | 1 | 1 | 0% | 2,121 | 2,650 | +25% | 0 | 0 | — |
case-20 | pass→pass | 10,761 | 5,888 | -45% | 1 | 1 | 0% | 2,120 | 2,814 | +33% | 0 | 0 | — |
case-21 | pass→pass | 10,661 | 7,508 | -30% | 1 | 1 | 0% | 2,110 | 3,175 | +50% | 0 | 0 | — |
case-22 | pass→pass | 11,528 | 8,607 | -25% | 1 | 1 | 0% | 2,215 | 3,335 | +51% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +18 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 7/26/2026 | +14% |
Other measured skills in the registry, with their headline benchmark lift.