R Plotly -Stacked Barchart with total amount of bars text - r

Problem: I have a stacked barchart. Above each of the stacked bars I want to have a total number (i.e. the sum of the two bars - here: total). I tried to take usage of add_text but it did not work out as expected. Any suggestions what to change?
Remark: I do not want to cast the data.table object. The target frame on which the visualizations shall be made is new_frame.
Many thanks
library(data.table)
library(plotly)
Animals <- c("giraffes", "orangutans", "monkeys")
SF_Zoo <- c(20, 14, 23)
LA_Zoo <- c(12, 18, 29)
data <- data.table(Animals, SF_Zoo, LA_Zoo)
new_frame <- data.table::melt(data, id.vars='Animals')
new_frame[, total := sum(value), by = Animals]
plot_ly(new_frame, x = ~Animals,
y = ~value,
type = 'bar',
name = ~variable,
hoverinfo = "text",
text = ~paste(variable, value, total)) %>%
## Not working
add_text(x = ~Animals, y = ~value,
text = ~total, textposition = "top center") %>%
layout(yaxis = list(title = 'Count'), barmode = 'stack')

Plotly is a powerful package and I feel with you ... some of the logic is not always self-evident. In particular when adding hovertexts, text elements, and/or annotations. You have to be careful about what are your variables and what is the textual element you want to add/control.
I hope the following allows you to step through the in-and-outs.
(I) Hovertext
With your data.table you have a data.frame that you provide to a trace (think broadly of a geom in ggplot). In this case a "bar" trace. I changed a bit the code to make this clearer by pulling the dataframe out and only use an empty plot_ly() call and adding deliberately a "bar"-trace.
This is strictly not necessary. However it allows you to understand what you are working on.
new_frame %>%
plot_ly() %>%
add_bars(x = ~Animals,
y = ~value,
type = 'bar',
name = ~variable,
hovertemplate = "my value: %{y}"
) %>%
## Not working
# add_text(x = ~Animals, y = ~value,
# text = "gotcha", textposition = "top center") %>%
layout(yaxis = list(title = 'Count'), barmode = 'stack')
With this code you have altered the presentation of the "hover" tooltip. The name parameter controls the "title" of the tooltip. For example, hovering over the plot shows the formatted hover, i.e. "my value: " and the name of the stacked bar "SF_Zoo". In the template definition you refer to the y-variable and not your dataframe (column) variable (name).
(II) Adding a "text"
Again think geom_text with adding a "text" trace. Note that trace is not fully correct here. You then map the position of the text to the x and y parameter of add_text() call.
This will display a text fragment at each mapped position (x,y). In the example, I provide a text vector of the same length than (x,y) combinations. If you provide a single string, this will be replicated. Here the text trace inherits from your new_frame. Note you could provide a new object here similar to what is defined in the next step.
(III) adding annotations
With add_annotations() you can add text elements and pointers. Again, you can provide a new object. As we only need the 3 totals for the group of animals, I reduce the data for this step to what is needed. With this "data format" I can now supply the variable total to the text parameter. I set showarrow = TRUE to provide an example on how to augment the text add on with an arrow.
new_frame %>%
plot_ly() %>%
add_bars(x = ~Animals,
y = ~value,
type = 'bar',
name = ~variable,
hovertemplate = "my value: %{y}"
) %>%
## Now let's add a text trace (think ggplot layer)
add_text( x = ~Animals
,y = ~value,
,text = c("one","two","three","four","five","six") # this overwrites your values with a string
,textposition = "center"
) %>%
## add annotation
add_annotations(data = new_frame %>% select(Animals, total) %>% unique()
,x = ~Animals
,y = ~total
,text = ~total
## remove arrow and offset
,showarrow = TRUE
## with showarrow = TRUE you can control the arrow
# , ax = 20
# , ay = 40
) %>%
## control the layout of the chart
layout(yaxis = list(title = 'Count'), barmode = 'stack')
Feel free to explore further options.
In summary, in plotly you provide a vector of values to x, y, or text. Strictly speaking these do not need to be organised as a dataframe. The dataframe allows you to make use of the ~notation for variables. There are 2 ways to add text. Make sure to understand how you use the position mapping (x, y) in combination with the text. For purists add_annotation() is more in line with a ggplot mind to have arbitrary text. But by defining the data object you supply to add_text() you can achieve a similar result. add_annotations() give you some additional styling elements with various arrow-designs.

