Lawrence Technological University
College of Arts and Science
Department of Mathematics and Computer Sciences

### A Slopegraph -- Self Referral Loopholes and Treatment Selection

"Urologists' Use of Intensity-Modulated Radiation Therapy for Prostate Cancer," Jean M. Mitchell, Ph.D., The New England Journal of Medicine, October 24, 2013;369:1629-37, showed that reimbursement loopholes have a significant effect on treatment selection. The stacked bar charts in this article clarify the secondary point that almost all of the growth in use of IMRT was in self referrals. Would a slopegraph like those introduced by Edward Tufte help illustrate the magnitude of the primary point?

Laying out the slopegraph requires a little trial and error to keep the output to less than a page but big enough to accurately represent the magnitude of the slopes and preserve the relationships among each slope and its end labels.

R would be a good place to start. R already has some libraries that help with slopegraphs. This is another approach -- mixing two widely available pieces of software, Ruby and LaTeX (Ruby 1.9.3 and TeX Live 2013.) The TeX Live Utility allows you to add the Gill Sans like font to the installed packages. The gillium package was only just added to LaTeX in December, 2013.

Gurari's DraTeX facilitates accurately aligning text by drawing a bounding box around the text and providing an ancillary coordinate system with [0,0] at the horizontal center of the text on its baseline.

\EntryExit(1,0,1,0) \Text(--Percentage of Cases--)
% This LaTeX reads: Draw the text "Percentage of Cases."  Assume the pen
% is at ancillary coordinates [1,0] (i.e. the center of the right edge of
% the text bounding box) when the drawing starts and returns there when the
% drawing ends.


A Ruby program:

# slopegraph.rb
=begin
A Ruby program to lay out a slopegraph.  The Ruby program's output
is TeX/LaTeX with Eitan Gurari's DraTeX package and Hirwen Harendal's
Gillium package.  The scaling and grouping calculations are done in
Ruby, leaving just the drawing of the slopes and the typesetting of
the groups of labels to be completed in LaTeX.

For this layout program there are 4 somewhat arbitrary design decisions:
1. Lines of labels are no closer than \baselineskip (12pts for 10pt font).
2. The scale will be increased until no more than 3 labels are placed
in a single group because they are separated by less than \baselineskip
3. For the first guess at scale we assume the y values are a little less
evenly divided than the labels in a bump chart.
4. Ruby's oddly named "inject" method may be only appreciated by old
C programmers who miss counted "for" loops.  e.g.
@labels.inject(0.0) {|sum,l| sum + l} / @labels.size
This program will also use this construct to build columns of labels
from the list of slopes.
=end
# Constants used in creating the TeX source file.
FileName = "SRvNSR"
% slopegraph.tex
\\documentclass{article}
\\usepackage{dratex,aldratex}
\\usepackage{gillius}
\\pagestyle{empty}
\\begin{document}

