Skip to content

Instantly share code, notes, and snippets.

@seabbs
Last active August 3, 2025 22:13
Show Gist options
  • Save seabbs/3cbd4e5bcdba30deec3081ba3838d556 to your computer and use it in GitHub Desktop.
Save seabbs/3cbd4e5bcdba30deec3081ba3838d556 to your computer and use it in GitHub Desktop.
CensoredDistributions.jl Interactive Demo with UnicodePlots
using UnicodePlots
using CensoredDistributions, Distributions
println("=== CensoredDistributions.jl Demo ===")
println("This package extends Distributions.jl to support censored distributions")
println("commonly needed in epidemiological applications.\n")
# Define the true distribution
true_dist = LogNormal(1.5, 0.75)
println("Starting with a LogNormal(1.5, 0.75) distribution")
println("This could represent, for example, an incubation period distribution.\n")
# Create different censoring scenarios
primary_event = Uniform(0, 7)
println("Primary event censoring window: Uniform(0, 7)")
println("This represents uncertainty in when the initial event (e.g., exposure) occurred.\n")
# 1. True distribution (no censoring)
println("1. TRUE DISTRIBUTION (no censoring)")
println(" This is our baseline - the actual underlying distribution")
# 2. Primary censoring only with truncation
println("\n2. PRIMARY CENSORING + TRUNCATION")
println(" Primary censoring accounts for uncertainty in the timing of the initial event")
println(" Truncation removes unobserved values (e.g., delays > 10 days)")
primary_only = primary_censored(true_dist, primary_event)
primary_truncated = truncated(primary_only; upper=10.0)
println(" Created: primary_censored(LogNormal, Uniform(0,7)) truncated at 10")
# 3. Within interval censoring with same truncation bounds
println("\n3. WITHIN INTERVAL CENSORING")
println(" Interval censoring accounts for discrete observation windows")
println(" E.g., symptoms reported to the nearest day rather than exact time")
within_interval = interval_censored(true_dist, 1.0)
within_interval_truncated = truncated(within_interval; upper=10.0)
println(" Created: interval_censored(LogNormal, 1.0) - observations rounded to nearest day")
# 4. Double interval censoring (primary + truncation + interval)
println("\n4. DOUBLE INTERVAL CENSORING")
println(" Combines primary event uncertainty + interval censoring + truncation")
println(" This is the most realistic scenario for epidemiological data")
double_censored_dist = double_interval_censored(true_dist; primary_event=primary_event,
upper=10.0, interval=1.0)
println(" Created: double_interval_censored with all effects combined")
# Plot CDFs for comparison
println("\n=== CUMULATIVE DISTRIBUTION FUNCTION COMPARISON ===")
println("CDFs show the probability of observing a value ≤ x")
x = 0:0.01:10
println("CDF Comparison:")
p1 = lineplot(x, cdf.(true_dist, x), title="Censoring Comparison",
name="True", color=:blue, width=80, height=20)
lineplot!(p1, x, cdf.(primary_truncated, x), name="Primary + Truncated",
color=:red)
lineplot!(p1, x, cdf.(within_interval_truncated, x), name="Within Interval [1,10]",
color=:yellow)
lineplot!(p1, x, cdf.(double_censored_dist, x), name="Double Censored",
color=:green)
println(p1)
# Generate samples for histogram comparison
println("\n=== SAMPLING FROM CENSORED DISTRIBUTIONS ===")
println("Generating 1000 samples from each distribution to show their shapes")
n_samples = 1000
println("Drawing $n_samples samples from each distribution...")
true_samples = rand(true_dist, n_samples)
println("✓ True distribution samples generated")
primary_samples = rand(primary_truncated, n_samples)
println("✓ Primary censored + truncated samples generated")
within_samples = rand(within_interval_truncated, n_samples)
println("✓ Within interval censored samples generated")
double_samples = rand(double_censored_dist, n_samples)
println("✓ Double interval censored samples generated")
# Plot histograms
println("\n=== HISTOGRAM COMPARISON ===")
println("Histograms show how censoring affects the observed distribution shape")
println("\nTrue Distribution:")
println("This is what we would observe with perfect measurement")
println(histogram(true_samples, nbins=15, title="True LogNormal"))
println("\nPrimary Censoring + Truncated:")
println("Notice how primary event uncertainty shifts the distribution")
println(histogram(primary_samples, nbins=15, title="Primary Censored + Truncated [0,10]"))
println("\nWithin Interval Censoring:")
println("Interval censoring creates a 'discretised' version of the continuous distribution")
println(histogram(within_samples, nbins=15, title="Within Interval [1,10]"))
println("\nDouble Interval Censored:")
println("This combines all censoring effects - most realistic for real epidemiological data")
println(histogram(double_samples, nbins=15, title="Double Censored (primary + truncation + interval)"))
println("\n=== SUMMARY ===")
println("CensoredDistributions.jl enables:")
println("• Primary event censoring (uncertainty in initial event timing)")
println("• Interval censoring (discrete observation windows)")
println("• Truncation (removing unobserved values)")
println("• Combinations of all censoring types")
println("• Full integration with Distributions.jl API (pdf, cdf, rand, etc.)")
println("• Efficient analytical solutions where possible")
println("\nThis is essential for realistic epidemiological modelling!")

