# pretty-confusion-matrix is one seaborn heatmap with two entry points that number classes differently

> The package plots a confusion matrix from a DataFrame or from two arrays, the way MATLAB's plotconfusion does. Its own examples disagree about whether classes start at zero or one, and the last three releases were about citing the work rather than changing it.

**wcipriano/pretty-print-confusion-matrix** — Confusion Matrix in Python: plot a pretty confusion matrix (like Matlab) in python using seaborn and matplotlib

- Repository: https://github.com/wcipriano/pretty-print-confusion-matrix
- Website: https://pypi.org/project/pretty-confusion-matrix/
- Stars: 537 · Forks: 155
- Language: Python
- License: Apache-2.0
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/wcipriano-pretty-print-confusion-matrix

## Two entry points, and only one of them labels classes from the data

The package offers `pp_matrix`, which takes a DataFrame, and `pp_matrix_from_data`, which takes `y_test` and `predictions` as NumPy arrays. Labels travel differently in each. With the DataFrame form, the axis text comes from the DataFrame itself, so you change the labels by rebuilding the frame with an index and columns of strings instead of the integers:

```python
col = ['Dog', 'Cat', 'Mouse', 'Fox', 'Bird', 'Chicken']
df_cm = pd.DataFrame(array, index=col, columns=col)
```

With the vector form, labels are a separate positional argument, `pp_matrix_from_data(y_test, predic, columns)`, and the page is explicit that the order must match the data representation. A wrong order here does not error, it relabels: the picture stays valid and the names move.

## The two documented examples number their classes differently

The DataFrame example builds its frame with `index=range(1, 7), columns=range(1, 7)`, so its classes run from one to six and it names six of them, ending in Chicken. The vectors example uses five names, ending in Bird, and says Dog will be assigned to the class 0, Cat to the class 1, and so on. One example is one-based with six classes, the other zero-based with five, in the same document.

That matters more than it looks, because the DataFrame route never mentions class zero while the vector route never mentions class six. Anyone moving a notebook from one entry point to the other has to decide what the off-by-one is, and the page never states a rule for it. If your labels come from an sklearn encoder you get zero-based, and the vector form matches; if your labels come from a hand-built DataFrame you built yourself, the one-based frame in the example will silently shift every name by one.

## The vectors example stops before it calls the function

The vectors snippet defines `y_test` as a long array of 1 through 5 repeating, then starts `predic` and stops partway through the list. The call to `pp_matrix_from_data` is not in the snippet at all, even though a later section tells you to edit exactly that line, `pp_matrix_from_data(y_test, predic)`, to add labels. So the sequence on the page is: an example that defines two arrays without finishing or plotting, and a later instruction to modify a call that the example never shows.

What is verifiable is the shape of the intended call: two arrays of class indices in, one array of class names in, one heatmap out. `y_test` repeating 1, 2, 3, 4, 5 is a synthetic pattern rather than a captured result, so the page gives you a syntax example rather than a worked case. The DataFrame example is the one that runs end to end, with a 6 by 6 array, a colormap of `PuRd`, and the call that produces the plot.

## Two Python constraints in one file, and they disagree

The project metadata says `requires-python = ">=3.10"` with no upper bound, while the dependency table in the same file says `python = ">=3.10,<3.14"`. The build backend is `poetry-core`, which resolves from the Poetry table, so the ceiling that matters at install time is 3.13 as the last supported interpreter, while the PEP 621 field advertises no ceiling to anything reading standard metadata.

The rest of the dependency table is written per interpreter, which is where the ceiling comes from. On Python 3.11 and newer you get numpy `^2.3.0`, pandas `^3.0.2` and scikit-learn `^1.8.0`; below 3.11 the floors drop to numpy `^2.2.0`, pandas `^2.3.3` and scikit-learn `^1.7.2`. matplotlib `^3.10.9` and seaborn `^0.13.2` are unconditional. So a 3.10 environment is running a materially older pandas and scikit-learn than a 3.11 one, which is the kind of difference that changes how your labels and arrays behave.

## Three of the last three releases are about citation metadata

