Combining plotly plots with highly variable widths - r

I want to combine several R plotly heatmaps, using plotly's subplot, with some a-priori defined width per each subplot, and these widths are highly variable, since they reflect real proportions of my data.
Here's an example dataset:
library(dplyr)
library(plotly)
library(grDevices)
set.seed(1)
df <- data.frame(row = rep(paste0("rid",1:100),10),
col = paste0("cid",unlist(lapply(1:10,function(x) rep(x,100)))),
val = rnorm(1000,-2,1))
Here I generate the list of plots:
plot.list <- lapply(1:10,function(i) plot_ly(z=c(dplyr::filter(df,col == paste0("cid",i))$val),x=dplyr::filter(df,col == paste0("cid",i))$col,y=dplyr::filter(df,col == paste0("cid",i))$row,
colors=colorRamp(c("darkblue","lightgray","darkred")),type="heatmap") %>%
layout(yaxis=list(title=NULL),xaxis=list(tickvals=i,ticktext=as.character(i))))
Here are the plot widths:
plot.widths <- c(0.33277,0.0663,0.28308,0.09323,0.12969,0.0603,0.00651,0.01149,0.01503,0.0016)
Clearly, they sum up to 1.
If I just try this:
subplot(plot.list,shareX=T,shareY=T,nrows=1,margin=0.001,widths=plot.widths) %>% layout(showlegend=F)
I get:
I realized that the plot widths that are smaller than 0.015 cause this.
Right now my quick fix is to scale up the widths of the plots that are below 0.015 and to reciprocally scale down the widths of the plots that are above 0.015 so that the minimal plot width is 0.015 and they still sum up to 1.
Like this:
below.cutoff.widths <- which(plot.widths < 0.015)
if(length(below.cutoff.widths) > 0){
scale.up.factor <- 0.015/min(plot.widths[below.cutoff.widths])
scale.down.factor <- sum(plot.widths[-below.cutoff.widths])/(1-sum(scale.up.factor*(plot.widths[below.cutoff.widths])))
plot.widths[-below.cutoff.widths] <- plot.widths[-below.cutoff.widths]/scale.down.factor
plot.widths[below.cutoff.widths] <- plot.widths[below.cutoff.widths]*scale.up.factor
plot.widths <- plot.widths-.Machine$double.eps
}
Which works and gives:
The problem is that in the example above this significantly distorts the original widths and therefore distorts the message that I'm trying to convey with this plot.
Any idea how to handle this better?
I don't suppose there's a way to simply convert the plot.list's plotly objects to ggplot objects and use gridExtra's arrangeGrob to arrange them together on a grid?

Related

how to match widths of two html dygraph plots while using htmltools::tagList

I use the excellent dygraphs package in R all the time. The synchronized plots work great but I am having difficulty in keeping the widths of the two synchronized plots identical when one dygraph has data only on primary axis while second has primary + secondary y axis both plotted. The "y2" axis labels reduces the width of the chart 2 thus throwing the x axis of both charts out of sync.
Take a look with the following toy example:
library(data.table)
library(magrittr)
library(lubridate)
library(dygraphs)
library(htmltools)
# create 2 small data.tables.
dt1 <- data.table(datetime = seq(ymd_hm(202205100800),by = "1 mins",length.out = 50))[order(datetime)]
dt2 <- data.table(datetime = seq(ymd_hm(202205100800),by = "1 mins",length.out = 50))[order(datetime)]
dt1[,temp1:=rnorm(50,10,0.5)]
dt2[,temp2:=rnorm(50,7,0.5)]
dt2[,power:=rnorm(50,100,0.5)] # scale is higher hence will be ploted on the secondary access.
d1 <- dygraph(dt1,group = "X")
d2 <- dygraph(dt2,group = "X") %>% dySeries("power",axis = "y2")
Now I combine the two charts using htmltools::browsable() function. I am open to use any other function to combine the two plots so long as they can be one html document.
browsable(tagList(d1,d2))
As you see the two charts are a little displaced. I would like the two x axis aligned exactly.
Thanks for reading and replicating my example !

Can I re-scale the x/y axis aspect ratio in R with rayshader?

