Pandas - построить два кадра данных - с общей легендой - PullRequest
3 голосов
/ 15 января 2020

Я стремлюсь построить два кадра данных на одном графике - один кадр минимальных месячных температур, другой кадр максимальных месячных температур в среднем за каждое десятилетие с 1930 года для города Канберра. Я хочу, чтобы два кадра данных имели общую легенду. Чтобы добавить в задачу, я хочу, чтобы легенда имела два столбца.

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

chart of minimum and maximum temperatures in Canberra since 1930 by decade

Ниже приведен полный код, включая данные из Интернета, но моя проблема в последних нескольких строках кода.

# Look at temperature data for Canberra

# --- initialise
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

import requests
import io

# --- some graphics management
plt.style.use('./bryan.mplstyle')
LOCATION = './Charts/Canb-'

def plot_save_and_close(ax, title, xlabel, ylabel, 
    filename, legend=True, bar_labels=0):
    """Add the usual chart annotations, 
    save to file and close the plot """

    ax.set_title(title)
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    fig = ax.figure
    fig.tight_layout(pad=1)
    if legend and not ax.get_legend():
        l = ax.legend(loc='best', fontsize='small')
    if bar_labels:
        for i, t in enumerate(ax.get_xticklabels()):
            if ((i-3) % bar_labels) != 0:
                t.set_visible(False)
    fig.savefig(filename, dpi=125)
    plt.close()


# --- Get the data
# Data from Australian Bureau pf Meteorology
url_stem = 'http://www.bom.gov.au/climate/change/hqsites/data/temp/'
canberra = '070351'
url_min = url_stem+'tmin.'+canberra+'.daily.csv'
url_max = url_stem+'tmax.'+canberra+'.daily.csv'

# download minimum temperatures
min_df = pd.read_csv(io.StringIO(requests.get(
    url_min).content.decode('utf-8')), 
    header=0, index_col=0, parse_dates=[0])
min_df = min_df.drop(min_df.index[0])[[min_df.columns[0]]]
min_df.columns = ['Minimum']

# download maximum temperatures
max_df = pd.read_csv(io.StringIO(requests.get(
    url_max).content.decode('utf-8')), 
    header=0, index_col=0, parse_dates=[0])
max_df = max_df.drop(max_df.index[0])[[max_df.columns[0]]]
max_df.columns = ['Maximum']

# combine into a single dataframe 
df = min_df.join(max_df, how='outer')

# let's augment with the latest daily data - TO DO

# provide some grouping tags for the data
df['calendar year'] = df.index.to_period(freq='A-DEC') 
df['winter year'] = df.index.to_period(freq='A-MAY') 
df['decade begining'] = (df.index.year // 10) * 10
df['tri-decade beginning'] = (((df.index.year - 1900) // 30) * 30) + 1900
df['julian day'] = df.index.dayofyear
df['Month'] = df.index.month

# check for missing data by year
#print('Missing max data by calandar year: ') 
#print(df[df['Maximum'].isna()].groupby('calendar year')['Maximum'].size())
#print('Missing min data by calandar year: ') 
#print(df[df['Minimum'].isna()].groupby('calendar year')['Minimum'].size())
# note: substantial missing data for Canberra between 1920-25 inclusive
df = df[df.index >= pd.Timestamp('1926-01-01')].copy() # not a slice
df_saved = df.copy() # keep a copy of the original to return to

# let's plot decadal monthly averages - both maximum and minimums
df = df_saved.copy()
df = df.groupby('decade begining').filter(lambda x: len(x) >= 3300) # close to full decades
df = df.groupby(['decade begining', 'Month'])['Maximum', 'Minimum'].mean().unstack(level=0)
max = df['Maximum']
min = df['Minimum']
colors = plt.cm.coolwarm(np.linspace(0,1,len(max.columns)))
ax = max.plot(color=colors, legend=True)
legend = ax.legend(title='Decade begining', ncol=2, loc='best',fontsize='small')
ax = min.plot(ax=ax, color=colors, legend=False)
plot_save_and_close(ax, 'Canberra: Average Monthly Min and Max Temp by Decade', 
    'Month', 'Degrees Celsius', LOCATION+'unsmoothed-decadal-average-monthly.png')

1 Ответ

1 голос
/ 15 января 2020

Проблема может быть Pandas внутренней, когда вызывается .plot, кажется, что легенда создается с использованием всех данных, связанных с осью. Вы можете решить monkeypatching Pandas, но я не верю, что это хорошая идея. Вот возможный обходной путь (безобразный), использующий две разные оси matplotlib:

ax = max.plot(color=colors)
ax2 = ax.twinx()
ax2 = min.plot(color=colors, ax=ax2, legend=False)
ax.legend(title='Decade begining', ncol=2, loc=1, fontsize='small')

min_ylim, max_lim = [f([ax.get_ylim()[0], ax2.get_ylim()[0]]) for f in [np.min, np.max]]
[axis.set_ylim([min_ylim, max_ylim]) for axis in [ax, ax2]]
ax2.set_yticks([])
plt.show()

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...