Related

How to group datapoints independently from coloring in plotly without splitting the legend?

I want to plot multiple points connected by lines in plotly. While there should be exactly one line per id-group (as defined in the dataset), the color of those lines should depend on a different column in the data.
Consider this example data:
df <- data.frame(
x = c(1,2,3,4,5,6),
y = c(0,0,3,1,4,5),
id= c(1,1,2,2,3,3), # defines belonging to line
group = c("A","A","A","A","B","B") # defines color of the point and line
)
if I plot it this way, it kind of works:
plot_ly(df,type = "scatter", mode = "lines+markers",
x = ~x,
y = ~y,
split = ~id,
color = ~group)
Unfortunately, the legend is also split along split and color:
I only want it to display the color part of the legend. In this example case the legend would only have two entries. One for "A" and one for "B".
Is there a simple way to do this that I am not seeing here?
Addendum: An id is always linked to one group only. There are no id values, that are shared bewtween multiple different group values
plotly's color parameter will always create traces based on the provided data (just like split, but with colors mapped).
However, we can combine the separate legend items via legendgroup and display only one of them:
library(plotly)
DF <- data.frame(
x = c(1,2,3,4,5,6),
y = c(0,0,3,1,4,5),
id= c(1,1,2,2,3,3), # defines belonging to line
group = c("A","A","A","A","B","B") # defines color of the point and line
)
fig <- plot_ly(DF, type = "scatter", mode = "lines+markers",
x = ~x,
y = ~y,
split = ~id,
color = ~group,
name = ~group,
legendgroup = ~group) %>%
layout(showlegend = TRUE) %>%
style(showlegend = FALSE, traces = 1)
fig
Please check my answer here for a more general approach.

Problem with Choropleth map's legend created by plotly in R based on categorical-data and sf map