CensoredDistributions.jl Interactive Demo

Quick Start

Copy and paste this code to run the demo:

using Pkg
Pkg.activate(temp=true)
Pkg.add(["CensoredDistributions", "Distributions", "UnicodePlots"])
include(download("https://gist.github.com/seabbs/3cbd4e5bcdba30deec3081ba3838d556/raw/censoreddistributions-demo-gist.jl"))

Expected Output

=== CensoredDistributions.jl Demo ===
This package extends Distributions.jl to support censored distributions
commonly needed in epidemiological applications.

Starting with a LogNormal(1.5, 0.75) distribution
This could represent, for example, an incubation period distribution.

Primary event censoring window: Uniform(0, 7)
This represents uncertainty in when the initial event (e.g., exposure) occurred.

1. TRUE DISTRIBUTION (no censoring)
   This is our baseline - the actual underlying distribution

2. PRIMARY CENSORING + TRUNCATION
   Primary censoring accounts for uncertainty in the timing of the initial event
   Truncation removes unobserved values (e.g., delays > 10 days)
   Created: primary_censored(LogNormal, Uniform(0,7)) truncated at 10

3. WITHIN INTERVAL CENSORING
   Interval censoring accounts for discrete observation windows
   E.g., symptoms reported to the nearest day rather than exact time
   Created: interval_censored(LogNormal, 1.0) - observations rounded to nearest day

4. DOUBLE INTERVAL CENSORING
   Combines primary event uncertainty + interval censoring + truncation
   This is the most realistic scenario for epidemiological data
   Created: double_interval_censored with all effects combined

=== CUMULATIVE DISTRIBUTION FUNCTION COMPARISON ===
CDFs show the probability of observing a value ≤ x
CDF Comparison:
       ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀Censoring Comparison⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀                       
       ┌────────────────────────────────────────────────────────────────────────────────┐                       
   0.9 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⢀⣴⣋⣀⣀⣀⣀⣀⣸│ True                  
       │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠉⠉⠉⠉⠉⠉⠉⠉⠀⠀⠀⠀⠀⣠⣞⣹⠤⠴⠒⠒⠋⠉⠉⠁│ Primary + Truncated   
       │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⢀⣀⡤⠤⠖⠒⡫⠟⠁⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀│ Within Interval [1,10]
       │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠉⠉⠉⠉⠉⠉⠉⢉⣠⠤⠖⠒⠉⠉⠀⠀⢀⡴⠋⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀│ Double Censored       
       │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠤⠤⠤⠤⠤⠤⣤⠼⠖⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠔⠋⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│                       
       │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⣀⡤⠒⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⠁⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│                       
       │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣸⠖⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣞⣁⣀⣀⣀⣀⣸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│                       
       │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠤⠤⠤⢤⡤⠾⠭⠼⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡴⢻⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│                       
       │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⢀⡤⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠔⠋⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│                       
       │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⢾⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠞⠁⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│                       
       │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡴⠋⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⢴⠛⠒⠒⠒⠒⠒⠒⠚⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│                       
       │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠒⣲⠞⠓⠒⠒⠒⠚⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡤⠚⠁⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│                       
       │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⠖⠉⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│                       
       │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠔⠋⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⢶⠛⠒⠒⠒⠒⠒⠒⠚⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│                       
       │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⠁⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠖⠋⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│                       
       │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⡶⠚⠓⠒⠒⠒⠒⠚⠀⠀⠀⠀⠀⠀⠀⣀⠴⠾⠭⠤⠤⠤⠤⠼⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│                       
       │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠖⢻⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⠤⠒⠋⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│                       
       │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠴⠋⠁⠀⢸⠀⠀⠀⠀⠀⣀⣠⢤⠶⠾⠭⠤⠤⠤⠤⠼⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│                       
     0 │⣀⣀⣀⣀⣀⣀⣤⣤⣒⣛⣓⣒⣒⣒⣶⣾⠤⠶⠶⠯⠭⠥⠤⠼⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│                       
       └────────────────────────────────────────────────────────────────────────────────┘                       
       ⠀0⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀10⠀                       

=== SAMPLING FROM CENSORED DISTRIBUTIONS ===
Generating 1000 samples from each distribution to show their shapes
Drawing 1000 samples from each distribution...
✓ True distribution samples generated
✓ Primary censored + truncated samples generated
✓ Within interval censored samples generated
✓ Double interval censored samples generated

