Matplotlib: противоречивое преобразование координат - PullRequest
0 голосов
/ 12 октября 2019

Я строю несколько гистограмм с накоплением (см. Рисунок ниже). Верхняя часть каждого стержня должна находиться в том же месте, что и нижняя часть стержня, сложенного непосредственно на нем. Это верно, если я измерю их в координатах данных. Однако, если я переведу эти координаты данных в координаты осей, он перестанет быть истинным.

Рассмотрим первую (синюю, левую нижнюю) полосу на рисунке. Измеренный в данных, он имеет низ 0, высоту 3 и верх 3. (Текст, написанный на нем, утверждает эти измерения.) Отлично. Измеренный по осям, он имеет нижнюю часть 0, высоту 0,952 и верхнюю часть 0,952.

Теперь рассмотрим оранжевую полосу непосредственно над ней. По данным, эта полоса имеет дно 3 - так же, как и верхняя часть полосы, на которой она находится. Но если я переведу координаты осей, у него будет дно 0,571, тогда как у первого (синего) бара у него вершина 0,952. Эти два числа должны быть равны, но они не равны.

Это странно, потому что в обоих случаях я передаю один и тот же вход - 3 - для преобразования данных в координаты осей.

Код использует только две команды для рисования: ax.bar для рисования столбцов и ax.text для рисования текста над каждым столбцом. Чтобы преобразовать данные в оси, я сначала преобразовываю данные в координаты экрана, а затем из экрана в оси;подробности см. в командах transform в коде.

The bottom of each equal the top of the one above, but that's only true in data coordinates

if True:
  import matplotlib.pyplot as plt
  import numpy as np
  import pandas as pd

def brief( # a utility to display numbmers briefly,
           # to (roughly) 3 significant digits
    f : float ) -> str:
  if abs(f) < 0.001: return "0"
  else: return ( "{0:.3g}" . format( f ) )

# the dimensions and contents of the data
df = pd.DataFrame( [ [3,1,2],
                     [1,2,3],
                     [2,3,1] ] )
nCols : int = len( df.columns )
nRows : int = len( df.index )

# Make space to draw
plt.subplots( 1, 1 )
ax = plt.subplot( 1, 1, 1 )

for rn in range( nRows ):
  # Plot one row after the other.
  # The bars in each row are the same color.
  if True: # These series describe the vertical
           # dimensions of row `rn` of bars
    if rn < 1: bottom = df.iloc[0,   :]*0 # a row of zeros
    else:      bottom = df.iloc[0:rn,:].sum()
    height = df.iloc[  rn,:]
    top = bottom + height
    middle = (bottom + top) / 2 # halfway from the bottom to the top
  ax.bar( # draw the bars
    np.arange( nCols ),
    height,
    width = [ 0.8 for i in range( nCols ) ],
    bottom = bottom )
  for cn in range( nCols ): # write text on each bar
    if True: # for for a single bar, translate its bottom,
      # height and top into the axes coordinate system.
      # First transform from data to display coordinates,
      # then from display to axes coordinates.
      bottom_in_axes = ( ax.transAxes.inverted().transform(
                           ax.transData.transform(( 0, bottom[cn] )) )
                         [1] )
      height_in_axes = ( ax.transAxes.inverted().transform(
                           ax.transData.transform(( 0, height[cn] )) )
                           [1] )
      top_in_axes    = ( ax.transAxes.inverted().transform(
                           ax.transData.transform(( 0, top[cn] )) )
                         [1] )
    ax.text( # write text on each bar
      float( cn ), 
      middle.iloc[cn],
      ( "Bottom, height, top:\n" +
        "Data: " +
        str( bottom[cn] ) + ", " +
        str( height[cn] ) + ", " +
        str( top[cn] ) + "\n" +
        "Axes: " +
        brief( bottom_in_axes ) + ", " +
        brief( height_in_axes ) + ", " +
        brief( top_in_axes ) ),
      verticalalignment = 'center',
      horizontalalignment = 'center',
      fontsize = 7 )

plt.savefig( "bars.png", format = "png" )
...