The version history reads like publishing admin rather than a feature trail. 0.7.3 on 2026-05-07 is titled Deploy to PyPI. 0.8.0 on 2026-05-14 is titled Zenodo DOI. 0.8.1 on 2026-05-15 is titled Zenodo citation file. The project file is at 0.8.1, matching the newest release, and the page carries a Zenodo badge alongside the PyPI one and a `CITATION.cff` at the root.

So three consecutive patch or minor bumps bought a home on PyPI, a DOI and a citation file. Nothing in that sequence touches the plotting code, which is consistent with the package being small: one module directory, `pretty_confusion_matrix/`, plus tests, a `Screenshots/` folder and a development readme.

Commits continued after the last release. The last push is dated 2026-07-21, more than two months after 0.8.1 and about ten weeks before now, and the repository is not archived. Anyone pinning should pin 0.8.1 and know that what is on the default branch is ahead of any published artifact.

## What you get is a labelled heatmap, with no scores attached

The scope is stated plainly: the module takes a NumPy matrix or two NumPy arrays and returns a printed confusion matrix. Styling is the extent of it. The colormap goes through the `cmap` argument of `pp_matrix`, and the page points at matplotlib's own list for what is available, `from matplotlib import colormaps` followed by `list(colormaps)`, with PuRd and Oranges given as examples.

No accuracy, no per-class precision or recall, no F1 and no normalisation appear anywhere in the examples. For a classification report with scores, the page points outward, to the scikit-learn model selection example for the confusion matrix and to a Stack Overflow answer on string axes rather than integers. Two MATLAB references sit at the top, `plotconfusion` and the categorical labels example, and the header calls the package a plot like Matlab rather than a metric.

For tooling, the repository carries a `.flake8`, a `.pre-commit-config.yaml`, a `poetry.lock` and dev pins on black, flake8, pytest and pytest-cov. Black is configured with `line-length = 79`, which is narrower than the default most Python projects live with, and the reason is not given.

## Conclusion

Use it when you want a labelled heatmap in one call and nothing else, since there are no accuracy or per-class scores in the package to argue about. Check two things before adopting it: the two entry points number their classes differently, one example 1 to 6 and the other 0 upward, so pick the DataFrame form for labelled data and verify the label order yourself. Pin 0.8.1 or later, and note the last push was on 2026-07-21 while the newest release is from May.

## FAQ

### How do I install pretty-confusion-matrix?

From PyPI with `pip install pretty-confusion-matrix`. The project declares `requires-python = ">=3.10"` in its metadata while the Poetry dependency table says `>=3.10,<3.14`, so 3.13 is the last interpreter the install table supports.

### What is the difference between pp_matrix and pp_matrix_from_data?

pp_matrix takes a DataFrame and reads its axis labels from the frame's index and columns. pp_matrix_from_data takes y_test and predictions as arrays plus a separate columns argument, and that argument is positional, so its order must match the data representation.

### Does pretty-confusion-matrix compute accuracy or F1?

No. The examples show a heatmap with counts and custom axis labels only, plus a colormap chosen through the cmap argument. For scores the page points to the scikit-learn confusion matrix example and a classification report thread instead.

### How do I put text labels on the axes in pretty-confusion-matrix?

For the DataFrame form, rebuild the frame with a list of strings as both index and columns, for example `col = ['Dog', 'Cat', 'Mouse', 'Fox', 'Bird', 'Chicken']` with `pd.DataFrame(array, index=col, columns=col)`. For the vector form, pass the labels as the third argument to pp_matrix_from_data.

### What is the latest release of pretty-confusion-matrix?

0.8.1, titled Zenodo citation file, published 2026-05-15, after 0.8.0 for the Zenodo DOI and 0.7.3 for the PyPI deployment. The last push on the repository is dated 2026-07-21 and it is not archived.

## Sources

- [License: Apache-2.0](https://github.com/wcipriano/pretty-print-confusion-matrix/blob/master/LICENSE)
- [Project website](https://pypi.org/project/pretty-confusion-matrix/)
- [README](https://github.com/wcipriano/pretty-print-confusion-matrix/blob/master/README.md)
- [Releases](https://github.com/wcipriano/pretty-print-confusion-matrix/releases)
- [wcipriano/pretty-print-confusion-matrix on GitHub](https://github.com/wcipriano/pretty-print-confusion-matrix)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/wcipriano-pretty-print-confusion-matrix
