FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Feature Request: multiple stacked bars · Issue #14086 · matplotlib/matplotlib · GitHub

Repository navigation

Feature Request: multiple stacked bars #14086

Closed as not planned
Closed as not planned

Description

Summary

I am looking for a one-call way to plot multiple stacked bars in one chart by giving it a 2D-Matrix for the y-axis.

This feature request is similar to #11048, where the bars are placed next to each other.

The documentation for matplotlib.pyplot.bar just allows scalar or array-like inputs.

The data used contains 2 x 8760 timesteps (hours). ATM I work with stackplot to generate the following graphs. Just the third graph is relevant to this issue.

My workaround would be to use stackplot for a lineplot:
`ax1.stackplot(x,y, labels=labels)
and duplicate the data in 'y' - shift the duplication just a second before the next timestep (hour) and merge those sets.

Expected outcome
I found a function that fits my problem but is not scalable for bigger arrays. (creation time is long and editing the view takes forever) But the bars in this case have a gap between the bars. I am looking for a way to adjust the width, so the gaps can be closed.
I think the value and x-axis labels are responsible for the long calculation, but I am not sure.

Matplotlib version

  • Operating system: Windows 10
  • Matplotlib version: 3.03
  • Matplotlib backend (print(matplotlib.get_backend())):
  • Python version: 3.6.7

Activity

  1. timhoffm commented on Apr 29, 2019

    Member

    Do you want something like https://matplotlib.org/gallery/lines_bars_and_markers/bar_stacked.html? If so, you can modify that example into a function that you can call on your 2D data.

  2. busiing commented on May 2, 2019

    Author

    I created a function:

    `

    def stacked_bar(index, np_array, width=1, labels=None):
        """Plots a stacked bar chart with the data and labels provided.
    
        Keyword arguments:
        index           -- 1-dimensional numpy array or nested list
                           containing data x-axis index
        np_array        -- 2-dimensional numpy array or nested list
                           containing data for each series in rows
        labels          -- (optional) list of series labels (these appear in
                           the legend)
        width           -- (optional) set width of bars
        """
    
        #ToDo sortierung nach änderungshäufigkeit, damit chp unten ist
    
        np_array = np.array(np_array)
        n_rows = np.size(np_array, 1)
        bottom = np.zeros(n_rows)
        plt_definitions = []
    
        index = np.arange(n_rows)
    
        for i, row_data in enumerate(np_array): #in range(n_columns):
            plt_definitions.append(plt.bar(index, np_array[i], width, bottom=bottom))
            bottom += np_array[i]
            if labels:
                plt.legend(plt_definitions, labels)
            labels.append("test" + str(i))
    
        if not labels:
            labels = []
            for i, row_data in enumerate(np_array):
    
                labels.append("test" + str(i))
    
        plt.legend(plt_definitions, labels)
        plt.show()
    
        return
    

    `

    With this example:
    `

    labels =  ['Cat A', 'Cat B', 'Cat C']
    x = [1,2,3,4,5]
    y = [[1,1,1,1,1],[1,2,3,1,1],[1,3,1,2,2]]
    

    `

    The result is:

    If the data contains yearly data for 4 categorys the result is:

    But the processing takes very long in my opinion.

    Any ideas how to speed it up a little? Or is there a particular reason why bar charts take longer than filled lines graphs like in the example picture of the first post?

  3. tacaswell commented on May 2, 2019

    Member

    The reason that the bar version is much slower to render is that each bar is drawn as its own rectangle (so you have 4*(len(data)) of them) where as with the Line2D version it only has 4 artists to draw.

    I suggest taking a look at fill_between with the step kwarg https://matplotlib.org/api/_as_gen/matplotlib.pyplot.fill_between.html .

  4. busiing commented on May 3, 2019

    Author

    I went with the fill_between idea, which is indeed pretty fast in comparision. Thank you for that!
    Here is my example code and the new function:
    `

        import numpy as np
        import matplotlib.pyplot as plt
    
        labels =  ['Cat A', 'Cat B', 'Cat C']
        x = [1,2,3,4,5]
        y = [[1,1,1,2,5],[1,2,3,1,1],[1,3,1,2,2]]    
    
        def stacked_bar2(index,np_array, labels=None):
            data = np.array(np_array)
            plt_definitions = []
            n_rows = np.size(np_array, 1)
            bottom = np.zeros(n_rows)
            top = np_array[0]
            for i, row_data in enumerate(np_array):  # in range(n_columns):
                top = np.sum([bottom, np_array[i]], axis=0)
                plt_definitions.append(plt.fill_between(index, top, y2=bottom,step='mid'))
                bottom += np_array[i]
    
            if not labels:
                labels = []
                for i, row_data in enumerate(np_array):
    
                    labels.append("test" + str(i))
    
            plt.xlim(left=min(index)-1,right=max(index)+1)
            plt.legend(plt_definitions, labels)
            plt.show()
    
            return
    
        stacked_bar2(x,y,labels)
    

    `

    The result for the example looks like this:

    For actual work it looks like this:

    1. The first and last bar are just shown for half of the width in comparision to the rest:

    step : {'pre', 'post', 'mid'}, optional - element

    Even if I enlarge the x-axis, the first and last bars are still smaller. Any ideas on that?

  5. timhoffm commented on May 3, 2019

    Member

    Even if I enlarge the x-axis, the first and last bars are still smaller. Any ideas on that?

    Set step='post' and add the last element again to your data.

    See https://matplotlib.org/devdocs/gallery/lines_bars_and_markers/step_demo.html

  6. busiing commented on May 6, 2019

    Author

    Ty @timhoffm

    I adjusted the code so it adds the last value of each row at the end, added one index value for index, bottom and top:
    `

    def stacked_bar(index,np_array, labels=None):
        np_array = np.array(np_array)
        plt_definitions = []
        n_rows = np.size(np_array, 1)
        n_columns = np.size(np_array, 0)
        np_array = np.append(np_array, np_array[0:n_columns, n_rows - 1:n_rows], axis=1)
        index = np.append(index,n_rows+1)
        bottom = np.zeros(n_rows+1)
        top = np_array[0]
        print(top)
        for i, row_data in enumerate(np_array):  # in range(n_columns):
            top = np.sum([bottom, np_array[i]], axis=0)
            plt_definitions.append(plt.fill_between(index, top, y2=bottom,step='post'))
            bottom += np_array[i]
    
        if not labels:
            labels = []
            for i, row_data in enumerate(np_array):
    
                labels.append("test" + str(i))
    
        plt.xlim(left=min(index) - 1, right=max(index) + 1)
        plt.legend(plt_definitions, labels)
    
        return
    

    `

    Works out pretty good.

    But I have a problem, if index is a time series.
    index = np.append(index,n_rows+1)

    I didn't figure out yet, how to add one element to a time series that fits its (unknown) resolution.

  7. timhoffm commented on May 6, 2019

    Member

    For an additional element, you could step by the difference between the last two elements, i.e. add a[-1] + (a[-1] - a[-2]) or in short 2*a[-1] - a[-2].

  8. busiing commented on May 7, 2019

    Author

    Figured it out.

    Now method works with integer index and datetime index for numpy and as well for pandas.

    import numpy as np
    import matplotlib.pyplot as plt
    import pandas as pd
    
    labels =  ['Cat A', 'Cat B', 'Cat C']
    integer = [1,2,3,4,5]
    y = np.array([[1,1,1,1,1],[1,2,1,2,1],[1,2,3,4,5]])
    
    np_datetime = np.arange('2005-01', '2005-06', dtype='datetime64[M]')
    
    pd_datetime = pd.date_range('2018-01-01', periods=5, freq='H')
    
    def add_last_index(x_axis_label, number_of_rows):
        """Method
        This method adds another x axis label to the array. If the input is a datetime, it adds the correct next datetime.
        If it's a integer, it adds one.
        :param x_axis_label:
        :param number_of_rows:
        :return:
        """
        if isinstance(x_axis_label[0],np.datetime64):
            print("This is a numpy datetime")
            # Calculate time granularity of the x axis ...
            timedelta = np.datetime64(x_axis_label[1]) - np.datetime64(x_axis_label[0])
            # ... and add one more label to the labels so that the last one is not cut out (because of the "post" step)
            x_axis_label = np.append(x_axis_label, x_axis_label[len(x_axis_label) - 1] + timedelta)
        elif isinstance(x_axis_label[0],pd.datetime):
            print("This is a pandas datetime")
            x_axis_label = pd.to_datetime(x_axis_label)
            # Calculate time granularity of the x axis ...
            timedelta = x_axis_label[len(x_axis_label)-1] - x_axis_label[0]
            # ... and add one more label to the labels so that the last one is not cut out (because of the "post" step)
            x_axis_label = x_axis_label.append(x_axis_label[1:2]+timedelta)
        else:
            # Add one more label to the labels so that the last one is not cut out (because of the "post" step)
            x_axis_label = np.append(x_axis_label, number_of_rows + 1)
        return x_axis_label
    
    def stacked_bar(x_axis_label, np_array, labels=None):
        np_array = np.array(np_array)
        plt_definitions = []
        number_of_rows = np.size(np_array, 1)
        n_columns = np.size(np_array, 0)
        np_array = np.append(np_array, np_array[0:n_columns, number_of_rows - 1:number_of_rows], axis=1)
    
        # Append additional index at the end
        x_axis_label = add_last_index(x_axis_label, number_of_rows)
    
        bottom = np.zeros(number_of_rows+1)
        top = np_array[0]
        for i, row_data in enumerate(np_array):  # in range(n_columns):
            top = np.sum([bottom, np_array[i]], axis=0)
            plt_definitions.append(plt.fill_between(x_axis_label, top, y2=bottom, step='post'))
            bottom += np_array[i]
    
        if not labels:
            labels = []
            for i, row_data in enumerate(np_array):
    
                labels.append("test" + str(i))
    
        plt.xlim(left=min(x_axis_label), right=max(x_axis_label))
        plt.legend(plt_definitions, labels)
    
        return
    
    stacked_bar(pd_datetime,y, labels=labels)
    plt.show()
    
    

    Worked out for the "small" example:

    As well for the "big" example:

  9. github-actions commented on Jun 14, 2023

    This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!

  10. timhoffm commented on Jun 14, 2023

    Member

    This is followed up on in #24313.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions


      Back | FazBrowse Home | New Git URL