Diagonal annotations in Plotly's categorical heatmap - r

Consider the following R snippet to render a heatmap with categorical axes:
library(plotly)
x <- c("Blue", "Red")
y <- c("One", "Two")
plot_ly(x = x, y = y, z = c(1:4)) %>%
add_heatmap(
opacity = 0.9
) %>%
add_annotations(
text = c(1:4),
showarrow = FALSE
)
This renders the following heatmap:
The annotations appear to be distributed diagonally and unevenly, starting from the bottom left cell. 1 and 3 are in the bottom left cell, and 2 and 4 in the upper right. Why is this? How should my annotation text be structured for it to be ordered more intuitively (horizontally or vertically)?

I can only speculate about the problem but in the provided image you can see that Plotly only used two values out of the 4 z-values. Your colorscale on the right goes from 1 to 2, not 1 to 4. This happens mMost likely because you provided only two x and y values.
Use a data frame
df <- expand.grid(x, y)
df <- transform(df, text = paste(Var1, Var2, sep='_'))
print(df)
Var1 Var2 text
1 Blue One Blue_One
2 Red One Red_One
3 Blue Two Blue_Two
4 Red Two Red_Two
You can now easily use add_annotations
add_annotations(x = df$Var1,
y = df$Var2,
text = df$text)
To get the following plot
Complete code
library(plotly)
x <- c("Blue", "Red")
y <- c("One", "Two")
df <- expand.grid(x, y)
df <- transform(df, text = paste(Var1, Var2, sep='_'))
p <- plot_ly(x = df$Var1,
y = df$Var2,
z = c(1:4)
) %>%
add_heatmap(opacity = 0.9
) %>%
add_annotations(x = df$Var1,
y = df$Var2,
text = df$text)
p
Alternatively you could loop over your values and add an annotation for each one.
library(plotly)
x <- c("Blue", "Red")
y <- c("One", "Two")
p <- plot_ly(x = x,
y = y,
z = c(1:4)
) %>%
add_heatmap(opacity = 0.9)
for (val_x in x)
{
for (val_y in y)
{
p <- add_annotations(p,
x = val_x,
y = val_y,
text = paste(val_x, val_y, sep = '_'))
}
}
p

Related

How can I successively add multiple lines to a graph in a plotly animation?

I want to show multiple lines being added to a plotly plot (as an animation) using R. For example, I have the following plotly line graphs (p, p2, p3):
library(plotly)
set.seed(3)
x = 1:10
y = 1:10
y2 = y^2
y3 = y^3
p = plot_ly(data = data.frame(x = x, y = y), x = ~ x, y = ~y, type = "scatter", mode = "lines")
p2 = plot_ly(data = data.frame(x = x, y = y2), x = ~ x, y = ~y2, type = "scatter", mode = "lines")
p3 = plot_ly(data = data.frame(x = x, y = y3), x = ~ x, y = ~y3, type = "scatter", mode = "lines")
Here p, p2, p3 are different plots but they all have the same x axis and different y axis. I want to be able to make an animation where the lines y, y2, y3 will successively appear in the plotly graph.
P.S: It does not strictly have to be done using plotly, but strongly preferred.
An idea might be to create a 'dataset' for each frame.
The first frame contains all values for y and all values for y2 and y3 are located outside the y-axis limits. For the second frame all values from y and y2 are shown and just the values from y3 are beyond the limit. In frame 3 all values are included.
library(tidyverse)
library(plotly)
# transform dataframe into a long format
df <- data.frame(x = 1:10,
y = 1:10) %>%
mutate(y2 = y^2,
y3 = y^3) %>%
pivot_longer(cols = -x,
names_to = "line",
values_to = "value")
# set the values for each frame and line
# (all lines not shown, need to hidden outside the plot limits - NA won't work)
df_plot <- map_df(1:3, ~ mutate(df, frame = .)) %>%
mutate(value = case_when(frame == 1 & line %in% c("y2", "y3") ~ -10,
frame == 2 & line %in% c("y3") ~ -10,
TRUE ~ value))
# create plot
plot_ly(data = df_plot,
x = ~x,
y = ~value,
color = ~line,
type = "scatter",
mode = "line",
frame = ~frame) %>%
layout(yaxis=list(range = c(0, 1000))) %>%
animation_opts(easing = "bounce")

R plotly Issues with hovering text in a trace loop

