Seaborn: countplot() с частотами
У меня есть Pandas DataFrame с столбцом под названием "AXLES", который может принимать целое значение от 3 до 12. Я пытаюсь использовать параметр seaborn countplot() для достижения следующего графика:
- левая ось y показывает частоты этих значений, происходящих в данных. Оси простираются на [0% -100%], отметки каждые 10%.
- правая ось y показывает фактические значения, значения соответствуют отметкам, определяемым левой осью y (обозначается каждые 10%.)
- ось x показывает категории для графиков [3, 4, 5, 6, 7, 8, 9, 10, 11, 12].
- Аннотации в верхней части столбцов показывают фактический процент этой категории.
Следующий код дает мне график ниже, с фактическими значениями, но я не мог найти способ преобразовать их в частоты. Я могу получить частоты с помощью df.AXLES.value_counts()/len(df.index)
, но я не уверен, как подключить эту информацию к Seaborn countplot()
.
Я также нашел обходное решение для аннотаций, но я не уверен, что это лучшая реализация.
Любая помощь будет оценена!
Спасибо
plt.figure(figsize=(12,8))
ax = sns.countplot(x="AXLES", data=dfWIM, order=[3,4,5,6,7,8,9,10,11,12])
plt.title('Distribution of Truck Configurations')
plt.xlabel('Number of Axles')
plt.ylabel('Frequency [%]')
for p in ax.patches:
ax.annotate('%{:.1f}'.format(p.get_height()), (p.get_x()+0.1, p.get_height()+50))
![введите описание изображения здесь]()
EDIT:
Я подошел ближе к тому, что мне нужно, со следующим кодом, используя Pandas "график штриховки, канаву Seaborn. Похоже, я использую так много обходных решений, и там должен быть более простой способ сделать это. Проблемы с этим подходом:
- Нет ключевого слова
order
в Pandas > функции графического объекта, поскольку у Seaborn countplot() есть, поэтому я не могу отображать все категории из 3-12, как это было в countplot(). Мне нужно показать их, даже если в этой категории нет данных.
-
Вторичная ось y по какой-то причине помещает столбцы и аннотацию (см. белые линии сетки, нарисованные над текстом и столбцами).
plt.figure(figsize=(12,8))
plt.title('Distribution of Truck Configurations')
plt.xlabel('Number of Axles')
plt.ylabel('Frequency [%]')
ax = (dfWIM.AXLES.value_counts()/len(df)*100).sort_index().plot(kind="bar", rot=0)
ax.set_yticks(np.arange(0, 110, 10))
ax2 = ax.twinx()
ax2.set_yticks(np.arange(0, 110, 10)*len(df)/100)
for p in ax.patches:
ax.annotate('{:.2f}%'.format(p.get_height()), (p.get_x()+0.15, p.get_height()+1))
![введите описание изображения здесь]()
Ответы
Ответ 1
Вы можете сделать это, создав осей twinx
для частот. Вы можете переключить две оси y вокруг так, чтобы частоты оставались слева, а счетчики справа, но без необходимости пересчитывать ось отсчетов (здесь мы используем tick_left()
и tick_right()
для перемещения тиков и set_label_position
для перемещения меток оси
Затем вы можете установить тики с помощью модуля matplotlib.ticker
, в частности ticker.MultipleLocator
и ticker.LinearLocator
.
Что касается ваших аннотаций, вы можете получить координаты x и y для всех 4 углов панели с помощью patch.get_bbox().get_points()
. Это, наряду с правильной настройкой горизонтального и вертикального выравнивания, означает, что вам не нужно добавлять какие-либо произвольные смещения в место аннотации.
Наконец, вам нужно отключить сетку для оси с двойкой, чтобы предотвратить появление линий сетки вверху баров (ax2.grid(None)
)
Вот рабочий script:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import matplotlib.ticker as ticker
# Some random data
dfWIM = pd.DataFrame({'AXLES': np.random.normal(8, 2, 5000).astype(int)})
ncount = len(dfWIM)
plt.figure(figsize=(12,8))
ax = sns.countplot(x="AXLES", data=dfWIM, order=[3,4,5,6,7,8,9,10,11,12])
plt.title('Distribution of Truck Configurations')
plt.xlabel('Number of Axles')
# Make twin axis
ax2=ax.twinx()
# Switch so count axis is on right, frequency on left
ax2.yaxis.tick_left()
ax.yaxis.tick_right()
# Also switch the labels over
ax.yaxis.set_label_position('right')
ax2.yaxis.set_label_position('left')
ax2.set_ylabel('Frequency [%]')
for p in ax.patches:
x=p.get_bbox().get_points()[:,0]
y=p.get_bbox().get_points()[1,1]
ax.annotate('{:.1f}%'.format(100.*y/ncount), (x.mean(), y),
ha='center', va='bottom') # set the alignment of the text
# Use a LinearLocator to ensure the correct number of ticks
ax.yaxis.set_major_locator(ticker.LinearLocator(11))
# Fix the frequency range to 0-100
ax2.set_ylim(0,100)
ax.set_ylim(0,ncount)
# And use a MultipleLocator to ensure a tick spacing of 10
ax2.yaxis.set_major_locator(ticker.MultipleLocator(10))
# Need to turn the grid on ax2 off, otherwise the gridlines end up on top of the bars
ax2.grid(None)
plt.savefig('snscounter.pdf')
![введите описание изображения здесь]()
Ответ 2
Я получил его для работы с использованием основного графика matplotlib
. Очевидно, у меня не было ваших данных, но приспособить их к вашим должно быть прямо.
![введите описание изображения здесь]()
Подход
Я использовал ось matplotlib
с двумя осями и построил данные в виде столбцов на втором объекте Axes
. Остальное - это просто какая-то ошибка, чтобы получить тики вправо и сделать аннотации.
Надеюсь, что это поможет.
Код
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
from mpl_toolkits.mplot3d import Axes3D
import seaborn as sns
tot = np.random.rand( 1 ) * 100
data = np.random.rand( 1, 12 )
data = data / sum(data,1) * tot
df = pd.DataFrame( data )
palette = sns.husl_palette(9, s=0.7 )
### Left Axis
# Plot nothing here, autmatically scales to second axis.
fig, ax1 = plt.subplots()
ax1.set_ylim( [0,100] )
# Remove grid lines.
ax1.grid( False )
# Set ticks and add percentage sign.
ax1.yaxis.set_ticks( np.arange(0,101,10) )
fmt = '%.0f%%'
yticks = matplotlib.ticker.FormatStrFormatter( fmt )
ax1.yaxis.set_major_formatter( yticks )
### Right Axis
# Plot data as bars.
x = np.arange(0,9,1)
ax2 = ax1.twinx()
rects = ax2.bar( x-0.4, np.asarray(df.loc[0,3:]), width=0.8 )
# Set ticks on x-axis and remove grid lines.
ax2.set_xlim( [-0.5,8.5] )
ax2.xaxis.set_ticks( x )
ax2.xaxis.grid( False )
# Set ticks on y-axis in 10% steps.
ax2.set_ylim( [0,tot] )
ax2.yaxis.set_ticks( np.linspace( 0, tot, 11 ) )
# Add labels and change colors.
for i,r in enumerate(rects):
h = r.get_height()
r.set_color( palette[ i % len(palette) ] )
ax2.text( r.get_x() + r.get_width()/2.0, \
h + 0.01*tot, \
r'%d%%'%int(100*h/tot), ha = 'center' )
Ответ 3
Я думаю, вы можете сначала установить y основных тиков вручную, а затем изменить каждую метку
dfWIM = pd.DataFrame({'AXLES': np.random.randint(3, 10, 1000)})
total = len(dfWIM)*1.
plt.figure(figsize=(12,8))
ax = sns.countplot(x="AXLES", data=dfWIM, order=[3,4,5,6,7,8,9,10,11,12])
plt.title('Distribution of Truck Configurations')
plt.xlabel('Number of Axles')
plt.ylabel('Frequency [%]')
for p in ax.patches:
ax.annotate('{:.1f}%'.format(100*p.get_height()/total), (p.get_x()+0.1, p.get_height()+5))
#put 11 ticks (therefore 10 steps), from 0 to the total number of rows in the dataframe
ax.yaxis.set_ticks(np.linspace(0, total, 11))
#adjust the ticklabel to the desired format, without changing the position of the ticks.
_ = ax.set_yticklabels(map('{:.1f}%'.format, 100*ax.yaxis.get_majorticklocs()/total))
![введите описание изображения здесь]()