I hope somebody will be able to help me with this chordDiagram visualisation I am trying to create. I am well aware that maybe this visualization type was not suitable for this particular data, but somehow it was something I had in my head (or how I wanted to visualize this data) and what I wanted to create, and now I think it is too late to give it up :) too curious how one can fix it. It is my first real post here, though I am an active user of stackoverflow and I genuinely admire the audience here.
So I have this data on the change in the size of area in km2 over time (d0) and I am trying to create a GIF out of it using example here: https://guyabel.com/post/animated-directional-chord-diagrams/
The data "d0":
Time <- as.numeric(c(10,10,10,100,100,100,200,200,200,5,5,5,50,50,50,0,0,0))
Year <- as.character(c(2050,2100,2200,2050,2100,2200,2050,2100,2200,2050,2100,2200,2050,2100,2200,2050,2100,2200))
Area_km2 <- as.numeric(c(4.3075211,7.1672926,17.2780622,5.9099250,8.2909189,16.9748961,6.5400554,8.9036313,16.5627228,3.0765610,6.3929883,18.0708108,5.3520782,8.4503856,16.7938196,0.5565978,1.8415855,12.5089476))
(d0 <- as.data.frame(cbind(Time,Year,Area_km2)))
I also have the color codes stored in a separate dataframe (d1) following the above mentioned example.
The data "d1":
year <- as.numeric(c(2050,2100,2200))
order1 <- as.character(c(1,2,3))
col1 <- c("#40A4D8","#33BEB7","#0C5BCE")
(d1 <- as.data.frame(cbind(year,order1,col1)))
So the idea was to have self-linking flows within each sector increasing in size over time, which will look like just growing segments in a final animated GIF (or like growing pie segments), but I noticed that regardless how hard I try I can't seem to manage to constrain the axis of each segment to limits of that particular year in an every single frame. It seems that the axis is being added on and keeps on adding over time, which is not what I want.
Like for example in the first figure (figure0) or "starting frame" the size of the links matches well the dataframe:
figure0
So it is
orig_year
Area_km2
.frame
2050
0.557
0
2100
1.84
0
2200
12.5
0
But when one plots next figure (figure1), the axis seems to have taken the values from the starting frame and added on the current values (4, 7.4 and 19 respectively) instead of (3.08, 6.39 and 18.1) or what should have been the values according the data frame:
figure1
orig_year
Area_km2
.frame
2050
3.08
1
2100
6.39
1
2200
18.1
1
And it keep on doing so as one loops through the data and creates new plots for the next frames. I wonder whether it is possible to constrain the axis and create the visualization in a way that the links just gradually increase over time and the axis is, so to say, following the increase or does also increase gradually following the data???
Any help is highly appreciated!
Thanks.
My code:
Sort decreasing
(d0 <- arrange(d0,Time))
Copy columns
(d0$Dest_year <- d0$Year)
Re-arrange data
library(tweenr)
(d2 <- d0 %>%
mutate(corridor=paste(Year,Dest_year,sep="->")) %>%
dplyr::select(Time,corridor,Area_km2) %>%
mutate(ease="linear") %>%
tweenr::tween_elements('Time','corridor','ease',nframes=30) %>%
tibble::as_tibble())
(d2 <- d2 %>%
separate(col=.group,into=c("orig_year","dest_year"),sep="->") %>%
dplyr::select(orig_year,dest_year,Area_km2,everything()))
d2$Time <- NULL
Create a directory to store the individual plots
dir.create("./plot-gif/")
Fixing scales
scale_gap <- function(Area_km2_m,Area_km2_max,gap_at_max=1,gaps=NULL) {
p <- Area_km2_m/Area_km2_max
if(length(gap_at_max)==1 & !is.null(gaps)) {
gap_at_max <- rep(gap_at_max,gaps)
}
gap_degree <- (360-sum(gap_at_max))*(1-p)
gap_m <- (gap_degree + sum(gap_at_max))/gaps
return(gap_m)
}
Function to derive the size of gaps in each frame for an animated GIF
(d3 <- d2 %>% group_by(orig_year) %>% mutate(gaps=scale_gap(Area_km2_m=Area_km2,Area_km2_max=max(.$Area_km2),gap_at_max=4,gaps=9)))
library(magrittr)
Get the values for axis limits
(axmax <- d2 %>% group_by(orig_year,.frame) %>% mutate(max=mean(Area_km2)))
Creating unique chordDiagrams for each frame
library(circlize)
for(f in unique(d2$.frame)){
png(file=paste0("./plot-gif/figure",f,".png"),height=7,width=7,units="in",res=500)
circos.clear()
par(mar=rep(0,4),cex=1)
circos.par(start.degree=90,track.margin=c(-0.1,0.1),
gap.degree=filter(d3,.frame==f)$gaps,
points.overflow.warning=FALSE)
chordDiagram(x=filter(d2,.frame==f),directional=2,order=d1$year,
grid.col=d1$col1,annotationTrack=c("grid","name","axis"),
transparency=0.25,annotationTrackHeight=c(0.05,0.1),
direction.type=c("diffHeight"),
diffHeight=-0.04,link.sort=TRUE,
xmax=axmax$max)
dev.off()
}
Now make a GIF
library(magick)
img <- image_read(path="./plot-gif/figure0.png")
for(f in unique(d2$.frame)[-1]){
img0 <- image_read(path=paste0("./plot-gif/figure",f,".png"))
img <- c(img,img0)
message(f)
}
img1 <- image_scale(image=img,geometry="720x720")
ani0 <- image_animate(image=img1,fps=10)
image_write(image=ani0,path="./plot-gif/figure.gif")
I will start with your d0 object. I first construct the d0 object but I do not convert everything to characters, just put them as the original numeric format. Also I reorder d0 by Time and Year:
Time = c(10,10,10,100,100,100,200,200,200,5,5,5,50,50,50,0,0,0)
Year = c(2050,2100,2200,2050,2100,2200,2050,2100,2200,2050,2100,2200,2050,2100,2200,2050,2100,2200)
Area_km2 = c(4.3075211,7.1672926,17.2780622,5.9099250,8.2909189,16.9748961,6.5400554,8.9036313,16.5627228,3.0765610,6.3929883,18.0708108,5.3520782,8.4503856,16.7938196,0.5565978,1.8415855,12.5089476)
d0 = data.frame(Time = Time,
Year = Year,
Area_km2 = Area_km2,
Dest_year = Year)
d0 = d0[order(d0$Time, d0$Year), ]
The key thing is to calculate proper values for "gaps" between sectors so that the same unit from data corresponds to the same degree in different plots.
We first calculate the maximal total width of the circular plot:
width = tapply(d0$Area_km2, d0$Time, sum)
max_width = max(width)
We assume there are n sectors (where n = 3 in d0). We let the first n-1 gaps to be 2 degrees and we dynamically adjust the last gap according to the total amount of values in each plot. For the plot with the largest total value, the last gap is also set to 2 degrees.
n = 3
degree_per_unit = (360 - n*2)/max_width
Now degree_per_unit can be shared between multiple plots. Every time we calculate the value for last_gap:
for(t in sort(unique(Time))) {
l = d0$Time == t
d0_current = d0[l, c("Year", "Dest_year", "Area_km2")]
last_gap = 360 - (n-1)*2 - sum(d0_current$Area_km2)*degree_per_unit
circos.par(gap.after = c(rep(2, n-1), last_gap))
chordDiagram(d0_current, grid.col = c("2050" = "red", "2100" = "blue", "2200" = "green"))
circos.clear()
title(paste0("Time = ", t, ", Sum = ", sum(d0_current$Area_km2)))
Sys.sleep(1)
}
Hi I have 2 formulas in google sheet that works great alone, but how can I put it together and get 1 result in the same cell, not side by side when we use "&"
Formula 1: =COUNTIFS(B1,"<>")*1-COUNTIFS(E1,"<>")*0
(when I have some text in cell B1 and E1 just count cell B1)
Formula 2: =(COUNTIFS(B1,"")*0+COUNTIFS(E1,"<>")*1)
(when I have some text in cell E1 and B1 is blank just count cell E1)
Already tried: =COUNTIFS(B1,"<>")*1-COUNTIFS(E1,"<>")*0&(COUNTIFS(B1,"")*0+COUNTIFS(E1,"<>")*1) unwanted result: 1 1 in the same cell
try:
=IF((B1<>"")*(E1<>""), 1,
IF((E1<>"")*(B1 =""), 1, 0))
I have arrayformula in the first row of a column so my values and calculations can start in Row 2 and for all the column length.
I have this situation:
https://docs.google.com/spreadsheets/d/11oDra7Vja4-5C0Uix7JTgLLSMG3gPj-6fkajXlWqqQk/edit?usp=sharing
I need a simply arithmetic operation:
Subtract above value of the same column for every row.
I'm using:
=arrayformula(IF(row(A:A)=1; "What I have now"; IF(ISBLANK(A:A); ""; A1:A-A2:A)))
but as you see is wrong.
How to do that?
UPDATED QUESTION:
And then in the second sheet I need a SUM operation with some blank cells in column:
How to do that?
https://docs.google.com/spreadsheets/d/11oDra7Vja4-5C0Uix7JTgLLSMG3gPj-6fkajXlWqqQk/edit#gid=931743679
If you want to have the array formula ion the header this is a bit weird as you need to allow the formula to technically access row 0, we can do this by constructing ranges.
=ArrayFormula(IF(
--(ROW(A1:A) > 2) + -ISBLANK(A1:A) = 1;
{0; A1:A} - {0; A2:A; 0};
""))
--(ROW(A1:A) > 2) + -ISBLANK(A1:A) = 1 Checks if the row is populated and not one of the first two rows in a way that works nicely with array formulas
{0; A1:A} - {0; A2:A; 0} does the following:
0 Data 156 123 110 95 42
- - - - - - -
0 156 123 110 95 42 0
= = = = = = =
0 33 13 15 53 42 42
N N Y Y Y Y N <- Is shown
^ ^ ^
| | Because Row is blank
| |
Because row not > 2, thus it is never evalauated even though the second would cause an error
I think this is quite tricky. The problem is that in an array formula the number of cells in each array must match - you can't mix an array starting in row 1 with an array starting in row 2 if they go all the way to the bottom of the spreadsheet.
Here is one way of getting the result you want
=arrayformula({"What I need";"";offset($A$1,1,0,count(A:A)-1)-offset($A$1,2,0,count(A:A)-1)})
You will need to change the ; and , for your locale.
I have built up an array using the {} notation to define the elements. In my locale a ; means go to the next row, so I have defined the first two cells directly as strings. After that I've chosen to use Offset to get the range A2:A5 (1 row down from A1, 0 rows across and 4 cells high) and subtract the range A3:A6 (2 rows down from A1, 0 rows across and 4 cells high) it so that gives me the other 4 cells.
B1 "What I need"
B2 ""
B3 A3-A2=33
B4 A4-A3=13
B5 A5-A4=15
B6 A6-A5=53
but will need an IF statement adding if there are any blank cells between the numbers.
In the particular case of your updated question where there are fewer numbers in column D than column C, the formula would be
=arrayformula({"Special Case";"";offset($D$1,1,0,count(D:D))+offset($C$1,2,0,count(D:D))})
But in the general case of there being blank cells anywhere, you would have to test everything
=arrayformula({"General Case";"";if(offset($D$1,1,0,rows(C:C)-2)="","",if(offset($C$1,2,0,Rows(C:C)-2)="","",offset($D$1,1,0,rows(C:C)-2)+offset($C$1,2,0,Rows(C:C)-2)))})
How would i go about counting the scores between C1 and C8 and entering the values into A2 and B2?
a1 = blue
b1 = red
a2 = team blue score
b2 = team red score
between c1 to c8 = winning team & score (NOTE: c1 = $a$1&" 1.25" )
c1 = blue 1.25
c2 = blue 2
c3 = red .5
c4 = draw
c5 = blue 1.5
c6 = blue 1.75
c7 = red 2
c8 = draw
So what I should get is:
A2 should = 6.5
B2 should = 2.5
You can get the total score of the blue team with
=sum(arrayformula(if(left(C1:C, 4)="blue", value(regexreplace(C1:C, "[^0-9.]", "")), 0)))
For the red team, use left(C1:C, 3)="red" in the formula.
The conversion from text to number happens in two steps: regexreplace removes all characters except . and 0-9; then value converts text to number.
It would be better to keep the winning team and their score in separate cells (team in column C, their score in column D), which would simplify the handling of this data: you'd only need =sumif(C1:C, "blue", D1:D).
Taking help of helper columns and without Array formula.These formula can adapt if you change team to Green or any other colour.
Formula in D1:(And Fill down)
=VALUE(RIGHT(C1,(LEN(C1)-LEN($A$1))))
Formula in E1:((And Fill down)
=LEFT(C1,(MIN(FIND({0,1,2,3,4,5,6,7,8,9},C1&"0123456789"))-2))
(And Fill down)
Formula in A2:
=SUMIF(E1:E9,"blue",D1:D9)
Formula in B2:
=SUMIF(E1:E9,"red",D1:D9)
Problem statement
I'd like to calculate the following formula in a Google Spreadsheet, where x and y are both ranges of n rows and 1 column and t is a variable, using only standard formulas:
Current situation
Right now I'm feeding x (say, A1:A10), y (say, B1:B10) and t (say, D1) to a custom function (myFunction(t, x, y), see below), but executing scripts is rather performance intensive, so I'd like to know if there is a way to make this calculation without using a custom function.
function myFunction(t, x, y)
{
var sum = 0;
for (var i = 0; i < x.length; i++)
{
var xi = parseInt(x[i]);
var yi = parseInt(y[i]);
sum += Math.min(t * xi, yi);
}
return sum;
}
In this example, E1 would become: =myFunction(D1, A1:A10, B1:B10)
Desired situation
I am looking for something like =SUM(MIN(D1 * A1:A10, B1:B10)), but a confirmation or an educated guess that this is not possible is of course also welcome.
I have not been able to test, but I think that the following formula can do what you need.
=SUM(ARRAYFORMULA(IF(ARRAYFORMULA(D1 * A1:A10) < B1:B10; ARRAYFORMULA(D1 * A1:A10); B1:B10)))
UPDATE
Indeed, a better approach is commented by Jelle Fresen, eliminating unnecessary redundancy.
=SUM(ARRAYFORMULA(IF(D1 * A1:A10 < B1:B10; D1 * A1:A10; B1:B10)))