Please help ! This question is very similar to this one that has been answered quite some time ago. However, I still cannot get my head around the solution:
How to create a chloropleth map in R Plotly based on a Categorical variable?
I'm trying to create an interactive Choropleth map in R for my shiny app based on categorical data, using plotly and sf data get from GADM. Here is a reproducible example:
library(raster)
library(tidyverse)
library(plotly)
library(sf)
# Get the map data in sf format
map_data <- getData("GADM", country = "FRA", level = 2, type = "sf")
# Transform sf data to modern crs object to avoid further warning message
st_crs(map_data) <- st_crs(map_data)
# Generate some random data for each region
department <- map %>% as.data.frame() %>% .[, 13]
set.seed(10, sample.kind="Rounding")
data <- sample(x = 0:1200, size = length(department), replace = T)
map_dat <- data.frame(department = department,
data = data)
# Assign Class as categories
map_dat <- map_dat %>%
mutate(Class = cut(data,
breaks = c(-Inf, 50, 100, 200, 500, 1000, Inf),
labels = c("< 50", "50 - 100", "100 - 200",
"200 - 500", "500 - 1000", "> 1000")))
# Join data and plot
plot_dat <- map %>% as.data.frame() %>%
left_join(map_dat, by = c("HASC_2" = "department")) %>%
st_as_sf()
plot_ly(plot_dat) %>%
add_sf(type = "scatter",
stroke = I("transparent"),
span = I(1),
alpha = 1,
split = ~NAME_2,
color = ~Class,
colors = "Reds",
text = ~paste0(NAME_2, "\n", data),
hoveron = "fills",
hoverinfo = "text") %>%
config(displayModeBar = F)
The problem that I have with the default legend is that it's too detail and cumbersome in some way, as I just want to display a small and compact box filled with categorical class that I assign, similar to the one in ggplot. I've try to split the map with categorical Class and the legend looks a little bit better, however my hover text does not work anymore, and still, I have no idea how to edit or change the style of the legend in order for it to look decent and neat, like to change the key symbols to become a circle or a square like we usually see in a legend map box.
plot_dat %>% plot_ly() %>%
add_sf(type = "scatter",
stroke = I("transparent"),
span = I(1),
alpha = 1,
split = ~Class,
color = ~Class,
colors = "Reds",
text = ~paste0(NAME_1, "\n", Count),
hoveron = "fills",
hoverinfo = "text") %>%
config(displayModeBar = F) %>%
layout(showlegend = F)
I've read through the documentation for plotly's Choropleth map in R and yet found no documentaion for categorical case (unlike Python). As I'm running out of options, my question is, is there any way to achive my desired goal here ? How can I create a proper legend box, or a bar for categorical data ?
Apologies if my question is not that clear. I'm eager to answer any questions if anyone has. Thank you in advance.
After getting it to work, I can't help but think...there has to be a better way.
This uses the library spPlot. This isn't a Cran package. Use the following to get this one.
devtools::install_github("GegznaV/spPlot")
You'll likely run into the same dependency issue I ran into. You will need the package ChemometricsWithR to get spPlot. It will try to install that package, but it's another one that you need to get through alternative means.
devtools::install_github("rwehrens/ChemometricsWithR")
I'm using the first plot_ly call you made. I added a few things: legendgroup = ~Class, name = ~Class, and showlegend = F. Then I piped in the function plotly_modify_legend to add the grouped legend.
(plt <- plot_ly(plot_dat) %>%
add_sf(type = "scatter",
stroke = I("transparent"),
span = I(1),
alpha = 1,
split = ~NAME_2,
legendgroup = ~Class, # group legends together by class
name = ~Class, # so region names aren't shown in the legend
color = ~Class,
showlegend = F, # don't show a legend for each region
colors = "Reds",
text = ~paste0(NAME_2, "\n", data),
hoveron = "fills",
hoverinfo = "text") %>%
config(displayModeBar = F) %>%
plotly_modify_legend(showlegend = T, traceorder = "grouped")) # group legend visible
Plotly orders the legend in the order in which the elements appear, which is inherently alphabetical. That equates to having a really odd order in the legend.
The 96 separate subplots (one for each region) needed to be reordered to fix this. I could have just found one of each and changed the first 6 (one for each legend group), but I didn't think that would be easier.
Instead, I captured the traces, extracted their assigned group, and reordered them. After that, I replaced the traces in the plotly object.
# extract the legend groups
trOrder = map(1:length(plt$x$data),
~plt$x$data[[.x]]$legendgroup) %>%
unlist()
# assign an order to the groups and reorder the data
newOrder = data.frame(id = 1:96, trOrder = trOrder) %>%
mutate(trOrder = factor(trOrder, levels(plot_dat$Class))) %>%
arrange(trOrder)
# reorder the traces using the indicies
a = map(newOrder$id,
~plt$x$data[[.x]])
# check it
a[[1]]$legendgroup
# [1] "< 50"
# replace the traces
plt$x$data <- a
plt # mum--nikan-tez - ausgezeichnet - travail exceptionnel - Фантастический
Now the legend makes sense.

R Plotly linked subplot with percentage histogram and categories coloured

