Update (or redraw?) matplotlib bar chart using y value from onclick

时间:2017-07-17 15:28:16

标签: python pandas matplotlib

I have a matplotlib bar chart that uses yerr to simulate a box plot.

I would like to

  1. click on this bar chart
  2. get the y value for this click
  3. draw a red horizontal line at this y value
  4. run a t-test of bar chart data vs y value using scipy.stats.ttest_1samp
  5. update bar chart colors (blue if t << -2 and red if t >> 2)

I can do each of these steps separately, but not together.

I don't know how to feed the y value back to run the t-test and update the chart. I can feed a y value on first run and correctly color the bar charts, but I can't update the bar charts with the click y value.

Here are some toy data.

import pandas as pd
import numpy as np

np.random.seed(12345)

df = pd.DataFrame([np.random.normal(32000,200000,3650), 
                   np.random.normal(43000,100000,3650), 
                   np.random.normal(43500,140000,3650), 
                   np.random.normal(48000,70000,3650)], 
                  index=[1992,1993,1994,1995])

And here is what I have pieced together to draw the chart and add the line. I would also like to add an inset that maps colors to t statistics, but I think that is separate from updating the bar chart and I can add that on my own.

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

class PointPicker(object):
    def __init__(self, df, y=0):

        # moments for bar chart "box plot"
        mus = df.mean(axis=1)
        sigmas = df.std(axis=1)
        obs = df.count(axis=1)
        ses = sigmas / np.sqrt(obs - 1)
        err = 1.96 * ses
        Nvars = len(df)

        # map t-ststistics to colors
        ttests = ttest_1samp(df.transpose(), y)
        RdBus = plt.get_cmap('RdBu')
        colors = RdBus(1 / (1 + np.exp(ttests.statistic)))

        self.fig = plt.figure()
        self.ax = self.fig.add_subplot(111)

        # bar chart "box plot"
        self.ax.bar(list(range(Nvars)), mus, yerr=ci, capsize=20, picker=5, color=colors)
        plt.xticks(list(range(Nvars)), df.index)
        plt.tick_params(top='off', bottom='off', left='off', right='off', labelleft='on', labelbottom='on')
        plt.gca().get_yaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))
        plt.title('Random Data for 1992 to 1995')

        self.fig.canvas.mpl_connect('pick_event', self.onpick)
        self.fig.canvas.mpl_connect('key_press_event', self.onpress)

    def onpress(self, event):
        """define some key press events"""
        if event.key.lower() == 'q':
            sys.exit()

    def onpick(self,event):
        x = event.mouseevent.xdata
        y = event.mouseevent.ydata
        self.ax.axhline(y=y, color='red')
        self.fig.canvas.draw()

if __name__ == '__main__':

    plt.ion()
    p = PointPicker(df, y=32000)
    plt.show()

After I click, the horizontal line appears, but the bar chart colors do not update.

enter image description here

1 个答案:

答案 0 :(得分:1)

您想使用ttests内的新y值重新计算onpick。然后,您可以像以前一样重新计算颜色。然后,您可以遍历使用ax.bar创建的栏(此处我将其保存为self.bars以便于访问),并使用bar.set_facecolor和新计算的颜色。

我还添加了一个try,除了构造以在第二次单击时更改行的y值,而不是创建新行。

import pandas as pd
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from scipy.stats import ttest_1samp

np.random.seed(12345)

df = pd.DataFrame([np.random.normal(32000,200000,3650), 
                   np.random.normal(43000,100000,3650), 
                   np.random.normal(43500,140000,3650), 
                   np.random.normal(48000,70000,3650)], 
                  index=[1992,1993,1994,1995])


class PointPicker(object):
    def __init__(self, df, y=0):

        # Store reference to the dataframe for access later
        self.df = df

        # moments for bar chart "box plot"
        mus = df.mean(axis=1)
        sigmas = df.std(axis=1)
        obs = df.count(axis=1)
        ses = sigmas / np.sqrt(obs - 1)
        err = 1.96 * ses
        Nvars = len(df)

        # map t-ststistics to colors
        ttests = ttest_1samp(df.transpose(), y)
        RdBus = plt.get_cmap('RdBu')
        colors = RdBus(1 / (1 + np.exp(ttests.statistic)))

        self.fig = plt.figure()
        self.ax = self.fig.add_subplot(111)

        # bar chart "box plot". Store reference to the bars here for access later
        self.bars = self.ax.bar(
                list(range(Nvars)), mus, yerr=ses, capsize=20, picker=5, color=colors)
        plt.xticks(list(range(Nvars)), df.index)
        plt.tick_params(top='off', bottom='off', left='off', right='off', labelleft='on', labelbottom='on')
        plt.gca().get_yaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))
        plt.title('Random Data for 1992 to 1995')

        self.fig.canvas.mpl_connect('pick_event', self.onpick)
        self.fig.canvas.mpl_connect('key_press_event', self.onpress)

    def onpress(self, event):
        """define some key press events"""
        if event.key.lower() == 'q':
            sys.exit()

    def onpick(self,event):
        x = event.mouseevent.xdata
        y = event.mouseevent.ydata

        # If a line already exists, just update its y value, else create a horizontal line
        try:
            self.line.set_ydata(y)
        except:
            self.line = self.ax.axhline(y=y, color='red')

        # Recalculate the ttest
        newttests = ttest_1samp(df.transpose(), y)
        RdBus = plt.get_cmap('RdBu')
        # Recalculate the colors
        newcolors = RdBus(1 / (1 + np.exp(newttests.statistic)))

        # Loop over bars and update their colors
        for bar, col in zip(self.bars, newcolors):
            bar.set_facecolor(col)

        self.fig.canvas.draw()

if __name__ == '__main__':

    #plt.ion()
    p = PointPicker(df, y=32000)
    plt.show()

这里有一些示例输出:

enter image description here

enter image description here

enter image description here