Violin plots

A violin plot is enhanced form of boxplot that also shows a kernel density estimation of the data. This is useful for bimodal data that are 'concealed' by a boxplot (Hintze & Nelson (1998) The American Statistician 52(2):181-4). The following code comes following a discussion in the forums on how to do this in IgorPro (http://www.igorexchange.com/node/6141). The 1D kernel density estimation method used here is Silverman's rule-of-thumb (Bowman & Azzalini (1997) Applied Smoothing Techniques for Data Analysis, OSP). R also uses this, but has options for other smoothing methods. The implementation here is to first make a boxplot. The code then calculates the violins and places them behind the boxplots in ProgBack (see example experiment). This could be turned into a Project to incorporate this into the WavePercentiles.ipf to generate Violin Plots directly, add other options for colour/formatting the boxplot etc.

#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


Edit: updated this procedure to use StatsKDE. Only runs in IP7. Violin Demo file is out of date.
ViolinDemo.pxp (79.43 KB)

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!

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!

In reply to 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

 

 

 

 

Image_20231017133152.png (52.92 KB) Graph0.png (25.15 KB)

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!

Forum

Support

Gallery

Igor Pro 9

Learn More

Igor XOP Toolkit

Learn More

Igor NIDAQ Tools MX

Learn More