The Background
I am using the plotly API in R to create two linked plots. The first is a scatter plot and the second is a bar chart that should show the percentage of data belonging to each category, in the current selection. I can't make the percentages behave as expected.
The problem
The plots render correctly and the interactive selection works fine. When I select a set of data points in the top scatter plot, I would like to see the percentage of that selection that belongs to each category. Instead what I see is the percentage of points in that selection in that category that belong to that category, in other words always 100%. I guess this is because I set color = ~c which applies a grouping to the category.
The Example
Here is a reproducible example to follow. First create some dummy data.
library(plotly)
n = 1000
make_axis = function(n) c(rnorm(n, -1, 1), rnorm(n, 2, 0.25))
data = data.frame(
x = make_axis(n),
y = make_axis(n),
c = rep(c("A", "B"), each = n)
)
Create a sharedData object and supply it to plot_ly() for the base plot.
shared_data = data %>%
highlight_key()
baseplot = plot_ly(shared_data)
Make the individual panels.
points = baseplot %>%
add_markers(x = ~x, y = ~y, color = ~c)
bars = baseplot %>%
add_histogram(x = ~c, color = ~c, histnorm = "percent", showlegend = FALSE) %>%
layout(barmode = "group")
And put them together in a linked subplot with selection and highlighting.
subplot(points, bars) %>%
layout(dragmode = "select") %>%
highlight("plotly_selected")
Here is a screenshot of this to illustrate the problem.
An Aside
Incidentally when I set histnorm = "" in add_histogram() then I get closer to the expected behaviour but I do want percentages and not counts. When I remove color = ~c then I get closer to the expected behaviour but I do want the consistent colour scheme.
What have I tried
I have tried manually supplying the colours but then some of the linked selection breaks. I have tried creating a separate summarised data set from the sharedData object first and then plotting that but again this breaks the linkage between the plots.
If anyone has any clues as to how to solve this I would be very grateful.
To me it seems the behaviour you are looking for isn't implemented in plotly.
Please see schema(): object ► traces ► histogram ► attributes ► histnorm ► description
However, here is the closest I was able to achive via add_bars and perprocessing the data (Sorry for adding data.table, you will be able to do the same in base R, just personal preference):
library(plotly)
library(data.table)
n = 1000
make_axis = function(n) c(rnorm(n, -1, 1), rnorm(n, 2, 0.25))
DT = data.table(
x = make_axis(n),
y = make_axis(n),
c = rep(c("A", "B"), each = n)
)
DT[, grp_percent := rep(100/.N, .N), by = "c"]
shared_data = DT %>%
highlight_key()
baseplot = plot_ly(shared_data)
# Make the individual panels.
points = baseplot %>%
add_markers(x = ~x, y = ~y, color = ~c)
bars = baseplot %>%
add_bars(x = ~c, y = ~grp_percent, color = ~c, showlegend = FALSE) %>%
layout(barmode = "group")
subplot(points, bars) %>%
layout(dragmode = "select") %>%
highlight("plotly_selected")
Unfortunately, the resulting hoverinfo isn't really desirable.

Plotly R - Include filter in the legend that doesn't affect graph

A feature of plotly that I really like is the ability to dive into the data by clicking on specific groupings in the legend. For example, if I set a column to color for a scatter plot, I can filter on the various color variables. However, I only know how to create this filter when assigning the column to color. Is there a way to assign a variable to the legend to filter without changing the design of the plot. For example is there a function like legend_filter in plotly I could use:
iris2 <- iris
iris2$sample <- sample(c('A','B'), nrow(iris2), replace = T)
p <- plot_ly(data = iris2, x = ~Sepal.Length, y = ~Petal.Length, color = ~Species,
# legend_filter = ~sample
)
p
such that 'A' and 'B' show up in the side bar to interactively click on, but aren't referenced on the graph?
Thanks
This method lets you toggle all of A and B on off as a group by clicking any one of the entries.
Legend is definitely cluttered side, and you have to add another set of markers for each level of your grouping variable. I don't think this is really the end result you're looking for, but I figured I might as well post anyway in case any pieces of it are useful.
plot_ly() %>%
add_markers(data = iris2[iris2$sample == "A",],
x = ~Sepal.Length,
y = ~Petal.Length,
color = ~Species,
legendgroup = "A",
name = "A") %>%
add_markers(data = iris2[iris2$sample == "B",],
x = ~Sepal.Length,
y = ~Petal.Length,
color = ~Species,
legendgroup = "B",
name = "B")
Yields

R: Colors in dumbell plot seem to mix in inappropriate ways

