Criando figuras científicas usando Matplotlib e Seaborn
Dados bem apresentados são um dos aspectos mais importantes da pesquisa científica. Em Python, as bibliotecas Matplotlib e Seaborn são poderosas bibliotecas de visualização de dados que podem ser usadas juntas para produzir figuras de qualidade com recursos personalizáveis.
Matplotlib x Seaborn
Quando usei Matplotlib e Seaborn pela primeira vez para criar figuras, não estava claro para mim por que e quando você usaria um ou outro. Eu recomendaria usar ambos. O Seaborn oferece uma ampla variedade de tipos de plotagem e paletas de cores e deve ser usado para realmente plotar seus dados, enquanto o Matplotlib permite formatar os detalhes da plotagem - tamanhos de fonte, legendas de figuras e texto de eixos.
Este artigo começará com como criar gráficos muito básicos e aumentar lentamente a complexidade para mostrar como aproveitar a personalização possível com Matplotlib e Seaborn. Para demonstrar, usaremos um conjunto de dados de gravações de EEG de roedores com diversas variáveis, como diferenças de genótipos, canais de eletrodos e IDs de animais. Isso nos dará uma intuição de como apresentar os dados com os quais os cientistas trabalham no dia a dia.
O artigo está organizado da seguinte forma:
- formate seus dados para plotagem
- criar um enredo
- executar reformatação básica (remover bordas de plotagem, rotular eixos e definir limites de eixos)
- executar reformatação mais detalhada (personalizando parâmetros de legenda, escalas de eixo, tamanhos de fonte)
- personalização de paletas marítimas
- criando subtramas
- composição de figuras complexas (subtramas e figure.subplot_mosaic)
A primeira etapa é formatar corretamente seus dados em um dataframe do pandas, um formato conhecido como dados organizados . Se você quiser ler mais sobre como organizar seus dados, este é um ótimo artigo . A variável independente (eixo x) deve estar em uma coluna e a variável dependente (eixo y) em outra coluna. Se existirem várias categorias para o seu conjunto de dados (por exemplo, Animal_ID, Channel, Genotype), essas categorias também devem estar em colunas separadas e você pode usar o parâmetro Seaborn hue (exemplos mostrados abaixo) para plotar várias variáveis em um gráfico.
Criando um gráfico
Começaremos plotando o DataFrame mostrado acima usando as configurações padrão do Matplotlib e Seaborn com muito pouca formatação adicional. sns.lineplot( ) traçará um gráfico de linha dos dados armazenados no D ataFrame onde 'Frequency' e 'Power' são colunas no DataFrame, e hue permite que você plote várias variantes (genótipo neste caso) em um gráfico. O eixo y foi definido para uma escala logarítmica devido à forma como as frequências mais baixas e mais altas são representadas no espectro de potência dos dados de EEG, a potência é maior em baixas frequências e torna-se cada vez menor à medida que a frequência aumenta.
fig, axs = plt.subplots(1,1, figsize=(20,15))
sns.lineplot(data= df, x='Frequency', y='Power',hue = 'Genotype',
errorbar = ("se"), linewidth = 2)
axs.set_yscale('log')
fig.savefig('basic_power_plot.jpg')
Figure 1. EEG Power Spectrum plot using Seaborn and Matplotlib’s default parameters and no additional reformatting.
O gráfico acima mostra o básico dos dados, mas é difícil de entender. Geralmente não está claro o que o gráfico está tentando mostrar devido à grande quantidade de espaços em branco, tamanhos de fonte pequenos, falta de eixos de rótulos ou título do gráfico. Os primeiros passos para tornar este gráfico mais legível serão remover a borda desnecessária da figura e definir limites e rótulos para os eixos x e y.
sns.set_style("white")
fig, axs = plt.subplots(1, 1, figsize=(20,15), sharex = True, sharey = True)
sns.lineplot(data = df, x = 'Frequency', y = 'Power', hue = 'Genotype',
errorbar = ('se'), linewidth = 4)
#remove the figure border
sns.despine()
#set limits and labels for the x and y axis
axs.set_yscale('log')
axs.set_xlim(1, 48)
axs.set_ylim(10**-2, 10**3)
axs.set_xlabel("Frequency (Hz)")
axs.set_ylabel("log Power (\\u03bc$\\mathregular{V^{2}}$)")
#save figure
fig.savefig('basic_plot.jpg')
Figure 2. Basic reformatting of Figure 1 to change axes limits and labels and remove outer plot border.
Além de simplesmente alterar os limites dos eixos, seu gráfico pode ser formatado adicionando:
- um título geral da figura,
- rótulos de fonte de eixos
- um objeto de legenda para editar os detalhes da legenda
- aumentando os pesos das linhas dos eixos e atualizando todos os caracteres de fonte dos eixos de uma só vez.
Também vou alterar os x-ticks para aumentar em incrementos de 6 até 48. A função axs.set_xlim() especifica os valores mínimo e máximo do eixo x, mas se você quiser especificar os valores que os ticks aumentam por você pode usar a função xticks .
sns.set_style("white", rc = {'font.size': 20})
fig, axs = plt.subplots(1, 1, figsize=(20,15), sharex = True, sharey = True)
sns.lineplot(data = df, x = 'Frequency', y = 'Power', hue = 'Genotype',
errorbar = ('se'), linewidth = 4, ax = axs)
#remove outer border
sns.despine()
#axes customisation
axs.set_yscale('log')
axs.set_xlim(1, 48)
axs.set_ylim(10**-2, 10**3)
axs.set_xlabel("Frequency (Hz)", fontsize = 25)
axs.set_ylabel("log Power (\\u03bc$\\mathregular{V^{2}}$)", fontsize = 25)
#update the fontsize of all characters on x and y axis
plt.rcParams.update({'font.size': 20})
#changing x-ticks
tick_values = list(range(1, 54, 6))
label_list = ['1', '6', '12', '18', '24', '30', '36', '42', '48']
axs.set_xticks(ticks = tick_values, labels = label_list)
#include an overall plot title
plt.suptitle('Condition 1 Overall Average', fontsize = 30, fontweight = 'bold')
#customise the legend
leg = plt.legend(loc = 'upper right', frameon = False)
leg.set_title('Genotype', prop = {'size': 25})
leg_lines = leg.get_lines()
leg_texts = leg.get_texts()
plt.setp(leg_lines[0], linewidth = 8)
plt.setp(leg_lines[1], linewidth = 8)
plt.setp(leg_texts, fontsize = 20)
#increase the width of the x and y axis
for axis in ['bottom', 'left']:
axs.spines[axis].set_linewidth(3)
#save figure
fig.savefig('detailed_reformatting_plot.jpg')
Figure 3. Customising the legend, font size and font weights.
A Seaborn oferece uma ampla variedade de tipos de plotagem, a capacidade de integrar estatísticas em suas plotagens (como erros padrão) e paletas de cores personalizadas. Para saber mais sobre as paletas de cores da Seaborn, confira a documentação .
Aqui estão alguns exemplos de paletas diferentes:
royal = ['darkseagreen', 'lightpink', 'coral', 'darkkhaki']
darjeeling = ['orange', 'lightgreen', 'skyblue', 'plum']
bright = ['lightblue', 'teal', 'orangered', 'darkred']
sns.palplot(royal)
sns.palplot(darjeeling)
sns.palplot(bright)
Figure 4. Examples of seaborn colour palettes
sns.set_style("white")
fig, axs = plt.subplots(1, 1, figsize=(20,15), sharex = True, sharey = True)
#specify colour palette parameters
palette_plot = ['darkred', 'plum']
hue_order = ['KO', 'WT']
#input colour palette parameters
sns.lineplot(data = df, x = 'Frequency (Hz)', y = 'Power',
hue = 'Genotype', hue_order = hue_order,errorbar = ('se'),
palette = palette_plot, linewidth = 4, ax = axs)
#remove outer border
sns.despine()
axs.set_yscale('log')
sns.despine()
axs.set_xlim(1, 48)
axs.set_ylim(10**-2, 10**3)
axs.set_xlabel("Frequency (Hz)", fontsize = 25)
axs.set_ylabel("log Power (\\u03bc$\\mathregular{V^{2}}$)", fontsize = 25)
#update the fontsize of all characters on x and y axis
plt.rcParams.update({'font.size': 20})
#changing x-ticks
tick_values = list(range(1, 54, 6))
label_list = ['1', '6', '12', '18', '24', '30', '36', '42', '48']
axs.set_xticks(ticks = tick_values, labels = label_list)
#include an overall plot title
plt.suptitle('Condition 1 Overall Average', fontsize = 30, fontweight = 'bold')
#customise the legend
leg = plt.legend(loc = 'upper right', frameon = False)
leg.set_title('Genotype', prop = {'size': 25})
leg_lines = leg.get_lines()
leg_texts = leg.get_texts()
plt.setp(leg_lines[0], linewidth = 8)
plt.setp(leg_lines[1], linewidth = 8)
plt.setp(leg_texts, fontsize = 20)
#increase the width of the x and y axis
for axis in ['bottom', 'left']:
axs.spines[axis].set_linewidth(3)
Figure 5. Figure showing custom Seaborn colour palettes.
Antes de entrar em como criar subplots, é importante diferenciar entre os argumentos fig,ax = plt.subplots() e plt.plot(), pois eles são frequentemente usados de forma intercambiável. No entanto, o plt.subplots() retorna uma tupla contendo uma figura e um objeto de eixos que torna mais fácil criar subplots e reformatá-los (objeto ax) ou alterar atributos de nível de figura (objeto fig) enquanto uma sintaxe ligeiramente diferente é usada para plt.plot(). Na minha opinião, é sempre melhor usar o método fig, ax = plt.subplots(). Se você gostaria de ler mais sobre isso, esta é uma ótima postagem no blog .
Criando subtramas
Agora que passamos por como criar e personalizar um único gráfico, podemos criar subplots com diferentes tipos de gráficos em uma figura. Para demonstrar, vamos plotar as diferenças sexuais entre os animais selvagens e knockout mostrados no gráfico acima.
Como ambos os gráficos compartilham os mesmos parâmetros para os eixos x e y, adicionei os parâmetros sharex e sharey à função subplots para compartilhar os limites entre os dois eixos do subplot. Junto com os eixos, a mesma legenda se aplica a ambos os gráficos, então removi a legenda do gráfico esquerdo e reformatei a legenda da direita. Também adicionei um título geral da figura e títulos para as subtramas individuais, com preenchimento adicionado ao título geral da figura para garantir que eles não se sobreponham.
sns.set_style("white")
fig, axs = plt.subplots(1,2, figsize=(20,10), sharex = True, sharey=True)
genotype_palette = ['plum', 'teal']
hue_order = ['KO', 'WT']
#plot individual line plots for male and female data
sns.lineplot(data= df_male, x='Frequency (Hz)', y='Power',hue = 'Genotype',
errorbar = ("se"), hue_order = hue_order, linewidth = 4,
palette = genotype_palette, ax = axs[0])
sns.lineplot(data= df_female, x='Frequency (Hz)', y='Power',hue = 'Genotype',
errorbar = ("se"), hue_order = hue_order, linewidth = 4,
palette = genotype_palette, ax = axs[1])
#remove figure border
sns.despine()
#subplot titles
axs[0].set_title('Male', fontsize = 25, fontweight = 'bold')
axs[1].set_title('Female', fontsize = 25, fontweight = 'bold')
#include an overall plot title
fig.suptitle('Condition 1 Sex Differences', y = 0.96, fontsize = 30,
fontweight = 'bold')
#remove first plot legend and customise the legend for second plot
axs[0].get_legend().remove()
# customise legend
leg_1 = axs[1].legend( loc = 'upper right', frameon = False)
leg_1.set_title('Genotype',prop={'size':20})
leg_lines = leg_1.get_lines()
plt.setp(leg_lines[0], linewidth=10)
plt.setp(leg_lines[1], linewidth=10)
#update the fontsize of all characters on x and y axis
plt.rcParams.update({'font.size': 20})
#set tick values - because of shared x and y axis you only have to set one axis
tick_values = list(range(0, 54, 6))
label_list = ['0', '6', '12', '18', '24', '30', '36', '42', '48']
axs[0].set_xticks(ticks = tick_values, labels = label_list)
#specify axes details
axs[0].set_yscale('log')
axs[0].set_xlim(1, 48)
axs[0].set_ylim(10**-1, 10**3)
axs[0].set_xlabel("Frequency (Hz)", fontsize = 20)
axs[1].set_xlabel("Frequency (Hz)", fontsize = 20)
axs[0].set_ylabel("log Power (\\u03bc$\\mathregular{V^{2}}$)", fontsize = 20)
#increase width of the x and y axis
for axis in ['bottom','left']:
axs[0].spines[axis].set_linewidth(2)
axs[1].spines[axis].set_linewidth(2)
Figure 6. Plot showing subplots of different conditions
A função fig.subplot_mosaic() é útil para colocar seus eixos em uma grade não uniforme. Mostrarei primeiro um exemplo de 4 subplots usando a função de mosaico onde todos os subplots são do mesmo tamanho e, em seguida, mostrarei como você pode alterar isso. A principal diferença ao usar o mosaico são as linhas fig e ax_dict. Além disso, o mesmo código acima se aplica - mas há mais subtramas e, portanto, mais parâmetros a serem alterados. Você também pode criar sua própria folha de estilo para aplicar a todos os parâmetros, para que não precise alterá-los manualmente todas as vezes.
Formatos de arquivo vetorizados
A função de mosaico é útil para criar gráficos prontos para publicação, embora a maioria dos pesquisadores use um programa secundário (como o adobe illustrator) para reformatar as figuras. Se você deseja formatar imagens diretamente no adobe illustrator, salve suas imagens como um arquivo vetorizado (por exemplo, arquivo svg).
sns.set_style("white")
fig = plt.figure(figsize = (30,20), constrained_layout = True)
ax_dict = fig.subplot_mosaic(
[
["genotype_plot", "channel_plot"],
["wt_bar_plot", "ko_bar_plot"],
],
)
#palette and hue for plot 1
palette_plot = ['plum', 'teal']
genotype_hue = ['KO', 'WT']
#palette and hue for plot 2
channel_hue = ['Somatosensory', 'Visual', 'Motor']
channel_palette = ['black', 'teal', 'red']
#genotype plot
sns.lineplot(data = df, x = 'Frequency (Hz)', y = 'Power', hue = 'Genotype',
hue_order = genotype_hue,errorbar = ('se'), linewidth = 4,
palette = palette_plot, ax = ax_dict["genotype_plot"])
#channel plot
sns.lineplot(data = df, x = 'Frequency (Hz)', y = 'Power',
hue = 'Channel Region', hue_order = channel_hue, errorbar = ('se'),
palette = channel_palette, linewidth = 2,
ax = ax_dict["channel_plot"])
#wt bar plot
sns.barplot(data = wt_df, x = 'Bands', y = 'Power', errorbar = ('se'),
palette = palette_wt, ax = ax_dict["wt_bar_plot"])
#wt strip plot
sns.stripplot(data = wt_df, x = 'Bands',y = 'Power', ax=ax_dict["wt_bar_plot"])
#ko bar plot
sns.barplot(data = ko_df, x = 'Bands', y = 'Power', errorbar = ('se'),
ax = ax_dict["ko_bar_plot"], palette = palette_het)
#wt strip plot
sns.stripplot(data = ko_df, x = 'Bands', y = 'Power',
ax = ax_dict["ko_bar_plot"])
#update the fontsize of all characters on x and y axis
plt.rcParams.update({'font.size': 20})
#axes configurations
sns.despine()
ax_dict["genotype_plot"].set_yscale('log')
ax_dict["channel_plot"].set_yscale('log')
ax_dict["wt_bar_plot"].set_yscale('log')
ax_dict["ko_bar_plot"].set_yscale('log')
#axes limits
ax_dict["genotype_plot"].set_xlim(1, 48)
ax_dict["genotype_plot"].set_ylim(10**-2, 10**3)
ax_dict["channel_plot"].set_xlim(1, 48)
ax_dict["channel_plot"].set_ylim(10**-2, 10**3)
#axes labels
ax_dict["genotype_plot"].set_xlabel("Frequency (Hz)", fontsize = 25)
ax_dict["genotype_plot"].set_ylabel("log Power (\\u03bc$\\mathregular{V^{2}}$)", fontsize = 25)
ax_dict["channel_plot"].set_xlabel("Frequency (Hz)", fontsize = 25)
ax_dict["channel_plot"].set_ylabel("log Power (\\u03bc$\\mathregular{V^{2}}$)", fontsize = 25)
ax_dict["wt_bar_plot"].set_xlabel("Frequency Bands (Hz)", fontsize = 25)
ax_dict["wt_bar_plot"].set_ylabel("log Power (\\u03bc$\\mathregular{V^{2}}$)", fontsize = 25)
ax_dict["ko_bar_plot"].set_xlabel("Frequency Bands (Hz)", fontsize = 25)
ax_dict["ko_bar_plot"].set_ylabel("log Power (\\u03bc$\\mathregular{V^{2}}$)", fontsize = 25)
#individual plot titles
ax_dict["genotype_plot"].set_title('Condition 1', fontsize = 35, fontweight = 'bold')
ax_dict["channel_plot"].set_title('Power Across Regions', fontsize = 35, fontweight = 'bold')
ax_dict["wt_bar_plot"].set_title('Wild-type', fontsize = 35, fontweight = 'bold')
ax_dict["ko_bar_plot"].set_title('Knockout', fontsize = 35, fontweight = 'bold')
#genotype legend
leg_genotype = ax_dict["genotype_plot"].legend(loc = 'upper right', frameon = False)
leg_genotype.set_title('Genotype', prop = {'size': 25})
leg_lines_1 = leg_genotype.get_lines()
plt.setp(leg_lines_1, linewidth = 8)
#custom legend for plot 2
leg_ch_region = ax_dict["channel_plot"].legend(loc = 'upper right', frameon = False)
leg_ch_region.set_title('Channel Region', prop = {'size': 25})
leg_lines_2 = leg_ch_region.get_lines()
plt.setp(leg_lines_2, linewidth = 8)
#set custom x_tick labels for lower two plots
label_list = ['band 1', 'band 2', 'band 3', 'band 4', 'band 5' ]
ax_dict["wt_bar_plot"].set_xticklabels(label_list)
ax_dict["ko_bar_plot"].set_xticklabels(label_list)
#change weight of x and y axis
for axis in ['bottom','left']:
ax_dict["genotype_plot"].spines[axis].set_linewidth(3)
ax_dict["channel_plot"].spines[axis].set_linewidth(3)
ax_dict["wt_bar_plot"].spines[axis].set_linewidth(3)
ax_dict["ko_bar_plot"].spines[axis].set_linewidth(3)
Figure 7. Plot showing how to use Matplotlib’s mosaic function to create different plot types in a uniform grid
#specify subplots on the inner right corner
inner = [
["wt_bar_plot"],
["ko_bar_plot"],
]
#specify order of all plots
outer_nested_mosaic = [
["genotype_plot", inner],
['channel_plot', 'channel_plot']
]
sns.set_style("white")
ax_dict = plt.figure(figsize = (30,20), constrained_layout = True).subplot_mosaic(
outer_nested_mosaic, empty_sentinel = None)
#palette and hue for plot 1
palette_plot = ['plum', 'teal']
genotype_hue = ['KO', 'WT']
#palette and hue for plot 2
channel_palette = ['black', 'teal', 'red']
channel_hue = ['Somatosensory', 'Visual', 'Motor']
#palette for plot 3 and 4
palette_wt = sns.color_palette("viridis")
palette_het = sns.color_palette("rocket")
sns.lineplot(data = df, x = 'Frequency (Hz)', y = 'Power', hue = 'Genotype', hue_order = genotype_hue,
errorbar = ('se'), linewidth = 6, palette = palette_plot, ax = ax_dict["genotype_plot"])
sns.lineplot(data = df, x = 'Frequency (Hz)', y = 'Power', hue = 'Channel Region', hue_order = channel_hue,
errorbar = ('se'), palette = channel_palette, linewidth = 6, ax = ax_dict["channel_plot"])
sns.barplot(data = wt_df, x = 'Bands', y = 'Power', errorbar = ('se'),palette = palette_wt,
ax = ax_dict["wt_bar_plot"])
sns.stripplot(data = wt_df, x = 'Bands', y = 'Power',ax = ax_dict["wt_bar_plot"])
sns.barplot(data = ko_df, x = 'Bands', y = 'Power', errorbar = ('se'), ax = ax_dict["ko_bar_plot"],
palette = palette_het)
sns.stripplot(data = ko_df, x = 'Bands', y = 'Power',ax = ax_dict["ko_bar_plot"])
#update the fontsize of all characters on x and y axis
plt.rcParams.update({'font.size': 20})
#axes configurations
sns.despine()
ax_dict["genotype_plot"].set_yscale('log')
ax_dict["channel_plot"].set_yscale('log')
ax_dict["wt_bar_plot"].set_yscale('log')
ax_dict["ko_bar_plot"].set_yscale('log')
#axes limits
ax_dict["genotype_plot"].set_xlim(1, 48)
ax_dict["genotype_plot"].set_ylim(10**-2, 10**3)
ax_dict["channel_plot"].set_xlim(1, 48)
ax_dict["channel_plot"].set_ylim(10**-2, 10**3)
#axes labels
ax_dict["genotype_plot"].set_xlabel("Frequency (Hz)", fontsize = 25)
ax_dict["genotype_plot"].set_ylabel("log Power (\\u03bc$\\mathregular{V^{2}}$)", fontsize = 25)
ax_dict["channel_plot"].set_xlabel("Frequency (Hz)", fontsize = 25)
ax_dict["channel_plot"].set_ylabel("log Power (\\u03bc$\\mathregular{V^{2}}$)", fontsize = 25)
ax_dict["wt_bar_plot"].set_xlabel("Frequency Bands (Hz)", fontsize = 25)
ax_dict["wt_bar_plot"].set_ylabel("log Power (\\u03bc$\\mathregular{V^{2}}$)", fontsize = 25)
ax_dict["ko_bar_plot"].set_xlabel("Frequency Bands (Hz)", fontsize = 25)
ax_dict["ko_bar_plot"].set_ylabel("log Power (\\u03bc$\\mathregular{V^{2}}$)", fontsize = 25)
#individual plot titles
ax_dict["genotype_plot"].set_title('Condition 1', fontsize = 35, fontweight = 'bold')
ax_dict["channel_plot"].set_title('Power Across Regions', fontsize = 35, fontweight = 'bold')
ax_dict["wt_bar_plot"].set_title('Wild-type', fontsize = 35, fontweight = 'bold')
ax_dict["ko_bar_plot"].set_title('Knockout', fontsize = 35, fontweight = 'bold')
#genotype legend
leg_genotype = ax_dict["genotype_plot"].legend(loc = 'upper right', frameon = False)
leg_genotype.set_title('Genotype', prop = {'size': 25})
leg_lines_1 = leg_genotype.get_lines()
plt.setp(leg_lines_1, linewidth = 8)
#custom legend for plot 2
leg_ch_region = ax_dict["channel_plot"].legend(loc = 'upper right', frameon = False)
leg_ch_region.set_title('Channel Region', prop = {'size': 25})
leg_lines_2 = leg_ch_region.get_lines()
plt.setp(leg_lines_2, linewidth = 8)
#set custom x_tick labels for lower two plots
label_list = ['band 1', 'band 2', 'band 3', 'band 4', 'band 5' ]
ax_dict["wt_bar_plot"].set_xticklabels(label_list)
ax_dict["ko_bar_plot"].set_xticklabels(label_list)
for axis in ['bottom','left']:
ax_dict["genotype_plot"].spines[axis].set_linewidth(3)
ax_dict["channel_plot"].spines[axis].set_linewidth(3)
ax_dict["wt_bar_plot"].spines[axis].set_linewidth(3)
ax_dict["ko_bar_plot"].spines[axis].set_linewidth(3)
Figure 9. Plot showing how to use Matplotlib’s mosaic function to create different plot types in a non-uniform grid





































![O que é uma lista vinculada, afinal? [Parte 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)