Select top 3 parents (groups) in plotly graph using R - r

What I want is to plot only 3 of my parents, the ones that spend the highest cost with below coding.
parent <- as.character(c("Sam","Elena","Sam","Jhon","Raul","Sam","Jhon","Sara","Paul","Chris"))
cost <- as.numeric(as.character(c(15000,10000,12000,15000,10000,12000,15000,14000,19000,2000)))
topic <- as.character(c("Banana","Banana","Berries","Apple","Watermelon","Banana","Berries","Avocado","Watermelon","Pinneaple"))
sample <- as.data.frame(cbind(parent,cost,topic))
sample$cost <- as.numeric(as.character(sample$cost))
sample$parent <- as.character(sample$parent)
sample$topic <- as.character(sample$topic)
# Color setting
ramp2 <- colorRamp(c("deepskyblue4", "white"))
ramp.list2 <- rgb( ramp2(seq(0, 1, length = 15)), max = 255)
plot_ly(sample, x = ~parent, y = ~cost, type = 'bar', color = ~topic) %>%
layout(yaxis = list(title = 'Cost'), xaxis = list(title = 'Parent'), barmode = 'stack', colorway = ramp.list2) %>%
config(displayModeBar = FALSE)
I tried to use transforms inside plotly function, like this:
transforms = list(
list(
type = 'aggregate',
groups = sample$parent,
aggregations = list(
list(
target = 'x',
func = 'max',
enabled = T))
))
But it still gives me the same output and I want to select only 3. Also, tried to use it like this:
transforms = list(
list(
type = 'filter',
target = 'y',
operation = '>',
value = cost[-3:-1]))
But it takes only cost without takin the full cost parent spent on and only gives me 2 parents instead of 3. And finally, it's not using ramp.list2 to select colors.

According to what I understood, you can use the following code to get the top 3 parents separately, as follows:
top_3 <- sample %>%
group_by(parent) %>%
summarise(cost = sum(cost)) %>%
arrange(-cost) %>%
head(3)
This will give you the following:
# A tibble: 3 x 2
# parent cost
# <chr> <dbl>
# 1 Sam 39000
# 2 Jhon 30000
# 3 Paul 19000
Then, in your plot_ly, you can just refer to these top_3 parents, as follows:
plot_ly(sample[sample$parent %in% top_3$parent,], x = ~parent, y = ~cost, type = 'bar', color = ~topic) %>%
layout(yaxis = list(title = 'Cost'), xaxis = list(title = 'Parent'), barmode = 'stack', colorway = ramp.list2) %>%
config(displayModeBar = FALSE)
which will produce the following plot:
Hope it helps.

Related

Adding traces to plotly animations in R

