matrix.py 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262
  1. """Functions to visualize matrices of data."""
  2. import warnings
  3. import matplotlib as mpl
  4. from matplotlib.collections import LineCollection
  5. import matplotlib.pyplot as plt
  6. from matplotlib import gridspec
  7. import numpy as np
  8. import pandas as pd
  9. try:
  10. from scipy.cluster import hierarchy
  11. _no_scipy = False
  12. except ImportError:
  13. _no_scipy = True
  14. from . import cm
  15. from .axisgrid import Grid
  16. from ._compat import get_colormap
  17. from .utils import (
  18. despine,
  19. axis_ticklabels_overlap,
  20. relative_luminance,
  21. to_utf8,
  22. _draw_figure,
  23. )
  24. __all__ = ["heatmap", "clustermap"]
  25. def _index_to_label(index):
  26. """Convert a pandas index or multiindex to an axis label."""
  27. if isinstance(index, pd.MultiIndex):
  28. return "-".join(map(to_utf8, index.names))
  29. else:
  30. return index.name
  31. def _index_to_ticklabels(index):
  32. """Convert a pandas index or multiindex into ticklabels."""
  33. if isinstance(index, pd.MultiIndex):
  34. return ["-".join(map(to_utf8, i)) for i in index.values]
  35. else:
  36. return index.values
  37. def _convert_colors(colors):
  38. """Convert either a list of colors or nested lists of colors to RGB."""
  39. to_rgb = mpl.colors.to_rgb
  40. try:
  41. to_rgb(colors[0])
  42. # If this works, there is only one level of colors
  43. return list(map(to_rgb, colors))
  44. except ValueError:
  45. # If we get here, we have nested lists
  46. return [list(map(to_rgb, color_list)) for color_list in colors]
  47. def _matrix_mask(data, mask):
  48. """Ensure that data and mask are compatible and add missing values.
  49. Values will be plotted for cells where ``mask`` is ``False``.
  50. ``data`` is expected to be a DataFrame; ``mask`` can be an array or
  51. a DataFrame.
  52. """
  53. if mask is None:
  54. mask = np.zeros(data.shape, bool)
  55. if isinstance(mask, np.ndarray):
  56. # For array masks, ensure that shape matches data then convert
  57. if mask.shape != data.shape:
  58. raise ValueError("Mask must have the same shape as data.")
  59. mask = pd.DataFrame(mask,
  60. index=data.index,
  61. columns=data.columns,
  62. dtype=bool)
  63. elif isinstance(mask, pd.DataFrame):
  64. # For DataFrame masks, ensure that semantic labels match data
  65. if not mask.index.equals(data.index) \
  66. and mask.columns.equals(data.columns):
  67. err = "Mask must have the same index and columns as data."
  68. raise ValueError(err)
  69. # Add any cells with missing data to the mask
  70. # This works around an issue where `plt.pcolormesh` doesn't represent
  71. # missing data properly
  72. mask = mask | pd.isnull(data)
  73. return mask
  74. class _HeatMapper:
  75. """Draw a heatmap plot of a matrix with nice labels and colormaps."""
  76. def __init__(self, data, vmin, vmax, cmap, center, robust, annot, fmt,
  77. annot_kws, cbar, cbar_kws,
  78. xticklabels=True, yticklabels=True, mask=None):
  79. """Initialize the plotting object."""
  80. # We always want to have a DataFrame with semantic information
  81. # and an ndarray to pass to matplotlib
  82. if isinstance(data, pd.DataFrame):
  83. plot_data = data.values
  84. else:
  85. plot_data = np.asarray(data)
  86. data = pd.DataFrame(plot_data)
  87. # Validate the mask and convert to DataFrame
  88. mask = _matrix_mask(data, mask)
  89. plot_data = np.ma.masked_where(np.asarray(mask), plot_data)
  90. # Get good names for the rows and columns
  91. xtickevery = 1
  92. if isinstance(xticklabels, int):
  93. xtickevery = xticklabels
  94. xticklabels = _index_to_ticklabels(data.columns)
  95. elif xticklabels is True:
  96. xticklabels = _index_to_ticklabels(data.columns)
  97. elif xticklabels is False:
  98. xticklabels = []
  99. ytickevery = 1
  100. if isinstance(yticklabels, int):
  101. ytickevery = yticklabels
  102. yticklabels = _index_to_ticklabels(data.index)
  103. elif yticklabels is True:
  104. yticklabels = _index_to_ticklabels(data.index)
  105. elif yticklabels is False:
  106. yticklabels = []
  107. if not len(xticklabels):
  108. self.xticks = []
  109. self.xticklabels = []
  110. elif isinstance(xticklabels, str) and xticklabels == "auto":
  111. self.xticks = "auto"
  112. self.xticklabels = _index_to_ticklabels(data.columns)
  113. else:
  114. self.xticks, self.xticklabels = self._skip_ticks(xticklabels,
  115. xtickevery)
  116. if not len(yticklabels):
  117. self.yticks = []
  118. self.yticklabels = []
  119. elif isinstance(yticklabels, str) and yticklabels == "auto":
  120. self.yticks = "auto"
  121. self.yticklabels = _index_to_ticklabels(data.index)
  122. else:
  123. self.yticks, self.yticklabels = self._skip_ticks(yticklabels,
  124. ytickevery)
  125. # Get good names for the axis labels
  126. xlabel = _index_to_label(data.columns)
  127. ylabel = _index_to_label(data.index)
  128. self.xlabel = xlabel if xlabel is not None else ""
  129. self.ylabel = ylabel if ylabel is not None else ""
  130. # Determine good default values for the colormapping
  131. self._determine_cmap_params(plot_data, vmin, vmax,
  132. cmap, center, robust)
  133. # Sort out the annotations
  134. if annot is None or annot is False:
  135. annot = False
  136. annot_data = None
  137. else:
  138. if isinstance(annot, bool):
  139. annot_data = plot_data
  140. else:
  141. annot_data = np.asarray(annot)
  142. if annot_data.shape != plot_data.shape:
  143. err = "`data` and `annot` must have same shape."
  144. raise ValueError(err)
  145. annot = True
  146. # Save other attributes to the object
  147. self.data = data
  148. self.plot_data = plot_data
  149. self.annot = annot
  150. self.annot_data = annot_data
  151. self.fmt = fmt
  152. self.annot_kws = {} if annot_kws is None else annot_kws.copy()
  153. self.cbar = cbar
  154. self.cbar_kws = {} if cbar_kws is None else cbar_kws.copy()
  155. def _determine_cmap_params(self, plot_data, vmin, vmax,
  156. cmap, center, robust):
  157. """Use some heuristics to set good defaults for colorbar and range."""
  158. # plot_data is a np.ma.array instance
  159. calc_data = plot_data.astype(float).filled(np.nan)
  160. if vmin is None:
  161. if robust:
  162. vmin = np.nanpercentile(calc_data, 2)
  163. else:
  164. vmin = np.nanmin(calc_data)
  165. if vmax is None:
  166. if robust:
  167. vmax = np.nanpercentile(calc_data, 98)
  168. else:
  169. vmax = np.nanmax(calc_data)
  170. self.vmin, self.vmax = vmin, vmax
  171. # Choose default colormaps if not provided
  172. if cmap is None:
  173. if center is None:
  174. self.cmap = cm.rocket
  175. else:
  176. self.cmap = cm.icefire
  177. elif isinstance(cmap, str):
  178. self.cmap = get_colormap(cmap)
  179. elif isinstance(cmap, list):
  180. self.cmap = mpl.colors.ListedColormap(cmap)
  181. else:
  182. self.cmap = cmap
  183. # Recenter a divergent colormap
  184. if center is not None:
  185. # Copy bad values
  186. # in mpl<3.2 only masked values are honored with "bad" color spec
  187. # (see https://github.com/matplotlib/matplotlib/pull/14257)
  188. bad = self.cmap(np.ma.masked_invalid([np.nan]))[0]
  189. # under/over values are set for sure when cmap extremes
  190. # do not map to the same color as +-inf
  191. under = self.cmap(-np.inf)
  192. over = self.cmap(np.inf)
  193. under_set = under != self.cmap(0)
  194. over_set = over != self.cmap(self.cmap.N - 1)
  195. vrange = max(vmax - center, center - vmin)
  196. normlize = mpl.colors.Normalize(center - vrange, center + vrange)
  197. cmin, cmax = normlize([vmin, vmax])
  198. cc = np.linspace(cmin, cmax, 256)
  199. self.cmap = mpl.colors.ListedColormap(self.cmap(cc))
  200. self.cmap.set_bad(bad)
  201. if under_set:
  202. self.cmap.set_under(under)
  203. if over_set:
  204. self.cmap.set_over(over)
  205. def _annotate_heatmap(self, ax, mesh):
  206. """Add textual labels with the value in each cell."""
  207. mesh.update_scalarmappable()
  208. height, width = self.annot_data.shape
  209. xpos, ypos = np.meshgrid(np.arange(width) + .5, np.arange(height) + .5)
  210. for x, y, m, color, val in zip(xpos.flat, ypos.flat,
  211. mesh.get_array().flat, mesh.get_facecolors(),
  212. self.annot_data.flat):
  213. if m is not np.ma.masked:
  214. lum = relative_luminance(color)
  215. text_color = ".15" if lum > .408 else "w"
  216. annotation = ("{:" + self.fmt + "}").format(val)
  217. text_kwargs = dict(color=text_color, ha="center", va="center")
  218. text_kwargs.update(self.annot_kws)
  219. ax.text(x, y, annotation, **text_kwargs)
  220. def _skip_ticks(self, labels, tickevery):
  221. """Return ticks and labels at evenly spaced intervals."""
  222. n = len(labels)
  223. if tickevery == 0:
  224. ticks, labels = [], []
  225. elif tickevery == 1:
  226. ticks, labels = np.arange(n) + .5, labels
  227. else:
  228. start, end, step = 0, n, tickevery
  229. ticks = np.arange(start, end, step) + .5
  230. labels = labels[start:end:step]
  231. return ticks, labels
  232. def _auto_ticks(self, ax, labels, axis):
  233. """Determine ticks and ticklabels that minimize overlap."""
  234. transform = ax.figure.dpi_scale_trans.inverted()
  235. bbox = ax.get_window_extent().transformed(transform)
  236. size = [bbox.width, bbox.height][axis]
  237. axis = [ax.xaxis, ax.yaxis][axis]
  238. tick, = axis.set_ticks([0])
  239. fontsize = tick.label1.get_size()
  240. max_ticks = int(size // (fontsize / 72))
  241. if max_ticks < 1:
  242. return [], []
  243. tick_every = len(labels) // max_ticks + 1
  244. tick_every = 1 if tick_every == 0 else tick_every
  245. ticks, labels = self._skip_ticks(labels, tick_every)
  246. return ticks, labels
  247. def plot(self, ax, cax, kws):
  248. """Draw the heatmap on the provided Axes."""
  249. # Remove all the Axes spines
  250. despine(ax=ax, left=True, bottom=True)
  251. # setting vmin/vmax in addition to norm is deprecated
  252. # so avoid setting if norm is set
  253. if kws.get("norm") is None:
  254. kws.setdefault("vmin", self.vmin)
  255. kws.setdefault("vmax", self.vmax)
  256. # Draw the heatmap
  257. mesh = ax.pcolormesh(self.plot_data, cmap=self.cmap, **kws)
  258. # Set the axis limits
  259. ax.set(xlim=(0, self.data.shape[1]), ylim=(0, self.data.shape[0]))
  260. # Invert the y axis to show the plot in matrix form
  261. ax.invert_yaxis()
  262. # Possibly add a colorbar
  263. if self.cbar:
  264. cb = ax.figure.colorbar(mesh, cax, ax, **self.cbar_kws)
  265. cb.outline.set_linewidth(0)
  266. # If rasterized is passed to pcolormesh, also rasterize the
  267. # colorbar to avoid white lines on the PDF rendering
  268. if kws.get('rasterized', False):
  269. cb.solids.set_rasterized(True)
  270. # Add row and column labels
  271. if isinstance(self.xticks, str) and self.xticks == "auto":
  272. xticks, xticklabels = self._auto_ticks(ax, self.xticklabels, 0)
  273. else:
  274. xticks, xticklabels = self.xticks, self.xticklabels
  275. if isinstance(self.yticks, str) and self.yticks == "auto":
  276. yticks, yticklabels = self._auto_ticks(ax, self.yticklabels, 1)
  277. else:
  278. yticks, yticklabels = self.yticks, self.yticklabels
  279. ax.set(xticks=xticks, yticks=yticks)
  280. xtl = ax.set_xticklabels(xticklabels)
  281. ytl = ax.set_yticklabels(yticklabels, rotation="vertical")
  282. plt.setp(ytl, va="center") # GH2484
  283. # Possibly rotate them if they overlap
  284. _draw_figure(ax.figure)
  285. if axis_ticklabels_overlap(xtl):
  286. plt.setp(xtl, rotation="vertical")
  287. if axis_ticklabels_overlap(ytl):
  288. plt.setp(ytl, rotation="horizontal")
  289. # Add the axis labels
  290. ax.set(xlabel=self.xlabel, ylabel=self.ylabel)
  291. # Annotate the cells with the formatted values
  292. if self.annot:
  293. self._annotate_heatmap(ax, mesh)
  294. def heatmap(
  295. data, *,
  296. vmin=None, vmax=None, cmap=None, center=None, robust=False,
  297. annot=None, fmt=".2g", annot_kws=None,
  298. linewidths=0, linecolor="white",
  299. cbar=True, cbar_kws=None, cbar_ax=None,
  300. square=False, xticklabels="auto", yticklabels="auto",
  301. mask=None, ax=None,
  302. **kwargs
  303. ):
  304. """Plot rectangular data as a color-encoded matrix.
  305. This is an Axes-level function and will draw the heatmap into the
  306. currently-active Axes if none is provided to the ``ax`` argument. Part of
  307. this Axes space will be taken and used to plot a colormap, unless ``cbar``
  308. is False or a separate Axes is provided to ``cbar_ax``.
  309. Parameters
  310. ----------
  311. data : rectangular dataset
  312. 2D dataset that can be coerced into an ndarray. If a Pandas DataFrame
  313. is provided, the index/column information will be used to label the
  314. columns and rows.
  315. vmin, vmax : floats, optional
  316. Values to anchor the colormap, otherwise they are inferred from the
  317. data and other keyword arguments.
  318. cmap : matplotlib colormap name or object, or list of colors, optional
  319. The mapping from data values to color space. If not provided, the
  320. default will depend on whether ``center`` is set.
  321. center : float, optional
  322. The value at which to center the colormap when plotting divergent data.
  323. Using this parameter will change the default ``cmap`` if none is
  324. specified.
  325. robust : bool, optional
  326. If True and ``vmin`` or ``vmax`` are absent, the colormap range is
  327. computed with robust quantiles instead of the extreme values.
  328. annot : bool or rectangular dataset, optional
  329. If True, write the data value in each cell. If an array-like with the
  330. same shape as ``data``, then use this to annotate the heatmap instead
  331. of the data. Note that DataFrames will match on position, not index.
  332. fmt : str, optional
  333. String formatting code to use when adding annotations.
  334. annot_kws : dict of key, value mappings, optional
  335. Keyword arguments for :meth:`matplotlib.axes.Axes.text` when ``annot``
  336. is True.
  337. linewidths : float, optional
  338. Width of the lines that will divide each cell.
  339. linecolor : color, optional
  340. Color of the lines that will divide each cell.
  341. cbar : bool, optional
  342. Whether to draw a colorbar.
  343. cbar_kws : dict of key, value mappings, optional
  344. Keyword arguments for :meth:`matplotlib.figure.Figure.colorbar`.
  345. cbar_ax : matplotlib Axes, optional
  346. Axes in which to draw the colorbar, otherwise take space from the
  347. main Axes.
  348. square : bool, optional
  349. If True, set the Axes aspect to "equal" so each cell will be
  350. square-shaped.
  351. xticklabels, yticklabels : "auto", bool, list-like, or int, optional
  352. If True, plot the column names of the dataframe. If False, don't plot
  353. the column names. If list-like, plot these alternate labels as the
  354. xticklabels. If an integer, use the column names but plot only every
  355. n label. If "auto", try to densely plot non-overlapping labels.
  356. mask : bool array or DataFrame, optional
  357. If passed, data will not be shown in cells where ``mask`` is True.
  358. Cells with missing values are automatically masked.
  359. ax : matplotlib Axes, optional
  360. Axes in which to draw the plot, otherwise use the currently-active
  361. Axes.
  362. kwargs : other keyword arguments
  363. All other keyword arguments are passed to
  364. :meth:`matplotlib.axes.Axes.pcolormesh`.
  365. Returns
  366. -------
  367. ax : matplotlib Axes
  368. Axes object with the heatmap.
  369. See Also
  370. --------
  371. clustermap : Plot a matrix using hierarchical clustering to arrange the
  372. rows and columns.
  373. Examples
  374. --------
  375. .. include:: ../docstrings/heatmap.rst
  376. """
  377. # Initialize the plotter object
  378. plotter = _HeatMapper(data, vmin, vmax, cmap, center, robust, annot, fmt,
  379. annot_kws, cbar, cbar_kws, xticklabels,
  380. yticklabels, mask)
  381. # Add the pcolormesh kwargs here
  382. kwargs["linewidths"] = linewidths
  383. kwargs["edgecolor"] = linecolor
  384. # Draw the plot and return the Axes
  385. if ax is None:
  386. ax = plt.gca()
  387. if square:
  388. ax.set_aspect("equal")
  389. plotter.plot(ax, cbar_ax, kwargs)
  390. return ax
  391. class _DendrogramPlotter:
  392. """Object for drawing tree of similarities between data rows/columns"""
  393. def __init__(self, data, linkage, metric, method, axis, label, rotate):
  394. """Plot a dendrogram of the relationships between the columns of data
  395. Parameters
  396. ----------
  397. data : pandas.DataFrame
  398. Rectangular data
  399. """
  400. self.axis = axis
  401. if self.axis == 1:
  402. data = data.T
  403. if isinstance(data, pd.DataFrame):
  404. array = data.values
  405. else:
  406. array = np.asarray(data)
  407. data = pd.DataFrame(array)
  408. self.array = array
  409. self.data = data
  410. self.shape = self.data.shape
  411. self.metric = metric
  412. self.method = method
  413. self.axis = axis
  414. self.label = label
  415. self.rotate = rotate
  416. if linkage is None:
  417. self.linkage = self.calculated_linkage
  418. else:
  419. self.linkage = linkage
  420. self.dendrogram = self.calculate_dendrogram()
  421. # Dendrogram ends are always at multiples of 5, who knows why
  422. ticks = 10 * np.arange(self.data.shape[0]) + 5
  423. if self.label:
  424. ticklabels = _index_to_ticklabels(self.data.index)
  425. ticklabels = [ticklabels[i] for i in self.reordered_ind]
  426. if self.rotate:
  427. self.xticks = []
  428. self.yticks = ticks
  429. self.xticklabels = []
  430. self.yticklabels = ticklabels
  431. self.ylabel = _index_to_label(self.data.index)
  432. self.xlabel = ''
  433. else:
  434. self.xticks = ticks
  435. self.yticks = []
  436. self.xticklabels = ticklabels
  437. self.yticklabels = []
  438. self.ylabel = ''
  439. self.xlabel = _index_to_label(self.data.index)
  440. else:
  441. self.xticks, self.yticks = [], []
  442. self.yticklabels, self.xticklabels = [], []
  443. self.xlabel, self.ylabel = '', ''
  444. self.dependent_coord = self.dendrogram['dcoord']
  445. self.independent_coord = self.dendrogram['icoord']
  446. def _calculate_linkage_scipy(self):
  447. linkage = hierarchy.linkage(self.array, method=self.method,
  448. metric=self.metric)
  449. return linkage
  450. def _calculate_linkage_fastcluster(self):
  451. import fastcluster
  452. # Fastcluster has a memory-saving vectorized version, but only
  453. # with certain linkage methods, and mostly with euclidean metric
  454. # vector_methods = ('single', 'centroid', 'median', 'ward')
  455. euclidean_methods = ('centroid', 'median', 'ward')
  456. euclidean = self.metric == 'euclidean' and self.method in \
  457. euclidean_methods
  458. if euclidean or self.method == 'single':
  459. return fastcluster.linkage_vector(self.array,
  460. method=self.method,
  461. metric=self.metric)
  462. else:
  463. linkage = fastcluster.linkage(self.array, method=self.method,
  464. metric=self.metric)
  465. return linkage
  466. @property
  467. def calculated_linkage(self):
  468. try:
  469. return self._calculate_linkage_fastcluster()
  470. except ImportError:
  471. if np.prod(self.shape) >= 10000:
  472. msg = ("Clustering large matrix with scipy. Installing "
  473. "`fastcluster` may give better performance.")
  474. warnings.warn(msg)
  475. return self._calculate_linkage_scipy()
  476. def calculate_dendrogram(self):
  477. """Calculates a dendrogram based on the linkage matrix
  478. Made a separate function, not a property because don't want to
  479. recalculate the dendrogram every time it is accessed.
  480. Returns
  481. -------
  482. dendrogram : dict
  483. Dendrogram dictionary as returned by scipy.cluster.hierarchy
  484. .dendrogram. The important key-value pairing is
  485. "reordered_ind" which indicates the re-ordering of the matrix
  486. """
  487. return hierarchy.dendrogram(self.linkage, no_plot=True,
  488. color_threshold=-np.inf)
  489. @property
  490. def reordered_ind(self):
  491. """Indices of the matrix, reordered by the dendrogram"""
  492. return self.dendrogram['leaves']
  493. def plot(self, ax, tree_kws):
  494. """Plots a dendrogram of the similarities between data on the axes
  495. Parameters
  496. ----------
  497. ax : matplotlib.axes.Axes
  498. Axes object upon which the dendrogram is plotted
  499. """
  500. tree_kws = {} if tree_kws is None else tree_kws.copy()
  501. tree_kws.setdefault("linewidths", .5)
  502. tree_kws.setdefault("colors", tree_kws.pop("color", (.2, .2, .2)))
  503. if self.rotate and self.axis == 0:
  504. coords = zip(self.dependent_coord, self.independent_coord)
  505. else:
  506. coords = zip(self.independent_coord, self.dependent_coord)
  507. lines = LineCollection([list(zip(x, y)) for x, y in coords],
  508. **tree_kws)
  509. ax.add_collection(lines)
  510. number_of_leaves = len(self.reordered_ind)
  511. max_dependent_coord = max(map(max, self.dependent_coord))
  512. if self.rotate:
  513. ax.yaxis.set_ticks_position('right')
  514. # Constants 10 and 1.05 come from
  515. # `scipy.cluster.hierarchy._plot_dendrogram`
  516. ax.set_ylim(0, number_of_leaves * 10)
  517. ax.set_xlim(0, max_dependent_coord * 1.05)
  518. ax.invert_xaxis()
  519. ax.invert_yaxis()
  520. else:
  521. # Constants 10 and 1.05 come from
  522. # `scipy.cluster.hierarchy._plot_dendrogram`
  523. ax.set_xlim(0, number_of_leaves * 10)
  524. ax.set_ylim(0, max_dependent_coord * 1.05)
  525. despine(ax=ax, bottom=True, left=True)
  526. ax.set(xticks=self.xticks, yticks=self.yticks,
  527. xlabel=self.xlabel, ylabel=self.ylabel)
  528. xtl = ax.set_xticklabels(self.xticklabels)
  529. ytl = ax.set_yticklabels(self.yticklabels, rotation='vertical')
  530. # Force a draw of the plot to avoid matplotlib window error
  531. _draw_figure(ax.figure)
  532. if len(ytl) > 0 and axis_ticklabels_overlap(ytl):
  533. plt.setp(ytl, rotation="horizontal")
  534. if len(xtl) > 0 and axis_ticklabels_overlap(xtl):
  535. plt.setp(xtl, rotation="vertical")
  536. return self
  537. def dendrogram(
  538. data, *,
  539. linkage=None, axis=1, label=True, metric='euclidean',
  540. method='average', rotate=False, tree_kws=None, ax=None
  541. ):
  542. """Draw a tree diagram of relationships within a matrix
  543. Parameters
  544. ----------
  545. data : pandas.DataFrame
  546. Rectangular data
  547. linkage : numpy.array, optional
  548. Linkage matrix
  549. axis : int, optional
  550. Which axis to use to calculate linkage. 0 is rows, 1 is columns.
  551. label : bool, optional
  552. If True, label the dendrogram at leaves with column or row names
  553. metric : str, optional
  554. Distance metric. Anything valid for scipy.spatial.distance.pdist
  555. method : str, optional
  556. Linkage method to use. Anything valid for
  557. scipy.cluster.hierarchy.linkage
  558. rotate : bool, optional
  559. When plotting the matrix, whether to rotate it 90 degrees
  560. counter-clockwise, so the leaves face right
  561. tree_kws : dict, optional
  562. Keyword arguments for the ``matplotlib.collections.LineCollection``
  563. that is used for plotting the lines of the dendrogram tree.
  564. ax : matplotlib axis, optional
  565. Axis to plot on, otherwise uses current axis
  566. Returns
  567. -------
  568. dendrogramplotter : _DendrogramPlotter
  569. A Dendrogram plotter object.
  570. Notes
  571. -----
  572. Access the reordered dendrogram indices with
  573. dendrogramplotter.reordered_ind
  574. """
  575. if _no_scipy:
  576. raise RuntimeError("dendrogram requires scipy to be installed")
  577. plotter = _DendrogramPlotter(data, linkage=linkage, axis=axis,
  578. metric=metric, method=method,
  579. label=label, rotate=rotate)
  580. if ax is None:
  581. ax = plt.gca()
  582. return plotter.plot(ax=ax, tree_kws=tree_kws)
  583. class ClusterGrid(Grid):
  584. def __init__(self, data, pivot_kws=None, z_score=None, standard_scale=None,
  585. figsize=None, row_colors=None, col_colors=None, mask=None,
  586. dendrogram_ratio=None, colors_ratio=None, cbar_pos=None):
  587. """Grid object for organizing clustered heatmap input on to axes"""
  588. if _no_scipy:
  589. raise RuntimeError("ClusterGrid requires scipy to be available")
  590. if isinstance(data, pd.DataFrame):
  591. self.data = data
  592. else:
  593. self.data = pd.DataFrame(data)
  594. self.data2d = self.format_data(self.data, pivot_kws, z_score,
  595. standard_scale)
  596. self.mask = _matrix_mask(self.data2d, mask)
  597. self._figure = plt.figure(figsize=figsize)
  598. self.row_colors, self.row_color_labels = \
  599. self._preprocess_colors(data, row_colors, axis=0)
  600. self.col_colors, self.col_color_labels = \
  601. self._preprocess_colors(data, col_colors, axis=1)
  602. try:
  603. row_dendrogram_ratio, col_dendrogram_ratio = dendrogram_ratio
  604. except TypeError:
  605. row_dendrogram_ratio = col_dendrogram_ratio = dendrogram_ratio
  606. try:
  607. row_colors_ratio, col_colors_ratio = colors_ratio
  608. except TypeError:
  609. row_colors_ratio = col_colors_ratio = colors_ratio
  610. width_ratios = self.dim_ratios(self.row_colors,
  611. row_dendrogram_ratio,
  612. row_colors_ratio)
  613. height_ratios = self.dim_ratios(self.col_colors,
  614. col_dendrogram_ratio,
  615. col_colors_ratio)
  616. nrows = 2 if self.col_colors is None else 3
  617. ncols = 2 if self.row_colors is None else 3
  618. self.gs = gridspec.GridSpec(nrows, ncols,
  619. width_ratios=width_ratios,
  620. height_ratios=height_ratios)
  621. self.ax_row_dendrogram = self._figure.add_subplot(self.gs[-1, 0])
  622. self.ax_col_dendrogram = self._figure.add_subplot(self.gs[0, -1])
  623. self.ax_row_dendrogram.set_axis_off()
  624. self.ax_col_dendrogram.set_axis_off()
  625. self.ax_row_colors = None
  626. self.ax_col_colors = None
  627. if self.row_colors is not None:
  628. self.ax_row_colors = self._figure.add_subplot(
  629. self.gs[-1, 1])
  630. if self.col_colors is not None:
  631. self.ax_col_colors = self._figure.add_subplot(
  632. self.gs[1, -1])
  633. self.ax_heatmap = self._figure.add_subplot(self.gs[-1, -1])
  634. if cbar_pos is None:
  635. self.ax_cbar = self.cax = None
  636. else:
  637. # Initialize the colorbar axes in the gridspec so that tight_layout
  638. # works. We will move it where it belongs later. This is a hack.
  639. self.ax_cbar = self._figure.add_subplot(self.gs[0, 0])
  640. self.cax = self.ax_cbar # Backwards compatibility
  641. self.cbar_pos = cbar_pos
  642. self.dendrogram_row = None
  643. self.dendrogram_col = None
  644. def _preprocess_colors(self, data, colors, axis):
  645. """Preprocess {row/col}_colors to extract labels and convert colors."""
  646. labels = None
  647. if colors is not None:
  648. if isinstance(colors, (pd.DataFrame, pd.Series)):
  649. # If data is unindexed, raise
  650. if (not hasattr(data, "index") and axis == 0) or (
  651. not hasattr(data, "columns") and axis == 1
  652. ):
  653. axis_name = "col" if axis else "row"
  654. msg = (f"{axis_name}_colors indices can't be matched with data "
  655. f"indices. Provide {axis_name}_colors as a non-indexed "
  656. "datatype, e.g. by using `.to_numpy()``")
  657. raise TypeError(msg)
  658. # Ensure colors match data indices
  659. if axis == 0:
  660. colors = colors.reindex(data.index)
  661. else:
  662. colors = colors.reindex(data.columns)
  663. # Replace na's with white color
  664. # TODO We should set these to transparent instead
  665. colors = colors.astype(object).fillna('white')
  666. # Extract color values and labels from frame/series
  667. if isinstance(colors, pd.DataFrame):
  668. labels = list(colors.columns)
  669. colors = colors.T.values
  670. else:
  671. if colors.name is None:
  672. labels = [""]
  673. else:
  674. labels = [colors.name]
  675. colors = colors.values
  676. colors = _convert_colors(colors)
  677. return colors, labels
  678. def format_data(self, data, pivot_kws, z_score=None,
  679. standard_scale=None):
  680. """Extract variables from data or use directly."""
  681. # Either the data is already in 2d matrix format, or need to do a pivot
  682. if pivot_kws is not None:
  683. data2d = data.pivot(**pivot_kws)
  684. else:
  685. data2d = data
  686. if z_score is not None and standard_scale is not None:
  687. raise ValueError(
  688. 'Cannot perform both z-scoring and standard-scaling on data')
  689. if z_score is not None:
  690. data2d = self.z_score(data2d, z_score)
  691. if standard_scale is not None:
  692. data2d = self.standard_scale(data2d, standard_scale)
  693. return data2d
  694. @staticmethod
  695. def z_score(data2d, axis=1):
  696. """Standarize the mean and variance of the data axis
  697. Parameters
  698. ----------
  699. data2d : pandas.DataFrame
  700. Data to normalize
  701. axis : int
  702. Which axis to normalize across. If 0, normalize across rows, if 1,
  703. normalize across columns.
  704. Returns
  705. -------
  706. normalized : pandas.DataFrame
  707. Noramlized data with a mean of 0 and variance of 1 across the
  708. specified axis.
  709. """
  710. if axis == 1:
  711. z_scored = data2d
  712. else:
  713. z_scored = data2d.T
  714. z_scored = (z_scored - z_scored.mean()) / z_scored.std()
  715. if axis == 1:
  716. return z_scored
  717. else:
  718. return z_scored.T
  719. @staticmethod
  720. def standard_scale(data2d, axis=1):
  721. """Divide the data by the difference between the max and min
  722. Parameters
  723. ----------
  724. data2d : pandas.DataFrame
  725. Data to normalize
  726. axis : int
  727. Which axis to normalize across. If 0, normalize across rows, if 1,
  728. normalize across columns.
  729. Returns
  730. -------
  731. standardized : pandas.DataFrame
  732. Noramlized data with a mean of 0 and variance of 1 across the
  733. specified axis.
  734. """
  735. # Normalize these values to range from 0 to 1
  736. if axis == 1:
  737. standardized = data2d
  738. else:
  739. standardized = data2d.T
  740. subtract = standardized.min()
  741. standardized = (standardized - subtract) / (
  742. standardized.max() - standardized.min())
  743. if axis == 1:
  744. return standardized
  745. else:
  746. return standardized.T
  747. def dim_ratios(self, colors, dendrogram_ratio, colors_ratio):
  748. """Get the proportions of the figure taken up by each axes."""
  749. ratios = [dendrogram_ratio]
  750. if colors is not None:
  751. # Colors are encoded as rgb, so there is an extra dimension
  752. if np.ndim(colors) > 2:
  753. n_colors = len(colors)
  754. else:
  755. n_colors = 1
  756. ratios += [n_colors * colors_ratio]
  757. # Add the ratio for the heatmap itself
  758. ratios.append(1 - sum(ratios))
  759. return ratios
  760. @staticmethod
  761. def color_list_to_matrix_and_cmap(colors, ind, axis=0):
  762. """Turns a list of colors into a numpy matrix and matplotlib colormap
  763. These arguments can now be plotted using heatmap(matrix, cmap)
  764. and the provided colors will be plotted.
  765. Parameters
  766. ----------
  767. colors : list of matplotlib colors
  768. Colors to label the rows or columns of a dataframe.
  769. ind : list of ints
  770. Ordering of the rows or columns, to reorder the original colors
  771. by the clustered dendrogram order
  772. axis : int
  773. Which axis this is labeling
  774. Returns
  775. -------
  776. matrix : numpy.array
  777. A numpy array of integer values, where each indexes into the cmap
  778. cmap : matplotlib.colors.ListedColormap
  779. """
  780. try:
  781. mpl.colors.to_rgb(colors[0])
  782. except ValueError:
  783. # We have a 2D color structure
  784. m, n = len(colors), len(colors[0])
  785. if not all(len(c) == n for c in colors[1:]):
  786. raise ValueError("Multiple side color vectors must have same size")
  787. else:
  788. # We have one vector of colors
  789. m, n = 1, len(colors)
  790. colors = [colors]
  791. # Map from unique colors to colormap index value
  792. unique_colors = {}
  793. matrix = np.zeros((m, n), int)
  794. for i, inner in enumerate(colors):
  795. for j, color in enumerate(inner):
  796. idx = unique_colors.setdefault(color, len(unique_colors))
  797. matrix[i, j] = idx
  798. # Reorder for clustering and transpose for axis
  799. matrix = matrix[:, ind]
  800. if axis == 0:
  801. matrix = matrix.T
  802. cmap = mpl.colors.ListedColormap(list(unique_colors))
  803. return matrix, cmap
  804. def plot_dendrograms(self, row_cluster, col_cluster, metric, method,
  805. row_linkage, col_linkage, tree_kws):
  806. # Plot the row dendrogram
  807. if row_cluster:
  808. self.dendrogram_row = dendrogram(
  809. self.data2d, metric=metric, method=method, label=False, axis=0,
  810. ax=self.ax_row_dendrogram, rotate=True, linkage=row_linkage,
  811. tree_kws=tree_kws
  812. )
  813. else:
  814. self.ax_row_dendrogram.set_xticks([])
  815. self.ax_row_dendrogram.set_yticks([])
  816. # PLot the column dendrogram
  817. if col_cluster:
  818. self.dendrogram_col = dendrogram(
  819. self.data2d, metric=metric, method=method, label=False,
  820. axis=1, ax=self.ax_col_dendrogram, linkage=col_linkage,
  821. tree_kws=tree_kws
  822. )
  823. else:
  824. self.ax_col_dendrogram.set_xticks([])
  825. self.ax_col_dendrogram.set_yticks([])
  826. despine(ax=self.ax_row_dendrogram, bottom=True, left=True)
  827. despine(ax=self.ax_col_dendrogram, bottom=True, left=True)
  828. def plot_colors(self, xind, yind, **kws):
  829. """Plots color labels between the dendrogram and the heatmap
  830. Parameters
  831. ----------
  832. heatmap_kws : dict
  833. Keyword arguments heatmap
  834. """
  835. # Remove any custom colormap and centering
  836. # TODO this code has consistently caused problems when we
  837. # have missed kwargs that need to be excluded that it might
  838. # be better to rewrite *in*clusively.
  839. kws = kws.copy()
  840. kws.pop('cmap', None)
  841. kws.pop('norm', None)
  842. kws.pop('center', None)
  843. kws.pop('annot', None)
  844. kws.pop('vmin', None)
  845. kws.pop('vmax', None)
  846. kws.pop('robust', None)
  847. kws.pop('xticklabels', None)
  848. kws.pop('yticklabels', None)
  849. # Plot the row colors
  850. if self.row_colors is not None:
  851. matrix, cmap = self.color_list_to_matrix_and_cmap(
  852. self.row_colors, yind, axis=0)
  853. # Get row_color labels
  854. if self.row_color_labels is not None:
  855. row_color_labels = self.row_color_labels
  856. else:
  857. row_color_labels = False
  858. heatmap(matrix, cmap=cmap, cbar=False, ax=self.ax_row_colors,
  859. xticklabels=row_color_labels, yticklabels=False, **kws)
  860. # Adjust rotation of labels
  861. if row_color_labels is not False:
  862. plt.setp(self.ax_row_colors.get_xticklabels(), rotation=90)
  863. else:
  864. despine(self.ax_row_colors, left=True, bottom=True)
  865. # Plot the column colors
  866. if self.col_colors is not None:
  867. matrix, cmap = self.color_list_to_matrix_and_cmap(
  868. self.col_colors, xind, axis=1)
  869. # Get col_color labels
  870. if self.col_color_labels is not None:
  871. col_color_labels = self.col_color_labels
  872. else:
  873. col_color_labels = False
  874. heatmap(matrix, cmap=cmap, cbar=False, ax=self.ax_col_colors,
  875. xticklabels=False, yticklabels=col_color_labels, **kws)
  876. # Adjust rotation of labels, place on right side
  877. if col_color_labels is not False:
  878. self.ax_col_colors.yaxis.tick_right()
  879. plt.setp(self.ax_col_colors.get_yticklabels(), rotation=0)
  880. else:
  881. despine(self.ax_col_colors, left=True, bottom=True)
  882. def plot_matrix(self, colorbar_kws, xind, yind, **kws):
  883. self.data2d = self.data2d.iloc[yind, xind]
  884. self.mask = self.mask.iloc[yind, xind]
  885. # Try to reorganize specified tick labels, if provided
  886. xtl = kws.pop("xticklabels", "auto")
  887. try:
  888. xtl = np.asarray(xtl)[xind]
  889. except (TypeError, IndexError):
  890. pass
  891. ytl = kws.pop("yticklabels", "auto")
  892. try:
  893. ytl = np.asarray(ytl)[yind]
  894. except (TypeError, IndexError):
  895. pass
  896. # Reorganize the annotations to match the heatmap
  897. annot = kws.pop("annot", None)
  898. if annot is None or annot is False:
  899. pass
  900. else:
  901. if isinstance(annot, bool):
  902. annot_data = self.data2d
  903. else:
  904. annot_data = np.asarray(annot)
  905. if annot_data.shape != self.data2d.shape:
  906. err = "`data` and `annot` must have same shape."
  907. raise ValueError(err)
  908. annot_data = annot_data[yind][:, xind]
  909. annot = annot_data
  910. # Setting ax_cbar=None in clustermap call implies no colorbar
  911. kws.setdefault("cbar", self.ax_cbar is not None)
  912. heatmap(self.data2d, ax=self.ax_heatmap, cbar_ax=self.ax_cbar,
  913. cbar_kws=colorbar_kws, mask=self.mask,
  914. xticklabels=xtl, yticklabels=ytl, annot=annot, **kws)
  915. ytl = self.ax_heatmap.get_yticklabels()
  916. ytl_rot = None if not ytl else ytl[0].get_rotation()
  917. self.ax_heatmap.yaxis.set_ticks_position('right')
  918. self.ax_heatmap.yaxis.set_label_position('right')
  919. if ytl_rot is not None:
  920. ytl = self.ax_heatmap.get_yticklabels()
  921. plt.setp(ytl, rotation=ytl_rot)
  922. tight_params = dict(h_pad=.02, w_pad=.02)
  923. if self.ax_cbar is None:
  924. self._figure.tight_layout(**tight_params)
  925. else:
  926. # Turn the colorbar axes off for tight layout so that its
  927. # ticks don't interfere with the rest of the plot layout.
  928. # Then move it.
  929. self.ax_cbar.set_axis_off()
  930. self._figure.tight_layout(**tight_params)
  931. self.ax_cbar.set_axis_on()
  932. self.ax_cbar.set_position(self.cbar_pos)
  933. def plot(self, metric, method, colorbar_kws, row_cluster, col_cluster,
  934. row_linkage, col_linkage, tree_kws, **kws):
  935. # heatmap square=True sets the aspect ratio on the axes, but that is
  936. # not compatible with the multi-axes layout of clustergrid
  937. if kws.get("square", False):
  938. msg = "``square=True`` ignored in clustermap"
  939. warnings.warn(msg)
  940. kws.pop("square")
  941. colorbar_kws = {} if colorbar_kws is None else colorbar_kws
  942. self.plot_dendrograms(row_cluster, col_cluster, metric, method,
  943. row_linkage=row_linkage, col_linkage=col_linkage,
  944. tree_kws=tree_kws)
  945. try:
  946. xind = self.dendrogram_col.reordered_ind
  947. except AttributeError:
  948. xind = np.arange(self.data2d.shape[1])
  949. try:
  950. yind = self.dendrogram_row.reordered_ind
  951. except AttributeError:
  952. yind = np.arange(self.data2d.shape[0])
  953. self.plot_colors(xind, yind, **kws)
  954. self.plot_matrix(colorbar_kws, xind, yind, **kws)
  955. return self
  956. def clustermap(
  957. data, *,
  958. pivot_kws=None, method='average', metric='euclidean',
  959. z_score=None, standard_scale=None, figsize=(10, 10),
  960. cbar_kws=None, row_cluster=True, col_cluster=True,
  961. row_linkage=None, col_linkage=None,
  962. row_colors=None, col_colors=None, mask=None,
  963. dendrogram_ratio=.2, colors_ratio=0.03,
  964. cbar_pos=(.02, .8, .05, .18), tree_kws=None,
  965. **kwargs
  966. ):
  967. """
  968. Plot a matrix dataset as a hierarchically-clustered heatmap.
  969. This function requires scipy to be available.
  970. Parameters
  971. ----------
  972. data : 2D array-like
  973. Rectangular data for clustering. Cannot contain NAs.
  974. pivot_kws : dict, optional
  975. If `data` is a tidy dataframe, can provide keyword arguments for
  976. pivot to create a rectangular dataframe.
  977. method : str, optional
  978. Linkage method to use for calculating clusters. See
  979. :func:`scipy.cluster.hierarchy.linkage` documentation for more
  980. information.
  981. metric : str, optional
  982. Distance metric to use for the data. See
  983. :func:`scipy.spatial.distance.pdist` documentation for more options.
  984. To use different metrics (or methods) for rows and columns, you may
  985. construct each linkage matrix yourself and provide them as
  986. `{row,col}_linkage`.
  987. z_score : int or None, optional
  988. Either 0 (rows) or 1 (columns). Whether or not to calculate z-scores
  989. for the rows or the columns. Z scores are: z = (x - mean)/std, so
  990. values in each row (column) will get the mean of the row (column)
  991. subtracted, then divided by the standard deviation of the row (column).
  992. This ensures that each row (column) has mean of 0 and variance of 1.
  993. standard_scale : int or None, optional
  994. Either 0 (rows) or 1 (columns). Whether or not to standardize that
  995. dimension, meaning for each row or column, subtract the minimum and
  996. divide each by its maximum.
  997. figsize : tuple of (width, height), optional
  998. Overall size of the figure.
  999. cbar_kws : dict, optional
  1000. Keyword arguments to pass to `cbar_kws` in :func:`heatmap`, e.g. to
  1001. add a label to the colorbar.
  1002. {row,col}_cluster : bool, optional
  1003. If ``True``, cluster the {rows, columns}.
  1004. {row,col}_linkage : :class:`numpy.ndarray`, optional
  1005. Precomputed linkage matrix for the rows or columns. See
  1006. :func:`scipy.cluster.hierarchy.linkage` for specific formats.
  1007. {row,col}_colors : list-like or pandas DataFrame/Series, optional
  1008. List of colors to label for either the rows or columns. Useful to evaluate
  1009. whether samples within a group are clustered together. Can use nested lists or
  1010. DataFrame for multiple color levels of labeling. If given as a
  1011. :class:`pandas.DataFrame` or :class:`pandas.Series`, labels for the colors are
  1012. extracted from the DataFrames column names or from the name of the Series.
  1013. DataFrame/Series colors are also matched to the data by their index, ensuring
  1014. colors are drawn in the correct order.
  1015. mask : bool array or DataFrame, optional
  1016. If passed, data will not be shown in cells where `mask` is True.
  1017. Cells with missing values are automatically masked. Only used for
  1018. visualizing, not for calculating.
  1019. {dendrogram,colors}_ratio : float, or pair of floats, optional
  1020. Proportion of the figure size devoted to the two marginal elements. If
  1021. a pair is given, they correspond to (row, col) ratios.
  1022. cbar_pos : tuple of (left, bottom, width, height), optional
  1023. Position of the colorbar axes in the figure. Setting to ``None`` will
  1024. disable the colorbar.
  1025. tree_kws : dict, optional
  1026. Parameters for the :class:`matplotlib.collections.LineCollection`
  1027. that is used to plot the lines of the dendrogram tree.
  1028. kwargs : other keyword arguments
  1029. All other keyword arguments are passed to :func:`heatmap`.
  1030. Returns
  1031. -------
  1032. :class:`ClusterGrid`
  1033. A :class:`ClusterGrid` instance.
  1034. See Also
  1035. --------
  1036. heatmap : Plot rectangular data as a color-encoded matrix.
  1037. Notes
  1038. -----
  1039. The returned object has a ``savefig`` method that should be used if you
  1040. want to save the figure object without clipping the dendrograms.
  1041. To access the reordered row indices, use:
  1042. ``clustergrid.dendrogram_row.reordered_ind``
  1043. Column indices, use:
  1044. ``clustergrid.dendrogram_col.reordered_ind``
  1045. Examples
  1046. --------
  1047. .. include:: ../docstrings/clustermap.rst
  1048. """
  1049. if _no_scipy:
  1050. raise RuntimeError("clustermap requires scipy to be available")
  1051. plotter = ClusterGrid(data, pivot_kws=pivot_kws, figsize=figsize,
  1052. row_colors=row_colors, col_colors=col_colors,
  1053. z_score=z_score, standard_scale=standard_scale,
  1054. mask=mask, dendrogram_ratio=dendrogram_ratio,
  1055. colors_ratio=colors_ratio, cbar_pos=cbar_pos)
  1056. return plotter.plot(metric=metric, method=method,
  1057. colorbar_kws=cbar_kws,
  1058. row_cluster=row_cluster, col_cluster=col_cluster,
  1059. row_linkage=row_linkage, col_linkage=col_linkage,
  1060. tree_kws=tree_kws, **kwargs)