I have some data from lab equipment that can be represented as a matrix by a contour plot/heatmap.
I would like to try illustrating this data in R with the rayshader package.
My problem is that the data is far from square in shape, the matrix is 33 rows by 48003 columns. When I plot this with rayshader I get a thin line:
library(dplyr)
library(rayshader)
set.seed(1742)
df <- matrix(rnorm(10000), nrow = 10)
rownames(df) <- 1:10
colnames(df) <- seq(0.01, 10, 0.01)
df %>%
sphere_shade(texture = "desert") %>%
plot_map()
Is there a way to make rayshader plot this as a square by manipulating the x/y aspect ratios? Or to plot them on an equivalent scale (one dimension collects data much faster than the other)? I can't find anything in the docs.
In this example, I tried naming the rows and columns so they were both collected over 10 minutes, but it didn't change the result.
The end result should look similar to:
library(plotly)
set.seed(1742)
plot_ly(z = ~matrix(rnorm(10000), nrow = 10)) %>%
add_surface()
Many thanks.
Solution for rayshader::plot_3d() is to use scale = c(x, y, z), which will alter the x/y/z aspect ratios. This was hidden, but didn't take that much sluthing to find the answer. It is a setting in rgl::par3d(), which is called by plot_3d().
However, I couldn't get plot_map() to work. When I tried adding the argument asp = 1, which is used by rgl::par3d(), it threw errors.

How to cut a dendrogram in r

Okay so I'm sure this has been asked before but I can't find a nice answer anywhere after many hours of searching.
I have some data, I run a classification then I make a dendrogram.
The problem has to do with aesthetics, specifically; (1) how to cut according to the number of groups (in this example I want 3), (2) make the group labels aligned with the branches of the trees, (2) Re-scale so that there aren't any huge gaps between the groups
More on (3). I have dataset which is very species rich and there would be ~1000 groups without cutting. If I cut at say 3, the tree has some branches on the right and one 'miles' off to the right which I would want to re-scale so that its closer. All of this is possible via external programs but I want to do it all in r!
Bonus points if you can put an average silhouette width plot nested into the top right of this plot
Here is example using iris data
library(ggplot2)
data(iris)
df = data.frame(iris)
df$Species = NULL
ED10 = vegdist(df,method="euclidean")
EucWard_10 = hclust(ED10,method="ward.D2")
hcd_ward10 = as.dendrogram(EucWard_10)
plot(hcd_ward10)
plot(cut(hcd_ward10, h = 10)$upper, main = "Upper tree of cut at h=75")
I suspect what you would want to look at is the dendextend R package (it also has a paper in bioinformatics).
I am not fully sure about your question on (3), since I am not sure I understand what rescaling means. What I can tell you is that you can do quite a lot of dendextend. Here is a quick example for coloring the branches and labels for 3 groups.
library(ggplot2)
library(vegan)
data(iris)
df = data.frame(iris)
df$Species = NULL
library(vegan)
ED10 = vegdist(df,method="euclidean")
EucWard_10 = hclust(ED10,method="ward.D2")
hcd_ward10 = as.dendrogram(EucWard_10)
plot(hcd_ward10)
install.packages("dendextend")
library(dendextend)
dend <- hcd_ward10
dend <- color_branches(dend, k = 3)
dend <- color_labels(dend, k = 3)
plot(dend)
You can also get an interactive dendrogram by using plotly (ggplot method is available through dendextend):
library(plotly)
library(ggplot2)
p <- ggplot(dend)
ggplotly(p)

How to adjust x labels in R boxplot