Following this post and this answer I have an additional question:
library(plotly)
# Create data
dat=data.frame(group = factor(rep(LETTERS[1:4], each=10)), my_x = rep(1:10, 4), my_y = rnorm(40))
str(dat)
# Let's do a first plot
p<-plot_ly(dat)
# Add a trace for each group using a loop
for(i in 1:length(levels(dat$group))){
subs <- subset(dat, group == levels(dat$group)[i])
p<-add_trace(p = p,
data = subs,
y=~my_y,
x=~my_x ,
name=levels(dat$group)[i],
type="scatter",
mode="markers+lines",
hoverinfo="text",
text=~paste0(levels(dat$group)[i], ": x=", round(my_x, 2), "y=", round(my_y, 2)))
}
p
Can anybody tell me why it is that when I hover over the data points, each of the labels shows the correct x and y values, however, they are all labelled as 'D:', while the legend shows the lines resemble A, B, C & D. I would like the hover text to be labeled correctly.
It could be an issue with the use of ~ in text. Try by creating the 'text' using the 'subs' data separately and then pass it on the add_trace
p <- plot_ly()
lvls <- levels(dat$group)
for(i in seq_along(lvls)){
subs <- droplevels(subset(dat, group == lvls[i]))
text1 <- with(subs, paste0(lvls[i], ": x=", round(my_x, 2), "y=", round(my_y, 2)))
p <- add_trace(p,
data = subs,
x = ~my_x,
y = ~my_y,
name = lvls[i],
type = 'scatter',
mode = 'markers+lines',
hoverinfo='text',
text=text1)
}
p
-output

plotly adding colorbar to mesh3d without using intensity (using facecolor)

I have a need to place a colorbar to a mesh3d plot that consists of several traces. Each mesh3d trace has a single color, but I need the colorbar to span all the trace-colors.
I am trying to combine a scatter3d with visible="legendonly" with the mesh3d, so achieve this. But when the mesh is plotted, the legend is removed.
Using the helicopter-example:
library(plotly)
library(geomorph)
plyFile <- 'http://people.sc.fsu.edu/~jburkardt/data/ply/chopper.ply'
dest <- basename(plyFile)
if (!file.exists(dest)) {
download.file(plyFile, dest)
}
mesh <- read.ply(dest, ShowSpecimen = F)
# see getS3method("shade3d", "mesh3d") for details on how to plot
# plot point cloud
x <- mesh$vb["xpts",]
y <- mesh$vb["ypts",]
z <- mesh$vb["zpts",]
m <- matrix(c(x,y,z), ncol=3, dimnames=list(NULL,c("x","y","z")))
# now figure out the colormap
zmean <- apply(t(mesh$it),MARGIN=1,function(row){mean(m[row,3])})
library(scales)
facecolor = colour_ramp(
brewer_pal(palette="RdBu")(9)
)(rescale(x=zmean))
plot_ly() %>%
# Creates the legend, and also the plotting space
add_trace(
x = x, y = y, z = z,
color = x,
colors = c("#ffffff", "#000000"),
# visible="legendonly",
type = "scatter3d",
mode="markers"
) %>%
# Adds the mesh, but removes the legend
add_trace(
x = x, y = y, z = z,
i = mesh$it[1,]-1, j = mesh$it[2,]-1, k = mesh$it[3,]-1,
facecolor = facecolor,
type = "mesh3d"
)
After a lot of hacking, I finally have a working solution.
In this case, by plotting a mesh3d area, with points that are "inside" the helicopter and will not be visible later, and then plot the actual helicopter later.
It seems that "visible='legendonly'" does not apply to mesh3d, as this option removes both the plot and legend.
library(plotly)
library(geomorph)
plyFile <- 'http://people.sc.fsu.edu/~jburkardt/data/ply/chopper.ply'
dest <- basename(plyFile)
if (!file.exists(dest)) {
download.file(plyFile, dest)
}
mesh <- read.ply(dest, ShowSpecimen = F)
# see getS3method("shade3d", "mesh3d") for details on how to plot
# plot point cloud
x <- mesh$vb["xpts",]
y <- mesh$vb["ypts",]
z <- mesh$vb["zpts",]
m <- matrix(c(x,y,z), ncol=3, dimnames=list(NULL,c("x","y","z")))
# now figure out the colormap
zmean <- apply(t(mesh$it),MARGIN=1,function(row){mean(m[row,3])})
# Get colors you want
cols = brewer_pal(palette="RdBu")(9)
# Ramp to add to facecolor
library(scales)
facecolor = colour_ramp(cols)(rescale(x=zmean))
# Create data.frame of colours and breakpoints.
# Must go from 0 to 1, plotly scales it based on values it self.
colz = data.frame(seq(0,1,length.out = length(cols)),
cols)
# Make stupid pointcloud to fool the colorbar
xx = c(min(x), max(x))
yy = c(min(y), max(y))
zz = c(min(z), max(z))
plot_ly() %>%
# Creates the legend, and also the plotting space
add_trace(
x = xx, y = yy, z = zz,
intensity = x,
colorscale = colz,
# visible="legendonly",
type = "mesh3d"
) %>%
# Adds the mesh
add_trace(
x = x, y = y, z = z,
i = mesh$it[1,]-1, j = mesh$it[2,]-1, k = mesh$it[3,]-1,
facecolor = facecolor,
showscale=FALSE,
type = "mesh3d"
)

