Я строю несколько гистограмм с накоплением (см. Рисунок ниже). Верхняя часть каждого стержня должна находиться в том же месте, что и нижняя часть стержня, сложенного непосредственно на нем. Это верно, если я измерю их в координатах данных. Однако, если я переведу эти координаты данных в координаты осей, он перестанет быть истинным.
Рассмотрим первую (синюю, левую нижнюю) полосу на рисунке. Измеренный в данных, он имеет низ 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](https://i.stack.imgur.com/X4W1P.png)
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" )