I am trying to develop a Business Cycle Clock similar to https://kosis.kr/visual/bcc/index/index.do?language=eng.
I've already achieved most of the things I wanted to replicate, but I can't figure it out how to add these traces (for example, in the link above set speed to 10 and trace length to 5 and then click on 'Apply' to understand what I mean).
Does anyone have any idea how to implement it? It would make the "clock" much easier to read. Thanks in advance.
Reprocible example:
library(plotly)
library(dplyr)
library(magrittr)
variable <- rep('A',10)
above_trend <- rnorm(10)
mom_increase <- rnorm(10)
ref_date <- seq.Date('2010-01-01' %>% as.Date,
length.out = 10,by='m')
full_clock_db <- cbind.data.frame(variable, above_trend, mom_increase, ref_date)
freq_aux = 'm'
ct = 'Brazil'
main_title = paste0('Business Cycle Clock para: ', ct)
m <- list(l=60, r=170, b=50, t=70, pad=4)
y_max_abs = 2
x_max_abs = 5
fig = plot_ly(
data = full_clock_db,
x = ~mom_increase,
y = ~above_trend,
color = ~variable,
frame = ~ref_date,
text = ~variable,
hoverinfo = "text",
type = 'scatter',
mode = 'markers'
) %>%
animation_opts( frame = 800,
transition = 500,
easing = "circle",
redraw = TRUE,
mode = "immediate") %>%
animation_slider(
currentvalue = list(prefix = "Período", font = list(color="red"))
)
fig
Another more elegant solution would be to rely on ggplot2 + gganimate:
library(ggplot2)
library(gganimate)
ggplot(full_clock_db, aes(x = mom_increase, y = above_trend)) +
geom_point(aes(group = 1L)) +
transition_time(ref_date) +
shadow_wake(wake_length = 0.1, alpha = .6)
You cna play with different shadow_* functions to find the one to your liking.
One way would be to use a line plot and repeat points as necessary. Here's an example as POC:
library(dplyr)
library(plotly)
e <- tibble(x = seq(-3, 3, 0.01)) %>%
mutate(y = dnorm(x)) %>%
mutate(iter = 1:n())
accumulate <- function(data, by, trace_length = 5L) {
data_traf <- data %>%
arrange({{ by }}) %>%
mutate(pos_end = 1:n(),
pos_start = pmax(pos_end - trace_length + 1L, 1L))
data_traf %>%
rowwise() %>%
group_map(~ data_traf %>% slice(seq(.x$pos_start, .x$pos_end, 1L)) %>%
mutate("..{{by}}.new" := .x %>% pull({{by}}))) %>%
bind_rows()
}
enew <- e %>%
accumulate(iter, 100)
plot_ly(x = ~ x, y = ~ y) %>%
add_trace(data = e, type = "scatter", mode = "lines",
line = list(color = "lightgray", width = 10)) %>%
add_trace(data = enew, frame = ~ ..iter.new,
type = "scatter", mode = "lines") %>%
animation_opts(frame = 20, 10)
The idea is that for each step, you keep the trace_length previous steps and assign them to the same frame counter (here ..iter.new). Then you plot lines instead of points and you have a sort of trace..

Plotly R : how to add dynamic annotation which belongs to frame