=== HISTOGRAM COMPARISON ===
Histograms show how censoring affects the observed distribution shape

True Distribution:
This is what we would observe with perfect measurement
                              True LogNormal               
                ┌                                        ┐ 
   [ 0.0,  5.0) ┤███████████████████████████████████  559  
   [ 5.0, 10.0) ┤████████████████████▎ 305                  
   [10.0, 15.0) ┤█████▊ 94                                 
   [15.0, 20.0) ┤█▍ 22                                     
   [20.0, 25.0) ┤▌ 9                                       
   [25.0, 30.0) ┤▍ 5                                       
   [30.0, 35.0) ┤▎ 3                                       
   [35.0, 40.0) ┤▏ 1                                       
   [40.0, 45.0) ┤  0                                       
   [45.0, 50.0) ┤▏ 1                                       
   [50.0, 55.0) ┤  0                                       
   [55.0, 60.0) ┤▏ 1                                       
                └                                        ┘ 
                                 Frequency                 

Primary Censoring + Truncated:
Notice how primary event uncertainty shifts the distribution
                    Primary Censored + Truncated [0,10]    
                ┌                                        ┐ 
   [ 0.0,  1.0) ┤▎ 1                                       
   [ 1.0,  2.0) ┤████▋ 21                                  
   [ 2.0,  3.0) ┤████████████▎ 55                          
   [ 3.0,  4.0) ┤██████████████████▍ 83                    
   [ 4.0,  5.0) ┤█████████████████████▊ 99                 
   [ 5.0,  6.0) ┤███████████████████████████████████  159  
   [ 6.0,  7.0) ┤█████████████████████████████████▎ 151    
   [ 7.0,  8.0) ┤█████████████████████████████▌ 134        
   [ 8.0,  9.0) ┤█████████████████████████████████▋ 153    
   [ 9.0, 10.0) ┤███████████████████████████████▋ 144      
                └                                        ┘ 
                                 Frequency                 

Within Interval Censoring:
Interval censoring creates a 'discretised' version of the continuous distribution
                          Within Interval [1,10]           
                ┌                                        ┐ 
   [ 0.0,  1.0) ┤███████▋ 40                               
   [ 1.0,  2.0) ┤██████████████████████████▏ 137           
   [ 2.0,  3.0) ┤███████████████████████████████████  184  
   [ 3.0,  4.0) ┤████████████████████████████████▊ 173     
   [ 4.0,  5.0) ┤████████████████████████▎ 127             
   [ 5.0,  6.0) ┤███████████████▏ 79                       
   [ 6.0,  7.0) ┤█████████████▌ 71                         
   [ 7.0,  8.0) ┤██████████████▋ 77                        
   [ 8.0,  9.0) ┤████████▌ 45                              
   [ 9.0, 10.0) ┤██████▌ 34                                
   [10.0, 11.0) ┤██████▍ 33                                
                └                                        ┘ 
                                 Frequency                 

Double Interval Censored:
This combines all censoring effects - most realistic for real epidemiological data
                Double Censored (primary + truncation + interval) 
                ┌                                        ┐ 
   [ 0.0,  1.0) ┤▎ 1                                       
   [ 1.0,  2.0) ┤██▎ 11                                    
   [ 2.0,  3.0) ┤███████████▊ 61                           
   [ 3.0,  4.0) ┤███████████████▉ 82                       
   [ 4.0,  5.0) ┤██████████████████████▌ 116               
   [ 5.0,  6.0) ┤██████████████████████████▍ 135           
   [ 6.0,  7.0) ┤███████████████████████████████▎ 160      
   [ 7.0,  8.0) ┤███████████████████████████████████  180  
   [ 8.0,  9.0) ┤██████████████████████████████▎ 155       
   [ 9.0, 10.0) ┤███████████████████▍ 99                   
                └                                        ┘ 
                                 Frequency                 

=== SUMMARY ===
CensoredDistributions.jl enables:
• Primary event censoring (uncertainty in initial event timing)
• Interval censoring (discrete observation windows)
• Truncation (removing unobserved values)
• Combinations of all censoring types
• Full integration with Distributions.jl API (pdf, cdf, rand, etc.)
• Efficient analytical solutions where possible

This is essential for realistic epidemiological modelling!

What This Demo Shows

  • Primary censoring shifts the distribution later due to uncertain event timing
  • Interval censoring discretises continuous distributions to match observation windows
  • Double censoring combines both effects for realistic epidemiological modelling
  • UnicodePlots provides terminal-based visualisation perfect for Julia REPL exploration

Perfect for understanding how censoring affects your epidemiological models!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment