-2

Trying to shade the bars in this chart based on the confidence that a selected y-value (represented by the red line) lies within a confidence interval. See recolorBars() method in the class example below.

While I understand colormaps, Normalize(), and ScalarMappable(), I'm stumped on which values to pass to Normalize() to create a color and shade for each bar.

Here's what my chart looks like when first generated. Interactive Bar Chart Screenshot

To generate the chart pictured above, call chart = interactiveChart(). The interactivity is based on click events, which trigger the setAxHLine() callback to set a red horizontal bar at the selected Y-val position. Eventually this method will also trigger the recolorBars() method.

Tested Code Example: (Notice that this is set up to run in a Jupyter notebook.)

%matplotlib notebook
# setup the environment 
import pandas as pd
import numpy as np
import statsmodels.stats.api as sms # for confidence intervals
from scipy.stats import sem # another confidence interval shorthand 
import matplotlib.cm as cm
import matplotlib.colors as col
import matplotlib.pyplot as plt
import mpl_toolkits.axes_grid1.inset_locator as mpl_il
from matplotlib.widgets import Button, Slider
# from matplotlib.ticker import FormatStrFormatter, ScalarFormatter

class interactiveBarChart:
    """
    A base class that can be used for creating clicable charts and solving
    the challenges of interpreting plots with confidence intervals.
    """
    # basic greys: lighter for regular, darker for emphasis
    greys = ['#afafaf','#7b7b7b'] # ticks and boxes, arrows, legend ticks and text
    # horizontal bar: nice red
    horzo_bar = '#004a80'
    # set bar colormap
    cmap = cm.get_cmap('RdBu')

    # instantiate the class
    def __init__(self): 
        """
        Initialize the data and a new figure.
        """
        # seed for data.
        np.random.seed(12345)
        # get some data to plot
        self.df = pd.DataFrame(np.c_[np.random.normal(33500,150000,3650), # np.c_ class to transpose array
                   np.random.normal(41000,90000,3650), 
                   np.random.normal(41000,120000,3650), 
                   np.random.normal(48000,55000,3650)], 
                  columns=[1992,1993,1994,1995])
        # get mean values to plot
        self.means = self.df.mean()        
        # calculate confidence interval high and low
        self.c_i = [ sms.DescrStatsW(self.df[i]).tconfint_mean() for i in self.df.columns ]
        # calculate the interval whole number
        self.intervals = [ invl[-1] - invl[0] for invl in self.c_i ] 

        # plot the bar chart and make a reference to the rectangles
        self.rects = plt.bar(
            range(len(self.df.columns)), 
            self.means,
            yerr=self.df.sem().values*1.96,
            align='center', 
            alpha=0.8, 
            color=self.greys[0],
            error_kw=dict(ecolor='gray', lw=2, capsize=7, capthick=2)
        )

        # set up a starting axhline
        self.horzo_slider = plt.axhline(y=40000, xmin=-.1, clip_on=False, zorder=1, color='#e82713')

        ## TICKS AND TEXT AND SPINES
        plt.title('Confidence Interval Interactivity: Click the Chart To Recolor', color=self.greys[1])
        plt.xticks(range(len(self.df.columns)), self.df.columns)        
        # do some formatting 
        self.formatArtists(plt.gca())


        ## EVENT HANDLING
        # reference the axes and setup pick events
        plt.gcf().canvas.mpl_connect('button_press_event', self.setAxHLine)


    def formatArtists(self, ax):
        """
        Does some recoloring and formatting of the ticks, labels, and spines.
        Receives the axes of the current figure.
        """
        # recolor the ticks
        ax.xaxis.set_tick_params(which='major', colors=self.greys[1])
        ax.yaxis.set_tick_params(which='major', colors=self.greys[1])

        # recolor the spines
        for pos in ['top', 'right', 'bottom', 'left']:
            ax.spines[pos].set_edgecolor(self.greys[0])


    ## EVENT HANDLERS
    def setAxHLine(self, event): 
        """
        Handle the logic for handling bar coloring when the slider 
        is moved up or down over the confidence intervals.
        """
        # remove first axhline
        self.horzo_slider.remove()
        self.horzo_slider = plt.axhline(y=event.ydata, xmin=-.1, clip_on=False, zorder=1, color='#e82713')
        # self.recolorBars(event)


    def recolorBars(self, event):
        """
        Handles all recoloring of the bars based on the confidence that the selected y-value is within a given interval on the chart.
        This function is called on a button press event and receives that data as an argument.
        """        

        # get the yval 
        y = event.ydata

        # how to determine the shades ?
#         abs_diffs = [ abs((mean + conf)-y|) for mean, conf in zip(self.means, self.intervals) ]

         # how to pass in the map to get the colors to apply to the bars?        
#        colors = [ cm.ScalarMappable(norm=col.Normalize(vmin=i[0] , vmax=i[-1]), cmap=self.cmap) for i in self.c_i ]

        # apply the colors in a list comprehension
        # [ rect.set_color(color) for rect, color in zip(self.rects, colors) ]


    def showPlot(self):
        """
        Convenience if not using the inline display setup %matplotlib notebook
        """
        plt.show()
Adestin
  • 153
  • 3
  • 15
  • Possible duplicate of [How to change colors automatically once a parameter is changed](http://stackoverflow.com/questions/43133017/how-to-change-colors-automatically-once-a-parameter-is-changed) – ImportanceOfBeingErnest Apr 26 '17 at 19:53
  • @ImportanceOfBeingErnest I looked at that code and found they had implemented something similar but a different way. They did solve for selecting a color value. But while they're redrawing each bar in a plot every time a y-value is selected, I simply want to change a color each time a y-value is selected. – Adestin Apr 28 '17 at 14:10
  • In the solution to the linked question only the color is changed, the bars keep their height and position. I do not see any difference at all between the approach there in your example. If you don't think you can use this solution, you would need to explain why and be more specific about the actual problem you have. – ImportanceOfBeingErnest Apr 28 '17 at 14:19
  • I suppose you were not allowed to put your Week 3 Assignment code here. It would be more ethically to design a similar example if you have a question for StackOverflow. – franchb Jul 14 '17 at 05:55
  • @IlyaRusin your English isn't clear. I think you're asking if it's ethical to post questions from a class here? If so, your instructor specifically requested students to use SO. – Adestin Jul 14 '17 at 18:20
  • Asking questions and putting the source code of a right solution is not the same. I saw a lot of solutions on GitHub and now here on StackOverflow. Sorry if I offended you and sorry for my bad English. Your code is perfect, but I suppose it reduces the value of the course. Instructors have invested in this fantastic course so much effort... – franchb Jul 15 '17 at 06:00
  • @IlyaRusin I see. I can understand your concern, but I think it's misplaced. The code I posted does not devalue anything. The instructions for your course clearly stipulated you were to use SO. And my "source code" is not the same as anyone else's code. It has flaws and is not perfect (for anyone looking to use it). – Adestin Jul 16 '17 at 12:43

1 Answers1

1

This is how I would handle this:

def recolorBars(self, event):      
    y = event.ydata
    for i, rect in enumerate(self.rects):
        t, p, _ = sms.DescrStatsW(self.df[self.df.columns[i]]).ttest_mean(y)
        rect.set_color(self.cpick.to_rgba((1 - p) * t / abs(t)))

When iterating through the bars, first test the value against the sample mean, then set the color based on p-value and test statistic t: (1 - p) * t

Also, you must define cpick at the same time as cmap and set it to (-1, 1) using:

cpick = cm.ScalarMappable(cmap=cmap)
cpick.set_array(np.linspace(-1, 1))

The modifications above got me this result

qmeeus
  • 2,341
  • 2
  • 12
  • 21