In R, add a trace in a density plotly

Using the plotly package in R, I would like to do a desity plot. Actually, I need to add one more density line in my graph. I have a data with the income information of some public company by geographical region. Something like this
head(data)
id income region
1 4556 1
2 6545 1
3 65465 2
4 54555 1
5 71442 2
6 5645 6
In a first moment, I analysed 5 and 6 regions' income with the following density plot
reg56<- data[data$region %in% c(5,6) , ]
dens <- with(reg56, tapply(income, INDEX = region, density))
df <- data.frame(
x = unlist(lapply(dens, "[[", "x")),
y = unlist(lapply(dens, "[[", "y")),
cut = rep(names(dens), each = length(dens[[1]]$x))
)
# plot the density
p<- plot_ly(df, x = x, y = y, color = cut)
But, I want more than this. I would like to add the total income, i.e. the income of all regions. I tried something this
data$aux<- 1
dens2 <- with(data, tapply(income, INDEX = 1, density))
df2 <- data.frame(
x = unlist(lapply(dens2, "[[", "x")),
y = unlist(lapply(dens2, "[[", "y")),
cut = rep(names(dens2), each = length(dens2[[1]]$x)) )
p<- plot_ly(df, x = x, y = y, color = cut)
p<- add_trace(p, df2, x = x, y = y, color = cut)
p
Error in FUN(X[[i]], ...) :
'options' must be a fully named list, or have no names (NULL)
Some solution for this?
Because you are not naming the parameters that you pass to add_trace, it interprets them as corresponding to the default parameter order. The usage of add_trace is
add_trace(p = last_plot(), ..., group, color, colors, symbol, symbols,
size, data = NULL, evaluate = FALSE)
So, in your function call where you provide the data.frame df2 as the 2nd parameter, this is assumed to be correspond to the ... parameter, which must be a named list. You need to specify data = df2, so that add_trace understands what this parameter is.
Lets generate some dummy data to demonstrate on
library(plotly)
set.seed(999)
data <- data.frame(id=1:500, income = round(rnorm(500,50000,15000)), region=sample(6,500,replace=T) )
Now, (after calculating df and df2 as in your example):
p <- plot_ly(df, x = x, y = y, color = cut) %>%
add_trace(data=df2, x = x, y = y, color = cut)
p

Using both group and color in plotly

I am trying to plot a number of dimensions in r using plotly - is it possible to use both color and group parameters on factor variables to have a line that changes color?
Example:
grp <- c(letters[c(1,1,1,1,2,2,2,2)])
a <- c(1,2,3,4,2,3,4,5)
b <- c(1,3,5,6,1,2,4,4)
lvl <- c(1,1,2,2,1,1,2,2)
df <- data.frame(grp, a, b, lvl)
When plotting this using ggplot() I am able to create the desired effect as below, with grp as to define each line and lvl to define the color of sections of the line:
ggplot(data = df, aes(x = a, y = b, group = grp, color = lvl)) + geom_line() + geom_point()
However, when I then call ggplotly() the line gets grouped and colored by lvl.
I'm searching for the same function. It seems that group and color is plotly kryptonite.
So far my only solution is to make a column of color codes and use that to define the colors of the markers:
library(scales)
library(plotly)
grp <- c(letters[c(1,1,1,1,2,2,2,2)])
a <- c(1,2,3,4,2,3,4,5)
b <- c(1,3,5,6,1,2,4,4)
lvl <- c(1,1,2,2,1,1,2,2)
df <- data.frame(grp, a, b, lvl)
Palette <- data.frame(lvl = unique(df$lvl), color = brewer_pal("seq",palette = "Reds",direction = -1)(length(unique(df$lvl))), stringsAsFactors = FALSE)
df <- merge(x = df, y = Palette, by = "lvl")
p <- plot_ly(df, x = a, y = b, group = grp, mode = "markers+lines", marker = list(color = color, size = 8), line = list(color = "black", width = 2))
p
however this trick is very cumbersome and does not work with "line" that only takes a single color input and looks like this. HOWEVER if you do not give an input to the "line" it displays two different colors that you have no control over. like this
I was trying to do the same thing and there is now an official way : you need to add a group_by statement before plot_ly (see https://github.com/ropensci/plotly/issues/418)
grp <- c(letters[c(1,1,1,1,2,2,2,2)])
a <- c(1,2,3,4,2,3,4,5)
b <- c(1,3,5,6,1,2,4,4)
lvl <- c(1,1,2,2,1,1,2,2)
df <- data.frame(grp, a, b, lvl)
df %>% group_by(grp) %>% plot_ly(x = a, y = b, mode = "markers+lines", color = lvl)

Resources