Pheatmap Draws Pretty Heatmaps. A tutorial of how to generate pretty... | by Yufeng | Towards Data Science

Heatmap is one of the must-have data visualization toolkits for data scientists.
In R, there are many packages to generate heatmaps, such as heatmap(), heatmap.2(), and heatmaply(). However, my favorite one is pheatmap(). I am very positive that you will agree with my choice after reading this post.
In this post, I will go over this powerful data visualization package, pheatmap, by applying it to the NBA players’ basic stats in the 2019–2020 season.
The raw data is from the basketball reference. You can either download the dataset manually or scrape the data by following one of my previous posts.
Ready to begin? Let’s go.
Language: R.Package name: pheatmap.
install.packages("pheatmap")library(pheatmap)
Data: 2019–2020 NBA players’ stats per game.
df = read.csv("../2019_2020_player_stats_pergame.csv")head(df)
Above is the head of the data frame we are working on. It’s okay that you don’t understand what the column names are because they are all stats of basketball. It doesn’t affect our exploration of heatmap plotting.
Data cleaning: filter out players who played less than 30 minutes per game, remove duplicates of players who got traded during the season and fill NA values with 0.
df_filt = df[df$MP >= 30 ,]TOT_players = df_filt[df_filt$Tm == "TOT","Player"]df_used = df_filt[((df_filt$Player %in% TOT_players) & (df_filt$Tm == "TOT")) | (!(df_filt$Player %in% TOT_players)),]df_used[is.na(df_used)] = 0
First, pheatmap only takes the numeric matrix object as input. So, we need to transfer the numeric part of the data frame to a matrix by removing the first 5 columns of categorical data.
df_num = as.matrix(df_used[,6:30])
Since the row names of the matrix are the default row labels in the heatmap, we’d better make them meaningful by avoiding numeric index.
rownames(df_num) = sapply(df_used$Player,function(x) strsplit(as.character(x),split = "\\\\")[[1]][1])
The different columns of the players’ data have a large variation in the range, so we need to scale them to keep the heatmap from being dominated by the large values.
df_num_scale = scale(df_num)
The scale function in R performs standard scaling to the columns of the input data, which first subtracts the column means from the columns (center step) and then divides the centered columns by the column standard deviations (scale step). This function is to scale the data to a distribution with mean as 0 and standard deviation as 1.
Its equation can be shown as below, where x is the data, u is the column means and s is the column standard deviations.
z = (x — u) / s
You can turn off the center step or the scale step in R by setting center = FALSE or scale = FALSE, respectively. Let’s visualize the effect of scaling by plotting out the density of players’ points per game before and after scaling.
plot(density(df$PTS),xlab = "Points Per Game",ylab="Density",main="Comparison between scaling data and raw data",col="red",lwd=3,ylim=c(0,0.45))lines(density(df_num_scale[,"PTS"]),col="blue",lwd=3)legend("topright",legend = c("raw","scaled"),col = c("red","blue"),lty = "solid",lwd=3)
After scaling the data is ready to be fed into the function.
Let’s look at the default pheatmap.
pheatmap(df_num_scale,main = "pheatmap default")
The default behavior of the function includes the hierarchical clustering of both rows and columns, in which we can observe similar players and stats types in close positions.
For example, there’s a super warm area in the middle part of the heatmap. It corresponds to a bunch of superstars, which includes James Harden, Luka Doncic, LeBron James, and Damian Lillard.
If you want to turn off the clustering, you can set either cluster_cols or cluster_rows to False. The code below cancels the column clustering.
pheatmap(df_num_scale,cluster_cols = F,main = "pheatmap row cluster")
Actually, the function itself can do both row and column scaling in the heatmap. It mainly serves as a visualization purpose for the comparison across rows or columns. The following code shows the row scaling heatmap.
pheatmap(df_num_scale,scale = "row",main = "pheatmap row scaling")
The annotation function is one of the most powerful features of pheatmap. Specifically, you can input an independent data frame with annotations to the rows or columns of the heatmap matrix.
For example, I annotated each player with their position, made it a data frame object and input it to the pheatmap function. One thing to note, the row names of the annotation data frame have to match the row names or column names of the heatmap matrix depending on your annotation target.
pos_df = data.frame("Pos" = df_used$Pos)rownames(pos_df) = rownames(df_num) # name matchingpheatmap(df_num_scale,cluster_cols = F,annotation_row = pos_df,main = "pheatmap row annotation")
You can see from the heatmap that there is another column of colors that indicate the position of the players.
We see the players are not clustered by their positions, which suggests the relationship between the players’ positions and their playing types are becoming vague with the evolution of basketball.
Also, we can add the column annotation as well. I named the stats with their categories that include Offence, Defence, and others.
cat_df = data.frame("category" = c(rep("other",3),rep("Off",13),rep("Def",3),"Off",rep("Def",2),rep("other",2),"Off"))rownames(cat_df) = colnames(df_num)
Then, I plot the heatmap with column annotation only. This time I only turn on the column clustering.
pheatmap(df_num_scale,cluster_rows = F, annotation_col = cat_df,main = "pheatmap column annotation")
We can see from the heatmap that the offense-related stats tend to be clustered together.
The last feature I would like to introduce is the heatmap cutting feature. Sometimes, it will give a clearer visualization if we cut the heatmap by the clustering.
By cutting a heatmap apart, the stand-alone blocks will represent its own population. Let’s see the row-wise cutting in the following example.
pheatmap(df_num_scale,cutree_rows = 4,main = "pheatmap row cut")
In the code, I input cutree_rows = 4 , which means cut the heatmap row-wise to 4 clusters.
The aforementioned group of superstars is present in the third block in the cut heatmap.
We can do a similar thing to the columns as below.
pheatmap(df_num_scale,cutree_cols = 4,main = "pheatmap column cut")
In this way, similar stats are shown close to each other.
Up until now, I have gone through all the major features of pheatmap. Of course, there are a lot more details in the package, such as the color palette, clustering distance metrics, and so on. For those who are interested, please refer to the function manual.
I hope this tutorial can help you strengthen your visualization toolkit. If you have enjoyed reading this post, you can also find interesting stuff in my other posts.