Violin plots
sjr51
#pragma rtGlobals=3 // Use modern global access method and strict wave access.
#include <Percentile and Box Plot>
//Before executing, you need to have plotted the boxplots using Percentile and Box Plot
//Plot the box plots into a target window using all waves from a source window
//This is a limitation of the functions, they rely on two graph windows (source and target) for making the violins.
//A width of 0.1 is good for the box plots and the boxplots look better with whiskers going to 0 and 100 rather than 10 and 90 (Igor default)
Menu "Macros"
"Violin Plot...", ViolinPlot()
"Scrub Violins", ClearViolins()
End
Function ViolinPlot ()
String sourceWindow // window with the waves to make boxplot and violins
String targetWindow // window with the boxplots to add violins
Variable trimValue=1 // violins can be trimmed (default) or plotted with long tails
Variable thickValue=0.1 // this is the width of the boxplot
Prompt sourceWindow, "What is the source graph?", popup, WinList("*", ";", "WIN:1")
Prompt targetWindow, "What is the box plot graph?", popup, WinList("*", ";", "WIN:1")
Prompt trimValue, "yes=1, no=0"
Prompt thickValue, "auto=0.1"
DoPrompt "Pick graphs", sourceWindow, targetWindow, trimValue, thickValue
String/G gSrcWin = sourceWindow //set global strings and variables
String/G gTgtWin = targetWindow
Variable/G gtrim = trimValue
Variable/G gthick = thickValue
DoWindow /F $sourceWindow
String wlist = Wavelist("*",";","WIN:")
String name
Variable nWaves = ItemsInList(wlist)
Variable i
for(i = 0; i < nWaves; i += 1)
name = StringFromList(i,wList) //picks waves from source window and sends them to be plotted as violins in target window
Wave w0 = $name
Violin(w0,i)
endfor
DoWindow /F $targetWindow
SetAxis/A/N=1/E=1 left
SetAxis bottom -0.5,(nWaves-0.5)
End
/// @param w 1D wave for plotting as violin
/// @param xpos variable to describe where violin will go on x-axis
Function Violin(w,xpos)
Wave w
Variable xpos
SVAR srcWin = gSrcWin //set local ref for global strings
SVAR tgtWin = gTgtWin
NVAR trim = gtrim
NVAR thick = gThick
Variable nPoints = numpnts(w)
Variable xMin, xMax
if(trim == 1)
xMin = wavemin(w)
xMax = wavemax(w) // trimmed violins
else
Variable bw = ((4 * sqrt(variance(w))^5) / (3 * numpnts(w)))^0.2 // this is Silverman's rule-of-thumb
xMin = wavemin(w) - (2*bw)
xMax = wavemax(w) + (2*bw) // not trimmed, limit kde to 2 bandwidths beyond min/max
endif
Variable dX = min((xMax - xMin) / 100,1) // delta x for StatsKDE
StatsKDE/Q/BWM=2/DEST=kdeWave/KT=1/S={xMin,dX,xMax} w
WAVE/Z w1 = kdeWave
Variable auc = sum(w1)
w1 /= auc * (thick / 2) // omit *thick for a violin=2 i.e. two half-violins that =1
String wnL = NameofWave(w) + "_kdeL"
Duplicate/D/O w1 $wnL
String wnR = NameofWave(w) + "_kdeR"
Duplicate/D/O w1 $wnR
String wnLx = NameofWave(w) + "_kdeLx"
Duplicate/D/O w1 $wnLx
String wnRx = NameofWave(w) + "_kdeRx"
Duplicate/D/O w1 $wnRx
String vwny = NameofWave(w) + "_kdevy"
String vwnx = NameofWave(w) + "_kdevx"
KillWaves/Z w1
Wave wnL1 = $wnL
Wave wnR1 = $wnR
Wave wnLx1 = $wnLx
Wave wnRx1 = $wnRx
wnLx1 = x //get x scale
wnRx1 = x
if (trim == 1)
InsertPoints 0,1, wnLx1 //add point, set to same as first point to complete the bottom of a trimmed violin
InsertPoints 0,1, wnRx1
wnLx1[0] = wnLx1[1]
wnRx1[0] = wnRx1[1]
InsertPoints 0,1, wnL1
InsertPoints 0,1, wnR1
wnL1[0] = 0
wnR1[0] = 0
endif
wnR1 *= -1 //make the other side of violin
Reverse wnR1, wnRx1 //reverse the righthand side of violin
Concatenate/O/NP {wnL1,wnR1}, $vwny // form the wave for y position of outline (will be x when vertical)
Concatenate/O/NP {wnLx1,wnRx1}, $vwnx // form the wave for x position of outline (will be y when vertical)
wave vwny1 = $vwny
wave vwnx1 = $vwnx
DoWindow/F $tgtWin //bring target window to the front
SetDrawLayer ProgBack // violins are drawn in ProgBack
SetDrawEnv ycoord=left, xcoord=bottom, fillfgc=(65535,0,0),fillpat=3 // violins are red
DrawPoly xpos+vwny1[0],vwnx1[0],1,1, vwny1,vwnx1 // draws violin
KillWaves/Z wnL1,wnR1,wnLx1,wnRx1 // clean-up
SetDrawLayer UserFront // set layer back
End
//Handy function to scrub the violins away
Function ClearViolins()
SVAR tgtWin = gTgtWin
DoWindow /F $tgtWin
SetDrawLayer /K ProgBack // kills ProgBack
SetDrawLayer UserFront
End
#include <Percentile and Box Plot>
//Before executing, you need to have plotted the boxplots using Percentile and Box Plot
//Plot the box plots into a target window using all waves from a source window
//This is a limitation of the functions, they rely on two graph windows (source and target) for making the violins.
//A width of 0.1 is good for the box plots and the boxplots look better with whiskers going to 0 and 100 rather than 10 and 90 (Igor default)
Menu "Macros"
"Violin Plot...", ViolinPlot()
"Scrub Violins", ClearViolins()
End
Function ViolinPlot ()
String sourceWindow // window with the waves to make boxplot and violins
String targetWindow // window with the boxplots to add violins
Variable trimValue=1 // violins can be trimmed (default) or plotted with long tails
Variable thickValue=0.1 // this is the width of the boxplot
Prompt sourceWindow, "What is the source graph?", popup, WinList("*", ";", "WIN:1")
Prompt targetWindow, "What is the box plot graph?", popup, WinList("*", ";", "WIN:1")
Prompt trimValue, "yes=1, no=0"
Prompt thickValue, "auto=0.1"
DoPrompt "Pick graphs", sourceWindow, targetWindow, trimValue, thickValue
String/G gSrcWin = sourceWindow //set global strings and variables
String/G gTgtWin = targetWindow
Variable/G gtrim = trimValue
Variable/G gthick = thickValue
DoWindow /F $sourceWindow
String wlist = Wavelist("*",";","WIN:")
String name
Variable nWaves = ItemsInList(wlist)
Variable i
for(i = 0; i < nWaves; i += 1)
name = StringFromList(i,wList) //picks waves from source window and sends them to be plotted as violins in target window
Wave w0 = $name
Violin(w0,i)
endfor
DoWindow /F $targetWindow
SetAxis/A/N=1/E=1 left
SetAxis bottom -0.5,(nWaves-0.5)
End
/// @param w 1D wave for plotting as violin
/// @param xpos variable to describe where violin will go on x-axis
Function Violin(w,xpos)
Wave w
Variable xpos
SVAR srcWin = gSrcWin //set local ref for global strings
SVAR tgtWin = gTgtWin
NVAR trim = gtrim
NVAR thick = gThick
Variable nPoints = numpnts(w)
Variable xMin, xMax
if(trim == 1)
xMin = wavemin(w)
xMax = wavemax(w) // trimmed violins
else
Variable bw = ((4 * sqrt(variance(w))^5) / (3 * numpnts(w)))^0.2 // this is Silverman's rule-of-thumb
xMin = wavemin(w) - (2*bw)
xMax = wavemax(w) + (2*bw) // not trimmed, limit kde to 2 bandwidths beyond min/max
endif
Variable dX = min((xMax - xMin) / 100,1) // delta x for StatsKDE
StatsKDE/Q/BWM=2/DEST=kdeWave/KT=1/S={xMin,dX,xMax} w
WAVE/Z w1 = kdeWave
Variable auc = sum(w1)
w1 /= auc * (thick / 2) // omit *thick for a violin=2 i.e. two half-violins that =1
String wnL = NameofWave(w) + "_kdeL"
Duplicate/D/O w1 $wnL
String wnR = NameofWave(w) + "_kdeR"
Duplicate/D/O w1 $wnR
String wnLx = NameofWave(w) + "_kdeLx"
Duplicate/D/O w1 $wnLx
String wnRx = NameofWave(w) + "_kdeRx"
Duplicate/D/O w1 $wnRx
String vwny = NameofWave(w) + "_kdevy"
String vwnx = NameofWave(w) + "_kdevx"
KillWaves/Z w1
Wave wnL1 = $wnL
Wave wnR1 = $wnR
Wave wnLx1 = $wnLx
Wave wnRx1 = $wnRx
wnLx1 = x //get x scale
wnRx1 = x
if (trim == 1)
InsertPoints 0,1, wnLx1 //add point, set to same as first point to complete the bottom of a trimmed violin
InsertPoints 0,1, wnRx1
wnLx1[0] = wnLx1[1]
wnRx1[0] = wnRx1[1]
InsertPoints 0,1, wnL1
InsertPoints 0,1, wnR1
wnL1[0] = 0
wnR1[0] = 0
endif
wnR1 *= -1 //make the other side of violin
Reverse wnR1, wnRx1 //reverse the righthand side of violin
Concatenate/O/NP {wnL1,wnR1}, $vwny // form the wave for y position of outline (will be x when vertical)
Concatenate/O/NP {wnLx1,wnRx1}, $vwnx // form the wave for x position of outline (will be y when vertical)
wave vwny1 = $vwny
wave vwnx1 = $vwnx
DoWindow/F $tgtWin //bring target window to the front
SetDrawLayer ProgBack // violins are drawn in ProgBack
SetDrawEnv ycoord=left, xcoord=bottom, fillfgc=(65535,0,0),fillpat=3 // violins are red
DrawPoly xpos+vwny1[0],vwnx1[0],1,1, vwny1,vwnx1 // draws violin
KillWaves/Z wnL1,wnR1,wnLx1,wnRx1 // clean-up
SetDrawLayer UserFront // set layer back
End
//Handy function to scrub the violins away
Function ClearViolins()
SVAR tgtWin = gTgtWin
DoWindow /F $tgtWin
SetDrawLayer /K ProgBack // kills ProgBack
SetDrawLayer UserFront
End
Edit: updated this procedure to use StatsKDE. Only runs in IP7. Violin Demo file is out of date.
Forum
Support
Gallery
Igor Pro 9
Learn More
Igor XOP Toolkit
Learn More
Igor NIDAQ Tools MX
Learn More
Hello! I'm trying to use this to make violin plots with my data but I'm running into some issues I just can't quite get my head around. When I run the script with the "demo" data everything works fine, but when I try to run the script with my own data things go wrong. The boxplot x-axis just expands and there is no violin plots that is overlaid on it. I also tried making a "dummy wave" with 20 random values and trying to make the violin plot with that, but it would do the same thing. I've attached some screenshots of what happens when I run the macro, and I also attached one of the waves that I'm trying to represent as a violin plot. Any help or guidance would be much appreciated! Thanks in advance!
July 22, 2018 at 03:59 pm - Permalink
I should point out that Igor 8 has violin plots built-in.
See https://www.wavemetrics.com/products/igorpro/creatinggraphs/2dgraphs/li…
July 22, 2018 at 05:54 pm - Permalink
I saw that actually but wanted to avoid having to update in the middle of a project in case any incompatibilities came up with a toolkit I was using...but I guess I'll have to give it a go in Igor 8. Thanks!
July 22, 2018 at 06:38 pm - Permalink
In reply to I saw that actually but… by JoeKo91
As Jim says IP8 has support for Violins and improved box plots. They're also far more customisable than this code snippet.
July 22, 2018 at 11:12 pm - Permalink
In reply to As Jim says IP8 has support… by sjr51
Hi SJR51,
I hope this message finds you well. I want to express my sincere gratitude for taking the time to read my message. I consider myself fortunate that you have developed the violin plot macro for Igor 6. As a first-year graduate student at Kent State University, our lab primarily utilizes Igor 6, and I had been diligently searching for a solution. It was through my research that I discovered your invaluable contribution as the sole creator of the violin plot on the internet.
I deeply appreciate your hard work and dedication to this project. I downloaded your Igor experiment file and followed the provided instructions meticulously. Everything worked seamlessly, with one minor exception. I encountered some difficulty in adding data points as marker, akin to Igor Pro 9 as depicted in the second picture. I was wondering if you could kindly offer some guidance on how to achieve this functionality within the limitations of Igor 6.
In our relatively small lab, the high cost of Igor Pro 9 is beyond our means, so I am particularly interested in optimizing the capabilities of Igor 6. Nevertheless, I want to emphasize my gratitude for all the effort and expertise you have poured into developing this plot. Your work truly showcases your remarkable skills as an engineer and researcher.
Thank you once again for your invaluable contribution to the Igor community.
Sincerely,
zach
October 17, 2023 at 10:48 am - Permalink
The current price today, Oct. 17, 2023, to upgrade a single-user academic license from Igor 6 to Igor 9 is $305 (other upgrade options vary in price). If you are making box plots and violin plots, the upgrade will make your life easier!
October 17, 2023 at 03:56 pm - Permalink