This is my code to create a boxplot in R that has 4 boxplots in one.
psnr_x265_256 <- c(39.998,39.998, 40.766, 38.507,38.224,40.666,38.329,40.218,44.746,38.222)
psnr_x264_256 <- c(39.653, 38.106,37.794,36.13,36.808,41.991,36.718,39.26,46.071,36.677)
psnr_xvid_256 <- c(33.04564,33.207269,32.715427,32.104696,30.445141,33.135261,32.669766, 31.657039,31.53103,31.585865)
psnr_mpeg2_256 <- c(32.4198,32.055051,31.424819,30.560274,30.740421,32.484694, 32.512268,32.04659,32.345848, 31)
all_errors = cbind(psnr_x265_256, psnr_x264_256, psnr_xvid_256,psnr_mpeg2_256)
modes = cbind(rep("PSNR",10))
journal_linear_data <-data.frame(psnr_x265_256, psnr_x264_256, psnr_xvid_256,psnr_mpeg2_256)
yvars <- c("psnr_x265_256","psnr_x264_256","psnr_xvid_256","psnr_mpeg2_256")
xvars <- c("x265","x264","xvid","mpeg2")
bmp(filename="boxplot_PSNR_256.bmp")
boxplot(journal_linear_data[,yvars], xlab=xvars, ylab="PSNR")
dev.off()
This is the image I get.
I want to have the corresponding values for each boxplot in x axis "x265","x264","xvid","mpeg2".
Do you have any idea how to fix this?
There are multiple ways of changing the labels for your boxplot variables. Probably the simplest way is changing the column names of your data frame:
colnames(journal_linear_data) <- c("x265","x264","xvid","mpeg2")
Even simpler: you could do this right at the creation of your data frame too:
journal_linear_data <- data.frame(x265=psnr_x265_256, x264=psnr_x264_256, xvid=psnr_xvid_256, mpeg2=psnr_mpeg2_256)
If you run into the problem of your labels not being shown or overlapping due to too few space, try rotating the x labels using the las parameter, e.g. las=2 or las=3.

lattice or latticeExtra combine multiple plots different yscaling (log10 and non-transformed)

I have a multiple variable time series were some of the variables have rather large ranges. I wish to make a single-page plot with multiple stacked plots of each variable were some of the variables have a log10 y-axis scaling. I am relatively new to lattice and have not been able to figure out how to effectively mix the log10 scaling with non-transformed axes and get a publication quality plot. If print.trellis is used the plots are not aligned and the padding needs some work, if c.trellis is used the layout is good, but only the y-scaling from only one plot is used. Any suggestions for an efficient solution, where I can replicate the output of c.trellis using the different y-scaling for each (original) object?
Example below:
require(lattice)
require(latticeExtra)
# make data.frame
d.date <- as.POSIXct(c("2009-12-15", "2010-01-15", "2010-02-15", "2010-03-15", "2010-04-15"))
CO2dat <- c(100,200,1000,9000,2000)
pHdat <- c(10,9,7,6,7)
tmp <- data.frame(date=d.date ,CO2dat=CO2dat ,pHdat=pHdat)
# make plots
plot1 <- xyplot(pHdat ~ date, data=tmp
, ylim=c(5,11)
, ylab="pHdat"
, xlab="Date"
, origin = 0, border = 0
, scales=list(y=list(alternating=1))
, panel = function(...){
panel.xyarea(...)
panel.xyplot(...)
}
)
# make plot with log y scale
plot2 <- xyplot(CO2dat ~ date, data=tmp
, ylim=c(10,10^4)
, ylab="CO2dat"
, xlab="Date"
, origin = 0, border = 0
, scales=list(y=list(alternating=1,log=10))
, yscale.components = yscale.components.log10ticks
, panel = function(...){
panel.xyarea(...)
panel.xyplot(...)
# plot CO2air uatm
panel.abline(h=log10(390),col="blue",type="l",...)
}
)
# plot individual figures using split
print(plot2, split=c(1,1,1,2), more=TRUE)
print(plot1, split=c(1,2,1,2), more=F)
# combine plots (more convenient)
comb <- c(plot1, plot2, x.same=F, y.same=F, layout = c(1, 2))
# plot combined figure
update(comb, ylab = c("pHdat","log10 CO2dat"))
Using #joran's idea, I can get the axes to be closer but not exact; also, reducing padding gets them closer together but changes the aspect ratio. In the picture below I've reduced the padding perhaps by too much to show the not exactness; if this close were desired, you'd clearly want to remove the x-axis labels on the top as well.
I looked into the code that sets up the layout and the margin on the left side is calculated from the width of the labels, so #joran's idea is probably the only thing that will work based on the printing using split, unless one were to rewrite the plot.trellis command. Perhaps the c method could work but I haven't found a way yet to set the scale components separately depending on the panel. That does seem more promising though.
mtheme <- standard.theme("pdf")
mtheme$layout.heights$bottom.padding <- -10
plot1b <- update(plot1, scales=list(y=list(alternating=1, at=5:10, labels=paste(" ",c(5:10)))))
plot2b <- update(plot2, par.settings=mtheme)
pdf(file="temp.pdf")
print(plot2b, split=c(1,1,1,2), more=TRUE)
print(plot1b, split=c(1,2,1,2), more=F)

Resources