Коробочный участок из морского корня

#python #pandas #dataframe #boxplot

Вопрос:

У меня есть многоиндексный фрейм данных «Панды», который я хочу построить в виде блок-схемы. Это должно быть легко сделать, но я обнаружил, что не могу получить именно то, что хочу. Данные выглядят следующим образом:

                        hedges  mask model_name  hedges_std  hedges_min  
    period    season                                                      
    2021-2025 winter  0.864328   1.0   ensemble    0.301748    0.124708   
          spring  0.740410   1.0   ensemble    0.202963    0.049319   
          summer  0.526264   1.0   ensemble    0.105750    0.162856   
          fall    0.531141   1.0   ensemble    0.046278    0.388827   
2025-2050 winter  1.715075   1.0   ensemble    0.373866    0.582819   
          spring  1.252963   1.0   ensemble    0.370402    0.408695   
          summer  0.854958   1.0   ensemble    0.076193    0.528038   
          fall    0.759645   1.0   ensemble    0.068928    0.498271   
2050-2075 winter  2.981373   1.0   ensemble    0.928940    1.139801   
          spring  2.042320   1.0   ensemble    0.748642    0.716289   
          summer  1.299277   1.0   ensemble    0.092611    0.812979   
          fall    1.108852   1.0   ensemble    0.109014    0.653199   
2021-2025 winter  0.864328   1.0   ensemble    0.301748    0.124708   
          spring  0.740410   1.0   ensemble    0.202963    0.049319   
          summer  0.526264   1.0   ensemble    0.105750    0.162856   
          fall    0.531141   1.0   ensemble    0.046278    0.388827   
2025-2050 winter  1.715075   1.0   ensemble    0.373866    0.582819   
          spring  1.252963   1.0   ensemble    0.370402    0.408695   
          summer  0.854958   1.0   ensemble    0.076193    0.528038   
          fall    0.759645   1.0   ensemble    0.068928    0.498271   
2050-2075 winter  2.981373   1.0   ensemble    0.928940    1.139801   
          spring  2.042320   1.0   ensemble    0.748642    0.716289   
          summer  1.299277   1.0   ensemble    0.092611    0.812979   
          fall    1.108852   1.0   ensemble    0.109014    0.653199   

                  hedges_max model_scenario  
period    season                             
2021-2025 winter    1.760912         ssp245  
          spring    1.189956         ssp245  
          summer    0.662142         ssp245  
          fall      0.687793         ssp245  
2025-2050 winter    2.423660         ssp245  
          spring    2.040903         ssp245  
          summer    1.055890         ssp245  
          fall      0.965831         ssp245  
2050-2075 winter    5.179203         ssp245  
          spring    3.898118         ssp245  
          summer    1.536149         ssp245  
          fall      1.435503         ssp245  
2021-2025 winter    1.760912         ssp585  
          spring    1.189956         ssp585  
          summer    0.662142         ssp585  
          fall      0.687793         ssp585  
2025-2050 winter    2.423660         ssp585  
          spring    2.040903         ssp585  
          summer    1.055890         ssp585  
          fall      0.965831         ssp585  
2050-2075 winter    5.179203         ssp585  
          spring    3.898118         ssp585  
          summer    1.536149         ssp585  
          fall      1.435503         ssp585  
 

Я хочу построить данные, показывающие по одному квадрату для каждого периода и сезона, разделенных цветом по сценарию. Каждое поле будет определяться его средним значением (хеджирование), стандартным отклонением (std) и потенциально минимальным и максимальным диапазоном. Идея заключается в том, чтобы показать, как будущие периоды изменят оценочные распределения хеджирования. Я пробовал различные комбинации вокруг:

 sns.boxplot(data=df, x="season", y="hedges", hue="model_scenario")
 

Моя ошибка Could not interpret input 'season' связана с мультииндексом, который мне явно нужно как-то сгруппировать или разделить, но именно здесь я продолжаю терпеть неудачу. Мы будем признательны за предложения о том, как отображать эти данные.

Комментарии:

1. Вы на 100% уверены, что «сезон» является доступной колонкой в кадре? Согласно исходному коду seaborn, это сообщение печатается, когда «сезон» отсутствует в кадре данных. Если проблема в том, что «сезон» является частью индекса, вы можете подумать о вызове df.reset_index() перед построением графика.

2. @dsillman2000 При попытке печати df["season"] я получаю ошибку «Ошибка ключа: «сезон»». Похоже, что период и сезон определяют мультииндекс. Если я разделю их или реорганизую, чтобы иметь только один индекс, я потеряю структуру, которую хочу построить.

Ответ №1:

Я предполагаю, что ваша цель-создать такую фигуру, как эта:

Блок-схема, сгенерированная bxp()

Поскольку у вас уже рассчитана статистика ваших ящиков, функция sns.boxplot() , а также matplotlib.axes.Axes.boxplot() из matplotlib (которая является серверной частью seaborn и вызывается внутри sns.boxplot() ), больше не являются функциями, которые вы можете использовать. Попытка ax.boxplot() рассчитать статистику сама по себе, поэтому это не тот путь, по которому нужно идти.

После вычисления boxplot-статистика matplotlib.axes.Axes.boxplot() вызывает [ matplotlib.axes.Axes.bxp() ](https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.bxp.html) и эту функцию вы тоже можете использовать.

Функция matplotlib.axes.Axes.boxplot() принимает диктант с этим соглашением об именах:

  • med: Медиана (скалярный поплавок),
  • q1: Первый квартиль (25-й процентиль) (скалярный поплавок),
  • q3: Третий квартиль (75-й процентиль) (скалярный поплавок),
  • whislo: Нижняя граница нижнего уса (скалярный поплавок),
  • whishi: Верхняя граница верхнего уса (скалярный поплавок),

и только с небольшими изменениями мы можем переименовать или создать нужные столбцы вашего фрейма данных. Но сначала сбросьте мультииндекс.

 # df is defined and the multiinde
df = df.rename({'hedges':'med', 'hedges_min':'whislo', 'hedges_max':'whishi'}, axis=1)
df['q1'] = df['med'] - df['hedges_std']
df['q3'] = df['med']   df['hedges_std']
df['label'] = df.apply(lambda x: '('  x['period']  ' , '  x['season']   ')', axis=1)
df = df[['med', 'whislo','whishi','q1','q3', 'label']] # this are the columns we need

>>> df.head(5)
        med    whislo    whishi        q1        q3                 label
0  0.864328  0.124708  1.760912  0.562580  1.166076  (2021-2025 , winter)
1  0.740410  0.049319  1.189956  0.537447  0.943373  (2021-2025 , spring)
2  0.526264  0.162856  0.662142  0.420514  0.632014  (2021-2025 , summer)
3  0.531141  0.388827  0.687793  0.484863  0.577419    (2021-2025 , fall)
4  1.715075  0.582819  2.423660  1.341209  2.088941  (2025-2050 , winter)
 

Я решил создать ярлык period , объединяющий season и. Каждая метка появляется дважды, каждый model_scenario раз ровно один раз.

Вот код, как я создал рисунок выше. Это не идеально, но это показывает, как это работает. Некоторые из разделов а соответствуют кодексу sns.boxplot() .

 from matplotlib import rcParams
import matplotlib.pyplot as plt

colors = ['lightblue', 'olive']
model_scenario = ["ssp245", "ssp585"]
fig, ax = plt.subplots(figsize=(9, 4))
ax.set_title('box plot')

x_tick_label = []
x_tick_position = []
for i, group in enumerate(data_to_plt.groupby('label')):
    for j in range(group[1].shape[0]):
        x_tick_label.append(group[0])
        x_tick_position.append(i)
        if j ==0:
            p = i - 0.15
        else:
            p = i   0.15
        artist_dict  = ax.bxp(
            bxpstats=[group[1].drop('label', axis=1).iloc[j].to_dict()], 
            showfliers=False, 
            patch_artist=True,
            positions=[p]
        )
        for box in artist_dict["boxes"]:
            box.update(dict(facecolor=colors[j],
                            zorder=.9,
                            edgecolor='gray',
                            linewidth=rcParams["lines.linewidth"])
            )
        if i == 0:
            rect = plt.Rectangle([0,0], 0, 0,
                                 linewidth=0,
                                 edgecolor='gray',
                                 facecolor=colors[j],
                                 label=model_scenario[j])
            ax.add_patch(rect)
            

ax.legend(loc='center left', bbox_to_anchor=(1, 0.5))
plt.xticks(x_tick_position, x_tick_label, rotation = 90)
 

Чтобы обобщить то, что я делаю с matplotlib:

  1. Я группируюсь по model_scenario ака labels
  2. Я создаю метки для легенды
  3. Я рисую коробки, используя bxp()
  4. Я переписываю крестики-нолики

Комментарии:

1. Это звучит здорово, но у меня все еще есть проблема с мультииндексом. Как вы его разделили? Я продолжаю получать KeyError: 'period' , когда пытаюсь убежать df.apply(lambda x:... . Был ли какой-то шаг, который вы здесь не включили? Спасибо

2. Заставил его работать с простым df=df.reset_index() . Спасибо за помощь!