pythonplotseabornkernel-densityprobability-density

Plotting a PDF of an angular distribution in Python


I am trying to plot the PDF of some data I have on the angular orientation of a particles in python, using sns. The data cover the -180,180 degree range and I have problems with the fitting of the distribution especially around the edges

For this image in particular, I used histplot to compare the fit to the histogram of the data (I also tried kdeplot and the result is the same) and you can see the PDF doesn't cover the edges and the sharp transition in the middle

This is the snippet of code I use for the plotting

for shear_value, color in zip(unique_shear_values, shear_palette_colors):
    subset = orientations[orientations['Shear'] == shear_value]
    sns.histplot(data=subset['Angle'], stat="density", kde=True, label=f'Shear: {shear_value}', color=color, bins = 128)

Is there any way to obtain a PDF that better fit the behavior of the data, especially at the edges?


Solution

  • A trick could be to copy all data with the angles below 0 adding 360º. And similarly copying all data with angles above 0 subtracting 360º. Then create the histogram with the double of bins. Also limit the x-axis to the range -180 +180.

    A drawback is that the y-axis will show everything with half the height. Creating a dummy twin axis allows to show the correct y-scaling.

    The following code illustrates the idea:

    import matplotlib.pyplot as plt
    import seaborn as sns
    import numpy as np
    
    # create some reproducible test data
    np.random.seed(20231011)
    y, x = np.random.randn(2, 600, 3).cumsum(axis=1).reshape(2, -1)
    angles = np.degrees(np.arctan2(y, x))
    
    # double the range by repeating the angles to the left and to the right
    angles = np.concatenate([angles, angles[angles < 0] + 360, angles[angles >= 0] - 360])
    
    fig, ax = plt.subplots()
    
    ax_twinx = ax.twinx()
    sns.histplot(angles, kde=True, stat='density',
                 binrange=(-360, 360), bins=360, kde_kws={'bw_adjust': 0.5}, ax=ax_twinx)
    ax.set_xlim(-180, 180)
    ax.set_ylim(0, ax_twinx.get_ylim()[1] * 2) # set the limits to twice the limits of the dummy axis
    ax_twinx.set_yticks([]) # remove the ticks of the dummy axis
    ax.set_ylabel(ax_twinx.get_ylabel()) # copy the y-label
    ax_twinx.set_ylabel(None) # remove the label of the dummy y-axis
    sns.despine()
    ax.set_title('repeating angles left and right')
    
    plt.tight_layout()
    plt.show()
    

    extending the range for a kdeplot of angles