Note
Go to the end to download the full example code or to run this example in your browser via JupyterLite.
Israel, Rosenthal and Wei: finding a valid generator#
Going from an annual transition matrix \(P\) to a generator is not as simple as \(\log P\). Israel, Rosenthal and Wei (2001) showed that for empirical rating matrices the principal logarithm usually has negative off-diagonal entries, which would be negative migration intensities. Their fix: set those entries to zero and absorb the change in the diagonal. A weighted variant spreads it over the row instead. Both leave \(e^{G}\) within a few basis points of \(P\).
import matplotlib.pyplot as plt
import numpy as np
from financialkit.credit import RATINGS, creditmetrics_matrix, generator_from_transition, is_valid_generator, regularize_generator, transition_from_generator
from financialkit.credit.visualizers import plot_transition_matrix
P = creditmetrics_matrix()
L = generator_from_transition(P)
off = L - np.diag(np.diag(L))
print(f"log(P) is a valid generator: {is_valid_generator(L)}; {np.sum(off < 0)} negative off-diagonal entries, most negative {off.min():.2e}")
ax = plot_transition_matrix(L, labels=RATINGS, fmt="{:.2f}")
ax.set_title("log(P) in % per year: negative intensities in red")

log(P) is a valid generator: False; 7 negative off-diagonal entries, most negative -3.10e-04
Text(0.5, 1.0, 'log(P) in % per year: negative intensities in red')
The two adjustments#
fig, axes = plt.subplots(1, 2, figsize=(12, 4.5))
for ax, method in zip(axes, ("diagonal", "weighted"), strict=True):
G = regularize_generator(L, method)
err = transition_from_generator(G) - P
plot_transition_matrix(err, labels=RATINGS, ax=ax, fmt="{:.2f}", scale=1e4)
ax.set_title(f"{method}: exp(G) - P in bp (max {1e4 * np.abs(err).max():.1f})")
print(f"{method:8s}: valid {is_valid_generator(G)}, max |exp(G) - P| = {np.abs(err).max():.2e}")
fig.tight_layout()
plt.show()

diagonal: valid True, max |exp(G) - P| = 2.39e-04
weighted: valid True, max |exp(G) - P| = 2.39e-04
Total running time of the script: (0 minutes 0.191 seconds)