How to add axis offset in matplotlib plot?

前端 未结 2 1350
孤街浪徒
孤街浪徒 2020-12-20 20:13

I\'m drawing several point plots in seaborn on the same graph. The x-axis is ordinal, not numerical; the ordinal values are the same for each point plot. I would like to s

相关标签:
2条回答
  • 2020-12-20 20:29

    Answering this for the most general case first. A dodge can be implemented by shifting the artists in the figure by some amount. It might be useful to use points as units of that shift. E.g. you may want to shift your markers on the plot by 5 points.
    This shift can be accomplished by adding a translation to the data transform of the artist. Here I propose a ScaledTranslation.

    Now to keep this most general, one may write a function which takes the plotting method, the axes and the data as input, and in addition some dodge to apply, e.g.

    draw_dodge(ax.errorbar, X, y, yerr =y/4., ax=ax, dodge=d, marker="d" )
    

    The full functional code:

    import matplotlib.pyplot as plt
    from matplotlib import transforms
    import numpy as np
    import pandas as pd
    
    
    def draw_dodge(*args, **kwargs):
        func = args[0]
        dodge = kwargs.pop("dodge", 0)
        ax = kwargs.pop("ax", plt.gca())
        trans = ax.transData  + transforms.ScaledTranslation(dodge/72., 0,
                                       ax.figure.dpi_scale_trans)
        artist = func(*args[1:], **kwargs)
        def iterate(artist):
            if hasattr(artist, '__iter__'):
                for obj in artist:
                    iterate(obj)
            else:
                artist.set_transform(trans)
        iterate(artist)
        return artist
    
    X = ["a", "b"]
    Y = np.array([[1,2],[2,2],[3,2],[1,4]])
    
    Dodge = np.arange(len(Y),dtype=float)*10
    Dodge -= Dodge.mean()
    
    fig, ax = plt.subplots()
    
    for y,d in zip(Y,Dodge):
        draw_dodge(ax.errorbar, X, y, yerr =y/4., ax=ax, dodge=d, marker="d" )
    
    ax.margins(x=0.4)
    plt.show()
    

    You may use this with ax.plot, ax.scatter etc. However not with any of the seaborn functions, because they don't return any useful artist to work with.


    Now for the case in question, the remaining problem is to get the data in a useful format. One option would be the following.

    df1 = pd.DataFrame({'x':list('ffffssss'), 
                        'y':[1,2,3,4,5,6,7,8], 
                        'h':list('abababab')})
    df2 = df1.copy()
    df2['y'] = df2['y']+0.5
    
    N = len(np.unique(df1["x"].values))*len([df1,df2])
    Dodge = np.linspace(-N,N,N)/N*10
    
    
    fig, ax = plt.subplots()
    k = 0
    for df in [df1,df2]:
        for (n, grp) in df.groupby("h"):
            x = grp.groupby("x").mean()
            std = grp.groupby("x").std()
            draw_dodge(ax.errorbar, x.index, x.values, 
                       yerr =std.values.flatten(), ax=ax, 
                       dodge=Dodge[k], marker="o", label=n)
            k+=1
    
    ax.legend()        
    ax.margins(x=0.4)
    plt.show()
    

    0 讨论(0)
  • 2020-12-20 20:46

    You can use linspace to easily shift your graphs to where you want them to start and end. The function also makes it very easy to scale the graph so they would be visually the same width

    import numpy as np
    import matplotlib.pyplot as plt
    
    import numpy as np
    import matplotlib.pyplot as plt
    
    start_offset = 3
    end_offset = start_offset
    y1 = np.random.randint(0, 10, 20) ##y1 has 20 random ints from 0 to 10
    y2 = np.random.randint(0, 10, 10) ##y2 has 10 random ints from 0 to 10
    x1 = np.linspace(0, 20, y1.size) ##create a number of steps from 0 to 20 equal to y1 array size-1
    x2 = np.linspace(0, 20, y2.size)
    plt.plot(x1, y1)
    plt.plot(x2, y2)
    plt.show()
    
    0 讨论(0)
提交回复
热议问题