5

I am plotting changes in mean and variance of some data with the following code

import matplotlib.pyplot as pyplot
import numpy

vis_mv(data, ax = None):
    if ax is None: ax = pyplot.gca()
    cmap = pyplot.get_cmap()
    colors = cmap(numpy.linspace(0, 1, len(data)))

    xs = numpy.arange(len(data)) + 1
    means = numpy.array([ numpy.mean(x) for x in data ])
    varis = numpy.array([ numpy.var(x) for x in data ])
    vlim = max(1, numpy.amax(varis))

    # variance
    ax.imshow([[0.,1.],[0.,1.]],
        cmap = cmap, interpolation = 'bicubic',
        extent = (1, len(data), -vlim, vlim), aspect = 'auto'
    )
    ax.fill_between(xs, -vlim, -varis, color = 'white')
    ax.fill_between(xs, varis, vlim, color = 'white')

    # mean
    ax.plot(xs, means, color = 'white', zorder = 1)
    ax.scatter(xs, means, color = colors, edgecolor = 'white', zorder = 2)

    return ax

This works perfectly fine: enter image description here but now I would like to be able to use this visualisation also in a vertical fashion as some kind of advanced color bar kind of thingy next to another plot. I hoped it would be possible to rotate the entire axis with all of its contents, but I could only find this question, which does not really have a solid answer yet either. Therefore, I tried to do it myself as follows:

from matplotlib.transforms import Affine2D

ax = vis_mv()
r = Affine2D().rotate_deg(90) + ax.transData

for x in ax.images + ax.lines + ax.collections:
    x.set_transform(r)

old = ax.axis()
ax.axis(old[2:4] + old[0:2])

This almost does the trick (note how the scattered points, which used to lie along the white line, are blown up and not rotated as expected). enter image description here Unfortunately the PathCollection holding the result of the scattering does not act as expected. After trying out some things, I found that scatter has some kind of offset transform, which seems to be the equivalent of the regular transform in other collections.

x = numpy.arange(5)
ax = pyplot.gca()
p0, = ax.plot(x)
p1 = ax.scatter(x,x)

ax.transData == p0.get_transform()           # True
ax.transData == p1.get_offset_transform()    # True

It seems like I might want to change the offset transform instead for the scatter plot, but I did not manage to find any method that allows me to change that transform on a PathCollection. Also, it would make it a lot more inconvenient to do what I actually want to do.

Would anyone know if there exists a possibility to change the offset transform?

Thanks in advance

Community
  • 1
  • 1
  • Can you post a sample image of what you have, and (approximately) what you want? – VBB May 10 '17 at 13:56
  • @VBB Is it more clear now? – Mr Tsjolder from codidact May 10 '17 at 19:56
  • 1
    How about just plotting everything vertically to begin with? `imshow`, `scatter` and `plot` will work as is, and you could use `ax.fill_betweenx` for the coloring. (PS: nice trick for shading) – VBB May 11 '17 at 05:00
  • @VBB I originally did everything vertically (except that I did not know of `ax.fill_betweenx`), but in order to have a closer look I rewrote my code to be horizontal in the hope that I would be able to rotate the entire thing. I could obviously have two functions, but I was hoping for a more elegant solution... – Mr Tsjolder from codidact May 11 '17 at 06:55
  • Possible duplicate of [How can I rotate a matplotlib plot through 90 degrees?](http://stackoverflow.com/questions/22540449/how-can-i-rotate-a-matplotlib-plot-through-90-degrees) – Mr Tsjolder from codidact May 16 '17 at 07:06
  • Why do you mark this as duplicate if this question explicitely asks about something the linked question does not ask about? – ImportanceOfBeingErnest May 16 '17 at 12:18
  • @ImportanceOfBeingErnest The original reason why I ask this question was exactly because I want to rotate an entire plot 90 degrees. Therefore I thought it might make sense to merge these questions. I also feel somehow stupid for having answered that question before asking this one. – Mr Tsjolder from codidact May 16 '17 at 14:02
  • Yes but here you ask specifically about a problem that arises when using one of the solutions (doesn't matter who wrote them) of the other answer. So it definitely makes sense to link them, but I would not consider this to be a duplicate. – ImportanceOfBeingErnest May 16 '17 at 14:21
  • 1
    @ImportanceOfBeingErnest I updated my question to 1) mention the existence of the other question 2) focus on the scatter plot – Mr Tsjolder from codidact May 16 '17 at 14:50

1 Answers1

6

Unfortunately the PathCollection does not have a .set_offset_transform() method, but one can access the private _transOffset attribute and set the rotating transformation to it.

import matplotlib.pyplot as plt
from matplotlib.transforms import Affine2D
from matplotlib.collections import PathCollection
import numpy as np; np.random.seed(3)

def vis_mv(data, ax = None):
    if ax is None: ax = plt.gca()
    cmap = plt.get_cmap()
    colors = cmap(np.linspace(0, 1, len(data)))

    xs = np.arange(len(data)) + 1
    means = np.array([ np.mean(x) for x in data ])
    varis = np.array([ np.var(x) for x in data ])
    vlim = max(1, np.amax(varis))

    # variance
    ax.imshow([[0.,1.],[0.,1.]],
        cmap = cmap, interpolation = 'bicubic',
        extent = (1, len(data), -vlim, vlim), aspect = 'auto'  )
    ax.fill_between(xs, -vlim, -varis, color = 'white')
    ax.fill_between(xs, varis, vlim, color = 'white')

    # mean
    ax.plot(xs, means, color = 'white', zorder = 1)
    ax.scatter(xs, means, color = colors, edgecolor = 'white', zorder = 2)

    return ax

data = np.random.normal(size=(9, 9))
ax  = vis_mv(data)


r = Affine2D().rotate_deg(90)

for x in ax.images + ax.lines + ax.collections:
    trans = x.get_transform()
    x.set_transform(r+trans)
    if isinstance(x, PathCollection):
        transoff = x.get_offset_transform()
        x._transOffset = r+transoff

old = ax.axis()
ax.axis(old[2:4] + old[0:2])


plt.show()

enter image description here

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712