# Visually represent two-dimensional decomposition

Lets say i have the decompositions of a financial result into 21 portfolios and 11 asset classes below. I can decompose the result into either portfolios or asset classes easily by using a waterfall chart (which my bosses really love), but I am looking for something, hopefully similar, which can decompose both dimensions simultanously.

P1 P2 P3 P4 P5 P6 P7 P8 P9 P10 P11 P12 P13 P14 P15 P16 P17 P18 P19 P20 P21 Total A
A1 -120 -100 -75 -395 -76 24 -2 -2 -10 0 -216 -46 -1 -18 -2 -26 -10 -4 0 -9 -6 -1094
A2 99 36 18 19 1 92 7 6 16 0 2 18 0 0 0 1 0 4 0 2 0 321
A3 75 23 12 8 0 62 5 4 10 0 0 6 0 0 0 0 0 2 0 1 0 208
A4 82 55 34 160 24 103 10 8 6 0 26 25 1 -8 -1 7 1 6 0 8 4 551
A5 7 5 1 2 0 6 1 1 1 0 1 -8 0 1 0 0 0 0 0 0 0 17
A6 2 1 1 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 7
A7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
A8 28 3 -1 -8 0 56 0 0 0 0 0 3 1 12 1 0 0 0 0 1 1 96
A9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
A10 1 1 1 4 1 -1 0 0 -1 0 2 0 0 0 0 0 0 0 0 0 0 7
A11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Total P 173 24 -10 -210 -52 343 21 17 22 0 -184 -3 1 -13 -2 -18 -9 9 0 3 0 113
• What do you mean by decomposition? What are you decomposing and why? Commented Oct 24, 2023 at 9:41
• @user2974951: the table contains a "Total" row and column, so the OP presumably would like to visualize how the separate entries contribute to these two-dimensional totals. Commented Oct 24, 2023 at 9:45
• Are you looking for a spineplot? Commented Oct 24, 2023 at 10:35

## 1 Answer

I would create a heatmap with the data except for the totals, and add a separate row and column next to the heatmap showing the totals. This does not really capture the "decomposition" aspect, but it should at least draw attention to particularly high or low values. (Just be careful to actually use black body radiation colors rather than, say, rainbow colors - see also here.)

R code (which I am not overly proud of; see also https://stackoverflow.com/q/46091522/452096):

dataset <- structure(c(-120, 99, 75, 82, 7, 2, 0, 28, 0, 1, 0, 173, -100,
36, 23, 55, 5, 1, 0, 3, 0, 1, 0, 24, -75, 18, 12, 34, 1, 1, 0,
-1, 0, 1, 0, -10, -395, 19, 8, 160, 2, 0, 0, -8, 0, 4, 0, -210,
-76, 1, 0, 24, 0, 0, 0, 0, 0, 1, 0, -52, 24, 92, 62, 103, 6,
2, 0, 56, 0, -1, 0, 343, -2, 7, 5, 10, 1, 0, 0, 0, 0, 0, 0, 21,
-2, 6, 4, 8, 1, 0, 0, 0, 0, 0, 0, 17, -10, 16, 10, 6, 1, 0, 0,
0, 0, -1, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -216, 2,
0, 26, 1, 0, 0, 0, 0, 2, 0, -184, -46, 18, 6, 25, -8, 0, 0, 3,
0, 0, 0, -3, -1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, -18, 0, 0,
-8, 1, 0, 0, 12, 0, 0, 0, -13, -2, 0, 0, -1, 0, 0, 0, 1, 0, 0,
0, -2, -26, 1, 0, 7, 0, 0, 0, 0, 0, 0, 0, -18, -10, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, -9, -4, 4, 2, 6, 0, 0, 0, 0, 0, 0, 0, 9,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -9, 2, 1, 8, 0, 0, 0, 1,
0, 0, 0, 3, -6, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 0, -1094, 321,
208, 551, 17, 7, 0, 96, 0, 7, 0, 113), dim = c(12L, 22L), dimnames = list(
c("A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10",
"A11", "Total P"), c("P1", "P2", "P3", "P4", "P5", "P6",
"P7", "P8", "P9", "P10", "P11", "P12", "P13", "P14", "P15",
"P16", "P17", "P18", "P19", "P20", "P21", "Total A")))

blackBodyRadiationColors <- function(x, max_value=1) {
# x should be between 0 (black) and 1 (white)
# if large x come out too bright, constrain the bright end of the palette
#     by setting max_value lower than 1
foo <- colorRamp(c(rgb(0,0,0),rgb(1,0,0),rgb(1,1,0),rgb(1,1,1)))(x*max_value)/255
apply(foo,1,function(bar)rgb(bar[1],bar[2],bar[3]))
}

plot(c(0,ncol(dataset)+2),c(0,nrow(dataset)+2),xaxt="n",yaxt="n",xlab="",ylab="",type="n",
xlim=c(0,ncol(dataset)+2),ylim=c(1,nrow(dataset)+2))
axis(1,at=1:(ncol(dataset)+1),labels=c(head(colnames(dataset),-1),"",tail(colnames(dataset),1)))
axis(2,at=rev(1:(nrow(dataset)+1)),labels=c(head(rownames(dataset),-1),"",tail(rownames(dataset),1)),las=1)

for ( ii in 1:(nrow(dataset)-1) ) {
for ( jj in 1:(ncol(dataset)-1) ) {
rescaled_value <- 0.2+0.6*(dataset[ii,jj]-min(dataset))/diff(range(dataset))
rect(jj-0.5,nrow(dataset)-ii+1.5,jj+0.5,nrow(dataset)-ii+2.5, col=blackBodyRadiationColors(rescaled_value),border=NA)
text(jj,nrow(dataset)-ii+2,dataset[ii,jj],col=ifelse(rescaled_value>0.5,"black","white"))
}
rescaled_value <- 0.2+0.6*(dataset[ii,ncol(dataset)]-min(dataset))/diff(range(dataset))
rect(ncol(dataset)+0.5,nrow(dataset)-ii+1.5,ncol(dataset)+1.5,nrow(dataset)-ii+2.5, col=blackBodyRadiationColors(rescaled_value),border=NA)
text(ncol(dataset)+1,nrow(dataset)-ii+2,dataset[ii,ncol(dataset)],col=ifelse(rescaled_value>0.5,"black","white"))
}
for ( jj in 1:(ncol(dataset)-1) ) {
rescaled_value <- 0.2+0.6*(dataset[nrow(dataset),jj]-min(dataset))/diff(range(dataset))
rect(jj-0.5,0.5,jj+0.5,1.5, col=blackBodyRadiationColors(rescaled_value),border=NA)
text(jj,1,dataset[nrow(dataset),jj],col=ifelse(rescaled_value>0.5,"black","white"))
}