1

I would like to put specific marker like second label bottom of the first label in a plot of matplotlib. The format of my files is like this:

File 1.txt

3
4
6
.
.
etc

file 2.txt

5
12
8
.
.
etc

file 3.txt

230.45
345.65
342.3
.
.
etc.

My script is this:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import unicode_literals
from numpy import *
from matplotlib.ticker import FormatStrFormatter
from matplotlib.ticker import MaxNLocator
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.ticker as tkr
import matplotlib.patches as patches

with open("1.txt") as f:
        lstx = [int(x) for x in f.read().split()]

with open("2.txt") as f:
            lsty = [int(x) for x in f.read().split()]

with open("3.txt") as f:
            lstz = [float(x) for x in f.read().split()]

def numfmt(x, pos):
    s = '{}'.format(int(x + 120))
    return s

def numfmty(y, pos):
    m = '{}'.format(int(y + 120))
    return m

       
x=np.array(lstx)
y=np.array(lsty)
z=np.array(lstz)
 
df = pd.DataFrame.from_dict(np.array([y,x,z]).T)
df.columns = ['X_value','Y_value','Z_value']
df['Z_value'] = pd.to_numeric(df['Z_value'])

fig, ax = plt.subplots(figsize=(11,9))

pivotted= df.pivot('X_value','Y_value','Z_value')

ax = sns.heatmap(pivotted, cmap='plasma_r', vmin=0.0, vmax=234.525)
cbar = ax.collections[0].colorbar
cbar.ax.tick_params(labelsize=20)
    
plt.gca().invert_yaxis()
xfmt = tkr.FuncFormatter(numfmt)
plt.gca().xaxis.set_major_formatter(xfmt)
yfmt = tkr.FuncFormatter(numfmty)
plt.gca().yaxis.set_major_formatter(yfmt)

plt.xlabel('\n Number', fontsize=24)
plt.ylabel('Number \n', fontsize=24)
plt.xticks(size=16)
plt.yticks(size=16)

plt.tight_layout()
major_ticks = np.arange(0, 33, 1)
minor_ticks = np.arange(0, 33, 1)
ax.set_xticks(major_ticks)
ax.set_xticks(minor_ticks, minor=True)
ax.set_yticks(major_ticks)
ax.set_yticks(minor_ticks, minor=True)
ax.grid(which='both')
ax.grid(which='minor', alpha=0.5)
ax.grid(which='major', alpha=0.5)

rect3 = patches.Rectangle((5,5),13,13,linewidth=1.7,linestyle='--',edgecolor='black',facecolor='none')

ax2 = ax.twiny()
ax2.xaxis.set_ticks_position("bottom")
ax2.xaxis.set_label_position("bottom")
newpos=[2,4,6]
newlabel=['*', '*', '*']
ax2.set_xticks(newpos)
ax2.set_xticklabels(newlabel)

ax.add_patch(rect3)
plt.grid()
plt.show()

I would like to put a marker '*' in the positions 125, 128, 130, 133, 138, 142 and 143 in both axis, with a size of 16. When I try to put them, these are very small, are up of the first label and the grid is move it. The output is this:

enter image description here How can I fit that? Thanks a lot

Hdez
  • 151
  • 7

1 Answers1

0

The following code adds stars at the indicated columns and rows.

Something confusing about seaborn is that it is quite opinionated giving priority to how formatting looks like and not caring too much about the internal representation. For example, the real tick positions are at the halves, but shown as integers.

Note that plt.tight_layout() is preferably one of the last commands just before plt.show(). Also note that if you created the ax beforehand, it is recommended to pass it as a parameter to sns.heatmap().

In the code below, the major ticks are at the halves to position the tick labels, while the minor ticks are at the integer positions to show the grid.

import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np
import pandas as pd
import seaborn as sns

x = np.tile(np.arange(0, 33, dtype=int), 33)
y = np.repeat(np.arange(0, 33, dtype=int), 33)
z = np.random.randint(0, 50, len(x)).astype(float)
z[x == y] = np.nan
z[np.abs(x - y) == 1] = 200
z[np.abs(x - y) == 2] = 150

df = pd.DataFrame.from_dict({'X_value': x, 'Y_value': y, 'Z_value': z})
pivotted = df.pivot('X_value', 'Y_value', 'Z_value')

fig, ax = plt.subplots(figsize=(11, 9))

sns.heatmap(pivotted, cmap='plasma_r', vmin=0.0, vmax=234.525, square=True, ax=ax)
ax.invert_yaxis()
cbar = ax.collections[0].colorbar
cbar.ax.tick_params(labelsize=20)

ax.set_xlabel('\n Number', fontsize=24)
ax.set_ylabel('Number \n', fontsize=24)
major_tick_pos = np.arange(0.5, 33, 1)
special_ticks = [125, 128, 130, 133, 138, 142, 143]
major_tick_labels = [('★ ' if i + 120 in special_ticks else '') + f'{i + 120}' for i in range(33)]
minor_tick_pos = np.arange(0, 34, 1)
ax.set_xticks(major_tick_pos)
ax.set_xticks(minor_tick_pos, minor=True)
ax.set_xticklabels(major_tick_labels, size=16, rotation=90)
ax.set_yticks(major_tick_pos)
ax.set_yticks(minor_tick_pos, minor=True)
ax.set_yticklabels(major_tick_labels, size=16, rotation=0)

ax.grid(which='minor', color='black', ls=':', alpha=0.5, lw=2)
ax.tick_params(axis='both', length=0)
rect3 = patches.Rectangle((5, 5), 13, 13, linewidth=1.7, linestyle='--', edgecolor='black', facecolor='none')
ax.add_patch(rect3)
plt.tight_layout()
plt.show()

resulting plot

PS: If you'd like the stars at the other side of the grid, both twinx() and twiny() are needed, only using the '★ ' if i + 120 in special_ticks else '' part of the labels.

An alternative idea would be to use annotations inside the cells to mark the special rows and columns:


stars = [['☆' if x in special_ticks or y in special_ticks  else ''  for x in range(120, 153)] 
         for y in range(120, 153)]
sns.heatmap(pivotted, cmap='plasma_r', vmin=0.0, vmax=234.525,
            annot=stars, fmt='s', annot_kws={'size':20}, square=True, ax=ax)

To change the tick label colors, an approach could be:

xticks = ax.set_xticklabels(major_tick_labels, size=16, rotation=90)
yticks = ax.set_yticklabels(major_tick_labels, size=16, rotation=0)
for t in xticks + yticks:
    if t.get_text().startswith('★'):
        t.set_color('crimson')
JohanC
  • 71,591
  • 8
  • 33
  • 66
  • Thanks a lot! It was what I looking for. I have a last question, if I want the stars with color, I tried with ax.set_yticklabels(major_tick_labels, size=16, rotation=0, color='red'), but the numbers are the color too. In this case, I must to do other variable that read ony the stars? Thanks – Hdez Sep 19 '20 at 18:01
  • See e.g. [this post](https://stackoverflow.com/questions/33159134/matplotlib-y-axis-label-with-multiple-colors) about multicolored labels. Getting it to work for tick labels is even more tricky. (I edited to post with a way to change the color of tick labels, but each label will only have one color) – JohanC Sep 19 '20 at 18:17