I am trying to produce a dumbell plot in R. In this case, there are four rows, and they need to have different and specific colors each. I define the colors as part of the dataset using colorRampPalette(). Then when I produce the plot, the colors get mixed in inappropriate ways. See the image below, and in particular the legend.
As you can see, the orange is supposed to be #7570B3 according to the legend. But this is not correct. The color 7570B3 is purple ! For this reason, the colors that I had defined in the dataset are mixed in the plot. "Alt 2" sound be in orange and "Alt 3" should be in purple.
Does anyone know how to fix this ? Any help would be very appreciated.
Here is a simple version of the code:
table_stats_scores <- data.frame(alt=c("alt1","alt2","alt3","alt4"),
average=c(15,20,10,5),
dumb_colors= colorRampPalette(brewer.pal(4,"Dark2"))(4),
min=c(10,15,5,0),max=c(20,25,15,10)
)
table_stats_scores # This is the dataset
table_stats_scores <- table_stats_scores[order(-
table_stats_scores$average),] # ordering
table_stats_scores$alt <- factor(table_stats_scores$alt,
levels = table_stats_scores$alt[order(table_stats_scores$average)])
# giving factor status to alternatives so that plot_ly() picks up on this
p <- plot_ly(table_stats_scores, x=table_stats_scores$average, color = ~
dumb_colors,
y=table_stats_scores$alt,text=table_stats_scores$alt) %>%
add_segments(x = ~min, xend = ~max, y = ~alt, yend = ~alt,name = "Min-Max
range", showlegend = FALSE, line = list(width = 4)) %>%
add_markers(x = ~average, y = ~alt, name = "Mean",
marker=list(size=8.5),showlegend = FALSE) %>%
add_text(textposition = "top right") %>%
layout(title = "Scores of alternatives",
xaxis = list(title = "scores"),
yaxis = list(title = "Alternatives")
)
p
Yes color can be an issue in plotly, because there are several ways to specify it, and the assignment order of the various elements from the dataframe can be hard to keep in sync.
The following changes were made:
added a list of brighter colors to your dataframe because I couldn't easily visualize the brewer.pal colors. Better to debug with something obvious.
changed the color parameter to the alt column, because it is really just used only indirectly to set the color, and mostly it determines the text in the legend.
added the colors to the text parameter (instead of alt) so I could see if it was assigning the colors correctly.
changed the sort order to the default "ascending" on the table_stat_scores sort because otherwise it assigned the colors in the incorrect order (don't completely understand this - seems like there is some mysterious sorting/re-ordering going on internally)
added a colors parameter to the add_segments and add_markers so that they set the color in the same way using the same column.
I think this gets you want you want:
library(plotly)
library(RColorBrewer)
table_stats_scores <- data.frame(alt=c("alt1","alt2","alt3","alt4"),
average=c(15,20,10,5),
dumb_colors= colorRampPalette(brewer.pal(4,"Dark2"))(4),
min=c(10,15,5,0),max=c(20,25,15,10)
)
table_stats_scores # This is the dataset
table_stats_scores$bright_colors <- c("#FF0000","#00FF00","#0000FF","#FF00FF")
table_stats_scores <- table_stats_scores[order(table_stats_scores$average),] # ordering
table_stats_scores$alt <- factor(table_stats_scores$alt,
levels = table_stats_scores$alt[order(table_stats_scores$average)])
# giving factor status to alternatives so that plot_ly() picks up on this
p <- plot_ly(table_stats_scores, x=~average, color = ~alt, y=~alt,text=~bright_colors) %>%
add_segments(x = ~min, xend = ~max, y = ~alt, yend = ~alt,name = "Min-Max range",
colors=~bright_colors, showlegend = FALSE, line = list(width = 4)) %>%
add_markers(x = ~average, y = ~alt, name = "Mean",
marker=list(size=8.5,colors=~bright_colors),showlegend = FALSE) %>%
add_text(textposition = "top right") %>%
layout(title = "Scores of alternatives",
xaxis = list(title = "scores"),
yaxis = list(title = "Alternatives")
)
p
yielding this:

Resources