I'm struggeling on a simple task. I have a database with 3 columns :
Year (numeric)
Age (numeric)
Pop (numeric)
Part60 : The % of individuals with age >= 60 (string like '% of poeple over 60 : 12%'). This value is the same for each rows of a year.
Dataset looks like :
I built a plotly bargraph with a frame based on the year. So I have a slider which allow me to show for each age the number of individuals and this is animated year by year.
I would like to add an anotation which shows the value of Part60 for the year of the frame... I know that it's possible with a ggplot sent to ggplotly function, however I want to do it from scratch with a plot_ly function as parameters are (for me) easier to control and follow the logic of my code.
This is my code :
gH <- plot_ly(data = dataH,
name = 'Hommes',
marker = list(color = ispfPalette[4]),
x = ~Pop,
y = ~Age,
frame = ~Annee)
gH <- gH %>% layout(yaxis = list(categoryorder="array",
categoryarray=dataH$Age))
gH <- gH %>% layout(yaxis = list(title = '',
zeroline = TRUE,
showline = TRUE,
showticklabels = TRUE,
showgrid = FALSE),
xaxis = list(title = '',
zeroline = TRUE,
showline = TRUE,
autorange = "reversed"),
shapes = hline(60))
gH <- gH %>% add_annotations(
x = 3000,
y = 62,
text = 'Part des 60 ans et + : 12 %',
showarrow = F,
color = ispfPalette[8]
Where text = 'Part des 60 ans et + : 12 %' should be replaced by something which allow me to get the value which belongs to the year of the slider.
Is someone may help me to do it ?
Thanks in advance for your great help.
Since I don't have your data, it's pretty difficult to give you the best answer. Although, here is a method in which you can add text that changes throughout the animation.
library(plotly)
library(tidyverse)
data(gapminder, package = "gapminder")
str(gapminder)
funModeling::df_status(gapminder)
# continent, lifeExp, year
gap <- gapminder %>% group_by(year, continent) %>%
summarise(Expectancy = mean(lifeExp))
# plot
p1 <- plot_ly(gap, x = ~Expectancy, y = ~continent,
frame = ~year, type = 'bar',
showlegend = F,
hovertemplate = paste0("Continent: %{y}<br>",
"<extra></extra>"),
texttemplate = "Life Expectancy: %{x:.2f}") %>%
layout(yaxis=list(title=""),
xaxis=list(title="Average Life Expectancy per Continent By Year"),
title=list(text=paste("Fancy Title")),
margin = list(t = 100))
p1
If you had text you wanted to animate that is not connected to each marker (bar, point, line), then you could do it this way.
# Something to add in the annotation text
gap2 <- gap %>% filter(continent == "Asia") %>%
droplevels() %>%
arrange(year)
# build to see frames
p2 <- plotly_build(p1)
# modify frames; need an annotation for each frame
# make sure the data is in order by year (order by frame)
lapply(1:nrow(gap2), # for each frame
function(i){
annotation = list(
data = gap2,
type = "text",
x = 77,
y = .5,
yref = "paper",
showarrow = F,
text = paste0("Asian Life Expectancy<br>",
sprintf("%.2f", gap2[i, ]$Expectancy)),
font = list(color = "#b21e29", size = 16))
p2$x$frames[[i]]$layout <<- list(annotations = list(annotation)) # change plot
})
p2
If anything is unclear, let me know.

Plotly animated map not showing countries with NA values

I posted this in the plotly community forum but got absolutely no activity! Hope you can help here:
I have map time-series data, some countries don’t have data and plotly does not plot them at all. I can have them outlined and they look different but it appears nowhere that the data is missing there (i.e. I want a legend entry). How can I achieve this? Here is a reprex:
library(plotly)
library(dplyr)
data = read.csv('https://github.com/lc5415/COVID19/raw/master/data.csv')
l <- list(color = toRGB("grey"), width = 0.5)
g <- list(
scope = 'world',
countrycolor = toRGB('grey'),
showframe = T,
showcoastlines = TRUE,
projection = list(type = 'natural earth')
)
map.time = data %>%
plot_geo() %>%
add_trace(z = ~Confirmed, color = ~Confirmed, frame = ~Date, colors = 'Blues',
text = ~Country, locations = ~Alpha.3.code, marker = list(line = l)) %>%
colorbar(title = 'Confirmed') %>%
layout(
title = 'Number of confirmed cases over time',
geo = g
) %>%
animation_opts(redraw = F) %>%
animation_slider(
currentvalue = list(
prefix = paste0("Days from ",
format(StartDate, "%B %dnd"),": "))) %>%
plotly_build()
map.time
Note that the countries with missing data (e.g. Russia) have as many data points as all other countries, the issue is not that they do not appear in the dtaframe passed to plotly.
The obvious way to handle this is to create a separate labels column for the tooltip that reads "No data" for NA values (with the actual value otherwise), then make your actual NA values 0. This will give a uniform appearance to all the countries but correctly tells you when a country has no data.
map.time = data %>%
mutate_if(is.numeric, function(x) {x[is.na(x)] <- -1; x}) %>%
plot_geo() %>%
add_trace(z = ~Confirmed, color = ~Confirmed, frame = ~Date, colors = 'Blues',
text = ~Country, locations = ~Alpha.3.code,
marker = list(line = l)) %>%
colorbar(title = 'Confirmed') %>%
layout(
title = 'Number of confirmed cases over time',
geo = g
) %>%
animation_opts(redraw = F) %>%
animation_slider(
currentvalue = list(
prefix = paste0("Days from ",
format(StartDate, "%B %dnd"),": "))) %>%
plotly_build()
Which gives:

Switching between two plots with groups in Plotly

I have a data frame with 2 columns, one that I want to use as a toggle (so display grp1 or grp2) and another where I want to split the data into different lines. I can't seem to figure out how to get it to work properly with plotly, I think there should be a simple straightforward way to do it but for the life of me I can't get it to stop mixing up the groups.
library(tidyverse)
library(plotly)
library(ggplot2)
# example data my group 1 would be social, group 2 would be grp
df1 = data.frame(grp = "A", social = "Facebook",
days = c("2020-01-01","2020-01-02","2020-01-03","2020-01-04"),
yval = c(0.1, 0.2, 0.3, 0.4))
df2 = df1
df2$grp = "B"
df2$yval = df2$yval + 0.2
df3 = df1
df3$grp = "C"
df3$yval = df3$yval + 0.4
df = rbind(df1, df2, df3)
aux = df
aux$social = "Twitter"
aux$yval = aux$yval + 0.1
df = rbind(aux, df)
rm(aux, df1, df2, df3)
df$days = as.Date(df$days)
df$social_group = paste(df$social, df$grp)
ggplot(data = df, mapping = aes(x = days, y = yval, color = social)) + geom_point() + geom_line() + facet_wrap(facets = ~social)
So what I'm trying to do is to create a plotly that lets me switch between the ggplot facets, by toggling a Facebook or Twitter button.
This is what I currently got, which starts well, but as soon as I toggle the buttons the groups seem to mix, which shouldn't be happening when I consider I'm filtering on another column...
facebook_annotations <- list(
data=df %>% filter(social=="Facebook"),
x=~days,
y=~yval,
color = ~grp,
hovertemplate = paste('%{x}', '<br>Hover text: %{text}<br>'),
text=~days
)
twitter_annotations <- list(
data=df %>% filter(social=="Twitter"),
x=~days,
y=~yval,
color = ~grp,
hovertemplate = paste('%{x}', '<br>Hover text: %{text}<br>'),
text=~days
)
# updatemenus component
updatemenus <- list(
list(
active = 0,
type = "buttons",
buttons = list(
list(
label = "Facebook",
method = "update",
args = list(list(visible = c(TRUE, FALSE)),
list(title = "Facebook",
annotations = list(facebook_annotations, c())))),
list(
label = "Twitter",
method = "update",
args = list(list(visible = c(FALSE, TRUE)),
list(title = "Twitter",
annotations = list(c(), twitter_annotations)))))
)
)
fig <- df %>% plot_ly(type="scatter", mode="lines")
fig <- fig %>% add_lines(
data=df %>% filter(social=="Facebook"),
x=~days,
y=~yval,
color = ~grp,
hovertemplate = paste('%{x}', '<br>Hover text: %{text}<br>'),
text=~days
)
fig <- fig %>% add_lines(
data=df %>% filter(social=="Twitter"),
x=~days,
y=~yval,
color = ~grp,
hovertemplate = paste('%{x}', '<br>Hover text: %{text}<br>'),
text=~days,
visible=FALSE
)
fig <- fig %>% layout(title="Facebook",
xaxis=list(title=""),
yaxis = list(range = c(0, 1), title = "My Title"),
updatemenus=updatemenus)
fig
What am I missing? It's driving me crazy, I'm even considering just adding each group as an individual trace, but that's not really practical when my actual case study has 8 groups...

Multiple lines/traces for each button in a Plotly drop down menu in R

I am trying to generate multiple graphs in Plotly for 30 different sales offices. Each graph would have 3 lines: sales, COGS, and inventory. I would like to keep this on one graph with 30 buttons for the different offices. This is the closest solution I could find on SO:
## Create random data. cols holds the parameter that should be switched
l <- lapply(1:100, function(i) rnorm(100))
df <- as.data.frame(l)
cols <- paste0(letters, 1:100)
colnames(df) <- cols
df[["c"]] <- 1:100
## Add trace directly here, since plotly adds a blank trace otherwise
p <- plot_ly(df,
type = "scatter",
mode = "lines",
x = ~c,
y= ~df[[cols[[1]]]],
name = cols[[1]])
## Add arbitrary number of traces
## Ignore first col as it has already been added
for (col in cols[-1]) {
p <- p %>% add_lines(x = ~c, y = df[[col]], name = col, visible = FALSE)
}
p <- p %>%
layout(
title = "Dropdown line plot",
xaxis = list(title = "x"),
yaxis = list(title = "y"),
updatemenus = list(
list(
y = 0.7,
## Add all buttons at once
buttons = lapply(cols, function(col) {
list(method="restyle",
args = list("visible", cols == col),
label = col)
})
)
)
)
print(p)
It works but only on graphs with single lines/traces. How can I modify this code to do the same thing but with graphs with 2 or more traces? or is there a better solution? Any help would be appreciated!
### EXAMPLE 2
#create fake time series data
library(plotly)
set.seed(1)
df <- data.frame(replicate(31,sample(200:500,24,rep=TRUE)))
cols <- paste0(letters, 1:31)
colnames(df) <- cols
#create time series
timeseries <- ts(df[[1]], start = c(2018,1), end = c(2019,12), frequency = 12)
fit <- auto.arima(timeseries, d=1, D=1, stepwise =FALSE, approximation = FALSE)
fore <- forecast(fit, h = 12, level = c(80, 95))
## Add trace directly here, since plotly adds a blank trace otherwise
p <- plot_ly() %>%
add_lines(x = time(timeseries), y = timeseries,
color = I("black"), name = "observed") %>%
add_ribbons(x = time(fore$mean), ymin = fore$lower[, 2], ymax = fore$upper[, 2],
color = I("gray95"), name = "95% confidence") %>%
add_ribbons(x = time(fore$mean), ymin = fore$lower[, 1], ymax = fore$upper[, 1],
color = I("gray80"), name = "80% confidence") %>%
add_lines(x = time(fore$mean), y = fore$mean, color = I("blue"), name = "prediction")
## Add arbitrary number of traces
## Ignore first col as it has already been added
for (col in cols[2:31]) {
timeseries <- ts(df[[col]], start = c(2018,1), end = c(2019,12), frequency = 12)
fit <- auto.arima(timeseries, d=1, D=1, stepwise =FALSE, approximation = FALSE)
fore <- forecast(fit, h = 12, level = c(80, 95))
p <- p %>%
add_lines(x = time(timeseries), y = timeseries,
color = I("black"), name = "observed", visible = FALSE) %>%
add_ribbons(x = time(fore$mean), ymin = fore$lower[, 2], ymax = fore$upper[, 2],
color = I("gray95"), name = "95% confidence", visible = FALSE) %>%
add_ribbons(x = time(fore$mean), ymin = fore$lower[, 1], ymax = fore$upper[, 1],
color = I("gray80"), name = "80% confidence", visible = FALSE) %>%
add_lines(x = time(fore$mean), y = fore$mean, color = I("blue"), name = "prediction", visible = FALSE)
}
p <- p %>%
layout(
title = "Dropdown line plot",
xaxis = list(title = "x"),
yaxis = list(title = "y"),
updatemenus = list(
list(
y = 0.7,
## Add all buttons at once
buttons = lapply(cols, function(col) {
list(method="restyle",
args = list("visible", cols == col),
label = col)
})
)
)
)
p
You were very close!
If for example you want graphs with 3 traces,
You only need to tweak two things:
Set visible the three first traces,
Modify buttons to show traces in groups of three.
My code:
## Create random data. cols holds the parameter that should be switched
library(plotly)
l <- lapply(1:99, function(i) rnorm(100))
df <- as.data.frame(l)
cols <- paste0(letters, 1:99)
colnames(df) <- cols
df[["c"]] <- 1:100
## Add trace directly here, since plotly adds a blank trace otherwise
p <- plot_ly(df,
type = "scatter",
mode = "lines",
x = ~c,
y= ~df[[cols[[1]]]],
name = cols[[1]])
p <- p %>% add_lines(x = ~c, y = df[[2]], name = cols[[2]], visible = T)
p <- p %>% add_lines(x = ~c, y = df[[3]], name = cols[[3]], visible = T)
## Add arbitrary number of traces
## Ignore first col as it has already been added
for (col in cols[4:99]) {
print(col)
p <- p %>% add_lines(x = ~c, y = df[[col]], name = col, visible = F)
}
p <- p %>%
layout(
title = "Dropdown line plot",
xaxis = list(title = "x"),
yaxis = list(title = "y"),
updatemenus = list(
list(
y = 0.7,
## Add all buttons at once
buttons = lapply(0:32, function(col) {
list(method="restyle",
args = list("visible", cols == c(cols[col*3+1],cols[col*3+2],cols[col*3+3])),
label = paste0(cols[col*3+1], " ",cols[col*3+2], " ",cols[col*3+3] ))
})
)
)
)
print(p)
PD: I only use 99 cols because I want 33 groups of 3 graphs

Resources