A slopegraph to illustrate a difference-in-differences analysis to
evaluate changes in IMRT use according to self-referral status.''
Data: Urologists' Use of Intensity-Modulated Radiation Therapy for
Prostate Cancer,'' Jean M. Mitchell, Ph.D., \\textit{The New
England Journal of Medicine}, October 24, 2013;369:1629-37.
Treatment selection in urology practices that own IMRT
facilities and self refer \\textit{v.} matched practices which do
not own IMRT facilities or self refer.\\\\
Chart source code:
\\texttt{http://medicalopensource.net/mcs/imrt-slopegraph.html}

\\vspace{\\fill}
\\sffamily
\\Draw
TexEnd = <<FOOTER
\\EndDraw
\\end{document}
FOOTER
TimeLabel = ["\\EntryExit(1,0,1,0) \\Text(--\\hfill Percentage of Cases in~~" +
"\\hfill Pre-Ownership Period--)",
"\\EntryExit(-1,0,-1,0) \\Text(--Percentage of cases in \\hfill" +
"~~Ownership Period \\hfill--)"]
# Slope data: label => [value-time-0,value-time-1]
Slopes = {"SR IMRT" => [13.1,38.6],
"SR Brachytherapy" => [18.6,5.6],
"SR Prostatectomy" => [17.7,16.6],
"SR Androgen deprivation" => [16.5,8.4],
"SR Active Surveillance" => [26.7,27.0],
"SR Other" => [7.3,3.9],
"no SR IMRT" => [14.3,15.6],
"no SR Brachytherapy" => [18.9,17.9],
"no SR Prostatectomy" => [21.9,23.8],
"no SR Androgen deprivation" => [15.6,11.4],
"no SR Active Surveillance" => [26.1,27.4],
"no SR Other" => [3.2,3.9]}
# Constants for the layout of the chart.
SlopeWidth = 100 # pts
Time0x = 50 # pts
MaxGroup = 3
Baselineskip = 12
ScaleMultiplier = 1.1
HighValue = Slopes.values.flatten.max
LowValue = Slopes.values.flatten.min
RangeValues = HighValue - LowValue
# Non-constant global with units of pts / percent.
$scale = Slopes.size * Baselineskip * ScaleMultiplier / RangeValues # A container class for groups of labels that are close together and are # drawn as a single node centered at the average value of the group. class LabelNode def initialize(label) @labels = [label] end def <<(label) @labels << label end def avg @labels.inject(0.0) {|sum,l| sum + l} / @labels.size end def near?(label) (label - avg).abs < Baselineskip * (@labels.size + 1) / 2.0 /$scale
end

def draw(time)
if time == 0
"\\EntryExit(1,0,1,0) " +
"\\Text(--#{@labels.reverse.map{|l| "\\hfill " + l}.join("~~")}--)"
else # time == 1
"\\EntryExit(-1,0,-1,0) " +
"\\Text(--#{@labels.reverse.map{|l|  l + " \\hfill"}.join("~~")}--)"
end
end
end
# Prepare the 2 lists of slope labels from the list of slopes.
labels = []
# For x = time 0, in ascending order: [value, "label value"]
labels = Slopes.sort{|a,b| a <=> b}.inject([]) do |lbs,s|
lbs << [s,s + " " + s.to_s]
end
# For x = time 1, in ascending order: [value, "value label"]
labels = Slopes.sort{|a,b| a <=> b}.inject([]) do |lbs,s|
lbs << [s,s.to_s + " " + s]
end
# Group labels in each list less than baselineskip * scale apart.
crowded = true
while crowded
max_len = 1
label_nodes = []
(0..1).each do |i|
(0...labels[i].size).each do |j|
if !label_nodes[i]
label_nodes[i] = [LabelNode.new(labels[i][j])]
elsif label_nodes[i][-1].near?(labels[i][j])
len = (label_nodes[i][-1] << labels[i][j]).size
max_len = len if len > max_len
else
label_nodes[i] += [LabelNode.new(labels[i][j])]
end
end
end
# Summarize each pass.
puts "Scale is #{$scale} pts/percent." puts "Maximum labels in a vertical grouping is #{max_len}" if max_len > MaxGroup crowded = true$scale *= ScaleMultiplier
else
crowded = false
end
# p label_nodes # Uncomment for debugging.
end

# Prepare the TeX file.
open(FileName + ".tex","w") do |f|
f.puts TexBegin
Slopes.each do |s|
f.puts "\\MoveTo(#{Time0x},#{(s - LowValue) * $scale})" f.puts "\\LineTo(#{Time0x + SlopeWidth},#{(s - LowValue) *$scale})"
end
(0..1).each do |i|
label_nodes[i].each do |n|
f.puts "\\MoveTo(#{Time0x - 5 + (SlopeWidth + 10) * i}," +
"#{(n.avg - LowValue) * $scale})" f.puts n.draw(i) end # Print column header. f.puts "\\MoveTo(#{Time0x - 5 + (SlopeWidth + 10) * i}," + "#{RangeValues *$scale + Baselineskip * 2})"
f.puts TimeLabel[i]
end
f.puts TexEnd
end
# Prepare the output using LaTeX.
puts pdflatex -output-format pdf #{FileName}.tex
puts pdflatex -output-format dvi #{FileName}.tex
puts dvipng -T tight -D 150 -o #{FileName}.png #{FileName}.dvi


The output

• On the console:
\$ ruby slopegraph.rb
Scale is 4.474576271186441 pts/percent.
Maximum labels in a vertical grouping is 10
Scale is 4.922033898305085 pts/percent.
Maximum labels in a vertical grouping is 10
Scale is 5.4142372881355945 pts/percent.
Maximum labels in a vertical grouping is 10
Scale is 5.955661016949154 pts/percent.
Maximum labels in a vertical grouping is 10
Scale is 6.5512271186440705 pts/percent.
Maximum labels in a vertical grouping is 8
Scale is 7.206349830508478 pts/percent.
Maximum labels in a vertical grouping is 8
Scale is 7.926984813559327 pts/percent.
Maximum labels in a vertical grouping is 8
Scale is 8.71968329491526 pts/percent.
Maximum labels in a vertical grouping is 7
Scale is 9.591651624406786 pts/percent.
Maximum labels in a vertical grouping is 5
Scale is 10.550816786847466 pts/percent.
Maximum labels in a vertical grouping is 5
Scale is 11.605898465532213 pts/percent.
Maximum labels in a vertical grouping is 3
This is pdfTeX, Version 3.1415926-2.5-1.40.14 (TeX Live 2013)
...
Output written on SRvNSR.pdf (1 page, 66781 bytes).
...
This is pdfTeX, Version 3.1415926-2.5-1.40.14 (TeX Live 2013)
...
Output written on SRvNSR.dvi (1 page, 22024 bytes).
... 