Chatomics Field GuideWhat They Don't Teach You

Conversion · bedGraph → bigWig

How to Convert bedGraph to bigWig (and What Changes Between Builds)

The conversion tool never introduces a coordinate bug, the chrom.sizes file you feed it is where builds and chromosome naming quietly go wrong.

By Ming "Tommy" Tang, Director of Bioinformatics in Big Pharma · Reviewed September 2026 · 3 min read

bedGraph
.bedgraph, .bg · coordinates: 0-based-half-open
bigWig
.bw, .bigwig · coordinates: 0-based-half-open

You need this conversion whenever a coverage track produced as plain-text bedGraph (from bedtools genomecov, MACS2 pileup, or deepTools with --outFileFormat bedgraph) has to go into a genome browser, IGV, or any deepTools command that expects an indexed track. bedGraph doesn't scale: it's flat text, so a browser has to pull the whole file to render any region. bigWig is the compressed, indexed binary form built for random access to exactly the region you're looking at.

Both formats use the same 0-based, half-open coordinate system, so the conversion itself never shifts a coordinate. What changes is representation and what survives the trip: a UCSC track type=bedGraph line, comments, or any metadata beyond the four required columns (chrom, start, end, value) gets dropped, not carried into the binary. bigWig also precomputes zoom-level summaries, so what you see at low zoom is an average, not the raw per-base value underneath.

The most common way this goes silently wrong is a genome-build or chromosome-naming mismatch in the chrom.sizes file. bedGraphToBigWig only checks that your bedGraph's chromosome names exist in chrom.sizes and that coordinates fit within the listed lengths, it has no concept of "build." Feed it an hg19 chrom.sizes against an hg38 bedGraph, or vice versa, and if the names and rough lengths look plausible, the tool finishes without complaint. You find out later, when a peak sits in the wrong gene or a track looks shifted by a few hundred bases.

The commands

Type your file names once; every command below updates.

  1. 01GNU coreutils sort

    bash
    sort -k1,1 -k2,2n sample.bedgraph > sorted.bedGraph

    -k1,1 sorts by chromosome name as a string, -k2,2n sorts numerically by start position. bedGraphToBigWig requires exactly this order, not the chr1, chr2, ..., chr10 order a chromosome-aware or natural sort would give you; getting this wrong is the single most common reason the conversion fails outright.

  2. 02UCSC bedGraphToBigWig

    bash
    bedGraphToBigWig sorted.bedGraph chrom.sizes sample.bw

    Reads the sorted bedGraph plus a two-column chrom.sizes file (chromosome name, length in bp) for the target build, and writes the compressed, indexed bigWig. It assumes the bedGraph is already sorted and that every chromosome name in it appears in chrom.sizes with a matching length; a mismatch on either produces an error or, worse, a bigWig with silently wrong coordinates.

  3. 03UCSC bedGraphToBigWig

    bash
    bedGraphToBigWig -blockSize=256 -itemsPerSlot=1024 sorted.bedGraph chrom.sizes sample.bw

    Same conversion with the index-tuning flags exposed by the tool: lower blockSize/itemsPerSlot trade a larger file for faster random access on many small-range queries. Leave these at their defaults unless you've actually measured a serving bottleneck.

  4. 04deepTools bamCoveragev3.5.6

    bash
    bamCoverage -b input.bam -o sample.bw --outFileFormat bigwig

    Alternative path that skips the bedGraph stage entirely, going straight from a coordinate-sorted, indexed BAM to bigWig, with optional on-the-fly normalization (--normalizeUsing RPKM/CPM/BPM/RPGC). It assumes the BAM is already sorted and has a .bai index; RPGC additionally requires an --effectiveGenomeSize value for your organism and build.

Coordinates, strand, names, builds

bedGraph and bigWig share the same 0-based, half-open coordinate system as the rest of the BED family, so the conversion itself never shifts a position: base 0 is the first base, and an interval's end coordinate is exclusive. Neither format carries strand information; both describe unstranded coverage, so a strand-specific signal (e.g., plus- and minus-strand RNA-seq coverage) has to live in two separate bedGraph/bigWig pairs, not one file with a strand column. Chromosome naming is where real damage happens: chrom.sizes must use the exact same naming scheme as the bedGraph (chr1 vs 1, chrM vs MT vs chrMT), and it must be the sizes file for the actual genome build the bedGraph's coordinates were generated against, not just any file with plausible-looking chromosome names. Anything beyond the four bedGraph columns, most commonly a UCSC track type=bedGraph display line or trailing comments, is dropped by the conversion rather than translated; if you need track name or color metadata in the browser, that has to be reapplied as a trackhub/session setting after the bigWig exists, not carried through the binary.

Check the output before you trust it

  1. 01chrom.sizes covers every chromosome present in the bedGraph

    bash
    comm -23 <(cut -f1 sorted.bedGraph | sort -u) <(cut -f1 chrom.sizes | sort -u)

    Expected No output. Anything printed here is a chromosome name in the bedGraph that chrom.sizes doesn't have, usually from a chr-prefix or build mismatch, and bedGraphToBigWig will reject it.

  2. 02bedGraph is actually sorted the way bedGraphToBigWig requires

    bash
    sort -c -k1,1 -k2,2n sorted.bedGraph

    Expected No error and exit status 0. Any 'disorder' message means the conversion will fail on this exact file.

  3. 03No overlapping intervals in the source bedGraph

    bash
    awk '{if($1==c && $2<e) print "overlap at "$1":"$2"-"$3; c=$1; e=$3}' sorted.bedGraph

    Expected No lines printed. bedGraphToBigWig treats any overlapping interval as fatal, not a warning, so this has to be clean before you convert.

  4. 04bigWig reports a chromosome count and coverage span that match the source

    bash
    bigWigInfo {output}

    Expected chromCount matches the number of distinct chromosomes in sorted.bedGraph, and basesCovered / min / max values fall in the range you'd expect from your coverage data, not near zero and not wildly larger than the genome.

  5. 05Signal lands where you expect in a browser

    Expected Loading {output} in IGV or the UCSC browser next to a locus you know should have signal (a housekeeping gene promoter for ChIP/ATAC, an exon for RNA-seq) shows a track that isn't flat, isn't empty, and isn't offset by roughly a gene's length. Any of those points to a chr-naming or build mismatch upstream, not a broken conversion command.

Errors you will see, and what they mean

bedGraphToBigWig refuses to run, reporting the input isn't sorted correctly
Cause: The file was sorted with plain `sort`, `sort -V`, or a chromosome-aware natural sort instead of `sort -k1,1 -k2,2n`. bedGraphToBigWig requires lexicographic chromosome order plus numeric start-position order, which does not match the chr1, chr2, ..., chr10 order most people expect visually. Fix: Re-sort with `sort -k1,1 -k2,2n {input} > sorted.bedGraph` and confirm with `sort -c -k1,1 -k2,2n sorted.bedGraph` before converting again.
Conversion aborts on an overlapping-interval error
Cause: Two rows cover the same base on the same chromosome, typically after concatenating per-sample or per-chromosome bedGraphs, or after a coverage/smoothing step that left duplicate rows. bedGraphToBigWig treats this as fatal. Fix: Track down and collapse the overlaps (recompute the coverage step, or merge/dedupe the offending rows) so intervals are strictly disjoint, then re-sort and re-convert.
bedGraphToBigWig errors that a chromosome in the bedGraph isn't found in chrom.sizes
Cause: chrom.sizes was trimmed to primary chromosomes only, or pulled from a different annotation source than the bedGraph, so a scaffold, contig, or chrM/MT entry present in the bedGraph is simply missing from chrom.sizes. Fix: Add the missing chromosome(s) to chrom.sizes with the correct length for your build, or filter those rows out of the bedGraph first if you don't need them, then re-run the conversion.
bigWig loads but the track is completely empty or flat in the browser
Cause: chrom.sizes uses a different naming scheme than the bedGraph (chr1 vs 1, chrM vs MT), or chrom.sizes is for the wrong genome build, so names and/or lengths don't actually line up with the bedGraph's coordinates even though the conversion completed without error. Fix: Regenerate chrom.sizes for the exact build and naming convention the bedGraph was produced against, diff the chromosome name lists between the two files, and reconvert.
Output bigWig is the right size but signal appears shifted or lands on the wrong gene
Cause: The bedGraph's coordinates come from one genome build (e.g., hg19) while you're comparing against another (hg38); bedGraphToBigWig only validates chromosome names and lengths against chrom.sizes, it has no way to detect that the underlying build itself is wrong. Fix: Confirm the build used for alignment/peak-calling matches both chrom.sizes and whatever you're comparing against; if it doesn't, liftOver/CrossMap the bedGraph or regenerate it from a build-matched BAM rather than patching coordinates after the fact.

Questions people ask

Why does bedGraphToBigWig say my file isn't sorted when I already sorted it?

You probably used a plain sort, a natural sort, or a chromosome-aware sort tool instead of sort -k1,1 -k2,2n. bedGraphToBigWig needs lexicographic order on chromosome name and numeric order on start position, which is not the chr1, chr2, ... chr10 order a human would pick. Re-sort with the exact flags and verify with sort -c -k1,1 -k2,2n before converting again.

Do I need a different chrom.sizes file for hg19 vs hg38?

Yes. Chromosome names are the same (chr1-chr22, chrX, chrY, chrM) in both builds, but the lengths differ, and coordinates that are valid in one build can be invalid or simply wrong in the other. Always pull chrom.sizes for the exact build you aligned against, not whatever file is sitting in a shared scripts folder.

Can I convert bedGraph to bigWig without UCSC tools?

For the bedGraph-to-bigWig step specifically, bedGraphToBigWig (the UCSC/ENCODE binary) is the standard and there isn't a bedtools equivalent for this last step. If you're starting from a BAM instead of an existing bedGraph, deepTools bamCoverage can go straight to bigWig and skip the bedGraph stage entirely, with normalization built in.

Why is my bigWig track empty in IGV even though the bedGraph clearly has data?

Check whether chrom.sizes and the bedGraph agree with the naming scheme the browser expects: chr1 versus 1, or chrM versus MT. A silent build mismatch produces the same symptom. Diff the chromosome name lists between your bedGraph and chrom.sizes before you reconvert.

What happens to the 'track type=bedGraph' header line UCSC tells you to add?

That line exists only for the UCSC Genome Browser's upload interface. Feed it to bedGraphToBigWig and the tool will choke on it as a malformed data row. Strip any track or comment lines before converting; pipelines that generate bedGraph programmatically usually never add one in the first place.

Related pages

Related reading on the blog

Sources

  1. Genome Browser bedGraph Track Format — 0-based half-open coordinates and the required 4-column bedGraph layout
  2. bedGraphToBigWig, ENCODE — tool identity and available versions
  3. BigWig and BigBed: enabling browsing of large distributed datasets — why bigWig is binary, compressed, and indexed for random access
  4. Human genome reference builds - GRCh38 or hg38 - b37 - hg19, GATK — hg19 vs hg38 chromosome naming and size differences
  5. bamCoverage, deepTools 3.5.6 documentation — BAM-to-bigWig alternative path and normalization options